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

IJHack / QtPass / 24581912466

17 Apr 2026 07:00PM UTC coverage: 21.789% (+0.5%) from 21.329%
24581912466

Pull #1032

github

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

26 of 44 new or added lines in 3 files covered. (59.09%)

1 existing line in 1 file now uncovered.

1174 of 5388 relevant lines covered (21.79%)

8.3 hits per line

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

53.7
/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()) {
16✔
35
  connect(&exec,
16✔
36
          static_cast<void (Executor::*)(int, int, const QString &,
37
                                         const QString &)>(&Executor::finished),
38
          this, &Pass::finished);
16✔
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);
16✔
46
  // Merge our vars into WSLENV rather than blindly appending a duplicate entry
47
  const QStringList wslenvVars = {
48
      QStringLiteral("PASSWORD_STORE_DIR/p"),
32✔
49
      QStringLiteral("PASSWORD_STORE_GENERATED_LENGTH/w"),
16✔
50
      QStringLiteral("PASSWORD_STORE_CHARACTER_SET/w")};
64✔
51
  QStringList existing = env.filter(QStringLiteral("WSLENV="));
32✔
52
  if (existing.isEmpty()) {
16✔
53
    env.append(QStringLiteral("WSLENV=") + wslenvVars.join(':'));
32✔
54
  } else {
55
    QString current = existing.first();
56
    QStringList parts =
NEW
57
        current.mid(7).split(':', Qt::SkipEmptyParts); // skip "WSLENV="
×
NEW
58
    for (const QString &v : wslenvVars) {
×
NEW
59
      if (!parts.contains(v))
×
60
        parts.append(v);
61
    }
NEW
62
    env.replaceInStrings(current, QStringLiteral("WSLENV=") + parts.join(':'));
×
63
  }
64
}
16✔
65

66
/**
67
 * @brief Executes a wrapper command.
68
 * @param id Process ID
69
 * @param app Application to execute
70
 * @param args Arguments
71
 * @param readStdout Whether to read stdout
72
 * @param readStderr Whether to read stderr
73
 */
74
void Pass::executeWrapper(PROCESS id, const QString &app,
×
75
                          const QStringList &args, bool readStdout,
76
                          bool readStderr) {
77
  executeWrapper(id, app, args, QString(), readStdout, readStderr);
×
78
}
×
79

80
void Pass::executeWrapper(PROCESS id, const QString &app,
×
81
                          const QStringList &args, QString input,
82
                          bool readStdout, bool readStderr) {
83
#ifdef QT_DEBUG
84
  dbg() << app << args;
85
#endif
86
  exec.execute(id, QtPassSettings::getPassStore(), app, args, std::move(input),
×
87
               readStdout, readStderr);
88
}
×
89

90
/**
91
 * @brief Initializes the pass wrapper environment.
92
 */
93
void Pass::init() {
1✔
94
#ifdef __APPLE__
95
  // If it exists, add the gpgtools to PATH
96
  if (QFile("/usr/local/MacGPG2/bin").exists())
97
    env.replaceInStrings("PATH=", "PATH=/usr/local/MacGPG2/bin:");
98
  // Add missing /usr/local/bin
99
  if (env.filter("/usr/local/bin").isEmpty())
100
    env.replaceInStrings("PATH=", "PATH=/usr/local/bin:");
101
#endif
102

103
  if (!QtPassSettings::getGpgHome().isEmpty()) {
2✔
104
    QDir absHome(QtPassSettings::getGpgHome());
×
105
    absHome.makeAbsolute();
×
106
    env << "GNUPGHOME=" + absHome.path();
×
107
  }
×
108
}
1✔
109

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

169
/**
170
 * @brief Pass::gpgSupportsEd25519 check if GPG supports ed25519 (ECC)
171
 * GPG 2.1+ supports ed25519 which is much faster for key generation
172
 * @return true if ed25519 is supported
173
 */
174
bool Pass::gpgSupportsEd25519() {
2✔
175
  QString out, err;
2✔
176
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(),
8✔
177
                                {"--version"}, &out, &err) != 0) {
178
    return false;
179
  }
180
  QRegularExpression versionRegex(R"(gpg \(GnuPG\) (\d+)\.(\d+))");
×
181
  QRegularExpressionMatch match = versionRegex.match(out);
×
182
  if (!match.hasMatch()) {
×
183
    return false;
184
  }
185
  int major = match.captured(1).toInt();
×
186
  int minor = match.captured(2).toInt();
×
187
  return major > 2 || (major == 2 && minor >= 1);
×
188
}
2✔
189

190
/**
191
 * @brief Pass::getDefaultKeyTemplate return default key generation template
192
 * Uses ed25519 if supported, otherwise falls back to RSA
193
 * @return GPG batch template string
194
 */
195
QString Pass::getDefaultKeyTemplate() {
1✔
196
  if (gpgSupportsEd25519()) {
1✔
197
    return QStringLiteral("%echo Generating a default key\n"
×
198
                          "Key-Type: EdDSA\n"
199
                          "Key-Curve: Ed25519\n"
200
                          "Subkey-Type: ECDH\n"
201
                          "Subkey-Curve: Curve25519\n"
202
                          "Name-Real: \n"
203
                          "Name-Comment: QtPass\n"
204
                          "Name-Email: \n"
205
                          "Expire-Date: 0\n"
206
                          "%no-protection\n"
207
                          "%commit\n"
208
                          "%echo done");
209
  }
210
  return QStringLiteral("%echo Generating a default key\n"
1✔
211
                        "Key-Type: RSA\n"
212
                        "Subkey-Type: RSA\n"
213
                        "Name-Real: \n"
214
                        "Name-Comment: QtPass\n"
215
                        "Name-Email: \n"
216
                        "Expire-Date: 0\n"
217
                        "%no-protection\n"
218
                        "%commit\n"
219
                        "%echo done");
220
}
221

222
namespace {
223
auto resolveWslGpgconfPath(const QString &lastPart) -> QString {
3✔
224
  int lastSep = lastPart.lastIndexOf('/');
3✔
225
  if (lastSep < 0) {
3✔
226
    lastSep = lastPart.lastIndexOf('\\');
2✔
227
  }
228
  if (lastSep >= 0) {
2✔
229
    return lastPart.left(lastSep + 1) + "gpgconf";
2✔
230
  }
231
  return QStringLiteral("gpgconf");
2✔
232
}
233

234
/**
235
 * @brief Finds the path to the gpgconf executable in the same directory as the
236
 * given GPG path.
237
 * @example
238
 * QString result = findGpgconfInGpgDir(gpgPath);
239
 * std::cout << result.toStdString() << std::endl; // Expected output: path to
240
 * gpgconf or empty string
241
 *
242
 * @param gpgPath - Absolute path to a GPG executable or related file used to
243
 * locate gpgconf.
244
 * @return QString - The full path to gpgconf if found and executable; otherwise
245
 * an empty QString.
246
 */
247
QString findGpgconfInGpgDir(const QString &gpgPath) {
1✔
248
  QFileInfo gpgInfo(gpgPath);
1✔
249
  if (!gpgInfo.isAbsolute()) {
1✔
250
    return QString();
251
  }
252

253
  QDir dir(gpgInfo.absolutePath());
1✔
254

255
#ifdef Q_OS_WIN
256
  QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
257
  if (candidateExe.isExecutable()) {
258
    return candidateExe.filePath();
259
  }
260
#endif
261

262
  QFileInfo candidate(dir.filePath("gpgconf"));
1✔
263
  if (candidate.isExecutable()) {
1✔
264
    return candidate.filePath();
×
265
  }
266
  return QString();
267
}
1✔
268

269
#if QT_VERSION < QT_VERSION_CHECK(5, 15, 0)
270
/**
271
 * @brief Splits a command string into arguments while respecting quotes and
272
 * escape characters.
273
 * @example
274
 * QStringList result = splitCommandCompat("cmd \"arg one\" 'arg two'
275
 * escaped\\ space");
276
 * // Expected output: ["cmd", "arg one", "arg two", "escaped space"]
277
 *
278
 * @param command - The input command string to split into individual arguments.
279
 * @return QStringList - A list of parsed command arguments.
280
 */
281
QStringList splitCommandCompat(const QString &command) {
282
  QStringList result;
283
  QString current;
284
  bool inSingleQuote = false;
285
  bool inDoubleQuote = false;
286
  bool escaping = false;
287
  for (QChar ch : command) {
288
    if (escaping) {
289
      current.append(ch);
290
      escaping = false;
291
      continue;
292
    }
293
    if (ch == '\\') {
294
      escaping = true;
295
      continue;
296
    }
297
    if (ch == '\'' && !inDoubleQuote) {
298
      inSingleQuote = !inSingleQuote;
299
      continue;
300
    }
301
    if (ch == '"' && !inSingleQuote) {
302
      inDoubleQuote = !inDoubleQuote;
303
      continue;
304
    }
305
    if (ch.isSpace() && !inSingleQuote && !inDoubleQuote) {
306
      if (!current.isEmpty()) {
307
        result.append(current);
308
        current.clear();
309
      }
310
      continue;
311
    }
312
    current.append(ch);
313
  }
314
  if (escaping) {
315
    current.append('\\');
316
  }
317
  if (!current.isEmpty()) {
318
    result.append(current);
319
  }
320
  return result;
321
}
322
#endif
323

324
} // namespace
325

326
/**
327
 * @brief Resolves the appropriate gpgconf command from a given GPG executable
328
 * path or command string.
329
 * @example
330
 * ResolvedGpgconfCommand result = Pass::resolveGpgconfCommand("wsl.exe
331
 * /usr/bin/gpg"); std::cout << result.first.toStdString() << std::endl; //
332
 * Expected output sample
333
 *
334
 * @param const QString &gpgPath - Path or command string pointing to the GPG
335
 * executable.
336
 * @return ResolvedGpgconfCommand - A pair containing the resolved gpgconf
337
 * command and its arguments.
338
 */
339
auto Pass::resolveGpgconfCommand(const QString &gpgPath)
8✔
340
    -> ResolvedGpgconfCommand {
341
  if (gpgPath.trimmed().isEmpty()) {
8✔
342
    return {"gpgconf", {}};
343
  }
344

345
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
346
  QStringList parts = QProcess::splitCommand(gpgPath);
7✔
347
#else
348
  QStringList parts = splitCommandCompat(gpgPath);
349
#endif
350

351
  if (parts.isEmpty()) {
7✔
352
    return {"gpgconf", {}};
353
  }
354

355
  const QString first = parts.first();
356
  if (first == "wsl" || first == "wsl.exe") {
9✔
357
    if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
9✔
358
      return {"gpgconf", {}};
359
    }
360
    if (parts.size() >= 2 &&
4✔
361
        QFileInfo(parts.last()).fileName().startsWith("gpg")) {
10✔
362
      QString wslGpgconf = resolveWslGpgconfPath(parts.last());
3✔
363
      parts.removeLast();
3✔
364
      parts.append(wslGpgconf);
365
      return {parts.first(), parts.mid(1)};
366
    }
367
    return {"gpgconf", {}};
368
  }
369

370
  if (!first.contains('/') && !first.contains('\\')) {
2✔
371
    return {"gpgconf", {}};
372
  }
373

374
  QString gpgconfPath = findGpgconfInGpgDir(gpgPath);
1✔
375
  if (!gpgconfPath.isEmpty()) {
1✔
376
    return {gpgconfPath, {}};
×
377
  }
378

379
  return {"gpgconf", {}};
380
}
8✔
381

382
/**
383
 * @brief Pass::GenerateGPGKeys internal gpg keypair generator . .
384
 * @param batch GnuPG style configuration string
385
 */
386
void Pass::GenerateGPGKeys(QString batch) {
×
387
  // Kill any stale GPG agents that might be holding locks on the key database
388
  // This helps avoid "database locked" timeouts during key generation
389
  QString gpgPath = QtPassSettings::getGpgExecutable();
×
390
  if (!gpgPath.isEmpty()) {
×
391
    ResolvedGpgconfCommand gpgconf = resolveGpgconfCommand(gpgPath);
×
392
    QStringList killArgs = gpgconf.arguments;
393
    killArgs << "--kill";
×
394
    killArgs << "gpg-agent";
×
395
    // Use same environment as key generation to target correct gpg-agent
396
    Executor::executeBlocking(env, gpgconf.program, killArgs);
×
397
  }
398

399
  executeWrapper(GPG_GENKEYS, gpgPath, {"--gen-key", "--no-tty", "--batch"},
×
400
                 std::move(batch));
401
}
×
402

403
/**
404
 * @brief Pass::listKeys list users
405
 * @param keystrings
406
 * @param secret list private keys
407
 * @return QList<UserInfo> users
408
 */
409
auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
×
410
  QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
×
411
  args.append(secret ? "--list-secret-keys" : "--list-keys");
×
412

413
  for (const QString &keystring : AS_CONST(keystrings)) {
×
414
    if (!keystring.isEmpty()) {
×
415
      args.append(keystring);
416
    }
417
  }
418
  QString p_out;
×
419
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
420
                                &p_out) != 0) {
421
    return QList<UserInfo>();
×
422
  }
423
  return parseGpgColonOutput(p_out, secret);
×
424
}
×
425

426
/**
427
 * @brief Pass::listKeys list users
428
 * @param keystring
429
 * @param secret list private keys
430
 * @return QList<UserInfo> users
431
 */
432
auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
×
433
  return listKeys(QStringList(keystring), secret);
×
434
}
435

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

495
/**
496
 * @brief Pass::updateEnv update the execution environment (used when
497
 * switching profiles)
498
 */
499
// key must include the trailing '=' (e.g. "FOO="); env.filter() does substring
500
// matching so the '=' anchors the lookup to avoid collisions with longer names.
501
void Pass::setEnvVar(const QString &key, const QString &value) {
38✔
502
  Q_ASSERT(key.endsWith('='));
503
  const QStringList existing = env.filter(key);
38✔
504
  for (const QString &entry : std::as_const(existing))
43✔
505
    env.removeAll(entry);
506
  if (!value.isEmpty())
38✔
507
    env.append(key + value);
56✔
508
}
38✔
509

510
void Pass::updateEnv() {
8✔
511
  setEnvVar(QStringLiteral("PASSWORD_STORE_SIGNING_KEY="),
16✔
512
            QtPassSettings::getPassSigningKey());
16✔
513
  setEnvVar(QStringLiteral("PASSWORD_STORE_DIR="),
16✔
514
            QtPassSettings::getPassStore());
16✔
515

516
  PasswordConfiguration passConfig = QtPassSettings::getPasswordConfiguration();
8✔
517
  setEnvVar(QStringLiteral("PASSWORD_STORE_GENERATED_LENGTH="),
16✔
518
            QString::number(passConfig.length));
8✔
519

520
  int sel = passConfig.selected;
8✔
521
  if (sel < 0 || sel >= PasswordConfiguration::CHARSETS_COUNT)
8✔
522
    sel = PasswordConfiguration::ALLCHARS;
523
  QString charset = passConfig.Characters[sel];
524
  if (charset.isEmpty())
8✔
525
    charset = passConfig.Characters[PasswordConfiguration::ALLCHARS];
1✔
526
  setEnvVar(QStringLiteral("PASSWORD_STORE_CHARACTER_SET="), charset);
16✔
527

528
  exec.setEnvironment(env);
8✔
529
}
8✔
530

531
/**
532
 * @brief Pass::getGpgIdPath return gpgid file path for some file (folder).
533
 * @param for_file which file (folder) would you like the gpgid file path for.
534
 * @return path to the gpgid file.
535
 */
536
auto Pass::getGpgIdPath(const QString &for_file) -> QString {
9✔
537
  QString passStore =
538
      QDir::fromNativeSeparators(QtPassSettings::getPassStore());
18✔
539
  QString normalizedFile = QDir::fromNativeSeparators(for_file);
9✔
540
  QString fullPath = normalizedFile.startsWith(passStore)
9✔
541
                         ? normalizedFile
9✔
542
                         : passStore + "/" + normalizedFile;
7✔
543
  QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
9✔
544
  bool found = false;
545
  while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
11✔
546
    if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
2✔
547
      found = true;
548
      break;
549
    }
550
    if (!gpgIdDir.cdUp()) {
×
551
      break;
552
    }
553
  }
554
  QString gpgIdPath(
555
      found ? gpgIdDir.absoluteFilePath(".gpg-id")
9✔
556
            : QDir(QtPassSettings::getPassStore()).filePath(".gpg-id"));
33✔
557

558
  return gpgIdPath;
9✔
559
}
9✔
560

561
/**
562
 * @brief Pass::getRecipientList return list of gpg-id's to encrypt for
563
 * @param for_file which file (folder) would you like recipients for
564
 * @return recipients gpg-id contents
565
 */
566
auto Pass::getRecipientList(const QString &for_file) -> QStringList {
6✔
567
  QFile gpgId(getGpgIdPath(for_file));
6✔
568
  if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
6✔
569
    return {};
×
570
  }
571
  QStringList recipients;
6✔
572
  while (!gpgId.atEnd()) {
20✔
573
    QString recipient(gpgId.readLine());
28✔
574
    recipient = recipient.split("#")[0].trimmed();
28✔
575
    if (!recipient.isEmpty() && Util::isValidKeyId(recipient)) {
14✔
576
      recipients += recipient;
577
    }
578
  }
579
  return recipients;
580
}
6✔
581

582
/**
583
 * @brief Pass::getRecipientString formatted string for use with GPG
584
 * @param for_file which file (folder) would you like recipients for
585
 * @param separator formating separator eg: " -r "
586
 * @param count
587
 * @return recipient string
588
 */
589
auto Pass::getRecipientString(const QString &for_file, const QString &separator,
2✔
590
                              int *count) -> QStringList {
591
  Q_UNUSED(separator)
592
  QStringList recipients = Pass::getRecipientList(for_file);
2✔
593
  if (count) {
2✔
594
    *count = recipients.size();
1✔
595
  }
596
  return recipients;
2✔
597
}
598

599
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
600
 */
601

602
/**
603
 * @brief Generates a random number bounded by the given value.
604
 * @param bound Upper bound (exclusive)
605
 * @return Random number in range [0, bound)
606
 */
607
auto Pass::boundedRandom(quint32 bound) -> quint32 {
1,224✔
608
  if (bound < 2) {
1,224✔
609
    return 0;
610
  }
611

612
  quint32 randval;
613
  // Rejection-sampling threshold to avoid modulo bias:
614
  // In quint32 arithmetic, (1 + ~bound) wraps to (2^32 - bound), so
615
  // (1 + ~bound) % bound == 2^32 % bound.
616
  // Values randval < max_mod_bound are rejected; accepted values produce a
617
  // uniform distribution when reduced with (randval % bound).
618
  const quint32 max_mod_bound = (1 + ~bound) % bound;
1,224✔
619

620
  do {
621
    randval = QRandomGenerator::system()->generate();
622
  } while (randval < max_mod_bound);
1,224✔
623

624
  return randval % bound;
1,224✔
625
}
626

627
/**
628
 * @brief Generates a random password from the given charset.
629
 * @param charset Characters to use in the password
630
 * @param length Desired password length
631
 * @return Generated password string
632
 */
633
auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
1,006✔
634
    -> QString {
635
  if (charset.isEmpty() || length == 0U) {
1,006✔
636
    return {};
637
  }
638
  QString out;
1,005✔
639
  for (unsigned int i = 0; i < length; ++i) {
2,229✔
640
    out.append(charset.at(static_cast<int>(
1,224✔
641
        boundedRandom(static_cast<quint32>(charset.length())))));
1,224✔
642
  }
643
  return out;
644
}
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