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

IJHack / QtPass / 23862950363

01 Apr 2026 05:54PM UTC coverage: 20.086% (+0.004%) from 20.082%
23862950363

push

github

web-flow
Merge pull request #890 from IJHack/fix/ai-findings-10-more

fix: address 10 more AI findings across 4 files

9 of 45 new or added lines in 7 files covered. (20.0%)

4 existing lines in 4 files now uncovered.

1030 of 5128 relevant lines covered (20.09%)

7.89 hits per line

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

4.84
/src/qtpass.cpp
1
// SPDX-FileCopyrightText: 2016 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 <utility>
18
#else
19
#define WIN32_LEAN_AND_MEAN /*_KILLING_MACHINE*/
20
#define WIN32_EXTRA_LEAN
21
#include <windows.h>
22
#include <winnetwk.h>
23
#undef DELETE
24
#endif
25

26
#ifdef QT_DEBUG
27
#include "debughelper.h"
28
#endif
29

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

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

51
  setMainWindow();
×
52
}
×
53

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

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

78
  QtPassSettings::initExecutables();
×
79

80
  QString version = QtPassSettings::getVersion();
×
81
  // dbg()<< version;
82

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

89
    if (QtPassSettings::getAutoclearSeconds() < 5) {
×
90
      QtPassSettings::setAutoclearSeconds(10);
×
91
    }
92
    if (QtPassSettings::getAutoclearPanelSeconds() < 5) {
×
93
      QtPassSettings::setAutoclearPanelSeconds(10);
×
94
    }
95
    if (!QtPassSettings::getPwgenExecutable().isEmpty()) {
×
96
      QtPassSettings::setUsePwgen(true);
×
97
    } else {
98
      QtPassSettings::setUsePwgen(false);
×
99
    }
100
    QtPassSettings::setPassTemplate("login\nurl");
×
101
  } else {
102
    // QStringList ver = version.split(".");
103
    // dbg()<< ver;
104
    // if (ver[0] == "0" && ver[1] == "8") {
105
    //// upgrade to 0.9
106
    // }
107
    if (QtPassSettings::getPassTemplate().isEmpty()) {
×
108
      QtPassSettings::setPassTemplate("login\nurl");
×
109
    }
110
  }
111

112
  QtPassSettings::setVersion(VERSION);
×
113

114
  if (!Util::configIsValid()) {
×
115
    m_mainWindow->config();
×
116
    if (freshStart && !Util::configIsValid()) {
×
117
      return false;
118
    }
119
  }
120

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

127
  freshStart = false;
×
128
  // startupPhase = false;
129
  return true;
×
130
}
131

132
/**
133
 * @brief Sets up the main window and connects signal handlers.
134
 */
135
void QtPass::setMainWindow() {
×
136
  m_mainWindow->restoreWindow();
×
137

138
  fusedav.setParent(m_mainWindow);
×
139

140
  // Signal handlers are connected for both pass implementations
141
  // Note: When pass binary changes, QtPass restart is required to reconnect
142
  // This is acceptable as pass binary change is infrequent
143
  connectPassSignalHandlers(QtPassSettings::getRealPass());
×
144
  connectPassSignalHandlers(QtPassSettings::getImitatePass());
×
145

146
  connect(m_mainWindow, &MainWindow::passShowHandlerFinished, this,
×
147
          &QtPass::passShowHandlerFinished);
×
148

149
  // only for ipass
150
  connect(QtPassSettings::getImitatePass(), &ImitatePass::startReencryptPath,
×
151
          m_mainWindow, &MainWindow::startReencryptPath);
×
152
  connect(QtPassSettings::getImitatePass(), &ImitatePass::endReencryptPath,
×
153
          m_mainWindow, &MainWindow::endReencryptPath);
×
154

NEW
155
  connect(m_mainWindow, &MainWindow::passGitInitNeeded, [this]() {
×
156
#ifdef QT_DEBUG
157
    dbg() << "Pass git init called";
158
#endif
159
    QtPassSettings::getPass()->GitInit();
×
160
  });
×
161

162
  connect(m_mainWindow, &MainWindow::generateGPGKeyPair, m_mainWindow,
×
NEW
163
          [this](const QString &batch) {
×
164
            QtPassSettings::getPass()->GenerateGPGKeys(batch);
×
165
            m_mainWindow->showStatusMessage(tr("Generating GPG key pair"),
×
166
                                            60000);
167
          });
×
168
}
×
169

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

186
  connect(pass, &Pass::finishedGitInit, this, &QtPass::passStoreChanged);
×
187
  connect(pass, &Pass::finishedGitPull, this, &QtPass::processFinished);
×
188
  connect(pass, &Pass::finishedGitPush, this, &QtPass::processFinished);
×
189
  connect(pass, &Pass::finishedInsert, this, &QtPass::finishedInsert);
×
190
  connect(pass, &Pass::finishedRemove, this, &QtPass::passStoreChanged);
×
191
  connect(pass, &Pass::finishedInit, this, &QtPass::passStoreChanged);
×
192
  connect(pass, &Pass::finishedMove, this, &QtPass::passStoreChanged);
×
193
  connect(pass, &Pass::finishedCopy, this, &QtPass::passStoreChanged);
×
194
  connect(pass, &Pass::finishedGenerateGPGKeys, this,
×
195
          &QtPass::onKeyGenerationComplete);
×
196
}
×
197

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

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

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

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

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

328
    m_mainWindow->flashText(output, false, true);
×
329
  }
330

331
  m_mainWindow->setUiElementsEnabled(true);
×
332
}
×
333

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

347
  m_mainWindow->setUiElementsEnabled(true);
×
348
}
×
349

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

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

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

383
    m_mainWindow->cleanKeygenDialog();
×
384
    m_mainWindow->showStatusMessage(tr("GPG key pair generated successfully"),
×
385
                                    10000);
386
  }
387

388
  processFinished(p_output, p_errout);
×
389
}
×
390

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

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

409
  output.replace(Util::protocolRegex(), R"(<a href="\1">\1</a>)");
×
410
  output.replace(QStringLiteral("\n"), "<br />");
×
411
  output = prefix + output + postfix;
×
412

413
  m_mainWindow->flashText(output, false, true);
×
414
}
×
415

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

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

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

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

474
  clippedText.clear();
×
475
}
×
476

477
/**
478
 * @brief MainWindow::copyTextToClipboard copies text to your clipboard
479
 * @param text
480
 */
481
void QtPass::copyTextToClipboard(const QString &text) {
×
482
  QClipboard *clip = QApplication::clipboard();
×
483
  if (!QtPassSettings::isUseSelection()) {
×
484
    clip->setText(text, QClipboard::Clipboard);
×
485
  } else {
486
    clip->setText(text, QClipboard::Selection);
×
487
  }
488

489
  clippedText = text;
×
490
  m_mainWindow->showStatusMessage(tr("Copied to clipboard"));
×
491
  if (QtPassSettings::isUseAutoclear()) {
×
492
    clearClipboardTimer.start();
×
493
  }
494
}
×
495

496
/**
497
 * @brief displays the text as qrcode
498
 * @param text
499
 */
500
void QtPass::showTextAsQRCode(const QString &text) {
×
501
  QProcess qrencode;
×
502
  qrencode.start(QtPassSettings::getQrencodeExecutable("/usr/bin/qrencode"),
×
503
                 QStringList() << "-o-"
×
504
                               << "-tPNG");
×
505
  qrencode.write(text.toUtf8());
×
506
  qrencode.closeWriteChannel();
×
507
  qrencode.waitForFinished();
×
508
  QByteArray output(qrencode.readAllStandardOutput());
×
509

510
  if (qrencode.exitStatus() || qrencode.exitCode()) {
×
511
    QString error(qrencode.readAllStandardError());
×
512
    m_mainWindow->showStatusMessage(error);
×
513
  } else {
514
    QPixmap image;
×
515
    image.loadFromData(output, "PNG");
×
516
    QDialog *popup = createQRCodePopup(image);
×
517
    popup->exec();
×
518
  }
×
519
}
×
520

521
/**
522
 * @brief QtPass::createQRCodePopup creates a popup dialog with the given QR
523
 * code image. This is extracted for testability. The caller is responsible
524
 * for showing and managing the popup lifecycle.
525
 * @param image The QR code pixmap to display
526
 * @return The created popup dialog
527
 */
528
QDialog *QtPass::createQRCodePopup(const QPixmap &image) {
1✔
529
  auto *popup = new QDialog(nullptr, Qt::Popup | Qt::FramelessWindowHint);
1✔
530
  popup->setAttribute(Qt::WA_DeleteOnClose);
1✔
531
  auto *layout = new QVBoxLayout;
1✔
532
  auto *popupLabel = new QLabel();
1✔
533
  layout->addWidget(popupLabel);
1✔
534
  popupLabel->setPixmap(image);
1✔
535
  popupLabel->setScaledContents(true);
1✔
536
  popupLabel->show();
1✔
537
  popup->setLayout(layout);
1✔
538
  popup->move(QCursor::pos());
1✔
539
  return popup;
1✔
540
}
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