QtPass Developer documentation
QtPass test coverage
Current view: top level - src/filecontent.h Coverage Total Hit
Test: QtPass Lines: 100.0 % 1 1
Test Date: 2026-11-14 13:19:18 Functions: - 0 0

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2018 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Parser for a decrypted password entry: password line, `key: value`
       6              :  * fields, remaining text and the OTP configuration.
       7              :  */
       8              : #ifndef SRC_FILECONTENT_H_
       9              : #define SRC_FILECONTENT_H_
      10              : 
      11              : #include <QList>
      12              : #include <QString>
      13              : #include <QStringList>
      14              : #include <functional>
      15              : 
      16              : /**
      17              :  * @struct NamedValue
      18              :  * @ingroup store
      19              :  * @brief A name/value pair parsed from a password file field.
      20              :  */
      21              : struct NamedValue {
      22              :   /**
      23              :    * @brief Field name (the part before the colon in "name: value").
      24              :    */
      25              :   QString name;
      26              :   /**
      27              :    * @brief Field value (the part after the colon in "name: value").
      28              :    */
      29              :   QString value;
      30              :   /**
      31              :    * @brief The field's verbatim source line, colon and spacing intact.
      32              :    *
      33              :    * The edit dialog writes an untouched field back from this, so saving
      34              :    * does not reformat `key:value` to `key: value`, trim the value, or drop
      35              :    * a field whose value is empty. Empty when the field has no source line
      36              :    * (a template field the file did not fill).
      37              :    */
      38              :   QString originalLine;
      39              : };
      40              : 
      41              : /**
      42              :  * @brief Two fields are equal when name and value match; originalLine is
      43              :  * formatting, not content, and is left out so tests can compare against
      44              :  * literals.
      45              :  * @param a One field.
      46              :  * @param b The other field.
      47              :  * @return true when both name and value are equal.
      48              :  */
      49              : inline bool operator==(const NamedValue &a, const NamedValue &b) {
      50              :   return a.name == b.name && a.value == b.value;
      51              : }
      52              : 
      53              : /**
      54              :  * @ingroup store
      55              :  * @brief The NamedValues class is mostly a list of NamedValue
      56              :  * but also has a method to take a specific NamedValue pair out of the list.
      57              :  */
      58          360 : class NamedValues : public QList<NamedValue> {
      59              : public:
      60              :   /** @brief Construct an empty list. */
      61              :   NamedValues();
      62              :   /**
      63              :    * @brief Construct a NamedValues list from an initializer list.
      64              :    * @param values Initial set of NamedValue pairs.
      65              :    */
      66              :   NamedValues(std::initializer_list<NamedValue> values);
      67              : 
      68              :   /**
      69              :    * @brief Remove and return the value for the named field.
      70              :    * @param name Field name to look up and remove.
      71              :    * @return The value of the removed field, or an empty string if not found.
      72              :    */
      73              :   auto takeValue(const QString &name) -> QString;
      74              : 
      75              :   /**
      76              :    * @brief Remove and return the named field, its original line included.
      77              :    * @param name Field name to look up and remove.
      78              :    * @return The removed NamedValue, or a default-constructed one (empty name)
      79              :    *         if not found.
      80              :    */
      81              :   auto takeNamedValue(const QString &name) -> NamedValue;
      82              : };
      83              : 
      84              : /**
      85              :  * @class FileContent
      86              :  * @ingroup store
      87              :  * @brief Represents the parsed contents of a password file.
      88              :  *
      89              :  * Splits the file into a password line, named key/value fields, and
      90              :  * remaining data. Use FileContent::parse to construct an instance.
      91              :  */
      92              : class FileContent {
      93              : public:
      94              :   /**
      95              :    * @brief Most named fields parse() takes from one entry.
      96              :    *
      97              :    * Every field becomes a row of widgets in the display panel. An entry is
      98              :    * compressed before it is encrypted, so a small one in a shared store
      99              :    * could decompress into millions of "a: b" lines within the plaintext
     100              :    * limit; lines past this many fields stay in the remaining text. They are
     101              :    * still looked at for an OTP configuration, as fields would be.
     102              :    */
     103              :   static constexpr int kMaxNamedFields = 1000;
     104              : 
     105              :   /**
     106              :    * @brief Most lines of remaining text getRemainingDataForDisplay() gives.
     107              :    *
     108              :    * The text browser makes a block, with its layout, per line. Past this
     109              :    * many the display text ends with one line saying how many more there
     110              :    * are; getRemainingData() keeps them all, so saving loses nothing. The
     111              :    * edit dialog uses the same number for the whole entry (password and
     112              :    * fields included) and does not load one with more lines.
     113              :    */
     114              :   static constexpr int kMaxDisplayLines = 10000;
     115              : 
     116              :   /**
     117              :    * @brief @p text cut to kMaxDisplayLines lines, as
     118              :    * getRemainingDataForDisplay() is: for showing an entry as it is
     119              :    * (displayAsIs).
     120              :    * @param text The text to show.
     121              :    * @return @p text when it is short enough, else its first kMaxDisplayLines
     122              :    *         lines and one saying how many more there are.
     123              :    */
     124              :   [[nodiscard]] static auto boundedForDisplay(const QString &text) -> QString;
     125              : 
     126              :   /**
     127              :    * @brief parse parses the given fileContent in a FileContent object.
     128              :    * The password is accessible through getPassword.
     129              :    * The named value pairs (name: value) are parsed and depending on the
     130              :    * templateFields and allFields parameters accessible through getNamedValues,
     131              :    * getRemainingData or getRemainingDataForDisplay.
     132              :    *
     133              :    * @param fileContent the file content to parse.
     134              :    *
     135              :    * @param templateFields the fields in the template.
     136              :    * Fields in the template will always be in getNamedValues() at the beginning
     137              :    * of the list in the same order.
     138              :    *
     139              :    * @param allFields whether all fields should be considered as named values.
     140              :    * If set to false only templateFields are returned in getNamedValues().
     141              :    *
     142              :    * @param alsoField a field besides those, by its trimmed name: a secret
     143              :    * the panel hides, which must not be left in the remaining text in plain
     144              :    * view. Empty: none.
     145              :    *
     146              :    * @return A FileContent instance with password, named values, and remaining
     147              :    *         data populated from the parsed input.
     148              :    */
     149              :   static auto parse(const QString &fileContent,
     150              :                     const QStringList &templateFields, bool allFields,
     151              :                     const std::function<bool(const QString &)> &alsoField = {})
     152              :       -> FileContent;
     153              : 
     154              :   /**
     155              :    * @return the password from the parsed file.
     156              :    */
     157              :   [[nodiscard]] auto getPassword() const -> QString;
     158              : 
     159              :   /**
     160              :    * @brief Like getPassword but empty when the password line is itself an
     161              :    * otpauth URI.
     162              :    *
     163              :    * `pass otp insert` writes the URI as the entry's only line, so it lands in
     164              :    * the password position. It is a shared secret, not a password: it must never
     165              :    * be rendered or placed on the clipboard. getPassword() still returns it so
     166              :    * the edit dialog round-trips the file unchanged.
     167              :    * @return The password, or an empty string when it is an otpauth URI.
     168              :    */
     169              :   [[nodiscard]] auto getPasswordForDisplay() const -> QString;
     170              : 
     171              :   /**
     172              :    * @return the named values in the file in the order of appearance, with
     173              :    * template values first.
     174              :    */
     175              :   [[nodiscard]] auto getNamedValues() const -> NamedValues;
     176              : 
     177              :   /**
     178              :    * @return the data that is not the password and not in getNamedValues.
     179              :    */
     180              :   [[nodiscard]] auto getRemainingData() const -> QString;
     181              : 
     182              :   /**
     183              :    * @brief Like getRemainingData but without data that should not be displayed
     184              :    * (like a TOTP secret).
     185              :    * @return Remaining data string safe for display.
     186              :    */
     187              :   [[nodiscard]] auto getRemainingDataForDisplay() const -> QString;
     188              : 
     189              :   /**
     190              :    * @brief How many lines getRemainingData() holds.
     191              :    * @return The line count; 0 when there is no remaining text.
     192              :    */
     193              :   [[nodiscard]] auto getRemainingLineCount() const -> qsizetype;
     194              : 
     195              :   /**
     196              :    * @brief Whether a field name designates the one-time password field.
     197              :    * @param name Field name as written in the file; surrounding whitespace is
     198              :    *        ignored.
     199              :    * @return true for "OTP" or "TOTP", compared case-insensitively.
     200              :    */
     201              :   [[nodiscard]] static auto isOtpFieldName(const QString &name) -> bool;
     202              : 
     203              :   /**
     204              :    * @brief Whether a field name names a secret: one of @p secrets occurs in
     205              :    * it as whole words, compared case-insensitively ("key" matches "API key"
     206              :    * and "Recovery key", not "keyboard layout").
     207              :    * @param name Field name as written in the file.
     208              :    * @param secrets The secret field names (AppSettings::secretFieldNames).
     209              :    * @return true when one of them occurs in @p name.
     210              :    */
     211              :   [[nodiscard]] static auto isSecretFieldName(const QString &name,
     212              :                                               const QStringList &secrets)
     213              :       -> bool;
     214              : 
     215              :   /**
     216              :    * @brief Whether a field value is an otpauth URI, and therefore a secret.
     217              :    *
     218              :    * A URI is a shared secret whatever the field is called, so both the
     219              :    * display-suppression and the OTP lookup key off the value as well as the
     220              :    * name; suppressing only `OTP:`/`TOTP:` leaked `2fa:` and friends.
     221              :    * @param value Field value; surrounding whitespace is ignored.
     222              :    * @return true when it starts with "otpauth://", compared case-insensitively.
     223              :    */
     224              :   [[nodiscard]] static auto isOtpUriValue(const QString &value) -> bool;
     225              : 
     226              :   /**
     227              :    * @brief The entry's raw one-time password configuration, if it has one.
     228              :    *
     229              :    * Looked up in this order:
     230              :    *   1. the value of the first field whose name satisfies isOtpFieldName();
     231              :    *   2. the first bare `otpauth://` line in the body, which is how the
     232              :    *      `pass-otp` extension stores it;
     233              :    *   3. the value of the first `OTP:` line that was not promoted to a named
     234              :    *      value, which is what happens when the template does not list `OTP`.
     235              :    *
     236              :    * The string is returned verbatim and is not validated here: it may be an
     237              :    * `otpauth://` URI or a bare base32 secret. Totp::parse() does the
     238              :    * validation.
     239              :    * @return The OTP configuration string, or an empty string when the entry
     240              :    *         has none.
     241              :    */
     242              :   [[nodiscard]] auto getOtpUri() const -> QString;
     243              : 
     244              : private:
     245              :   /**
     246              :    * @brief Assemble a parsed entry; only parse() calls this.
     247              :    * @param password The first line.
     248              :    * @param namedValues The fields promoted out of the body.
     249              :    * @param remainingData The body lines that did not become fields.
     250              :    * @param remainingDataDisplay @p remainingData without secret-bearing
     251              :    *        lines, cut at kMaxDisplayLines.
     252              :    * @param otpUri The OTP configuration found, or empty.
     253              :    * @param remainingLines Line count of @p remainingData.
     254              :    */
     255              :   FileContent(QString password, NamedValues namedValues, QString remainingData,
     256              :               QString remainingDataDisplay, QString otpUri,
     257              :               qsizetype remainingLines);
     258              : 
     259              :   /** @brief The first line of the entry, verbatim. */
     260              :   QString password;
     261              :   /** @brief Fields in order of appearance, template fields first. */
     262              :   NamedValues namedValues;
     263              :   /** @brief Body lines that are not fields, joined with newlines; complete. */
     264              :   QString remainingData, remainingDataDisplay; /**< remainingData for the
     265              :       panel: OTP-bearing lines and hidden secrets left out, at most
     266              :       kMaxDisplayLines lines plus a "more" line. */
     267              :   /** @brief Raw OTP configuration (URI or bare base32), or empty. */
     268              :   QString otpUri;
     269              :   /** @brief Number of lines in remainingData. */
     270              :   qsizetype remainingLines = 0;
     271              : };
     272              : 
     273              : #endif // SRC_FILECONTENT_H_
        

Generated by: LCOV version 2.0-1