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

IJHack / QtPass / 24288991547

11 Apr 2026 06:39PM UTC coverage: 20.84% (-0.008%) from 20.848%
24288991547

push

github

web-flow
fix: exclude passwords from clipboard history on all platforms (#970)

* fix: set x-kde-passwordManagerHint when copying passwords on Linux

* fix: set password hints for clipboard on all platforms

* fix: properly guard clipboard hint code for each platform (#348)

* fix: add Windows clipboard security hints as DWORDs

* fix: correct Linux/macOS hints and add selection support check

* fix: add QClipboard::Mode declaration for Windows

* refactor: hoist clipboard mode selection before platform branches

0 of 5 new or added lines in 1 file covered. (0.0%)

7 existing lines in 1 file now uncovered.

1106 of 5307 relevant lines covered (20.84%)

7.74 hits per line

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

4.8
/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, [this]() {
×
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);
×
191
}
×
192

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

472
/**
473
 * @brief MainWindow::copyTextToClipboard copies text to your clipboard
474
 * @param text
475
 */
476
void QtPass::copyTextToClipboard(const QString &text) {
×
477
  QClipboard *clip = QApplication::clipboard();
×
478

479
  QClipboard::Mode mode = QClipboard::Clipboard;
NEW
480
  if (QtPassSettings::isUseSelection() && clip->supportsSelection()) {
×
481
    mode = QClipboard::Selection;
482
  }
483

484
#ifndef Q_OS_WIN
NEW
485
  auto *mimeData = new QMimeData();
×
NEW
486
  mimeData->setText(text);
×
487
#ifdef Q_OS_LINUX
NEW
488
  mimeData->setData("x-kde-passwordManagerHint", QByteArray("secret"));
×
489
#endif
490
#ifdef Q_OS_MAC
491
  mimeData->setData("application/x-nspasteboard-concealed-type", QByteArray());
492
#endif
NEW
493
  clip->setMimeData(mimeData, mode);
×
494
#else
495
  auto *mimeData = new QMimeData();
496
  mimeData->setText(text);
497
  const auto dwordBytes = [](quint32 value) {
498
    return QByteArray(reinterpret_cast<const char *>(&value), sizeof(value));
499
  };
500
  mimeData->setData("ExcludeClipboardContentFromMonitorProcessing",
501
                    dwordBytes(1));
502
  mimeData->setData("CanIncludeInClipboardHistory", dwordBytes(0));
503
  mimeData->setData("CanUploadToCloudClipboard", dwordBytes(0));
504
  clip->setMimeData(mimeData, mode);
505
#endif
506

507
  clippedText = text;
×
508
  m_mainWindow->showStatusMessage(tr("Copied to clipboard"));
×
509
  if (QtPassSettings::isUseAutoclear()) {
×
510
    clearClipboardTimer.start();
×
511
  }
512
}
×
513

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

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

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