• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

IJHack / QtPass / 24361228529

13 Apr 2026 06:56PM UTC coverage: 20.814%. First build
24361228529

Pull #996

github

web-flow
Merge 19bbca4ad into 3b97df208
Pull Request #996: fix: validate .gpg-id key IDs to filter bogus entries (#288)

0 of 7 new or added lines in 1 file covered. (0.0%)

1110 of 5333 relevant lines covered (20.81%)

7.75 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

71.23
/src/util.cpp
1
// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3

4
/**
5
 * @class Util
6
 * @brief Static utility functions implementation.
7
 *
8
 * Implementation of utility functions for path handling, binary discovery,
9
 * and configuration validation.
10
 *
11
 * @see util.h
12
 */
13

14
#include "util.h"
15
#include <QDir>
16
#include <QFileInfo>
17
#ifdef Q_OS_WIN
18
#include <windows.h>
19
#else
20
#include <sys/time.h>
21
#endif
22
#include "qtpasssettings.h"
23

24
#ifdef QT_DEBUG
25
#include "debughelper.h"
26
#endif
27

28
QProcessEnvironment Util::_env;
29
bool Util::_envInitialised = false;
30

31
/**
32
 * @brief Initializes the process environment and augments PATH with
33
 * platform-specific GPG locations.
34
 * @example
35
 * Util::initialiseEnvironment();
36
 *
37
 * @note On macOS, appends common MacGPG2 and /usr/local/bin paths if available.
38
 * @note On Windows, appends common WinGPG and GnuPG installation paths if
39
 * available.
40
 */
41
void Util::initialiseEnvironment() {
14✔
42
  if (!_envInitialised) {
14✔
43
    _env = QProcessEnvironment::systemEnvironment();
1✔
44
#ifdef __APPLE__
45
    QString path = _env.value("PATH");
46
    if (!path.contains("/usr/local/MacGPG2/bin") &&
47
        QDir("/usr/local/MacGPG2/bin").exists())
48
      path += ":/usr/local/MacGPG2/bin";
49
    if (!path.contains("/usr/local/bin"))
50
      path += ":/usr/local/bin";
51
    _env.insert("PATH", path);
52
#endif
53
#ifdef Q_OS_WIN
54
    QString path = _env.value("PATH");
55
    if (!path.contains("C:\\Program Files\\WinGPG\\x86") &&
56
        QDir("C:\\Program Files\\WinGPG\\x86").exists())
57
      path += ";C:\\Program Files\\WinGPG\\x86";
58
    if (!path.contains("C:\\Program Files\\GnuPG\\bin") &&
59
        QDir("C:\\Program Files\\GnuPG\\bin").exists())
60
      path += ";C:\\Program Files\\GnuPG\\bin";
61
    _env.insert("PATH", path);
62
#endif
63
#ifdef QT_DEBUG
64
    dbg() << _env.value("PATH");
65
#endif
66
    _envInitialised = true;
1✔
67
  }
68
}
14✔
69

70
/**
71
 * @brief Resolves the path to the password store directory.
72
 * @details Initializes the environment, checks for the {@code
73
 * PASSWORD_STORE_DIR} variable, and falls back to a platform-specific default
74
 * location under the user's home directory.
75
 * @return QString - Normalized path to the password store folder.
76
 */
77
auto Util::findPasswordStore() -> QString {
2✔
78
  QString path;
2✔
79
  initialiseEnvironment();
2✔
80
  if (_env.contains("PASSWORD_STORE_DIR")) {
4✔
81
    path = _env.value("PASSWORD_STORE_DIR");
×
82
  } else {
83
#ifdef Q_OS_WIN
84
    path = QDir::homePath() + QDir::separator() + "password-store" +
85
           QDir::separator();
86
#else
87
    path = QDir::homePath() + QDir::separator() + ".password-store" +
4✔
88
           QDir::separator();
89
#endif
90
  }
91
  return Util::normalizeFolderPath(path);
4✔
92
}
93

94
auto Util::normalizeFolderPath(const QString &path) -> QString {
13✔
95
  QString normalizedPath = path;
96
  if (!normalizedPath.endsWith("/") &&
33✔
97
      !normalizedPath.endsWith(QDir::separator())) {
7✔
98
    normalizedPath += QDir::separator();
7✔
99
  }
100
  return QDir::toNativeSeparators(normalizedPath);
26✔
101
}
102

103
/**
104
 * @brief Finds the absolute path of a binary by searching the PATH environment
105
 * variable.
106
 *
107
 * Iterates through each PATH entry, checks whether the binary exists and is
108
 * executable, and returns the first matching absolute file path. On Windows, if
109
 * no local match is found, it may fall back to a WSL invocation when the binary
110
 * name is valid and WSL appears to support it.
111
 *
112
 * @example
113
 * QString result = Util::findBinaryInPath("git");
114
 * // Expected output sample: "/usr/bin/git" or "wsl git"
115
 *
116
 * @param QString binary - The name of the binary to locate.
117
 * @return QString - The absolute path to the binary, or an empty string if not
118
 * found.
119
 */
120
auto Util::findBinaryInPath(QString binary) -> QString {
12✔
121
  initialiseEnvironment();
12✔
122

123
  QString ret;
12✔
124

125
  const QString binaryWithSep = QDir::separator() + binary;
12✔
126

127
  if (_env.contains("PATH")) {
24✔
128
    QString path = _env.value("PATH");
24✔
129
    const QChar delimiter = QDir::separator() == '\\' ? ';' : ':';
130
    QStringList entries = path.split(delimiter);
12✔
131

132
    for (const QString &entryConst : entries) {
192✔
133
      QString fullPath = entryConst + binaryWithSep;
178✔
134
      QFileInfo qfi(fullPath);
178✔
135
#ifdef Q_OS_WIN
136
      if (!qfi.exists()) {
137
        QString fullPathExe = fullPath + ".exe";
138
        qfi = QFileInfo(fullPathExe);
139
      }
140
#endif
141
      if (!qfi.exists()) {
178✔
142
        continue;
168✔
143
      }
144
      if (!qfi.isExecutable()) {
10✔
145
        continue;
×
146
      }
147

148
      ret = qfi.absoluteFilePath();
10✔
149
      break;
150
    }
178✔
151
  }
152
#ifdef Q_OS_WIN
153
  if (ret.isEmpty()) {
154
    // Validate binary name before attempting WSL fallback: require a non-empty,
155
    // whitespace-free program name to avoid confusing WSL invocations.
156
    const bool hasWhitespace =
157
        binary.contains(QRegularExpression(QStringLiteral("\\s")));
158
    if (!binary.isEmpty() && !hasWhitespace) {
159
      QString wslCommand = QStringLiteral("wsl ") + binary;
160
#ifdef QT_DEBUG
161
      dbg() << "Util::findBinaryInPath(): falling back to WSL for binary"
162
            << binary;
163
#endif
164
      QString out, err;
165
      if (Executor::executeBlocking(wslCommand, {"--version"}, &out, &err) ==
166
              0 &&
167
          !out.isEmpty() && err.isEmpty()) {
168
#ifdef QT_DEBUG
169
        dbg() << "Util::findBinaryInPath(): using WSL binary" << wslCommand;
170
#endif
171
        ret = wslCommand;
172
      }
173
    }
174
  }
175
#endif
176

177
  return ret;
12✔
178
}
179

180
/**
181
 * @brief Checks whether the current QtPass configuration is valid.
182
 * @example
183
 * bool result = Util::configIsValid();
184
 * std::cout << std::boolalpha << result << std::endl; // Expected output: true
185
 * or false
186
 *
187
 * @return bool - True if the configuration file exists and the required
188
 * executable is available; otherwise false.
189
 */
190
auto Util::configIsValid() -> bool {
1✔
191
  const QString configFilePath =
192
      QDir(QtPassSettings::getPassStore()).filePath(".gpg-id");
3✔
193
  if (!QFile(configFilePath).exists()) {
1✔
194
    return false;
195
  }
196

197
  const QString executable = QtPassSettings::isUsePass()
×
198
                                 ? QtPassSettings::getPassExecutable()
×
199
                                 : QtPassSettings::getGpgExecutable();
×
200

201
  if (executable.startsWith(QStringLiteral("wsl "))) {
×
202
    QString out;
×
203
    QString err;
×
204
    if (Executor::executeBlocking(QStringLiteral("wsl"),
×
205
                                  {QStringLiteral("--version")}, &out,
×
206
                                  &err) == 0 &&
×
207
        !out.isEmpty() && err.isEmpty()) {
×
208
      return true;
209
    }
210
  }
211
  return QFile(executable).exists();
×
212
}
213

214
/**
215
 * @brief Returns a directory path derived from a model index, optionally
216
 * relative to the pass store.
217
 * @example
218
 * QString result = Util::getDir(index, true, model, storeModel);
219
 * std::cout << result.toStdString() << std::endl; // Expected output: relative
220
 * directory path with trailing separator
221
 *
222
 * @param QModelIndex &index - Source index used to resolve the file or
223
 * directory path.
224
 * @param bool forPass - If true, returns a path relative to the pass store;
225
 * otherwise returns an absolute path.
226
 * @param QFileSystemModel &model - File system model used to obtain file
227
 * information.
228
 * @param StoreModel &storeModel - Proxy model used to map the provided index to
229
 * the source model.
230
 * @return QString - The resolved directory path, always ending with the
231
 * platform's directory separator.
232
 */
233
auto Util::getDir(const QModelIndex &index, bool forPass,
3✔
234
                  const QFileSystemModel &model, const StoreModel &storeModel)
235
    -> QString {
236
  QString abspath =
237
      QDir(QtPassSettings::getPassStore()).absolutePath() + QDir::separator();
9✔
238
  if (!index.isValid()) {
239
    return forPass ? "" : abspath;
2✔
240
  }
241
  QFileInfo info = model.fileInfo(storeModel.mapToSource(index));
1✔
242
  QString filePath =
243
      (info.isFile() ? info.absolutePath() : info.absoluteFilePath());
1✔
244
  if (forPass) {
1✔
245
    filePath = QDir(abspath).relativeFilePath(filePath);
×
246
  }
247
  filePath += QDir::separator();
1✔
248
  return filePath;
249
}
1✔
250

251
auto Util::endsWithGpg() -> const QRegularExpression & {
23✔
252
  static const QRegularExpression expr{"\\.gpg$"};
23✔
253
  return expr;
23✔
254
}
255

256
/**
257
 * @brief Returns a regex matching common remote/network protocol schemes.
258
 *
259
 * Matches http://, https://, ftp://, ftps://, ssh://, sftp://, webdav://,
260
 * webdavs://
261
 *
262
 * Note: Local file URLs (file:///) are intentionally excluded by design, as
263
 * they represent local paths rather than network protocols. If this behavior
264
 * needs to change, update both this function and the corresponding test.
265
 *
266
 * @return QRegularExpression reference
267
 */
268
auto Util::protocolRegex() -> const QRegularExpression & {
4✔
269
  static const QRegularExpression regex{
270
      "((?:https?|ftp|ssh|sftp|ftps|webdav|webdavs)://[^\" <>\\)\\]\\[]+)"};
4✔
271
  return regex;
4✔
272
}
273

274
auto Util::newLinesRegex() -> const QRegularExpression & {
8✔
275
  static const QRegularExpression regex{"[\r\n]"};
8✔
276
  return regex;
8✔
277
}
278

NEW
279
auto Util::isValidKeyId(const QString &keyId) -> bool {
×
NEW
280
  if (keyId.isEmpty()) {
×
281
    return false;
282
  }
283
  QString normalized = keyId;
NEW
284
  if (normalized.startsWith("0x")) {
×
NEW
285
    normalized = normalized.mid(2);
×
286
  }
287
  const int len = normalized.size();
NEW
288
  if (len < 8 || len > 40) {
×
289
    return false;
290
  }
NEW
291
  for (QChar c : normalized) {
×
292
    const char lc = c.toLatin1();
NEW
293
    if ((lc >= '0' && lc <= '9') || (lc >= 'A' && lc <= 'F') ||
×
294
        (lc >= 'a' && lc <= 'f')) {
295
      continue;
296
    }
297
    return false;
298
  }
299
  return true;
300
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc