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

IJHack / QtPass / 24575619378

17 Apr 2026 04:27PM UTC coverage: 21.307% (-0.02%) from 21.329%
24575619378

Pull #1032

github

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

6 of 45 new or added lines in 2 files covered. (13.33%)

1 existing line in 1 file now uncovered.

1148 of 5388 relevant lines covered (21.31%)

8.06 hits per line

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

43.58
/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
  // Merge our vars into WSLENV rather than blindly appending a duplicate entry
47
  const QStringList wslenvVars = {
48
      QStringLiteral("PASSWORD_STORE_DIR/p"),
18✔
49
      QStringLiteral("PASSWORD_STORE_GENERATED_LENGTH/w"),
9✔
50
      QStringLiteral("PASSWORD_STORE_CHARACTER_SET/w")};
36✔
51
  QStringList existing = env.filter(QStringLiteral("WSLENV="));
18✔
52
  if (existing.isEmpty()) {
9✔
53
    env.append(QStringLiteral("WSLENV=") + wslenvVars.join(':'));
18✔
54
  } else {
55
    QString current = existing.first();
NEW
56
    QString val = current.mid(7); // skip "WSLENV="
×
NEW
57
    for (const QString &v : wslenvVars) {
×
NEW
58
      if (!val.contains(v))
×
NEW
59
        val += ':' + v;
×
60
    }
NEW
61
    env.replaceInStrings(current, QStringLiteral("WSLENV=") + val);
×
62
  }
63
}
9✔
64

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

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

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

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

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

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

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

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

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

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

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

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

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

323
} // namespace
324

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

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

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

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

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

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

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

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

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

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

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

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

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

494
/**
495
 * @brief Pass::updateEnv update the execution environment (used when
496
 * switching profiles)
497
 */
NEW
498
void Pass::setEnvVar(const QString &key, const QString &value) {
×
NEW
499
  QStringList existing = env.filter(key);
×
NEW
500
  if (value.isEmpty()) {
×
NEW
501
    for (const QString &entry : existing)
×
502
      env.removeAll(entry);
NEW
503
  } else if (existing.isEmpty()) {
×
NEW
504
    env.append(key + value);
×
505
  } else {
NEW
506
    env.replaceInStrings(existing.first(), key + value);
×
507
  }
NEW
508
}
×
509

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

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

NEW
520
  int sel = passConfig.selected;
×
NEW
521
  if (sel < 0 || sel >= PasswordConfiguration::CHARSETS_COUNT)
×
522
    sel = PasswordConfiguration::ALLCHARS;
NEW
523
  setEnvVar(QStringLiteral("PASSWORD_STORE_CHARACTER_SET="),
×
NEW
524
            passConfig.Characters[sel]);
×
525

526
  exec.setEnvironment(env);
×
527
}
×
528

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

556
  return gpgIdPath;
9✔
557
}
9✔
558

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

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

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

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

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

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

622
  return randval % bound;
1,224✔
623
}
624

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