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

IJHack / QtPass / 24575278984

17 Apr 2026 04:19PM UTC coverage: 21.226% (-0.1%) from 21.329%
24575278984

Pull #1032

github

web-flow
Merge b0d5002ee into 89ef5cc93
Pull Request #1032: fix: set PASSWORD_STORE_* env vars and grey out pass-managed controls

1 of 28 new or added lines in 2 files covered. (3.57%)

1 existing line in 1 file now uncovered.

1143 of 5385 relevant lines covered (21.23%)

8.05 hits per line

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

41.86
/src/pass.cpp
1
// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "pass.h"
4
#include "gpgkeystate.h"
5
#include "helpers.h"
6
#include "qtpasssettings.h"
7
#include "util.h"
8
#include <QDir>
9
#include <QFileInfo>
10
#include <QProcess>
11
#include <QRandomGenerator>
12
#include <QRegularExpression>
13
#include <utility>
14

15
#ifdef QT_DEBUG
16
#include "debughelper.h"
17
#endif
18

19
using Enums::GIT_INIT;
20
using Enums::GIT_PULL;
21
using Enums::GIT_PUSH;
22
using Enums::GPG_GENKEYS;
23
using Enums::PASS_COPY;
24
using Enums::PASS_INIT;
25
using Enums::PASS_INSERT;
26
using Enums::PASS_MOVE;
27
using Enums::PASS_OTP_GENERATE;
28
using Enums::PASS_REMOVE;
29
using Enums::PASS_SHOW;
30

31
/**
32
 * @brief Pass::Pass wrapper for using either pass or the pass imitation
33
 */
34
Pass::Pass() : wrapperRunning(false), env(QProcess::systemEnvironment()) {
9✔
35
  connect(&exec,
9✔
36
          static_cast<void (Executor::*)(int, int, const QString &,
37
                                         const QString &)>(&Executor::finished),
38
          this, &Pass::finished);
9✔
39

40
  // This was previously using direct QProcess signals.
41
  // The code now uses Executor instead of raw QProcess for better control.
42
  // connect(&process, SIGNAL(error(QProcess::ProcessError)), this,
43
  //        SIGNAL(error(QProcess::ProcessError)));
44

45
  connect(&exec, &Executor::starting, this, &Pass::startingExecuteWrapper);
9✔
46
  env.append("WSLENV=PASSWORD_STORE_DIR/p:PASSWORD_STORE_GENERATED_LENGTH/"
9✔
47
             "w:PASSWORD_STORE_CHARACTER_SET/w");
48
}
9✔
49

50
/**
51
 * @brief Executes a wrapper command.
52
 * @param id Process ID
53
 * @param app Application to execute
54
 * @param args Arguments
55
 * @param readStdout Whether to read stdout
56
 * @param readStderr Whether to read stderr
57
 */
58
void Pass::executeWrapper(PROCESS id, const QString &app,
×
59
                          const QStringList &args, bool readStdout,
60
                          bool readStderr) {
61
  executeWrapper(id, app, args, QString(), readStdout, readStderr);
×
62
}
×
63

64
void Pass::executeWrapper(PROCESS id, const QString &app,
×
65
                          const QStringList &args, QString input,
66
                          bool readStdout, bool readStderr) {
67
#ifdef QT_DEBUG
68
  dbg() << app << args;
69
#endif
70
  exec.execute(id, QtPassSettings::getPassStore(), app, args, std::move(input),
×
71
               readStdout, readStderr);
72
}
×
73

74
/**
75
 * @brief Initializes the pass wrapper environment.
76
 */
77
void Pass::init() {
1✔
78
#ifdef __APPLE__
79
  // If it exists, add the gpgtools to PATH
80
  if (QFile("/usr/local/MacGPG2/bin").exists())
81
    env.replaceInStrings("PATH=", "PATH=/usr/local/MacGPG2/bin:");
82
  // Add missing /usr/local/bin
83
  if (env.filter("/usr/local/bin").isEmpty())
84
    env.replaceInStrings("PATH=", "PATH=/usr/local/bin:");
85
#endif
86

87
  if (!QtPassSettings::getGpgHome().isEmpty()) {
2✔
88
    QDir absHome(QtPassSettings::getGpgHome());
×
89
    absHome.makeAbsolute();
×
90
    env << "GNUPGHOME=" + absHome.path();
×
91
  }
×
92
}
1✔
93

94
/**
95
 * @brief Pass::Generate use either pwgen or internal password
96
 * generator
97
 * @param length of the desired password
98
 * @param charset to use for generation
99
 * @return the password
100
 */
101
auto Pass::generatePassword(unsigned int length, const QString &charset)
1,006✔
102
    -> QString {
103
  QString passwd;
1,006✔
104
  if (QtPassSettings::isUsePwgen()) {
1,006✔
105
    // --secure goes first as it overrides --no-* otherwise
106
    QStringList args;
×
107
    args.append("-1");
×
108
    if (!QtPassSettings::isLessRandom()) {
×
109
      args.append("--secure");
×
110
    }
111
    args.append(QtPassSettings::isAvoidCapitals() ? "--no-capitalize"
×
112
                                                  : "--capitalize");
113
    args.append(QtPassSettings::isAvoidNumbers() ? "--no-numerals"
×
114
                                                 : "--numerals");
115
    if (QtPassSettings::isUseSymbols()) {
×
116
      args.append("--symbols");
×
117
    }
118
    args.append(QString::number(length));
×
119
    // executeBlocking returns 0 on success, non-zero on failure
120
    if (Executor::executeBlocking(QtPassSettings::getPwgenExecutable(), args,
×
121
                                  &passwd) == 0) {
122
      static const QRegularExpression literalNewLines{"[\\n\\r]"};
×
123
      passwd.remove(literalNewLines);
×
124
    } else {
125
      passwd.clear();
×
126
#ifdef QT_DEBUG
127
      qDebug() << __FILE__ << ":" << __LINE__ << "\t"
128
               << "pwgen fail";
129
#endif
130
      // Error is already handled by clearing passwd; no need for critical
131
      // signal here
132
    }
133
  } else {
134
    // Validate charset - if CUSTOM is selected but chars are empty,
135
    // fall back to ALLCHARS to prevent weak passwords (issue #780)
136
    QString effectiveCharset = charset;
137
    if (effectiveCharset.isEmpty()) {
1,006✔
138
      effectiveCharset = QtPassSettings::getPasswordConfiguration()
2✔
139
                             .Characters[PasswordConfiguration::ALLCHARS];
140
    }
141
    if (effectiveCharset.length() > 0) {
1,006✔
142
      passwd = generateRandomPassword(effectiveCharset, length);
2,012✔
143
    } else {
144
      emit critical(
×
145
          tr("No characters chosen"),
×
146
          tr("Can't generate password, there are no characters to choose from "
×
147
             "set in the configuration!"));
148
    }
149
  }
150
  return passwd;
1,006✔
151
}
152

153
/**
154
 * @brief Pass::gpgSupportsEd25519 check if GPG supports ed25519 (ECC)
155
 * GPG 2.1+ supports ed25519 which is much faster for key generation
156
 * @return true if ed25519 is supported
157
 */
158
bool Pass::gpgSupportsEd25519() {
2✔
159
  QString out, err;
2✔
160
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(),
8✔
161
                                {"--version"}, &out, &err) != 0) {
162
    return false;
163
  }
164
  QRegularExpression versionRegex(R"(gpg \(GnuPG\) (\d+)\.(\d+))");
×
165
  QRegularExpressionMatch match = versionRegex.match(out);
×
166
  if (!match.hasMatch()) {
×
167
    return false;
168
  }
169
  int major = match.captured(1).toInt();
×
170
  int minor = match.captured(2).toInt();
×
171
  return major > 2 || (major == 2 && minor >= 1);
×
172
}
2✔
173

174
/**
175
 * @brief Pass::getDefaultKeyTemplate return default key generation template
176
 * Uses ed25519 if supported, otherwise falls back to RSA
177
 * @return GPG batch template string
178
 */
179
QString Pass::getDefaultKeyTemplate() {
1✔
180
  if (gpgSupportsEd25519()) {
1✔
181
    return QStringLiteral("%echo Generating a default key\n"
×
182
                          "Key-Type: EdDSA\n"
183
                          "Key-Curve: Ed25519\n"
184
                          "Subkey-Type: ECDH\n"
185
                          "Subkey-Curve: Curve25519\n"
186
                          "Name-Real: \n"
187
                          "Name-Comment: QtPass\n"
188
                          "Name-Email: \n"
189
                          "Expire-Date: 0\n"
190
                          "%no-protection\n"
191
                          "%commit\n"
192
                          "%echo done");
193
  }
194
  return QStringLiteral("%echo Generating a default key\n"
1✔
195
                        "Key-Type: RSA\n"
196
                        "Subkey-Type: RSA\n"
197
                        "Name-Real: \n"
198
                        "Name-Comment: QtPass\n"
199
                        "Name-Email: \n"
200
                        "Expire-Date: 0\n"
201
                        "%no-protection\n"
202
                        "%commit\n"
203
                        "%echo done");
204
}
205

206
namespace {
207
auto resolveWslGpgconfPath(const QString &lastPart) -> QString {
3✔
208
  int lastSep = lastPart.lastIndexOf('/');
3✔
209
  if (lastSep < 0) {
3✔
210
    lastSep = lastPart.lastIndexOf('\\');
2✔
211
  }
212
  if (lastSep >= 0) {
2✔
213
    return lastPart.left(lastSep + 1) + "gpgconf";
2✔
214
  }
215
  return QStringLiteral("gpgconf");
2✔
216
}
217

218
/**
219
 * @brief Finds the path to the gpgconf executable in the same directory as the
220
 * given GPG path.
221
 * @example
222
 * QString result = findGpgconfInGpgDir(gpgPath);
223
 * std::cout << result.toStdString() << std::endl; // Expected output: path to
224
 * gpgconf or empty string
225
 *
226
 * @param gpgPath - Absolute path to a GPG executable or related file used to
227
 * locate gpgconf.
228
 * @return QString - The full path to gpgconf if found and executable; otherwise
229
 * an empty QString.
230
 */
231
QString findGpgconfInGpgDir(const QString &gpgPath) {
1✔
232
  QFileInfo gpgInfo(gpgPath);
1✔
233
  if (!gpgInfo.isAbsolute()) {
1✔
234
    return QString();
235
  }
236

237
  QDir dir(gpgInfo.absolutePath());
1✔
238

239
#ifdef Q_OS_WIN
240
  QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
241
  if (candidateExe.isExecutable()) {
242
    return candidateExe.filePath();
243
  }
244
#endif
245

246
  QFileInfo candidate(dir.filePath("gpgconf"));
1✔
247
  if (candidate.isExecutable()) {
1✔
248
    return candidate.filePath();
×
249
  }
250
  return QString();
251
}
1✔
252

253
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
254
/**
255
 * @brief Splits a command string into arguments while respecting quotes and
256
 * escape characters.
257
 * @example
258
 * QStringList result = splitCommandCompat("cmd \"arg one\" 'arg two'
259
 * escaped\\ space");
260
 * // Expected output: ["cmd", "arg one", "arg two", "escaped space"]
261
 *
262
 * @param command - The input command string to split into individual arguments.
263
 * @return QStringList - A list of parsed command arguments.
264
 */
265
QStringList splitCommandCompat(const QString &command) {
266
  QStringList result;
267
  QString current;
268
  bool inSingleQuote = false;
269
  bool inDoubleQuote = false;
270
  bool escaping = false;
271
  for (QChar ch : command) {
272
    if (escaping) {
273
      current.append(ch);
274
      escaping = false;
275
      continue;
276
    }
277
    if (ch == '\\') {
278
      escaping = true;
279
      continue;
280
    }
281
    if (ch == '\'' && !inDoubleQuote) {
282
      inSingleQuote = !inSingleQuote;
283
      continue;
284
    }
285
    if (ch == '"' && !inSingleQuote) {
286
      inDoubleQuote = !inDoubleQuote;
287
      continue;
288
    }
289
    if (ch.isSpace() && !inSingleQuote && !inDoubleQuote) {
290
      if (!current.isEmpty()) {
291
        result.append(current);
292
        current.clear();
293
      }
294
      continue;
295
    }
296
    current.append(ch);
297
  }
298
  if (escaping) {
299
    current.append('\\');
300
  }
301
  if (!current.isEmpty()) {
302
    result.append(current);
303
  }
304
  return result;
305
}
306
#endif
307

308
} // namespace
309

310
/**
311
 * @brief Resolves the appropriate gpgconf command from a given GPG executable
312
 * path or command string.
313
 * @example
314
 * ResolvedGpgconfCommand result = Pass::resolveGpgconfCommand("wsl.exe
315
 * /usr/bin/gpg"); std::cout << result.first.toStdString() << std::endl; //
316
 * Expected output sample
317
 *
318
 * @param const QString &gpgPath - Path or command string pointing to the GPG
319
 * executable.
320
 * @return ResolvedGpgconfCommand - A pair containing the resolved gpgconf
321
 * command and its arguments.
322
 */
323
auto Pass::resolveGpgconfCommand(const QString &gpgPath)
8✔
324
    -> ResolvedGpgconfCommand {
325
  if (gpgPath.trimmed().isEmpty()) {
8✔
326
    return {"gpgconf", {}};
327
  }
328

329
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
330
  QStringList parts = QProcess::splitCommand(gpgPath);
7✔
331
#else
332
  QStringList parts = splitCommandCompat(gpgPath);
333
#endif
334

335
  if (parts.isEmpty()) {
7✔
336
    return {"gpgconf", {}};
337
  }
338

339
  const QString first = parts.first();
340
  if (first == "wsl" || first == "wsl.exe") {
9✔
341
    if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
9✔
342
      return {"gpgconf", {}};
343
    }
344
    if (parts.size() >= 2 &&
4✔
345
        QFileInfo(parts.last()).fileName().startsWith("gpg")) {
10✔
346
      QString wslGpgconf = resolveWslGpgconfPath(parts.last());
3✔
347
      parts.removeLast();
3✔
348
      parts.append(wslGpgconf);
349
      return {parts.first(), parts.mid(1)};
350
    }
351
    return {"gpgconf", {}};
352
  }
353

354
  if (!first.contains('/') && !first.contains('\\')) {
2✔
355
    return {"gpgconf", {}};
356
  }
357

358
  QString gpgconfPath = findGpgconfInGpgDir(gpgPath);
1✔
359
  if (!gpgconfPath.isEmpty()) {
1✔
360
    return {gpgconfPath, {}};
×
361
  }
362

363
  return {"gpgconf", {}};
364
}
8✔
365

366
/**
367
 * @brief Pass::GenerateGPGKeys internal gpg keypair generator . .
368
 * @param batch GnuPG style configuration string
369
 */
370
void Pass::GenerateGPGKeys(QString batch) {
×
371
  // Kill any stale GPG agents that might be holding locks on the key database
372
  // This helps avoid "database locked" timeouts during key generation
373
  QString gpgPath = QtPassSettings::getGpgExecutable();
×
374
  if (!gpgPath.isEmpty()) {
×
375
    ResolvedGpgconfCommand gpgconf = resolveGpgconfCommand(gpgPath);
×
376
    QStringList killArgs = gpgconf.arguments;
377
    killArgs << "--kill";
×
378
    killArgs << "gpg-agent";
×
379
    // Use same environment as key generation to target correct gpg-agent
380
    Executor::executeBlocking(env, gpgconf.program, killArgs);
×
381
  }
382

383
  executeWrapper(GPG_GENKEYS, gpgPath, {"--gen-key", "--no-tty", "--batch"},
×
384
                 std::move(batch));
385
}
×
386

387
/**
388
 * @brief Pass::listKeys list users
389
 * @param keystrings
390
 * @param secret list private keys
391
 * @return QList<UserInfo> users
392
 */
393
auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
×
394
  QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
×
395
  args.append(secret ? "--list-secret-keys" : "--list-keys");
×
396

397
  for (const QString &keystring : AS_CONST(keystrings)) {
×
398
    if (!keystring.isEmpty()) {
×
399
      args.append(keystring);
400
    }
401
  }
402
  QString p_out;
×
403
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
404
                                &p_out) != 0) {
405
    return QList<UserInfo>();
×
406
  }
407
  return parseGpgColonOutput(p_out, secret);
×
408
}
×
409

410
/**
411
 * @brief Pass::listKeys list users
412
 * @param keystring
413
 * @param secret list private keys
414
 * @return QList<UserInfo> users
415
 */
416
auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
×
417
  return listKeys(QStringList(keystring), secret);
×
418
}
419

420
/**
421
 * @brief Pass::processFinished reemits specific signal based on what process
422
 * has finished
423
 * @param id    id of Pass process that was scheduled and finished
424
 * @param exitCode  return code of a process
425
 * @param out   output generated by process(if capturing was requested, empty
426
 *              otherwise)
427
 * @param err   error output generated by process(if capturing was requested,
428
 *              or error occurred)
429
 */
430
void Pass::finished(int id, int exitCode, const QString &out,
×
431
                    const QString &err) {
432
  auto pid = static_cast<PROCESS>(id);
433
  if (exitCode != 0) {
×
434
    emit processErrorExit(exitCode, err);
×
435
    return;
×
436
  }
437
  switch (pid) {
×
438
  case GIT_INIT:
×
439
    emit finishedGitInit(out, err);
×
440
    break;
×
441
  case GIT_PULL:
×
442
    emit finishedGitPull(out, err);
×
443
    break;
×
444
  case GIT_PUSH:
×
445
    emit finishedGitPush(out, err);
×
446
    break;
×
447
  case PASS_SHOW:
×
448
    emit finishedShow(out);
×
449
    break;
×
450
  case PASS_OTP_GENERATE:
×
451
    emit finishedOtpGenerate(out);
×
452
    break;
×
453
  case PASS_INSERT:
×
454
    emit finishedInsert(out, err);
×
455
    break;
×
456
  case PASS_REMOVE:
×
457
    emit finishedRemove(out, err);
×
458
    break;
×
459
  case PASS_INIT:
×
460
    emit finishedInit(out, err);
×
461
    break;
×
462
  case PASS_MOVE:
×
463
    emit finishedMove(out, err);
×
464
    break;
×
465
  case PASS_COPY:
×
466
    emit finishedCopy(out, err);
×
467
    break;
×
468
  case GPG_GENKEYS:
×
469
    emit finishedGenerateGPGKeys(out, err);
×
470
    break;
×
471
  default:
472
#ifdef QT_DEBUG
473
    dbg() << "Unhandled process type" << pid;
474
#endif
475
    break;
476
  }
477
}
478

479
/**
480
 * @brief Pass::updateEnv update the execution environment (used when
481
 * switching profiles)
482
 */
483
void Pass::updateEnv() {
×
484
  // put PASSWORD_STORE_SIGNING_KEY in env
485
  QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY=");
×
486
  QString currentSigningKey = QtPassSettings::getPassSigningKey();
×
487
  if (envSigningKey.isEmpty()) {
×
488
    if (!currentSigningKey.isEmpty()) {
×
489
      // dbg()<< "Added
490
      // PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey;
491
      env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
492
    }
493
  } else {
494
    if (currentSigningKey.isEmpty()) {
×
495
      env.removeAll(envSigningKey.first());
496
    } else {
497
      // dbg()<< "Update
498
      // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey;
499
      env.replaceInStrings(envSigningKey.first(),
×
500
                           "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
501
    }
502
  }
503
  // put PASSWORD_STORE_DIR in env
504
  QStringList store = env.filter("PASSWORD_STORE_DIR=");
×
505
  if (store.isEmpty()) {
×
506
    env.append("PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore());
×
507
  } else {
508
    // dbg()<< "Update
509
    // PASSWORD_STORE_DIR with " + passStore;
510
    env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" +
×
511
                                            QtPassSettings::getPassStore());
×
512
  }
513
  // put PASSWORD_STORE_GENERATED_LENGTH in env
NEW
514
  PasswordConfiguration passConfig = QtPassSettings::getPasswordConfiguration();
×
NEW
515
  QString lenKey = QStringLiteral("PASSWORD_STORE_GENERATED_LENGTH=");
×
NEW
516
  QString lenVal = lenKey + QString::number(passConfig.length);
×
517
  QStringList envLen = env.filter(lenKey);
NEW
518
  if (envLen.isEmpty()) {
×
519
    env.append(lenVal);
520
  } else {
NEW
521
    env.replaceInStrings(envLen.first(), lenVal);
×
522
  }
523
  // put PASSWORD_STORE_CHARACTER_SET in env (clamp selected to valid range)
NEW
524
  int sel = passConfig.selected;
×
NEW
525
  if (sel < 0 || sel >= PasswordConfiguration::CHARSETS_COUNT)
×
526
    sel = PasswordConfiguration::ALLCHARS;
527
  QString charset = passConfig.Characters[sel];
NEW
528
  QString charKey = QStringLiteral("PASSWORD_STORE_CHARACTER_SET=");
×
529
  QStringList envChar = env.filter(charKey);
NEW
530
  if (charset.isEmpty()) {
×
NEW
531
    for (const QString &entry : envChar)
×
532
      env.removeAll(entry);
533
  } else {
NEW
534
    QString charVal = charKey + charset;
×
NEW
535
    if (envChar.isEmpty()) {
×
536
      env.append(charVal);
537
    } else {
NEW
538
      env.replaceInStrings(envChar.first(), charVal);
×
539
    }
540
  }
541
  exec.setEnvironment(env);
×
542
}
×
543

544
/**
545
 * @brief Pass::getGpgIdPath return gpgid file path for some file (folder).
546
 * @param for_file which file (folder) would you like the gpgid file path for.
547
 * @return path to the gpgid file.
548
 */
549
auto Pass::getGpgIdPath(const QString &for_file) -> QString {
9✔
550
  QString passStore =
551
      QDir::fromNativeSeparators(QtPassSettings::getPassStore());
18✔
552
  QString normalizedFile = QDir::fromNativeSeparators(for_file);
9✔
553
  QString fullPath = normalizedFile.startsWith(passStore)
9✔
554
                         ? normalizedFile
9✔
555
                         : passStore + "/" + normalizedFile;
7✔
556
  QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
9✔
557
  bool found = false;
558
  while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
11✔
559
    if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
2✔
560
      found = true;
561
      break;
562
    }
563
    if (!gpgIdDir.cdUp()) {
×
564
      break;
565
    }
566
  }
567
  QString gpgIdPath(
568
      found ? gpgIdDir.absoluteFilePath(".gpg-id")
9✔
569
            : QDir(QtPassSettings::getPassStore()).filePath(".gpg-id"));
33✔
570

571
  return gpgIdPath;
9✔
572
}
9✔
573

574
/**
575
 * @brief Pass::getRecipientList return list of gpg-id's to encrypt for
576
 * @param for_file which file (folder) would you like recipients for
577
 * @return recipients gpg-id contents
578
 */
579
auto Pass::getRecipientList(const QString &for_file) -> QStringList {
6✔
580
  QFile gpgId(getGpgIdPath(for_file));
6✔
581
  if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
6✔
582
    return {};
×
583
  }
584
  QStringList recipients;
6✔
585
  while (!gpgId.atEnd()) {
20✔
586
    QString recipient(gpgId.readLine());
28✔
587
    recipient = recipient.split("#")[0].trimmed();
28✔
588
    if (!recipient.isEmpty() && Util::isValidKeyId(recipient)) {
14✔
589
      recipients += recipient;
590
    }
591
  }
592
  return recipients;
593
}
6✔
594

595
/**
596
 * @brief Pass::getRecipientString formatted string for use with GPG
597
 * @param for_file which file (folder) would you like recipients for
598
 * @param separator formating separator eg: " -r "
599
 * @param count
600
 * @return recipient string
601
 */
602
auto Pass::getRecipientString(const QString &for_file, const QString &separator,
2✔
603
                              int *count) -> QStringList {
604
  Q_UNUSED(separator)
605
  QStringList recipients = Pass::getRecipientList(for_file);
2✔
606
  if (count) {
2✔
607
    *count = recipients.size();
1✔
608
  }
609
  return recipients;
2✔
610
}
611

612
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
613
 */
614

615
/**
616
 * @brief Generates a random number bounded by the given value.
617
 * @param bound Upper bound (exclusive)
618
 * @return Random number in range [0, bound)
619
 */
620
auto Pass::boundedRandom(quint32 bound) -> quint32 {
1,224✔
621
  if (bound < 2) {
1,224✔
622
    return 0;
623
  }
624

625
  quint32 randval;
626
  // Rejection-sampling threshold to avoid modulo bias:
627
  // In quint32 arithmetic, (1 + ~bound) wraps to (2^32 - bound), so
628
  // (1 + ~bound) % bound == 2^32 % bound.
629
  // Values randval < max_mod_bound are rejected; accepted values produce a
630
  // uniform distribution when reduced with (randval % bound).
631
  const quint32 max_mod_bound = (1 + ~bound) % bound;
1,224✔
632

633
  do {
634
    randval = QRandomGenerator::system()->generate();
635
  } while (randval < max_mod_bound);
1,224✔
636

637
  return randval % bound;
1,224✔
638
}
639

640
/**
641
 * @brief Generates a random password from the given charset.
642
 * @param charset Characters to use in the password
643
 * @param length Desired password length
644
 * @return Generated password string
645
 */
646
auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
1,006✔
647
    -> QString {
648
  if (charset.isEmpty() || length == 0U) {
1,006✔
649
    return {};
650
  }
651
  QString out;
1,005✔
652
  for (unsigned int i = 0; i < length; ++i) {
2,229✔
653
    out.append(charset.at(static_cast<int>(
1,224✔
654
        boundedRandom(static_cast<quint32>(charset.length())))));
1,224✔
655
  }
656
  return out;
657
}
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