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

IJHack / QtPass / 24607260560

18 Apr 2026 02:56PM UTC coverage: 22.902% (+1.0%) from 21.908%
24607260560

Pull #1037

github

web-flow
Merge e738f9626 into dfd4e5b4c
Pull Request #1037: feat: implement pass grep content search (#109)

72 of 145 new or added lines in 5 files covered. (49.66%)

225 existing lines in 6 files now uncovered.

1296 of 5659 relevant lines covered (22.9%)

8.61 hits per line

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

6.69
/src/qtpass.cpp
1
// SPDX-FileCopyrightText: 2018 Anne Jan Brouwer
2
// SPDX-License-Identifier: GPL-3.0-or-later
3
#include "qtpass.h"
4
#include "mainwindow.h"
5
#include "qtpasssettings.h"
6
#include "util.h"
7
#include <QApplication>
8
#include <QClipboard>
9
#include <QDialog>
10
#include <QLabel>
11
#include <QPixmap>
12
#include <QVBoxLayout>
13

14
#ifndef Q_OS_WIN
15
#include <QInputDialog>
16
#include <QLineEdit>
17
#include <QMimeData>
18
#include <utility>
19
#else
20
#define WIN32_LEAN_AND_MEAN /*_KILLING_MACHINE*/
21
#define WIN32_EXTRA_LEAN
22
#include <windows.h>
23
#include <winnetwk.h>
24
#undef DELETE
25
#include <QMimeData>
26
#endif
27

28
#ifdef QT_DEBUG
29
#include "debughelper.h"
30
#endif
31

32
/**
33
 * @brief Constructs a QtPass instance.
34
 * @param mainWindow The main window reference
35
 */
36
QtPass::QtPass(MainWindow *mainWindow)
×
37
    : m_mainWindow(mainWindow), freshStart(true) {
×
38
  setClipboardTimer();
×
39
  clearClipboardTimer.setSingleShot(true);
×
40
  connect(&clearClipboardTimer, &QTimer::timeout, this,
×
41
          &QtPass::clearClipboard);
×
42

43
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
44
#pragma GCC diagnostic push
45
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
46
#endif
47
  QObject::connect(qApp, &QApplication::aboutToQuit, this,
×
48
                   &QtPass::clearClipboard);
×
49
#if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0)
50
#pragma GCC diagnostic pop
51
#endif
52

53
  setMainWindow();
×
54
}
×
55

56
/**
57
 * @brief QtPass::~QtPass destroy!
58
 */
59
QtPass::~QtPass() {
×
60
#ifdef Q_OS_WIN
61
  if (QtPassSettings::isUseWebDav())
62
    WNetCancelConnection2A(QtPassSettings::getPassStore().toUtf8().constData(),
63
                           0, 1);
64
#else
65
  if (fusedav.state() == QProcess::Running) {
×
66
    fusedav.terminate();
×
67
    fusedav.waitForFinished(2000);
×
68
  }
69
#endif
70
}
×
71

72
/**
73
 * @brief QtPass::init make sure we are ready to go as soon as
74
 * possible
75
 */
76
auto QtPass::init() -> bool {
×
77
  QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
×
78
  QtPassSettings::setPassStore(passStore);
×
79

80
  QtPassSettings::initExecutables();
×
81

82
  QString version = QtPassSettings::getVersion();
×
83

84
  // Config updates
85
  if (version.isEmpty()) {
×
86
#ifdef QT_DEBUG
87
    dbg() << "assuming fresh install";
88
#endif
89

90
    if (QtPassSettings::getAutoclearSeconds() < 5) {
×
91
      QtPassSettings::setAutoclearSeconds(10);
×
92
    }
93
    if (QtPassSettings::getAutoclearPanelSeconds() < 5) {
×
94
      QtPassSettings::setAutoclearPanelSeconds(10);
×
95
    }
96
    if (!QtPassSettings::getPwgenExecutable().isEmpty()) {
×
97
      QtPassSettings::setUsePwgen(true);
×
98
    } else {
99
      QtPassSettings::setUsePwgen(false);
×
100
    }
101
    QtPassSettings::setPassTemplate("login\nurl");
×
102
  } else {
103
    if (QtPassSettings::getPassTemplate().isEmpty()) {
×
104
      QtPassSettings::setPassTemplate("login\nurl");
×
105
    }
106
  }
107

108
  QtPassSettings::setVersion(VERSION);
×
109

110
  if (!Util::configIsValid()) {
×
111
    m_mainWindow->config();
×
112
    if (freshStart && !Util::configIsValid()) {
×
113
      return false;
114
    }
115
  }
116

117
  // Note: WebDAV mount needs to happen before accessing the store,
118
  // but ideally should be done after Window is shown to avoid long delay.
119
  if (QtPassSettings::isUseWebDav()) {
×
120
    mountWebDav();
×
121
  }
122

123
  freshStart = false;
×
124
  return true;
×
125
}
126

127
/**
128
 * @brief Sets up the main window and connects signal handlers.
129
 */
130
void QtPass::setMainWindow() {
×
131
  m_mainWindow->restoreWindow();
×
132

133
  fusedav.setParent(m_mainWindow);
×
134

135
  // Signal handlers are connected for both pass implementations
136
  // Note: When pass binary changes, QtPass restart is required to reconnect
137
  // This is acceptable as pass binary change is infrequent
138
  connectPassSignalHandlers(QtPassSettings::getRealPass());
×
139
  connectPassSignalHandlers(QtPassSettings::getImitatePass());
×
140

141
  connect(m_mainWindow, &MainWindow::passShowHandlerFinished, this,
×
142
          &QtPass::passShowHandlerFinished);
×
143

144
  // only for ipass
145
  connect(QtPassSettings::getImitatePass(), &ImitatePass::startReencryptPath,
×
146
          m_mainWindow, &MainWindow::startReencryptPath);
×
147
  connect(QtPassSettings::getImitatePass(), &ImitatePass::endReencryptPath,
×
148
          m_mainWindow, &MainWindow::endReencryptPath);
×
149

150
  connect(m_mainWindow, &MainWindow::passGitInitNeeded, []() {
×
151
#ifdef QT_DEBUG
152
    dbg() << "Pass git init called";
153
#endif
154
    QtPassSettings::getPass()->GitInit();
×
155
  });
×
156

157
  connect(m_mainWindow, &MainWindow::generateGPGKeyPair, m_mainWindow,
×
158
          [this](const QString &batch) {
×
159
            QtPassSettings::getPass()->GenerateGPGKeys(batch);
×
160
            m_mainWindow->showStatusMessage(tr("Generating GPG key pair"),
×
161
                                            60000);
162
          });
×
163
}
×
164

165
/**
166
 * @brief Connects pass signal handlers to QtPass slots.
167
 * @param pass The pass instance to connect
168
 */
169
void QtPass::connectPassSignalHandlers(Pass *pass) {
×
170
  connect(pass, &Pass::error, this, &QtPass::processError);
×
171
  connect(pass, &Pass::processErrorExit, this, &QtPass::processErrorExit);
×
172
  connect(pass, &Pass::critical, m_mainWindow, &MainWindow::critical);
×
173
  connect(pass, &Pass::startingExecuteWrapper, m_mainWindow,
×
174
          &MainWindow::executeWrapperStarted);
×
175
  connect(pass, &Pass::statusMsg, m_mainWindow, &MainWindow::showStatusMessage);
×
176
  connect(pass, &Pass::finishedShow, m_mainWindow,
×
177
          &MainWindow::passShowHandler);
×
178
  connect(pass, &Pass::finishedOtpGenerate, m_mainWindow,
×
179
          &MainWindow::passOtpHandler);
×
180

181
  connect(pass, &Pass::finishedGitInit, this, &QtPass::passStoreChanged);
×
182
  connect(pass, &Pass::finishedGitPull, this, &QtPass::processFinished);
×
183
  connect(pass, &Pass::finishedGitPush, this, &QtPass::processFinished);
×
184
  connect(pass, &Pass::finishedInsert, this, &QtPass::finishedInsert);
×
185
  connect(pass, &Pass::finishedRemove, this, &QtPass::passStoreChanged);
×
186
  connect(pass, &Pass::finishedInit, this, &QtPass::passStoreChanged);
×
187
  connect(pass, &Pass::finishedMove, this, &QtPass::passStoreChanged);
×
188
  connect(pass, &Pass::finishedCopy, this, &QtPass::passStoreChanged);
×
189
  connect(pass, &Pass::finishedGenerateGPGKeys, this,
×
190
          &QtPass::onKeyGenerationComplete);
×
NEW
191
  connect(pass, &Pass::finishedGrep, m_mainWindow, &MainWindow::onGrepFinished);
×
UNCOV
192
}
×
193

194
/**
195
 * @brief QtPass::mountWebDav is some scary voodoo magic
196
 */
197
void QtPass::mountWebDav() {
×
198
#ifdef Q_OS_WIN
199
  char dst[20] = {0};
200
  NETRESOURCEA netres;
201
  memset(&netres, 0, sizeof(netres));
202
  netres.dwType = RESOURCETYPE_DISK;
203
  netres.lpLocalName = nullptr;
204
  // Store QByteArray in variables to ensure lifetime during WNetUseConnectionA
205
  // call
206
  QByteArray webDavUrlUtf8 = QtPassSettings::getWebDavUrl().toUtf8();
207
  QByteArray webDavPasswordUtf8 = QtPassSettings::getWebDavPassword().toUtf8();
208
  QByteArray webDavUserUtf8 = QtPassSettings::getWebDavUser().toUtf8();
209
  netres.lpRemoteName = const_cast<char *>(webDavUrlUtf8.constData());
210
  DWORD size = sizeof(dst);
211
  DWORD r = WNetUseConnectionA(
212
      reinterpret_cast<HWND>(m_mainWindow->effectiveWinId()), &netres,
213
      const_cast<char *>(webDavPasswordUtf8.constData()),
214
      const_cast<char *>(webDavUserUtf8.constData()),
215
      CONNECT_TEMPORARY | CONNECT_INTERACTIVE | CONNECT_REDIRECT, dst, &size,
216
      0);
217
  if (r == NO_ERROR) {
218
    QtPassSettings::setPassStore(dst);
219
  } else {
220
    char message[256] = {0};
221
    FormatMessageA(FORMAT_MESSAGE_FROM_SYSTEM, 0, r, 0, message,
222
                   sizeof(message), 0);
223
    m_mainWindow->flashText(tr("Failed to connect WebDAV:\n") + message +
224
                                " (0x" + QString::number(r, 16) + ")",
225
                            true);
226
  }
227
#else
228
  fusedav.start("fusedav", QStringList()
×
229
                               << "-o"
×
230
                               << "nonempty"
×
231
                               << "-u"
×
232
                               << "\"" + QtPassSettings::getWebDavUser() + "\""
×
233
                               << QtPassSettings::getWebDavUrl()
×
234
                               << "\"" + QtPassSettings::getPassStore() + "\"");
×
235
  fusedav.waitForStarted();
×
236
  if (fusedav.state() == QProcess::Running) {
×
237
    QString pwd = QtPassSettings::getWebDavPassword();
×
238
    bool ok = true;
×
239
    if (pwd.isEmpty()) {
×
240
      pwd = QInputDialog::getText(m_mainWindow, tr("QtPass WebDAV password"),
×
241
                                  tr("Enter password to connect to WebDAV:"),
×
242
                                  QLineEdit::Password, "", &ok);
243
    }
244
    if (ok && !pwd.isEmpty()) {
×
245
      fusedav.write(pwd.toUtf8() + '\n');
×
246
      fusedav.closeWriteChannel();
×
247
      fusedav.waitForFinished(2000);
×
248
    } else {
249
      fusedav.terminate();
×
250
    }
251
  }
252
  QString error = fusedav.readAllStandardError();
×
253
  int prompt = error.indexOf("Password:");
×
254
  if (prompt >= 0) {
×
255
    error.remove(0, prompt + 10);
×
256
  }
257
  if (fusedav.state() != QProcess::Running) {
×
258
    error = tr("fusedav exited unexpectedly\n") + error;
×
259
  }
260
  if (error.size() > 0) {
×
261
    m_mainWindow->flashText(
×
262
        tr("Failed to start fusedav to connect WebDAV:\n") + error, true);
×
263
  }
264
#endif
265
}
×
266

267
/**
268
 * @brief QtPass::processError something went wrong
269
 * @param error
270
 */
271
void QtPass::processError(QProcess::ProcessError error) {
×
272
  QString errorString;
×
273
  switch (error) {
×
274
  case QProcess::FailedToStart:
×
275
    errorString = tr("QProcess::FailedToStart");
×
276
    break;
×
277
  case QProcess::Crashed:
×
278
    errorString = tr("QProcess::Crashed");
×
279
    break;
×
280
  case QProcess::Timedout:
×
281
    errorString = tr("QProcess::Timedout");
×
282
    break;
×
283
  case QProcess::ReadError:
×
284
    errorString = tr("QProcess::ReadError");
×
285
    break;
×
286
  case QProcess::WriteError:
×
287
    errorString = tr("QProcess::WriteError");
×
288
    break;
×
289
  case QProcess::UnknownError:
×
290
    errorString = tr("QProcess::UnknownError");
×
291
    break;
×
292
  }
293
  m_mainWindow->flashText(errorString, true);
×
294
  m_mainWindow->setUiElementsEnabled(true);
×
295
}
×
296

297
/**
298
 * @brief Handles process error exit.
299
 * @param exitCode The exit code
300
 * @param p_error The error message
301
 */
302
void QtPass::processErrorExit(int exitCode, const QString &p_error) {
×
303
  if (nullptr != m_mainWindow->getKeygenDialog()) {
×
304
    m_mainWindow->cleanKeygenDialog();
×
305
    if (exitCode != 0) {
×
306
      m_mainWindow->showStatusMessage(tr("GPG key pair generation failed"),
×
307
                                      10000);
308
    }
309
  }
310

311
  if (!p_error.isEmpty()) {
×
312
    QString output;
×
313
    QString error = p_error.toHtmlEscaped();
×
314
    if (exitCode == 0) {
×
315
      //  https://github.com/IJHack/qtpass/issues/111
316
      output = "<span style=\"color: darkgray;\">" + error + "</span><br />";
×
317
    } else {
318
      output = "<span style=\"color: red;\">" + error + "</span><br />";
×
319
    }
320

321
    output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
322
    output.replace(QStringLiteral("\n"), "<br />");
×
323

324
    m_mainWindow->flashText(output, false, true);
×
325
  }
326

327
  m_mainWindow->setUiElementsEnabled(true);
×
328
}
×
329

330
/**
331
 * @brief QtPass::processFinished background process has finished
332
 * @param exitCode
333
 * @param exitStatus
334
 * @param output    stdout from a process
335
 * @param errout    stderr from a process
336
 */
337
void QtPass::processFinished(const QString &p_output, const QString &p_errout) {
×
338
  showInTextBrowser(p_output);
×
339
  //    Sometimes there is error output even with 0 exit code, which is
340
  //    assumed in this function
341
  processErrorExit(0, p_errout);
×
342

343
  m_mainWindow->setUiElementsEnabled(true);
×
344
}
×
345

346
/**
347
 * @brief Called when pass store has changed.
348
 * @param p_out Output from the process
349
 * @param p_err Error output
350
 */
351
void QtPass::passStoreChanged(const QString &p_out, const QString &p_err) {
×
352
  processFinished(p_out, p_err);
×
353
  doGitPush();
×
354
}
×
355

356
/**
357
 * @brief Called when an insert operation has finished.
358
 * @param p_output Output from the process
359
 * @param p_errout Error output
360
 */
361
void QtPass::finishedInsert(const QString &p_output, const QString &p_errout) {
×
362
  processFinished(p_output, p_errout);
×
363
  doGitPush();
×
364
  m_mainWindow->on_treeView_clicked(m_mainWindow->getCurrentTreeViewIndex());
×
365
}
×
366

367
/**
368
 * @brief Called when GPG key generation is complete.
369
 * @param p_output Standard output from the key generation process
370
 * @param p_errout Standard error output from the key generation process
371
 */
372
void QtPass::onKeyGenerationComplete(const QString &p_output,
×
373
                                     const QString &p_errout) {
374
  if (nullptr != m_mainWindow->getKeygenDialog()) {
×
375
#ifdef QT_DEBUG
376
    qDebug() << "Keygen Done";
377
#endif
378

379
    m_mainWindow->cleanKeygenDialog();
×
380
    m_mainWindow->showStatusMessage(tr("GPG key pair generated successfully"),
×
381
                                    10000);
382
  }
383

384
  processFinished(p_output, p_errout);
×
385
}
×
386

387
/**
388
 * @brief Called when the password show handler has finished.
389
 * @param output The password content to display
390
 */
391
void QtPass::passShowHandlerFinished(QString output) {
×
392
  showInTextBrowser(std::move(output));
×
393
}
×
394

395
/**
396
 * @brief Displays output text in the main window's text browser.
397
 * @param output The text to display
398
 * @param prefix Optional prefix to prepend to the output
399
 * @param postfix Optional postfix to append to the output
400
 */
401
void QtPass::showInTextBrowser(QString output, const QString &prefix,
×
402
                               const QString &postfix) {
403
  output = output.toHtmlEscaped();
×
404

405
  output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
406
  output.replace(QStringLiteral("\n"), "<br />");
×
407
  output = prefix + output + postfix;
×
408

409
  m_mainWindow->flashText(output, false, true);
×
410
}
×
411

412
/**
413
 * @brief Performs automatic git push if enabled in settings.
414
 */
415
void QtPass::doGitPush() {
×
416
  if (QtPassSettings::isAutoPush()) {
×
417
    m_mainWindow->onPush();
×
418
  }
419
}
×
420

421
/**
422
 * @brief Sets the text to be stored in clipboard and handles clipboard
423
 * operations.
424
 * @param password The password or text to store
425
 * @param p_output Additional output text
426
 */
427
void QtPass::setClippedText(const QString &password, const QString &p_output) {
×
428
  if (QtPassSettings::getClipBoardType() != Enums::CLIPBOARD_NEVER &&
×
429
      !p_output.isEmpty()) {
430
    clippedText = password;
×
431
    if (QtPassSettings::getClipBoardType() == Enums::CLIPBOARD_ALWAYS) {
×
432
      copyTextToClipboard(password);
×
433
    }
434
  }
435
}
×
436
/**
437
 * @brief Clears the stored clipped text.
438
 */
439
void QtPass::clearClippedText() { clippedText = ""; }
×
440

441
/**
442
 * @brief Sets the clipboard clear timer based on autoclear settings.
443
 */
444
void QtPass::setClipboardTimer() {
×
445
  clearClipboardTimer.setInterval(MS_PER_SECOND *
×
446
                                  QtPassSettings::getAutoclearSeconds());
×
447
}
×
448

449
/**
450
 * @brief MainWindow::clearClipboard remove clipboard contents.
451
 */
452
void QtPass::clearClipboard() {
×
453
  QClipboard *clipboard = QApplication::clipboard();
×
454
  bool cleared = false;
455
  if (this->clippedText == clipboard->text(QClipboard::Selection)) {
×
456
    clipboard->clear(QClipboard::Selection);
×
457
    clipboard->setText(QString(""), QClipboard::Selection);
×
458
    cleared = true;
459
  }
460
  if (this->clippedText == clipboard->text(QClipboard::Clipboard)) {
×
461
    clipboard->clear(QClipboard::Clipboard);
×
462
    cleared = true;
463
  }
464
  if (cleared) {
×
465
    m_mainWindow->showStatusMessage(tr("Clipboard cleared"));
×
466
  } else {
467
    m_mainWindow->showStatusMessage(tr("Clipboard not cleared"));
×
468
  }
469

470
  clippedText.clear();
×
471
}
×
472

473
/**
474
 * @brief Build clipboard MIME data with platform-specific security hints.
475
 * @param text - Plain text to copy
476
 * @return QMimeData with text and security hints
477
 */
478
auto buildClipboardMimeData(const QString &text) -> QMimeData * {
1✔
479
  auto *mimeData = new QMimeData();
1✔
480
  mimeData->setText(text);
1✔
481
#ifdef Q_OS_LINUX
482
  mimeData->setData("x-kde-passwordManagerHint", QByteArray("secret"));
2✔
483
#endif
484
#ifdef Q_OS_MAC
485
  mimeData->setData("application/x-nspasteboard-concealed-type", QByteArray());
486
#endif
487
#ifdef Q_OS_WIN
488
  mimeData->setData("ExcludeClipboardContentFromMonitorProcessing",
489
                    dwordBytes(1));
490
  mimeData->setData("CanIncludeInClipboardHistory", dwordBytes(0));
491
  mimeData->setData("CanUploadToCloudClipboard", dwordBytes(0));
492
#endif
493
  return mimeData;
1✔
494
}
495

496
/**
497
 * @brief MainWindow::copyTextToClipboard copies text to your clipboard
498
 * @param text
499
 */
500
void QtPass::copyTextToClipboard(const QString &text) {
×
501
  QClipboard *clip = QApplication::clipboard();
×
502

503
  QClipboard::Mode mode = QClipboard::Clipboard;
504
  if (QtPassSettings::isUseSelection() && clip->supportsSelection()) {
×
505
    mode = QClipboard::Selection;
506
  }
507

508
  auto *mimeData = buildClipboardMimeData(text);
×
509
  clip->setMimeData(mimeData, mode);
×
510

511
  clippedText = text;
×
512
  m_mainWindow->showStatusMessage(tr("Copied to clipboard"));
×
513
  if (QtPassSettings::isUseAutoclear()) {
×
514
    clearClipboardTimer.start();
×
515
  }
516
}
×
517

518
/**
519
 * @brief displays the text as qrcode
520
 * @param text
521
 */
522
void QtPass::showTextAsQRCode(const QString &text) {
×
523
  QProcess qrencode;
×
524
  qrencode.start(QtPassSettings::getQrencodeExecutable("/usr/bin/qrencode"),
×
525
                 QStringList() << "-o-"
×
526
                               << "-tPNG");
×
527
  qrencode.write(text.toUtf8());
×
528
  qrencode.closeWriteChannel();
×
529
  qrencode.waitForFinished();
×
530
  QByteArray output(qrencode.readAllStandardOutput());
×
531

532
  if (qrencode.exitStatus() || qrencode.exitCode()) {
×
533
    QString error(qrencode.readAllStandardError());
×
534
    m_mainWindow->showStatusMessage(error);
×
535
  } else {
536
    QPixmap image;
×
537
    image.loadFromData(output, "PNG");
×
538
    QDialog *popup = createQRCodePopup(image);
×
539
    popup->exec();
×
540
  }
×
541
}
×
542

543
/**
544
 * @brief QtPass::createQRCodePopup creates a popup dialog with the given QR
545
 * code image. This is extracted for testability. The caller is responsible
546
 * for showing and managing the popup lifecycle.
547
 * @param image The QR code pixmap to display
548
 * @return The created popup dialog
549
 */
550
QDialog *QtPass::createQRCodePopup(const QPixmap &image) {
1✔
551
  auto *popup = new QDialog(nullptr, Qt::Popup | Qt::FramelessWindowHint);
1✔
552
  popup->setAttribute(Qt::WA_DeleteOnClose);
1✔
553
  auto *layout = new QVBoxLayout;
1✔
554
  auto *popupLabel = new QLabel();
1✔
555
  layout->addWidget(popupLabel);
1✔
556
  popupLabel->setPixmap(image);
1✔
557
  popupLabel->setScaledContents(true);
1✔
558
  popupLabel->show();
1✔
559
  popup->setLayout(layout);
1✔
560
  popup->move(QCursor::pos());
1✔
561
  return popup;
1✔
562
}
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