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

IJHack / QtPass / 24084061007

07 Apr 2026 01:31PM UTC coverage: 20.115% (+0.3%) from 19.861%
24084061007

push

github

web-flow
fix: kill stale GPG agents before key generation (#895)

* fix: kill stale GPG agents before key generation (#815)

Before generating GPG keys, kill any stale gpg-agent processes that
might be holding locks on the key database. This helps prevent
'timeout while waiting on locked key database' errors.

The issue occurs when another GPG process (possibly from another
application) holds a lock on the key database, causing key generation
to fail with timeout errors.

* test: add test for gpgconf --kill gpg-agent

* fix: use gpgconf directly instead of gpg with args

* fix: derive gpgconf path from gpg executable directory

* fix: handle WSL and Windows paths in gpgconf resolution

- Add resolveGpgconfCommand() helper for path resolution
- Handle WSL wrappers (wsl gpg2, wsl -d Ubuntu gpg2)
- Detect shell commands (wsl sh -c) and fallback to PATH
- Check .exe extension on Windows
- Add unit tests for all path cases
- Resolves CodeRabbit review finding

* Update tests/auto/executor/tst_executor.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Update tests/auto/executor/tst_executor.cpp

Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>

* Translations update from Hosted Weblate (#893)

* Translated using Weblate (Estonian)

Currently translated at 100.0% (215 of 215 strings)

Translation: QtPass/QtPass
Translate-URL: https://hosted.weblate.org/projects/qtpass/qtpass/et/

* Translated using Weblate (Russian)

Currently translated at 98.1% (211 of 215 strings)

Translation: QtPass/QtPass
Translate-URL: https://hosted.weblate.org/projects/qtpass/qtpass/ru/

---------

Co-authored-by: Priit Jõerüüt <jrthwlate@users.noreply.hosted.weblate.org>
Co-authored-by: Сергей <asvmail.as@gmail.com>

* fix: add Russian translation for Signing Key integrity message (#896)

* fix: restore preprocessor directives broken by rebase

* fix: address remaining CodeRabbit review comments

- Return... (continued)

22 of 45 new or added lines in 3 files covered. (48.89%)

7 existing lines in 1 file now uncovered.

1052 of 5230 relevant lines covered (20.11%)

7.76 hits per line

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

39.81
/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 "helpers.h"
5
#include "qtpasssettings.h"
6
#include "util.h"
7
#include <QDir>
8
#include <QFileInfo>
9
#include <QProcess>
10
#include <QRandomGenerator>
11
#include <QRegularExpression>
12
#include <utility>
13

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

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

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

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

44
  connect(&exec, &Executor::starting, this, &Pass::startingExecuteWrapper);
11✔
45
  env.append("WSLENV=PASSWORD_STORE_DIR/p");
11✔
46
}
11✔
47

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

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

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

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

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

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

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

204
auto Pass::resolveGpgconfCommand(const QString &gpgPath)
8✔
205
    -> ResolvedGpgconfCommand {
206
  if (gpgPath.trimmed().isEmpty()) {
8✔
207
    return {"gpgconf", {}};
208
  }
209

210
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
211
  QStringList parts = QProcess::splitCommand(gpgPath);
7✔
212
#else
213
  QStringList parts = QStringList{gpgPath};
214
#endif
215
  if (!parts.isEmpty()) {
7✔
216
    QString first = parts.first();
217
    if (first == "wsl" || first == "wsl.exe") {
9✔
218
      if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
9✔
219
        return {"gpgconf", {}};
220
      }
221
      if (QFileInfo(parts.last()).fileName().startsWith("gpg")) {
8✔
222
        QString lastPart = parts.last();
223
        int lastSep = lastPart.lastIndexOf('/');
3✔
224
        if (lastSep < 0) {
3✔
225
          lastSep = lastPart.lastIndexOf('\\');
2✔
226
        }
227
        if (lastSep >= 0) {
2✔
228
          parts.removeLast();
1✔
229
          parts.append(lastPart.left(lastSep + 1) + "gpgconf");
2✔
230
        } else {
231
          parts.removeLast();
2✔
232
          parts.append("gpgconf");
4✔
233
        }
234
        return {parts.first(), parts.mid(1)};
235
      }
236
      return {"gpgconf", {}};
237
    }
238
  }
239

240
  if (!gpgPath.contains('/') && !gpgPath.contains('\\')) {
2✔
241
    return {"gpgconf", {}};
242
  }
243

244
  QFileInfo gpgInfo(gpgPath);
1✔
245
  if (!gpgInfo.isAbsolute()) {
1✔
246
    return {"gpgconf", {}};
247
  }
248

249
  QDir dir(gpgInfo.absolutePath());
1✔
250

251
#ifdef Q_OS_WIN
252
  QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
253
  if (candidateExe.isExecutable()) {
254
    return {candidateExe.filePath(), {}};
255
  }
256
#endif
257

258
  QFileInfo candidate(dir.filePath("gpgconf"));
1✔
259
  if (candidate.isExecutable()) {
1✔
260
    return {candidate.filePath(), {}};
261
  }
262

263
  return {"gpgconf", {}};
264
}
8✔
265

266
/**
267
 * @brief Pass::GenerateGPGKeys internal gpg keypair generator . .
268
 * @param batch GnuPG style configuration string
269
 */
270
void Pass::GenerateGPGKeys(QString batch) {
×
271
  // Kill any stale GPG agents that might be holding locks on the key database
272
  // This helps avoid "database locked" timeouts during key generation
273
  QString gpgPath = QtPassSettings::getGpgExecutable();
×
274
  if (!gpgPath.isEmpty()) {
×
NEW
275
    ResolvedGpgconfCommand gpgconf = resolveGpgconfCommand(gpgPath);
×
276
    QStringList killArgs = gpgconf.arguments;
NEW
277
    killArgs << "--kill";
×
NEW
278
    killArgs << "gpg-agent";
×
279
    // Use same environment as key generation to target correct gpg-agent
NEW
280
    Executor::executeBlocking(env, gpgconf.program, killArgs);
×
281
  }
282

283
  executeWrapper(GPG_GENKEYS, gpgPath, {"--gen-key", "--no-tty", "--batch"},
×
284
                 std::move(batch));
285
}
×
286

287
/**
288
 * @brief Pass::listKeys list users
289
 * @param keystrings
290
 * @param secret list private keys
291
 * @return QList<UserInfo> users
292
 */
293
auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
×
294
  QList<UserInfo> users;
×
295
  QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
×
296
  args.append(secret ? "--list-secret-keys" : "--list-keys");
×
297

298
  for (const QString &keystring : AS_CONST(keystrings)) {
×
299
    if (!keystring.isEmpty()) {
×
300
      args.append(keystring);
301
    }
302
  }
303
  QString p_out;
×
304
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
305
                                &p_out) != 0) {
306
    return users;
307
  }
308
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
309
  const QStringList keys =
310
      p_out.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
×
311
#else
312
  const QStringList keys =
313
      p_out.split(Util::newLinesRegex(), QString::SkipEmptyParts);
314
#endif
315
  UserInfo current_user;
×
316
  for (const QString &key : keys) {
×
317
    QStringList props = key.split(':');
×
318
    if (props.size() < 10) {
×
319
      continue;
320
    }
321
    if (props[0] == (secret ? "sec" : "pub")) {
×
322
      if (!current_user.key_id.isEmpty()) {
×
323
        users.append(current_user);
324
      }
325
      current_user = UserInfo();
×
326
      current_user.key_id = props[4];
×
327
      current_user.name = props[9].toUtf8();
×
328
      current_user.validity = props[1][0].toLatin1();
×
329
      current_user.created.setSecsSinceEpoch(props[5].toUInt());
×
330
      current_user.expiry.setSecsSinceEpoch(props[6].toUInt());
×
331
    } else if (current_user.name.isEmpty() && props[0] == "uid") {
×
332
      current_user.name = props[9];
×
333
    } else if ((props[0] == "fpr") && props[9].endsWith(current_user.key_id)) {
×
334
      current_user.key_id = props[9];
×
335
    }
336
  }
337
  if (!current_user.key_id.isEmpty()) {
×
338
    users.append(current_user);
339
  }
340
  return users;
341
}
×
342

343
/**
344
 * @brief Pass::listKeys list users
345
 * @param keystring
346
 * @param secret list private keys
347
 * @return QList<UserInfo> users
348
 */
349
auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
×
350
  return listKeys(QStringList(keystring), secret);
×
351
}
352

353
/**
354
 * @brief Pass::processFinished reemits specific signal based on what process
355
 * has finished
356
 * @param id    id of Pass process that was scheduled and finished
357
 * @param exitCode  return code of a process
358
 * @param out   output generated by process(if capturing was requested, empty
359
 *              otherwise)
360
 * @param err   error output generated by process(if capturing was requested,
361
 *              or error occurred)
362
 */
363
void Pass::finished(int id, int exitCode, const QString &out,
×
364
                    const QString &err) {
365
  auto pid = static_cast<PROCESS>(id);
366
  if (exitCode != 0) {
×
367
    emit processErrorExit(exitCode, err);
×
368
    return;
×
369
  }
370
  switch (pid) {
×
371
  case GIT_INIT:
×
372
    emit finishedGitInit(out, err);
×
373
    break;
×
374
  case GIT_PULL:
×
375
    emit finishedGitPull(out, err);
×
376
    break;
×
377
  case GIT_PUSH:
×
378
    emit finishedGitPush(out, err);
×
379
    break;
×
380
  case PASS_SHOW:
×
381
    emit finishedShow(out);
×
382
    break;
×
383
  case PASS_OTP_GENERATE:
×
384
    emit finishedOtpGenerate(out);
×
385
    break;
×
386
  case PASS_INSERT:
×
387
    emit finishedInsert(out, err);
×
388
    break;
×
389
  case PASS_REMOVE:
×
390
    emit finishedRemove(out, err);
×
391
    break;
×
392
  case PASS_INIT:
×
393
    emit finishedInit(out, err);
×
394
    break;
×
395
  case PASS_MOVE:
×
396
    emit finishedMove(out, err);
×
397
    break;
×
398
  case PASS_COPY:
×
399
    emit finishedCopy(out, err);
×
400
    break;
×
401
  case GPG_GENKEYS:
×
402
    emit finishedGenerateGPGKeys(out, err);
×
403
    break;
×
404
  default:
405
#ifdef QT_DEBUG
406
    dbg() << "Unhandled process type" << pid;
407
#endif
408
    break;
409
  }
410
}
411

412
/**
413
 * @brief Pass::updateEnv update the execution environment (used when
414
 * switching profiles)
415
 */
416
void Pass::updateEnv() {
×
417
  // put PASSWORD_STORE_SIGNING_KEY in env
418
  QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY=");
×
419
  QString currentSigningKey = QtPassSettings::getPassSigningKey();
×
420
  if (envSigningKey.isEmpty()) {
×
421
    if (!currentSigningKey.isEmpty()) {
×
422
      // dbg()<< "Added
423
      // PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey;
424
      env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
425
    }
426
  } else {
427
    if (currentSigningKey.isEmpty()) {
×
428
      // dbg() << "Removed
429
      // PASSWORD_STORE_SIGNING_KEY";
430
      env.removeAll(envSigningKey.first());
431
    } else {
432
      // dbg()<< "Update
433
      // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey;
434
      env.replaceInStrings(envSigningKey.first(),
×
435
                           "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
436
    }
437
  }
438
  // put PASSWORD_STORE_DIR in env
439
  QStringList store = env.filter("PASSWORD_STORE_DIR=");
×
440
  if (store.isEmpty()) {
×
441
    // dbg()<< "Added
442
    // PASSWORD_STORE_DIR";
443
    env.append("PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore());
×
444
  } else {
445
    // dbg()<< "Update
446
    // PASSWORD_STORE_DIR with " + passStore;
447
    env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" +
×
448
                                            QtPassSettings::getPassStore());
×
449
  }
450
  exec.setEnvironment(env);
×
451
}
×
452

453
/**
454
 * @brief Pass::getGpgIdPath return gpgid file path for some file (folder).
455
 * @param for_file which file (folder) would you like the gpgid file path for.
456
 * @return path to the gpgid file.
457
 */
458
auto Pass::getGpgIdPath(const QString &for_file) -> QString {
8✔
459
  QString passStore =
460
      QDir::fromNativeSeparators(QtPassSettings::getPassStore());
16✔
461
  QString normalizedFile = QDir::fromNativeSeparators(for_file);
8✔
462
  QString fullPath = normalizedFile.startsWith(passStore)
8✔
463
                         ? normalizedFile
8✔
464
                         : passStore + "/" + normalizedFile;
6✔
465
  QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
8✔
466
  bool found = false;
467
  while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
10✔
468
    if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
2✔
469
      found = true;
470
      break;
471
    }
472
    if (!gpgIdDir.cdUp()) {
×
473
      break;
474
    }
475
  }
476
  QString gpgIdPath(found ? gpgIdDir.absoluteFilePath(".gpg-id")
8✔
477
                          : QtPassSettings::getPassStore() + ".gpg-id");
22✔
478

479
  return gpgIdPath;
8✔
480
}
8✔
481

482
/**
483
 * @brief Pass::getRecipientList return list of gpg-id's to encrypt for
484
 * @param for_file which file (folder) would you like recipients for
485
 * @return recipients gpg-id contents
486
 */
487
auto Pass::getRecipientList(const QString &for_file) -> QStringList {
5✔
488
  QFile gpgId(getGpgIdPath(for_file));
5✔
489
  if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
5✔
490
    return {};
×
491
  }
492
  QStringList recipients;
5✔
493
  while (!gpgId.atEnd()) {
14✔
494
    QString recipient(gpgId.readLine());
18✔
495
    recipient = recipient.split("#")[0].trimmed();
18✔
496
    if (!recipient.isEmpty()) {
9✔
497
      recipients += recipient;
498
    }
499
  }
500
  return recipients;
501
}
5✔
502

503
/**
504
 * @brief Pass::getRecipientString formatted string for use with GPG
505
 * @param for_file which file (folder) would you like recipients for
506
 * @param separator formating separator eg: " -r "
507
 * @param count
508
 * @return recipient string
509
 */
510
auto Pass::getRecipientString(const QString &for_file, const QString &separator,
2✔
511
                              int *count) -> QStringList {
512
  Q_UNUSED(separator)
513
  QStringList recipients = Pass::getRecipientList(for_file);
2✔
514
  if (count) {
2✔
515
    *count = recipients.size();
1✔
516
  }
517
  return recipients;
2✔
518
}
519

520
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
521
 */
522

523
/**
524
 * @brief Generates a random number bounded by the given value.
525
 * @param bound Upper bound (exclusive)
526
 * @return Random number in range [0, bound)
527
 */
528
auto Pass::boundedRandom(quint32 bound) -> quint32 {
1,160✔
529
  if (bound < 2) {
1,160✔
530
    return 0;
531
  }
532

533
  quint32 randval;
534
  const quint32 max_mod_bound = (1 + ~bound) % bound;
1,160✔
535

536
  do {
537
    randval = QRandomGenerator::system()->generate();
538
  } while (randval < max_mod_bound);
1,160✔
539

540
  return randval % bound;
1,160✔
541
}
542

543
/**
544
 * @brief Generates a random password from the given charset.
545
 * @param charset Characters to use in the password
546
 * @param length Desired password length
547
 * @return Generated password string
548
 */
549
auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
1,004✔
550
    -> QString {
551
  QString out;
1,004✔
552
  for (unsigned int i = 0; i < length; ++i) {
2,164✔
553
    out.append(charset.at(static_cast<int>(
1,160✔
554
        boundedRandom(static_cast<quint32>(charset.length())))));
1,160✔
555
  }
556
  return out;
1,004✔
557
}
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