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

IJHack / QtPass / 24584462778

17 Apr 2026 08:07PM UTC coverage: 21.818% (+0.5%) from 21.329%
24584462778

Pull #1032

github

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

28 of 46 new or added lines in 3 files covered. (60.87%)

1 existing line in 1 file now uncovered.

1176 of 5390 relevant lines covered (21.82%)

8.68 hits per line

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

54.13
/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")};
80✔
51
  const QString wslenvPrefix = QStringLiteral("WSLENV=");
16✔
52
  auto it = std::find_if(env.begin(), env.end(), [&](const QString &s) {
32✔
53
    return s.startsWith(wslenvPrefix);
2,048✔
54
  });
55
  if (it == env.end()) {
16✔
56
    env.append(wslenvPrefix + wslenvVars.join(':'));
32✔
57
  } else {
58
    QStringList parts =
NEW
59
        it->mid(wslenvPrefix.size()).split(':', Qt::SkipEmptyParts);
×
NEW
60
    for (const QString &v : wslenvVars) {
×
NEW
61
      if (!parts.contains(v))
×
62
        parts.append(v);
63
    }
NEW
64
    *it = wslenvPrefix + parts.join(':');
×
65
  }
66
}
16✔
67

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

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

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

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

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

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

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

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

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

255
  QDir dir(gpgInfo.absolutePath());
1✔
256

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

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

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

326
} // namespace
327

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

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

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

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

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

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

381
  return {"gpgconf", {}};
382
}
8✔
383

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

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

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

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

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

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

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

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

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

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

530
  exec.setEnvironment(env);
8✔
531
}
8✔
532

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

560
  return gpgIdPath;
9✔
561
}
9✔
562

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

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

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

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

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

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

626
  return randval % bound;
1,224✔
627
}
628

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