10#include <QRegularExpression>
12#include <QCoreApplication>
13#include <QStringTokenizer>
25static auto isOtpUri(
const QString &value) ->
bool {
41 const qsizetype colon = line.indexOf(
':');
46 isOtpUri(line.right(line.length() - colon - 1));
59 QString remainingField;
65 auto pick() const -> QString {
66 if (!field.isEmpty()) {
69 return bareLine.isEmpty() ? remainingField : bareLine;
85auto takeNamedValue(
const QString &line,
const QStringList &templateFields,
87 const std::function<
bool(
const QString &)> &alsoField,
88 NamedValues &namedValues, OtpCandidates &otp) ->
bool {
89 const qsizetype colon = line.indexOf(u
':');
93 const QString name = line.left(colon);
94 const QString value = line.right(line.length() - colon - 1);
96 if (!templateFields.contains(name) &&
97 !(allFields && !value.startsWith(QStringLiteral(
"//"))) &&
98 !(alsoField && alsoField(name.trimmed()))) {
101 otp.remainingField = value.trimmed();
105 namedValues.append({name.trimmed(), value.trimmed(), line});
110 otp.field = value.trimmed();
120auto moreLinesNotShown(qsizetype count) -> QString {
124 const int n =
static_cast<int>(
125 std::min<qsizetype>(count, std::numeric_limits<int>::max()));
126 return QCoreApplication::translate(
"FileContent",
127 "… %n more line(s) not shown",
nullptr, n);
133 const QStringList &templateFields,
bool allFields,
134 const std::function<
bool(
const QString &)> &alsoField)
139 QStringTokenizer tokens = QStringView(fileContent).tokenize(u
'\n');
140 auto it = tokens.begin();
142 if (it != tokens.end()) {
149 qsizetype displayedLines = 0;
150 qsizetype notShown = 0;
161 for (; it != tokens.end(); ++it) {
162 const QString line = it->toString();
163 bool secretPastTheCap =
false;
170 if (takeNamedValue(line, templateFields, allFields, alsoField,
175 takeNamedValue(line, templateFields, allFields, alsoField, pastTheCap,
179 secretPastTheCap = !pastTheCap.isEmpty() && alsoField &&
180 alsoField(pastTheCap.first().name);
190 if (
isOtpUri(line) && otp.bareLine.isEmpty()) {
192 otp.bareLine = line.trimmed();
199 if (displayedLines > 0) {
218 const QString trimmed = name.trimmed();
219 return trimmed.compare(QStringLiteral(
"OTP"), Qt::CaseInsensitive) == 0 ||
220 trimmed.compare(QStringLiteral(
"TOTP"), Qt::CaseInsensitive) == 0;
224 const QStringList &secrets) ->
bool {
227 thread_local QStringList cachedSecrets;
228 thread_local QStringList cachedWords;
229 thread_local QRegularExpression cachedPattern;
230 thread_local bool cachedYet =
false;
231 constexpr auto options = QRegularExpression::CaseInsensitiveOption |
232 QRegularExpression::UseUnicodePropertiesOption;
233 const auto whole = [](
const QString &alternatives) {
234 return QStringLiteral(
"(?<!\\w)(?:%1)(?!\\w)").arg(alternatives);
236 if (!cachedYet || secrets != cachedSecrets) {
238 for (
const QString &secret : secrets) {
239 const QString word = secret.trimmed();
240 if (!word.isEmpty()) {
241 cachedWords << QRegularExpression::escape(word);
244 cachedSecrets = secrets;
247 QRegularExpression(whole(cachedWords.join(QLatin1Char(
'|'))), options);
249 if (cachedWords.isEmpty()) {
252 if (cachedPattern.isValid()) {
253 return cachedPattern.match(name).hasMatch();
258 cachedWords.cbegin(), cachedWords.cend(),
259 [&name, &whole, options](
const QString &word) {
260 return QRegularExpression(whole(word), options).match(name).hasMatch();
265 return value.trimmed().startsWith(QStringLiteral(
"otpauth://"),
266 Qt::CaseInsensitive);
301 end = text.indexOf(QLatin1Char(
'\n'), end + 1);
306 const qsizetype more =
308 return text.left(end) + QLatin1Char(
'\n') + moreLinesNotShown(more);
325 for (
int i = 0; i < length(); ++i) {
326 if (at(i).name == name) {
static auto boundedForDisplay(const QString &text) -> QString
text cut to kMaxDisplayLines lines, as getRemainingDataForDisplay() is: for showing an entry as it is...
FileContent(QString password, NamedValues namedValues, QString remainingData, QString remainingDataDisplay, QString otpUri, qsizetype remainingLines)
Assemble a parsed entry; only parse() calls this.
qsizetype remainingLines
Number of lines in remainingData.
QString remainingData
Body lines that are not fields, joined with newlines; complete.
auto getRemainingLineCount() const -> qsizetype
How many lines getRemainingData() holds.
QString password
The first line of the entry, verbatim.
static auto isOtpFieldName(const QString &name) -> bool
Whether a field name designates the one-time password field.
QString otpUri
Raw OTP configuration (URI or bare base32), or empty.
static constexpr int kMaxDisplayLines
Most lines of remaining text getRemainingDataForDisplay() gives.
auto getRemainingData() const -> QString
auto getNamedValues() const -> NamedValues
static constexpr int kMaxNamedFields
Most named fields parse() takes from one entry.
static auto parse(const QString &fileContent, const QStringList &templateFields, bool allFields, const std::function< bool(const QString &)> &alsoField={}) -> FileContent
parse parses the given fileContent in a FileContent object. The password is accessible through getPas...
static auto isSecretFieldName(const QString &name, const QStringList &secrets) -> bool
Whether a field name names a secret: one of secrets occurs in it as whole words, compared case-insens...
NamedValues namedValues
Fields in order of appearance, template fields first.
auto getRemainingDataForDisplay() const -> QString
Like getRemainingData but without data that should not be displayed (like a TOTP secret).
auto getPassword() const -> QString
auto getOtpUri() const -> QString
The entry's raw one-time password configuration, if it has one.
QString remainingDataDisplay
auto getPasswordForDisplay() const -> QString
Like getPassword but empty when the password line is itself an otpauth URI.
static auto isOtpUriValue(const QString &value) -> bool
Whether a field value is an otpauth URI, and therefore a secret.
The NamedValues class is mostly a list of NamedValue but also has a method to take a specific NamedVa...
auto takeValue(const QString &name) -> QString
Remove and return the value for the named field.
auto takeNamedValue(const QString &name) -> NamedValue
Remove and return the named field, its original line included.
NamedValues()
Construct an empty list.
static auto isLineHidden(const QString &line) -> bool
Whether a body line carries a TOTP shared secret and must stay out of the parsed display: a bare otpa...
static auto isOtpUri(const QString &value) -> bool
Shorthand for the public FileContent::isOtpUriValue.
Parser for a decrypted password entry: password line, key: value fields, remaining text and the OTP c...
A name/value pair parsed from a password file field.