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

IJHack / QtPass / 24095207830

07 Apr 2026 05:30PM UTC coverage: 20.217% (-0.005%) from 20.222%
24095207830

Pull #900

github

web-flow
Merge ba51c6fee into c7edbe340
Pull Request #900: fix: spelling corrections and code quality improvements

3 of 24 new or added lines in 2 files covered. (12.5%)

7 existing lines in 1 file now uncovered.

1060 of 5243 relevant lines covered (20.22%)

7.74 hits per line

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

6.09
/src/imitatepass.cpp
1
// SPDX-FileCopyrightText: 2016 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "imitatepass.h"
4
#include "qtpasssettings.h"
5
#include "util.h"
6
#include <QDirIterator>
7
#include <QRegularExpression>
8
#include <utility>
9

10
#ifdef QT_DEBUG
11
#include "debughelper.h"
12
#endif
13

14
using Enums::CLIPBOARD_ALWAYS;
15
using Enums::CLIPBOARD_NEVER;
16
using Enums::CLIPBOARD_ON_DEMAND;
17
using Enums::GIT_ADD;
18
using Enums::GIT_COMMIT;
19
using Enums::GIT_COPY;
20
using Enums::GIT_INIT;
21
using Enums::GIT_MOVE;
22
using Enums::GIT_PULL;
23
using Enums::GIT_PUSH;
24
using Enums::GIT_RM;
25
using Enums::GPG_GENKEYS;
26
using Enums::INVALID;
27
using Enums::PASS_COPY;
28
using Enums::PASS_INIT;
29
using Enums::PASS_INSERT;
30
using Enums::PASS_MOVE;
31
using Enums::PASS_OTP_GENERATE;
32
using Enums::PASS_REMOVE;
33
using Enums::PASS_SHOW;
34
using Enums::PROCESS_COUNT;
35

36
/**
37
 * @brief ImitatePass::ImitatePass for situations when pass is not available
38
 * we imitate the behavior of pass https://www.passwordstore.org/
39
 */
40
ImitatePass::ImitatePass() = default;
20✔
41

42
static auto pgit(const QString &path) -> QString {
×
43
  if (!QtPassSettings::getGitExecutable().startsWith("wsl ")) {
×
44
    return path;
45
  }
46
  QString res = "$(wslpath " + path + ")";
×
47
  return res.replace('\\', '/');
×
48
}
49

50
static auto pgpg(const QString &path) -> QString {
×
51
  if (!QtPassSettings::getGpgExecutable().startsWith("wsl ")) {
×
52
    return path;
53
  }
54
  QString res = "$(wslpath " + path + ")";
×
55
  return res.replace('\\', '/');
×
56
}
57

58
/**
59
 * @brief ImitatePass::GitInit git init wrapper
60
 */
61
void ImitatePass::GitInit() {
×
62
  executeGit(GIT_INIT, {"init", pgit(QtPassSettings::getPassStore())});
×
63
}
×
64

65
/**
66
 * @brief ImitatePass::GitPull git pull wrapper
67
 */
68
void ImitatePass::GitPull() { executeGit(GIT_PULL, {"pull"}); }
×
69

70
/**
71
 * @brief ImitatePass::GitPull_b git pull wrapper
72
 */
73
void ImitatePass::GitPull_b() {
×
74
  Executor::executeBlocking(QtPassSettings::getGitExecutable(), {"pull"});
×
75
}
×
76

77
/**
78
 * @brief ImitatePass::GitPush git push wrapper
79
 */
80
void ImitatePass::GitPush() {
×
81
  if (QtPassSettings::isUseGit()) {
×
82
    executeGit(GIT_PUSH, {"push"});
×
83
  }
84
}
×
85

86
/**
87
 * @brief ImitatePass::Show shows content of file
88
 */
89
void ImitatePass::Show(QString file) {
×
90
  file = QtPassSettings::getPassStore() + file + ".gpg";
×
91
  QStringList args = {"-d",      "--quiet",     "--yes",   "--no-encrypt-to",
92
                      "--batch", "--use-agent", pgpg(file)};
×
93
  executeGpg(PASS_SHOW, args);
×
94
}
×
95

96
/**
97
 * @brief ImitatePass::OtpGenerate generates an otp code
98
 */
99
void ImitatePass::OtpGenerate(QString file) {
×
100
#ifdef QT_DEBUG
101
  dbg() << "No OTP generation code for fake pass yet, attempting for file: " +
102
               file;
103
#else
104
  Q_UNUSED(file)
105
#endif
106
}
×
107

108
/**
109
 * @brief ImitatePass::Insert create new file with encrypted content
110
 *
111
 * @param file      file to be created
112
 * @param newValue  value to be stored in file
113
 * @param overwrite whether to overwrite existing file
114
 */
115
void ImitatePass::Insert(QString file, QString newValue, bool overwrite) {
×
116
  file = file + ".gpg";
×
117
  QString gpgIdPath = Pass::getGpgIdPath(file);
×
118
  if (!verifyGpgIdFile(gpgIdPath)) {
×
119
    emit critical(tr("Check .gpgid file signature!"),
×
120
                  tr("Signature for %1 is invalid.").arg(gpgIdPath));
×
121
    return;
×
122
  }
123
  transactionHelper trans(this, PASS_INSERT);
×
124
  QStringList recipients = Pass::getRecipientList(file);
×
125
  if (recipients.isEmpty()) {
×
126
    // Already emit critical signal to notify user of error - no need to throw
127
    emit critical(tr("Can not edit"),
×
128
                  tr("Could not read encryption key to use, .gpg-id "
×
129
                     "file missing or invalid."));
130
    return;
131
  }
132
  QStringList args = {"--batch", "-eq", "--output", pgpg(file)};
×
133
  for (auto &r : recipients) {
×
134
    args.append("-r");
×
135
    args.append(r);
136
  }
137
  if (overwrite) {
×
138
    args.append("--yes");
×
139
  }
140
  args.append("-");
×
141
  executeGpg(PASS_INSERT, args, newValue);
×
142
  if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
×
143
    // Git is used when enabled - this is the standard pass workflow
144
    if (!overwrite) {
×
145
      executeGit(GIT_ADD, {"add", pgit(file)});
×
146
    }
147
    QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
×
148
    path.replace(Util::endsWithGpg(), "");
×
149
    QString msg =
150
        QString(overwrite ? "Edit" : "Add") + " for " + path + " using QtPass.";
×
NEW
151
    gitCommit(file, msg);
×
152
  }
153
}
×
154

155
/**
156
 * @brief ImitatePass::gitCommit commit a file to git with an appropriate commit
157
 * message
158
 * @param file
159
 * @param msg
160
 */
NEW
161
void ImitatePass::gitCommit(const QString &file, const QString &msg) {
×
162
  if (file.isEmpty()) {
×
163
    executeGit(GIT_COMMIT, {"commit", "-m", msg});
×
164
  } else {
165
    executeGit(GIT_COMMIT, {"commit", "-m", msg, "--", pgit(file)});
×
166
  }
167
}
×
168

169
/**
170
 * @brief ImitatePass::Remove custom implementation of "pass remove"
171
 */
172
void ImitatePass::Remove(QString file, bool isDir) {
×
173
  file = QtPassSettings::getPassStore() + file;
×
174
  transactionHelper trans(this, PASS_REMOVE);
×
175
  if (!isDir) {
×
176
    file += ".gpg";
×
177
  }
178
  if (QtPassSettings::isUseGit()) {
×
179
    executeGit(GIT_RM, {"rm", (isDir ? "-rf" : "-f"), pgit(file)});
×
180
    // Normalize path the same way as add/edit operations
181
    QString path = QDir(QtPassSettings::getPassStore()).relativeFilePath(file);
×
182
    path.replace(Util::endsWithGpg(), "");
×
NEW
183
    gitCommit(file, "Remove for " + path + " using QtPass.");
×
184
  } else {
185
    if (isDir) {
×
186
      QDir dir(file);
×
187
      dir.removeRecursively();
×
188
    } else {
×
189
      QFile(file).remove();
×
190
    }
191
  }
192
}
×
193

194
/**
195
 * @brief ImitatePass::Init initialize pass repository
196
 *
197
 * @param path      path in which new password-store will be created
198
 * @param users     list of users who shall be able to decrypt passwords in
199
 * path
200
 */
201
auto ImitatePass::checkSigningKeys(const QStringList &signingKeys) -> bool {
×
202
  QString out;
×
203
  QStringList args =
204
      QStringList{"--status-fd=1", "--list-secret-keys"} + signingKeys;
×
205
  int result =
206
      Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args, &out);
×
207
  if (result != 0) {
×
208
#ifdef QT_DEBUG
209
    dbg() << "GPG list-secret-keys failed with code:" << result;
210
#endif
211
    return false;
212
  }
213
  for (auto &key : signingKeys) {
×
214
    if (out.contains("[GNUPG:] KEY_CONSIDERED " + key)) {
×
215
      return true;
216
    }
217
  }
218
  return false;
219
}
×
220

221
void ImitatePass::writeGpgIdFile(const QString &gpgIdFile,
×
222
                                 const QList<UserInfo> &users) {
223
  QFile gpgId(gpgIdFile);
×
224
  if (!gpgId.open(QIODevice::WriteOnly | QIODevice::Text)) {
×
225
    emit critical(tr("Cannot update"),
×
226
                  tr("Failed to open .gpg-id for writing."));
×
227
    return;
228
  }
229
  bool secret_selected = false;
230
  for (const UserInfo &user : users) {
×
231
    if (user.enabled) {
×
232
      gpgId.write((user.key_id + "\n").toUtf8());
×
233
      secret_selected |= user.have_secret;
×
234
    }
235
  }
236
  gpgId.close();
×
237
  if (!secret_selected) {
×
238
    emit critical(
×
239
        tr("Check selected users!"),
×
240
        tr("None of the selected keys have a secret key available.\n"
×
241
           "You will not be able to decrypt any newly added passwords!"));
242
  }
243
}
×
244

245
auto ImitatePass::signGpgIdFile(const QString &gpgIdFile,
×
246
                                const QStringList &signingKeys) -> bool {
247
  QStringList args;
×
248
  // Use only the first signing key; multiple --default-key options would
249
  // override each other and only the last one would take effect.
250
  if (!signingKeys.isEmpty()) {
×
251
#ifdef QT_DEBUG
252
    if (signingKeys.size() > 1) {
253
      dbg() << "Multiple signing keys configured; using only the first key:"
254
            << signingKeys.first();
255
    }
256
#endif
257
    args.append(QStringList{"--default-key", signingKeys.first()});
×
258
  }
259
  args.append(QStringList{"--yes", "--detach-sign", gpgIdFile});
×
260
  int result =
261
      Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args);
×
262
  if (result != 0) {
×
263
#ifdef QT_DEBUG
264
    dbg() << "GPG signing failed with code:" << result;
265
#endif
266
    emit critical(tr("GPG signing failed!"),
×
267
                  tr("Failed to sign %1.").arg(gpgIdFile));
×
268
    return false;
×
269
  }
270
  if (!verifyGpgIdFile(gpgIdFile)) {
×
271
    emit critical(tr("Check .gpgid file signature!"),
×
272
                  tr("Signature for %1 is invalid.").arg(gpgIdFile));
×
273
    return false;
×
274
  }
275
  return true;
276
}
×
277

278
void ImitatePass::gitAddGpgId(const QString &gpgIdFile,
×
279
                              const QString &gpgIdSigFile, bool addFile,
280
                              bool addSigFile) {
281
  if (addFile) {
×
282
    executeGit(GIT_ADD, {"add", pgit(gpgIdFile)});
×
283
  }
284
  QString commitPath = gpgIdFile;
285
  commitPath.replace(Util::endsWithGpg(), "");
×
NEW
286
  gitCommit(gpgIdFile, "Added " + commitPath + " using QtPass.");
×
287
  if (!addSigFile) {
×
288
    return;
289
  }
290
  executeGit(GIT_ADD, {"add", pgit(gpgIdSigFile)});
×
291
  commitPath = gpgIdSigFile;
×
292
  commitPath.replace(QRegularExpression("\\.gpg$"), "");
×
NEW
293
  gitCommit(gpgIdSigFile, "Added " + commitPath + " using QtPass.");
×
294
}
×
295

296
void ImitatePass::Init(QString path, const QList<UserInfo> &users) {
×
297
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
298
  QStringList signingKeys =
299
      QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts);
×
300
#else
301
  QStringList signingKeys =
302
      QtPassSettings::getPassSigningKey().split(" ", QString::SkipEmptyParts);
303
#endif
304
  QString gpgIdSigFile = path + ".gpg-id.sig";
×
305
  bool addSigFile = false;
306
  if (!signingKeys.isEmpty()) {
×
307
    if (!checkSigningKeys(signingKeys)) {
×
308
      emit critical(tr("No signing key!"),
×
309
                    tr("None of the secret signing keys is available.\n"
×
310
                       "You will not be able to change the user list!"));
311
      return;
×
312
    }
313
    QFileInfo checkFile(gpgIdSigFile);
×
314
    if (!checkFile.exists() || !checkFile.isFile()) {
×
315
      addSigFile = true;
316
    }
317
  }
×
318

319
  QString gpgIdFile = path + ".gpg-id";
×
320
  bool addFile = false;
321
  transactionHelper trans(this, PASS_INIT);
×
322
  if (QtPassSettings::isAddGPGId(true)) {
×
323
    QFileInfo checkFile(gpgIdFile);
×
324
    if (!checkFile.exists() || !checkFile.isFile()) {
×
325
      addFile = true;
326
    }
327
  }
×
328
  writeGpgIdFile(gpgIdFile, users);
×
329

330
  if (!signingKeys.isEmpty()) {
×
331
    if (!signGpgIdFile(gpgIdFile, signingKeys)) {
×
332
      return;
333
    }
334
  }
335

336
  if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit() &&
×
337
      !QtPassSettings::getGitExecutable().isEmpty()) {
×
338
    gitAddGpgId(gpgIdFile, gpgIdSigFile, addFile, addSigFile);
×
339
  }
340
  reencryptPath(path);
×
341
}
342

343
/**
344
 * @brief ImitatePass::verifyGpgIdFile verify detached gpgid file signature.
345
 * @param file which gpgid file.
346
 * @return was verification successful?
347
 */
348
auto ImitatePass::verifyGpgIdFile(const QString &file) -> bool {
×
349
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
350
  QStringList signingKeys =
351
      QtPassSettings::getPassSigningKey().split(" ", Qt::SkipEmptyParts);
×
352
#else
353
  QStringList signingKeys =
354
      QtPassSettings::getPassSigningKey().split(" ", QString::SkipEmptyParts);
355
#endif
356
  if (signingKeys.isEmpty()) {
×
357
    return true;
358
  }
359
  QString out;
×
360
  QStringList args =
361
      QStringList{"--verify", "--status-fd=1", pgpg(file) + ".sig", pgpg(file)};
×
362
  int result =
363
      Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args, &out);
×
364
  if (result != 0) {
×
365
#ifdef QT_DEBUG
366
    dbg() << "GPG verify failed with code:" << result;
367
#endif
368
    return false;
369
  }
370
  QRegularExpression re(
371
      R"(^\[GNUPG:\] VALIDSIG ([A-F0-9]{40}) .* ([A-F0-9]{40})\r?$)",
372
      QRegularExpression::MultilineOption);
×
373
  QRegularExpressionMatch m = re.match(out);
×
374
  if (!m.hasMatch()) {
×
375
    return false;
376
  }
377
  QStringList fingerprints = m.capturedTexts();
×
378
  fingerprints.removeFirst();
×
379
  for (auto &key : signingKeys) {
×
380
    if (fingerprints.contains(key)) {
×
381
      return true;
×
382
    }
383
  }
384
  return false;
×
385
}
×
386

387
/**
388
 * @brief ImitatePass::removeDir delete folder recursive.
389
 * @param dirName which folder.
390
 * @return was removal successful?
391
 */
392
auto ImitatePass::removeDir(const QString &dirName) -> bool {
1✔
393
  bool result = true;
394
  QDir dir(dirName);
1✔
395

396
  if (dir.exists(dirName)) {
1✔
397
    for (const QFileInfo &info :
398
         dir.entryInfoList(QDir::NoDotAndDotDot | QDir::System | QDir::Hidden |
399
                               QDir::AllDirs | QDir::Files,
400
                           QDir::DirsFirst)) {
1✔
401
      if (info.isDir()) {
×
402
        result = removeDir(info.absoluteFilePath());
×
403
      } else {
404
        result = QFile::remove(info.absoluteFilePath());
×
405
      }
406

407
      if (!result) {
×
408
        return result;
409
      }
410
    }
411
    result = dir.rmdir(dirName);
1✔
412
  }
413
  return result;
414
}
1✔
415

416
/**
417
 * @brief ImitatePass::reencryptPath reencrypt all files under the chosen
418
 * directory
419
 *
420
 * This is still quite experimental..
421
 * @param dir
422
 */
423
auto ImitatePass::verifyGpgIdForDir(const QString &file,
×
424
                                    QStringList &gpgIdFilesVerified,
425
                                    QStringList &gpgId) -> bool {
426
  QString gpgIdPath = Pass::getGpgIdPath(file);
×
427
  if (gpgIdFilesVerified.contains(gpgIdPath)) {
×
428
    return true;
429
  }
430
  if (!verifyGpgIdFile(gpgIdPath)) {
×
431
    emit critical(tr("Check .gpgid file signature!"),
×
432
                  tr("Signature for %1 is invalid.").arg(gpgIdPath));
×
433
    return false;
×
434
  }
435
  gpgIdFilesVerified.append(gpgIdPath);
436
  gpgId = getRecipientList(file);
×
437
  gpgId.sort();
438
  return true;
439
}
440

441
auto ImitatePass::getKeysFromFile(const QString &fileName) -> QStringList {
×
442
  QStringList args = {
443
      "-v",          "--no-secmem-warning", "--no-permission-warning",
444
      "--list-only", "--keyid-format=long", pgpg(fileName)};
×
445
  QString keys;
×
446
  QString err;
×
447
  Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args, &keys,
×
448
                            &err);
449
  QStringList actualKeys;
×
450
  keys += err;
451
#if QT_VERSION >= QT_VERSION_CHECK(5, 15, 0)
452
  QStringList key = keys.split(Util::newLinesRegex(), Qt::SkipEmptyParts);
×
453
#else
454
  QStringList key = keys.split(Util::newLinesRegex(), QString::SkipEmptyParts);
455
#endif
456
  QListIterator<QString> itr(key);
457
  while (itr.hasNext()) {
×
458
    QString current = itr.next();
459
    QStringList cur = current.split(" ");
×
460
    if (cur.length() > 4) {
×
461
      QString actualKey = cur.takeAt(4);
×
462
      if (actualKey.length() == 16) {
×
463
        actualKeys << actualKey;
464
      }
465
    }
466
  }
467
  actualKeys.sort();
468
  return actualKeys;
×
469
}
×
470

471
auto ImitatePass::reencryptSingleFile(const QString &fileName,
×
472
                                      const QStringList &recipients) -> bool {
473
#ifdef QT_DEBUG
474
  dbg() << "reencrypt " << fileName << " for " << recipients;
475
#endif
476
  QString local_lastDecrypt;
×
477
  QStringList args = {
478
      "-d",      "--quiet",     "--yes",       "--no-encrypt-to",
479
      "--batch", "--use-agent", pgpg(fileName)};
×
480
  int result = Executor::executeBlocking(QtPassSettings::getGpgExecutable(),
×
481
                                         args, &local_lastDecrypt);
482

483
  if (result != 0 || local_lastDecrypt.isEmpty()) {
×
484
#ifdef QT_DEBUG
485
    dbg() << "Decrypt error on re-encrypt for:" << fileName;
486
#endif
487
    return false;
488
  }
489

490
  if (local_lastDecrypt.right(1) != "\n") {
×
491
    local_lastDecrypt += "\n";
×
492
  }
493

494
  // Use passed recipients instead of re-reading from file
495
  if (recipients.isEmpty()) {
×
496
    emit critical(tr("Can not edit"),
×
497
                  tr("Could not read encryption key to use, .gpg-id "
×
498
                     "file missing or invalid."));
499
    return false;
×
500
  }
501

502
  // Encrypt to temporary file for atomic replacement
503
  QString tempPath = fileName + ".reencrypt.tmp";
×
504
  args = QStringList{"--yes", "--batch", "-eq", "--output", pgpg(tempPath)};
×
505
  for (const auto &i : recipients) {
×
506
    args.append("-r");
×
507
    args.append(i);
508
  }
509
  args.append("-");
×
510
  result = Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
511
                                     local_lastDecrypt);
512

513
  if (result != 0) {
×
514
#ifdef QT_DEBUG
515
    dbg() << "Encrypt error on re-encrypt for:" << fileName;
516
#endif
517
    QFile::remove(tempPath);
×
518
    return false;
519
  }
520

521
  // Verify encryption worked by attempting to decrypt the temp file
522
  QString verifyOutput;
×
523
  args = QStringList{"-d", "--quiet", "--batch", "--use-agent", pgpg(tempPath)};
×
524
  result = Executor::executeBlocking(QtPassSettings::getGpgExecutable(), args,
×
525
                                     &verifyOutput);
526
  if (result != 0 || verifyOutput.isEmpty()) {
×
527
#ifdef QT_DEBUG
528
    dbg() << "Verification failed for:" << tempPath;
529
#endif
530
    QFile::remove(tempPath);
×
531
    return false;
532
  }
533
  // Verify content matches original decrypted content (defense in depth)
534
  if (verifyOutput.trimmed() != local_lastDecrypt.trimmed()) {
×
535
#ifdef QT_DEBUG
536
    dbg() << "Verification content mismatch for:" << tempPath;
537
#endif
538
    QFile::remove(tempPath);
×
539
    return false;
540
  }
541

542
  // Atomic replace with backup: rename original to .bak, rename temp to
543
  // original, then remove backup
544
  QString backupPath = fileName + ".reencrypt.bak";
×
545
  if (!QFile::rename(fileName, backupPath)) {
×
546
#ifdef QT_DEBUG
547
    dbg() << "Failed to backup original file:" << fileName;
548
#endif
549
    QFile::remove(tempPath);
×
550
    return false;
551
  }
552
  if (!QFile::rename(tempPath, fileName)) {
×
553
#ifdef QT_DEBUG
554
    dbg() << "Failed to rename temp file to:" << fileName;
555
#endif
556
    // Restore backup and clean up temp file
557
    QFile::rename(backupPath, fileName);
×
558
    QFile::remove(tempPath);
×
559
    emit critical(
×
560
        tr("Re-encryption failed"),
×
561
        tr("Failed to replace %1. Original has been restored.").arg(fileName));
×
562
    return false;
×
563
  }
564
  // Success - remove backup
565
  QFile::remove(backupPath);
×
566

567
  if (!QtPassSettings::isUseWebDav() && QtPassSettings::isUseGit()) {
×
568
    Executor::executeBlocking(QtPassSettings::getGitExecutable(),
×
569
                              {"add", pgit(fileName)});
570
    QString path =
571
        QDir(QtPassSettings::getPassStore()).relativeFilePath(fileName);
×
572
    path.replace(Util::endsWithGpg(), "");
×
573
    Executor::executeBlocking(QtPassSettings::getGitExecutable(),
×
574
                              {"commit", pgit(fileName), "-m",
575
                               "Re-encrypt for " + path + " using QtPass."});
×
576
  }
577

578
  return true;
579
}
×
580

581
/**
582
 * @brief Create git backup commit before re-encryption.
583
 * @return true if backup created or not needed, false if backup failed.
584
 */
585
auto ImitatePass::createBackupCommit() -> bool {
×
586
  if (!QtPassSettings::isUseGit() ||
×
587
      QtPassSettings::getGitExecutable().isEmpty()) {
×
588
    return true;
589
  }
590
  emit statusMsg(tr("Creating backup commit"), 2000);
×
591
  const QString git = QtPassSettings::getGitExecutable();
×
592
  QString statusOut;
×
593
  if (Executor::executeBlocking(git, {"status", "--porcelain"}, &statusOut) !=
×
594
      0) {
595
    emit critical(
×
596
        tr("Backup commit failed"),
×
597
        tr("Could not inspect git status. Re-encryption was aborted."));
×
598
    return false;
×
599
  }
600
  if (!statusOut.trimmed().isEmpty()) {
×
601
    if (Executor::executeBlocking(git, {"add", "-A"}) != 0 ||
×
602
        Executor::executeBlocking(
×
603
            git, {"commit", "-m", "Backup before re-encryption"}) != 0) {
604
      emit critical(tr("Backup commit failed"),
×
605
                    tr("Re-encryption was aborted because a git backup could "
×
606
                       "not be created."));
607
      return false;
×
608
    }
609
  }
610
  return true;
611
}
×
612

613
void ImitatePass::reencryptPath(const QString &dir) {
×
614
  emit statusMsg(tr("Re-encrypting from folder %1").arg(dir), 3000);
×
615
  emit startReencryptPath();
×
616
  if (QtPassSettings::isAutoPull()) {
×
617
    emit statusMsg(tr("Updating password-store"), 2000);
×
618
    GitPull_b();
×
619
  }
620

621
  // Create backup before re-encryption - abort if it fails
622
  if (!createBackupCommit()) {
×
623
    emit endReencryptPath();
×
624
    return;
×
625
  }
626

627
  QDir currentDir;
×
628
  QDirIterator gpgFiles(dir, QStringList() << "*.gpg", QDir::Files,
×
629
                        QDirIterator::Subdirectories);
×
630
  QStringList gpgIdFilesVerified;
×
631
  QStringList gpgId;
×
632
  int successCount = 0;
633
  int failCount = 0;
634
  while (gpgFiles.hasNext()) {
×
635
    QString fileName = gpgFiles.next();
×
636
    if (gpgFiles.fileInfo().path() != currentDir.path()) {
×
637
      if (!verifyGpgIdForDir(fileName, gpgIdFilesVerified, gpgId)) {
×
638
        emit endReencryptPath();
×
639
        return;
640
      }
641
      if (gpgId.isEmpty() && !gpgIdFilesVerified.isEmpty()) {
×
642
        emit critical(tr("GPG ID verification failed"),
×
643
                      tr("Could not verify .gpg-id for directory."));
×
644
        emit endReencryptPath();
×
645
        return;
646
      }
647
    }
648
    QStringList actualKeys = getKeysFromFile(fileName);
×
649
    if (actualKeys != gpgId) {
×
650
      if (reencryptSingleFile(fileName, gpgId)) {
×
651
        successCount++;
×
652
      } else {
653
        failCount++;
×
654
        emit critical(tr("Re-encryption failed"),
×
655
                      tr("Failed to re-encrypt %1").arg(fileName));
×
656
      }
657
    }
658
  }
659

660
  if (failCount > 0) {
×
661
    emit statusMsg(tr("Re-encryption completed: %1 succeeded, %2 failed")
×
662
                       .arg(successCount)
×
663
                       .arg(failCount),
×
664
                   5000);
665
  } else {
666
    emit statusMsg(
×
667
        tr("Re-encryption completed: %1 files re-encrypted").arg(successCount),
×
668
        3000);
669
  }
670

671
  if (QtPassSettings::isAutoPush()) {
×
672
    emit statusMsg(tr("Updating password-store"), 2000);
×
673
    GitPush();
×
674
  }
675
  emit endReencryptPath();
×
676
}
×
677

678
auto ImitatePass::resolveMoveDestination(const QString &src,
5✔
679
                                         const QString &dest, bool force)
680
    -> QString {
681
  QFileInfo srcFileInfo(src);
5✔
682
  QFileInfo destFileInfo(dest);
5✔
683
  QString destFile;
5✔
684
  QString srcFileBaseName = srcFileInfo.fileName();
5✔
685

686
  if (srcFileInfo.isFile()) {
5✔
687
    if (destFileInfo.isFile()) {
4✔
688
      if (!force) {
2✔
689
#ifdef QT_DEBUG
690
        dbg() << "Destination file already exists";
691
#endif
692
        return QString();
693
      }
694
      destFile = dest;
1✔
695
    } else if (destFileInfo.isDir()) {
2✔
696
      destFile = QDir(dest).filePath(srcFileBaseName);
2✔
697
    } else {
698
      destFile = dest;
1✔
699
    }
700

701
    if (destFile.endsWith(".gpg", Qt::CaseInsensitive)) {
6✔
702
      destFile.chop(4);
3✔
703
    }
704
    destFile.append(".gpg");
3✔
705
  } else if (srcFileInfo.isDir()) {
1✔
706
    if (destFileInfo.isDir()) {
×
707
      destFile = QDir(dest).filePath(srcFileBaseName);
×
708
    } else if (destFileInfo.isFile()) {
×
709
#ifdef QT_DEBUG
710
      dbg() << "Destination is a file";
711
#endif
712
      return QString();
713
    } else {
714
      destFile = dest;
×
715
    }
716
  } else {
717
#ifdef QT_DEBUG
718
    dbg() << "Source file does not exist";
719
#endif
720
    return QString();
721
  }
722
  return destFile;
723
}
5✔
724

725
void ImitatePass::executeMoveGit(const QString &src, const QString &destFile,
×
726
                                 bool force) {
727
  QStringList args;
×
728
  args << "mv";
×
729
  if (force) {
×
730
    args << "-f";
×
731
  }
732
  args << pgit(src);
×
733
  args << pgit(destFile);
×
734
  executeGit(GIT_MOVE, args);
×
735

736
  QString relSrc = QDir(QtPassSettings::getPassStore()).relativeFilePath(src);
×
737
  relSrc.replace(Util::endsWithGpg(), "");
×
738
  QString relDest =
739
      QDir(QtPassSettings::getPassStore()).relativeFilePath(destFile);
×
740
  relDest.replace(Util::endsWithGpg(), "");
×
741
  QString message = QString("Moved for %1 to %2 using QtPass.");
×
742
  message = message.arg(relSrc, relDest);
×
NEW
743
  gitCommit("", message);
×
744
}
×
745

746
void ImitatePass::Move(const QString src, const QString dest,
×
747
                       const bool force) {
748
  transactionHelper trans(this, PASS_MOVE);
×
749
  QString destFile = resolveMoveDestination(src, dest, force);
×
750
  if (destFile.isEmpty()) {
×
751
    return;
752
  }
753

754
#ifdef QT_DEBUG
755
  dbg() << "Move Source: " << src;
756
  dbg() << "Move Destination: " << destFile;
757
#endif
758

759
  if (QtPassSettings::isUseGit()) {
×
760
    executeMoveGit(src, destFile, force);
×
761
  } else {
762
    QDir qDir;
×
763
    if (force) {
×
764
      qDir.remove(destFile);
×
765
    }
766
    qDir.rename(src, destFile);
×
767
  }
×
768
}
769

770
void ImitatePass::Copy(const QString src, const QString dest,
×
771
                       const bool force) {
772
  QFileInfo destFileInfo(dest);
×
773
  transactionHelper trans(this, PASS_COPY);
×
774
  if (QtPassSettings::isUseGit()) {
×
775
    QStringList args;
×
776
    args << "cp";
×
777
    if (force) {
×
778
      args << "-f";
×
779
    }
780
    args << pgit(src);
×
781
    args << pgit(dest);
×
782
    executeGit(GIT_COPY, args);
×
783

784
    QString message = QString("Copied from %1 to %2 using QtPass.");
×
785
    message = message.arg(src, dest);
×
NEW
786
    gitCommit("", message);
×
787
  } else {
788
    QDir qDir;
×
789
    if (force) {
×
790
      qDir.remove(dest);
×
791
    }
792
    QFile::copy(src, dest);
×
793
  }
×
794
  // reecrypt all files under the new folder
795
  if (destFileInfo.isDir()) {
×
796
    reencryptPath(destFileInfo.absoluteFilePath());
×
797
  } else if (destFileInfo.isFile()) {
×
798
    reencryptPath(destFileInfo.dir().path());
×
799
  }
800
}
×
801

802
/**
803
 * @brief ImitatePass::executeGpg easy wrapper for running gpg commands
804
 * @param args
805
 */
806
void ImitatePass::executeGpg(PROCESS id, const QStringList &args, QString input,
×
807
                             bool readStdout, bool readStderr) {
808
  executeWrapper(id, QtPassSettings::getGpgExecutable(), args, std::move(input),
×
809
                 readStdout, readStderr);
810
}
×
811

812
/**
813
 * @brief ImitatePass::executeGit easy wrapper for running git commands
814
 * @param args
815
 */
816
void ImitatePass::executeGit(PROCESS id, const QStringList &args, QString input,
×
817
                             bool readStdout, bool readStderr) {
818
  executeWrapper(id, QtPassSettings::getGitExecutable(), args, std::move(input),
×
819
                 readStdout, readStderr);
820
}
×
821

822
/**
823
 * @brief ImitatePass::finished this function is overloaded to ensure
824
 *                              identical behaviour to RealPass ie. only PASS_*
825
 *                              processes are visible inside Pass::finish, so
826
 *                              that interface-wise it all looks the same
827
 * @param id
828
 * @param exitCode
829
 * @param out
830
 * @param err
831
 */
832
void ImitatePass::finished(int id, int exitCode, const QString &out,
×
833
                           const QString &err) {
834
#ifdef QT_DEBUG
835
  dbg() << "Imitate Pass";
836
#endif
837
  static QString transactionOutput;
×
838
  PROCESS pid = transactionIsOver(static_cast<PROCESS>(id));
×
839
  transactionOutput.append(out);
×
840

841
  if (exitCode == 0) {
×
842
    if (pid == INVALID) {
×
843
      return;
844
    }
845
  } else {
846
    while (pid == INVALID) {
×
847
      id = exec.cancelNext();
×
848
      if (id == -1) {
×
849
        //  this is probably irrecoverable and shall not happen
850
#ifdef QT_DEBUG
851
        dbg() << "No such transaction!";
852
#endif
853
        return;
854
      }
855
      pid = transactionIsOver(static_cast<PROCESS>(id));
×
856
    }
857
  }
858
  Pass::finished(pid, exitCode, transactionOutput, err);
×
859
  transactionOutput.clear();
×
860
}
861

862
/**
863
 * @brief executeWrapper    overrided so that every execution is a transaction
864
 * @param id
865
 * @param app
866
 * @param args
867
 * @param input
868
 * @param readStdout
869
 * @param readStderr
870
 */
871
void ImitatePass::executeWrapper(PROCESS id, const QString &app,
×
872
                                 const QStringList &args, QString input,
873
                                 bool readStdout, bool readStderr) {
874
  transactionAdd(id);
×
875
  Pass::executeWrapper(id, app, args, input, readStdout, readStderr);
×
876
}
×
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