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

IJHack / QtPass / 24097410871

07 Apr 2026 06:20PM UTC coverage: 20.282% (+0.06%) from 20.222%
24097410871

Pull #900

github

web-flow
Merge 3fdaf029b into 85a69d0b9
Pull Request #900: fix: spelling corrections and code quality improvements

11 of 40 new or added lines in 2 files covered. (27.5%)

7 existing lines in 1 file now uncovered.

1065 of 5251 relevant lines covered (20.28%)

7.74 hits per line

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

41.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
namespace {
205
auto resolveWslGpgconfPath(const QString &lastPart) -> QString {
5✔
206
  int lastSep = lastPart.lastIndexOf('/');
5✔
207
  if (lastSep < 0) {
5✔
208
    lastSep = lastPart.lastIndexOf('\\');
2✔
209
  }
210
  if (lastSep >= 0) {
2✔
211
    return lastPart.left(lastSep + 1) + "gpgconf";
6✔
212
  }
213
  return QStringLiteral("gpgconf");
2✔
214
}
215

216
QString findGpgconfInGpgDir(const QString &gpgPath) {
1✔
217
  QFileInfo gpgInfo(gpgPath);
1✔
218
  if (!gpgInfo.isAbsolute()) {
1✔
219
    return QString();
220
  }
221

222
  QDir dir(gpgInfo.absolutePath());
1✔
223

224
#ifdef Q_OS_WIN
225
  QFileInfo candidateExe(dir.filePath("gpgconf.exe"));
226
  if (candidateExe.isExecutable()) {
227
    return candidateExe.filePath();
228
  }
229
#endif
230

231
  QFileInfo candidate(dir.filePath("gpgconf"));
1✔
232
  if (candidate.isExecutable()) {
1✔
233
    return candidate.filePath();
1✔
234
  }
235
  return QString();
236
}
1✔
237
} // namespace
238

239
namespace {
240
auto resolveWslGpgconf(const QStringList &parts) -> ResolvedGpgconfCommand {
7✔
241
  if (parts.size() >= 2 && parts.at(1).startsWith("sh")) {
13✔
242
    return {"gpgconf", {}};
1✔
243
  }
244
  for (int i = 1; i < parts.size(); ++i) {
10✔
245
    const QString baseName = QFileInfo(parts.at(i)).fileName();
9✔
246
    if (baseName == "gpg" || baseName == "gpg.exe" || baseName == "gpg2" ||
29✔
247
        baseName == "gpg2.exe") {
248
      QStringList newParts = parts;
249
      newParts[i] = resolveWslGpgconfPath(parts.at(i));
10✔
250
      return {newParts.first(), newParts.mid(1)};
251
    }
252
  }
253
  return {"gpgconf", {}};
1✔
254
}
7✔
255
} // namespace
256

257
auto Pass::resolveGpgconfCommand(const QString &gpgPath)
10✔
258
    -> ResolvedGpgconfCommand {
259
  if (gpgPath.trimmed().isEmpty()) {
10✔
260
    return {"gpgconf", {}};
261
  }
262

263
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
264
  QStringList parts = QProcess::splitCommand(gpgPath);
9✔
265
#else
266
  QStringList parts = QStringList{gpgPath};
267
#endif
268

269
  if (parts.isEmpty()) {
9✔
270
    return {"gpgconf", {}};
271
  }
272

273
  const QString first = parts.first();
274
  if (first == "wsl" || first == "wsl.exe") {
11✔
275
    return resolveWslGpgconf(parts);
7✔
276
  }
277

278
  if (!gpgPath.contains('/') && !gpgPath.contains('\\')) {
2✔
279
    return {"gpgconf", {}};
280
  }
281

282
  QString gpgconfPath = findGpgconfInGpgDir(gpgPath);
1✔
283
  if (!gpgconfPath.isEmpty()) {
1✔
284
    return {gpgconfPath, {}};
1✔
285
  }
286

287
  return {"gpgconf", {}};
288
}
3✔
289

290
/**
291
 * @brief Pass::GenerateGPGKeys internal gpg keypair generator . .
292
 * @param batch GnuPG style configuration string
293
 */
294
void Pass::GenerateGPGKeys(QString batch) {
×
295
  // Kill any stale GPG agents that might be holding locks on the key database
296
  // This helps avoid "database locked" timeouts during key generation
297
  QString gpgPath = QtPassSettings::getGpgExecutable();
×
298
  if (!gpgPath.isEmpty()) {
×
299
    ResolvedGpgconfCommand gpgconf = resolveGpgconfCommand(gpgPath);
×
300
    QStringList killArgs = gpgconf.arguments;
301
    killArgs << "--kill";
×
302
    killArgs << "gpg-agent";
×
303
    // Use same environment as key generation to target correct gpg-agent
304
    Executor::executeBlocking(env, gpgconf.program, killArgs);
×
305
  }
306

307
  executeWrapper(GPG_GENKEYS, gpgPath, {"--gen-key", "--no-tty", "--batch"},
×
308
                 std::move(batch));
309
}
×
310

311
namespace {
NEW
312
auto parseGpgKeyLine(const QString &line, bool secret, UserInfo &current_user)
×
313
    -> bool {
314
  constexpr int GPG_MIN_FIELDS = 10;
315
  constexpr int GPG_FIELD_VALIDITY = 1;
316
  constexpr int GPG_FIELD_KEY_ID = 4;
317
  constexpr int GPG_FIELD_CREATED = 5;
318
  constexpr int GPG_FIELD_EXPIRY = 6;
319
  constexpr int GPG_FIELD_USERID = 9;
320

NEW
321
  QStringList props = line.split(':');
×
NEW
322
  if (props.size() < GPG_MIN_FIELDS) {
×
323
    return false;
324
  }
325

326
  const QString recordType = props[0];
NEW
327
  const QString keyPrefix = secret ? "sec" : "pub";
×
328

NEW
329
  if (recordType == keyPrefix) {
×
NEW
330
    if (!current_user.key_id.isEmpty()) {
×
NEW
331
      return true;
×
332
    }
NEW
333
    current_user.key_id = props[GPG_FIELD_KEY_ID];
×
NEW
334
    current_user.name = props[GPG_FIELD_USERID].toUtf8();
×
NEW
335
    current_user.validity = props[GPG_FIELD_VALIDITY][0].toLatin1();
×
NEW
336
    bool okCreated = false;
×
337
    const qint64 createdSecs = props[GPG_FIELD_CREATED].toLongLong(&okCreated);
NEW
338
    if (okCreated) {
×
NEW
339
      current_user.created.setSecsSinceEpoch(createdSecs);
×
340
    }
NEW
341
    bool okExpiry = false;
×
342
    const qint64 expirySecs = props[GPG_FIELD_EXPIRY].toLongLong(&okExpiry);
NEW
343
    if (okExpiry) {
×
NEW
344
      current_user.expiry.setSecsSinceEpoch(expirySecs);
×
345
    }
NEW
346
  } else if (current_user.name.isEmpty() && recordType == "uid") {
×
NEW
347
    current_user.name = props[GPG_FIELD_USERID];
×
NEW
348
  } else if (recordType == "fpr" &&
×
NEW
349
             props[GPG_FIELD_USERID].endsWith(current_user.key_id)) {
×
NEW
350
    current_user.key_id = props[GPG_FIELD_USERID];
×
351
  }
352
  return false;
353
}
354
} // namespace
355

356
/**
357
 * @brief Pass::listKeys list users
358
 * @param keystrings
359
 * @param secret list private keys
360
 * @return QList<UserInfo> users
361
 */
362
auto Pass::listKeys(QStringList keystrings, bool secret) -> QList<UserInfo> {
×
363
  QList<UserInfo> users;
×
364
  QStringList args = {"--no-tty", "--with-colons", "--with-fingerprint"};
×
365
  args.append(secret ? "--list-secret-keys" : "--list-keys");
×
366

367
  for (const QString &keystring : AS_CONST(keystrings)) {
×
368
    if (!keystring.isEmpty()) {
×
369
      args.append(keystring);
370
    }
371
  }
372
  QString p_out;
×
373
  if (Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
374
                                &p_out) != 0) {
375
    return users;
376
  }
377
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
378
  const QStringList keys =
379
      p_out.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
×
380
#else
381
  const QStringList keys =
382
      p_out.split(Util::newLinesRegex(), QString::SkipEmptyParts);
383
#endif
384
  UserInfo current_user;
×
385
  for (const QString &key : keys) {
×
NEW
386
    if (parseGpgKeyLine(key, secret, current_user)) {
×
387
      users.append(current_user);
388
    }
389
  }
390
  if (!current_user.key_id.isEmpty()) {
×
391
    users.append(current_user);
392
  }
393
  return users;
394
}
×
395

396
/**
397
 * @brief Pass::listKeys list users
398
 * @param keystring
399
 * @param secret list private keys
400
 * @return QList<UserInfo> users
401
 */
402
auto Pass::listKeys(const QString &keystring, bool secret) -> QList<UserInfo> {
×
403
  return listKeys(QStringList(keystring), secret);
×
404
}
405

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

465
/**
466
 * @brief Pass::updateEnv update the execution environment (used when
467
 * switching profiles)
468
 */
469
void Pass::updateEnv() {
×
470
  // put PASSWORD_STORE_SIGNING_KEY in env
471
  QStringList envSigningKey = env.filter("PASSWORD_STORE_SIGNING_KEY=");
×
472
  QString currentSigningKey = QtPassSettings::getPassSigningKey();
×
473
  if (envSigningKey.isEmpty()) {
×
474
    if (!currentSigningKey.isEmpty()) {
×
475
      // dbg()<< "Added
476
      // PASSWORD_STORE_SIGNING_KEY with" + currentSigningKey;
477
      env.append("PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
478
    }
479
  } else {
480
    if (currentSigningKey.isEmpty()) {
×
481
      // dbg() << "Removed
482
      // PASSWORD_STORE_SIGNING_KEY";
483
      env.removeAll(envSigningKey.first());
484
    } else {
485
      // dbg()<< "Update
486
      // PASSWORD_STORE_SIGNING_KEY with " + currentSigningKey;
487
      env.replaceInStrings(envSigningKey.first(),
×
488
                           "PASSWORD_STORE_SIGNING_KEY=" + currentSigningKey);
×
489
    }
490
  }
491
  // put PASSWORD_STORE_DIR in env
492
  QStringList store = env.filter("PASSWORD_STORE_DIR=");
×
493
  if (store.isEmpty()) {
×
494
    // dbg()<< "Added
495
    // PASSWORD_STORE_DIR";
496
    env.append("PASSWORD_STORE_DIR=" + QtPassSettings::getPassStore());
×
497
  } else {
498
    // dbg()<< "Update
499
    // PASSWORD_STORE_DIR with " + passStore;
500
    env.replaceInStrings(store.first(), "PASSWORD_STORE_DIR=" +
×
501
                                            QtPassSettings::getPassStore());
×
502
  }
503
  exec.setEnvironment(env);
×
504
}
×
505

506
/**
507
 * @brief Pass::getGpgIdPath return gpgid file path for some file (folder).
508
 * @param for_file which file (folder) would you like the gpgid file path for.
509
 * @return path to the gpgid file.
510
 */
511
auto Pass::getGpgIdPath(const QString &for_file) -> QString {
8✔
512
  QString passStore =
513
      QDir::fromNativeSeparators(QtPassSettings::getPassStore());
16✔
514
  QString normalizedFile = QDir::fromNativeSeparators(for_file);
8✔
515
  QString fullPath = normalizedFile.startsWith(passStore)
8✔
516
                         ? normalizedFile
8✔
517
                         : passStore + "/" + normalizedFile;
6✔
518
  QDir gpgIdDir(QFileInfo(fullPath).absoluteDir());
8✔
519
  bool found = false;
520
  while (gpgIdDir.exists() && gpgIdDir.absolutePath().startsWith(passStore)) {
10✔
521
    if (QFile(gpgIdDir.absoluteFilePath(".gpg-id")).exists()) {
2✔
522
      found = true;
523
      break;
524
    }
525
    if (!gpgIdDir.cdUp()) {
×
526
      break;
527
    }
528
  }
529
  QString gpgIdPath(found ? gpgIdDir.absoluteFilePath(".gpg-id")
8✔
530
                          : QtPassSettings::getPassStore() + ".gpg-id");
22✔
531

532
  return gpgIdPath;
8✔
533
}
8✔
534

535
/**
536
 * @brief Pass::getRecipientList return list of gpg-id's to encrypt for
537
 * @param for_file which file (folder) would you like recipients for
538
 * @return recipients gpg-id contents
539
 */
540
auto Pass::getRecipientList(const QString &for_file) -> QStringList {
5✔
541
  QFile gpgId(getGpgIdPath(for_file));
5✔
542
  if (!gpgId.open(QIODevice::ReadOnly | QIODevice::Text)) {
5✔
543
    return {};
×
544
  }
545
  QStringList recipients;
5✔
546
  while (!gpgId.atEnd()) {
14✔
547
    QString recipient(gpgId.readLine());
18✔
548
    recipient = recipient.split("#")[0].trimmed();
18✔
549
    if (!recipient.isEmpty()) {
9✔
550
      recipients += recipient;
551
    }
552
  }
553
  return recipients;
554
}
5✔
555

556
/**
557
 * @brief Pass::getRecipientString formatted string for use with GPG
558
 * @param for_file which file (folder) would you like recipients for
559
 * @param separator formating separator eg: " -r "
560
 * @param count
561
 * @return recipient string
562
 */
563
auto Pass::getRecipientString(const QString &for_file, const QString &separator,
2✔
564
                              int *count) -> QStringList {
565
  Q_UNUSED(separator)
566
  QStringList recipients = Pass::getRecipientList(for_file);
2✔
567
  if (count) {
2✔
568
    *count = recipients.size();
1✔
569
  }
570
  return recipients;
2✔
571
}
572

573
/* Copyright (C) 2017 Jason A. Donenfeld <Jason@zx2c4.com>. All Rights Reserved.
574
 */
575

576
/**
577
 * @brief Generates a random number bounded by the given value.
578
 * @param bound Upper bound (exclusive)
579
 * @return Random number in range [0, bound)
580
 */
581
auto Pass::boundedRandom(quint32 bound) -> quint32 {
1,160✔
582
  if (bound < 2) {
1,160✔
583
    return 0;
584
  }
585

586
  quint32 randval;
587
  // Reject values below max_mod_bound (computed as 1 + ~bound == 2^32 -
588
  // bound) to avoid modulo bias and ensure uniform randval % bound.
589
  const quint32 max_mod_bound = (1 + ~bound) % bound;
1,160✔
590

591
  do {
592
    randval = QRandomGenerator::system()->generate();
593
  } while (randval < max_mod_bound);
1,160✔
594

595
  return randval % bound;
1,160✔
596
}
597

598
/**
599
 * @brief Generates a random password from the given charset.
600
 * @param charset Characters to use in the password
601
 * @param length Desired password length
602
 * @return Generated password string
603
 */
604
auto Pass::generateRandomPassword(const QString &charset, unsigned int length)
1,004✔
605
    -> QString {
606
  if (charset.isEmpty() || length == 0U) {
1,004✔
607
    return {};
608
  }
609
  QString out;
1,003✔
610
  for (unsigned int i = 0; i < length; ++i) {
2,163✔
611
    out.append(charset.at(static_cast<int>(
1,160✔
612
        boundedRandom(static_cast<quint32>(charset.length())))));
1,160✔
613
  }
614
  return out;
615
}
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