QtPass Developer documentation
QtPass test coverage
Current view: top level - src/qtpass.cpp (source / functions) Coverage Total Hit
Test: QtPass Lines: 97.7 % 131 128
Test Date: 2026-11-14 13:19:18 Functions: 100.0 % 14 14

            Line data    Source code
       1              : // SPDX-FileCopyrightText: 2018 Anne Jan Brouwer
       2              : // SPDX-License-Identifier: GPL-3.0-or-later
       3              : /**
       4              :  * @file
       5              :  * @brief Implements QtPass; init() holds the settings migrations run on every
       6              :  *        start-up and showTextAsQRCode() drives the qrencode process.
       7              :  */
       8              : #include "qtpass.h"
       9              : #include "executor.h"
      10              : #include "imitatepass.h"
      11              : #include "qtpasssettings.h"
      12              : #include "settingsconstants.h"
      13              : #include "util.h"
      14              : #include <QApplication>
      15              : #include <QDialog>
      16              : #include <QLabel>
      17              : #include <QPixmap>
      18              : #include <QVBoxLayout>
      19              : 
      20              : #include "qtpasslogging.h"
      21              : 
      22          146 : QtPass::QtPass(QObject *parent) : QObject(parent), m_clipboard(this) {
      23              :   // Both implementations are wired up: the active one can change through the
      24              :   // configuration dialog, and reconnecting on every switch is not worth it.
      25          146 :   connectPassSignalHandlers(QtPassSettings::getRealPass());
      26          146 :   connectPassSignalHandlers(QtPassSettings::getImitatePass());
      27          146 : }
      28              : 
      29          277 : QtPass::~QtPass() = default;
      30              : 
      31          134 : void QtPass::init() {
      32          134 :   QString passStore = QtPassSettings::getPassStore(Util::findPasswordStore());
      33          134 :   QtPassSettings::setPassStore(passStore);
      34              : 
      35          134 :   QtPassSettings::initExecutables();
      36              : 
      37          268 :   QString version = QtPassSettings::getVersion();
      38              : 
      39              :   // Config updates
      40          134 :   if (version.isEmpty()) {
      41            4 :     qCDebug(lcQtPass) << "assuming fresh install";
      42            4 :     AppSettings s = QtPassSettings::load();
      43            4 :     if (s.autoclearSeconds < 5)
      44            4 :       s.autoclearSeconds = 10;
      45            4 :     if (s.autoclearPanelSeconds < 5)
      46            4 :       s.autoclearPanelSeconds = 10;
      47            4 :     s.usePwgen = !s.pwgenExecutable.isEmpty();
      48            4 :     s.passTemplate = QStringLiteral("login\nurl\nOTP");
      49            4 :     QtPassSettings::save(s);
      50              :     // A fresh profile already has the new useOtp default, so record the
      51              :     // migration as done. Without this, turning OTP off during the first session
      52              :     // would be undone by the migration branch on the next launch.
      53            8 :     QtPassSettings::getInstance()->setValue(
      54            8 :         SettingsConstants::otpMigratedToNative, true);
      55            4 :   } else {
      56          130 :     AppSettings s = QtPassSettings::load();
      57              :     bool changed = false;
      58          130 :     if (s.passTemplate.isEmpty()) {
      59            1 :       s.passTemplate = QStringLiteral("login\nurl\nOTP");
      60              :       changed = true;
      61              :     }
      62              :     // `useOtp` used to gate the Unix-only pass-otp extension and now gates
      63              :     // built-in TOTP, so an existing profile's stored false is stale rather than
      64              :     // a preference. The new default of true never reaches these users: the
      65              :     // fresh-install branch above already persisted the key, and the serializer
      66              :     // writes it unconditionally. Enable it once, remembered by its own key so a
      67              :     // deliberate opt-out is not overridden on the next launch.
      68          130 :     if (!QtPassSettings::getInstance()
      69          260 :              ->value(SettingsConstants::otpMigratedToNative, false)
      70          130 :              .toBool()) {
      71            1 :       s.useOtp = true;
      72            2 :       QtPassSettings::getInstance()->setValue(
      73            2 :           SettingsConstants::otpMigratedToNative, true);
      74              :       changed = true;
      75              :     }
      76          129 :     if (changed) {
      77            1 :       QtPassSettings::save(s);
      78              :     }
      79          130 :   }
      80              : 
      81          268 :   QtPassSettings::setVersion(VERSION);
      82          134 : }
      83              : 
      84          292 : void QtPass::connectPassSignalHandlers(Pass *pass) {
      85          292 :   connect(pass, &Pass::processErrorExit, this, &QtPass::processErrorExit);
      86          292 :   connect(pass, &Pass::finishedGitInit, this, &QtPass::passStoreChanged);
      87          292 :   connect(pass, &Pass::finishedGitPull, this, &QtPass::processFinished);
      88          292 :   connect(pass, &Pass::finishedGitPush, this, &QtPass::processFinished);
      89          292 :   connect(pass, &Pass::finishedInsert, this, &QtPass::finishedInsert);
      90          292 :   connect(pass, &Pass::finishedRemove, this, &QtPass::passStoreChanged);
      91          292 :   connect(pass, &Pass::finishedInit, this, &QtPass::passStoreChanged);
      92          292 :   connect(pass, &Pass::finishedMove, this, &QtPass::passStoreChanged);
      93          292 :   connect(pass, &Pass::finishedCopy, this, &QtPass::passStoreChanged);
      94          292 :   connect(pass, &Pass::finishedGenerateGPGKeys, this,
      95          292 :           &QtPass::onKeyGenerationComplete);
      96          292 : }
      97              : 
      98           37 : void QtPass::reportError(int exitCode, const QString &error) {
      99           37 :   if (error.isEmpty()) {
     100           12 :     return;
     101              :   }
     102              :   const QString colour =
     103           31 :       exitCode == 0 ? QStringLiteral("darkgray") : QStringLiteral("red");
     104           50 :   emit outputReady(formatOutput(
     105           50 :       error, QStringLiteral("<span style=\"color: %1;\">").arg(colour),
     106           25 :       QStringLiteral("</span><br />")));
     107              : }
     108              : 
     109           23 : void QtPass::processErrorExit(int exitCode, const QString &error) {
     110           23 :   reportError(exitCode, error);
     111           23 :   emit operationFinished();
     112           23 : }
     113              : 
     114           14 : void QtPass::processFinished(const QString &output, const QString &errout) {
     115              :   // A silent command (git push with nothing to push, say) has nothing to
     116              :   // show; re-setting the browser's HTML for it would only reset its scroll.
     117           14 :   if (!output.isEmpty()) {
     118            6 :     emit outputReady(formatOutput(output));
     119              :   }
     120           14 :   reportError(0, errout);
     121           14 :   emit operationFinished();
     122           14 : }
     123              : 
     124            5 : void QtPass::passStoreChanged(const QString &output, const QString &errout) {
     125            5 :   processFinished(output, errout);
     126            5 :   doGitPush();
     127            5 : }
     128              : 
     129            2 : void QtPass::finishedInsert(const QString &output, const QString &errout) {
     130            2 :   processFinished(output, errout);
     131            2 :   doGitPush();
     132            2 :   emit entryInserted();
     133            2 : }
     134              : 
     135            2 : void QtPass::onKeyGenerationComplete(const QString &output,
     136              :                                      const QString &errout) {
     137              :   // The KeygenDialog listens to the same signal and closes itself.
     138            2 :   emit statusMessage(tr("GPG key pair generated successfully"), 10000);
     139            2 :   processFinished(output, errout);
     140            2 : }
     141              : 
     142           45 : auto QtPass::formatOutput(QString output, const QString &prefix,
     143              :                           const QString &postfix) -> QString {
     144              :   // Escapes and links only launchable http(s) URLs; anything else stays
     145              :   // plain text (the text browser opens external links on click).
     146           45 :   output = Util::linkifyUrls(output);
     147           90 :   output.replace(QStringLiteral("\n"), "<br />");
     148           90 :   return prefix + output + postfix;
     149              : }
     150              : 
     151            7 : void QtPass::doGitPush() {
     152            7 :   if (QtPassSettings::isAutoPush()) {
     153            5 :     emit pushRequested();
     154              :   }
     155            7 : }
     156              : 
     157            5 : void QtPass::showTextAsQRCode(const QString &text) {
     158            5 :   const AppSettings s = QtPassSettings::load();
     159              :   const QString qrExe = s.qrencodeExecutable.isEmpty()
     160            5 :                             ? QStringLiteral("/usr/bin/qrencode")
     161              :                             : s.qrencodeExecutable;
     162            5 :   QProcess qrencode;
     163            5 :   Executor::detachFromTerminal(qrencode);
     164           15 :   qrencode.start(qrExe, QStringList() << "-o-"
     165           10 :                                       << "-tPNG");
     166              :   // A missing or non-executable binary never "finishes": exitStatus() stays
     167              :   // NormalExit and exitCode() 0, which used to fall through to an empty popup.
     168            5 :   if (!qrencode.waitForStarted()) {
     169            2 :     emit statusMessage(tr("Could not start qrencode: %1").arg(qrExe), 2000);
     170            1 :     return;
     171              :   }
     172            4 :   qrencode.write(text.toUtf8());
     173            4 :   qrencode.closeWriteChannel();
     174              :   // A hung qrencode also leaves exitStatus()/exitCode() at their defaults.
     175            4 :   if (!qrencode.waitForFinished()) {
     176            0 :     qrencode.kill();
     177            0 :     emit statusMessage(tr("qrencode did not finish in time"), 2000);
     178            0 :     return;
     179              :   }
     180            4 :   QByteArray output(qrencode.readAllStandardOutput());
     181              : 
     182            4 :   const bool crashed = qrencode.exitStatus() != QProcess::NormalExit;
     183              :   // exitCode() is meaningless after a crash, so only consult it otherwise.
     184            4 :   const bool failed = !crashed && qrencode.exitCode() != 0;
     185            2 :   if (crashed || failed) {
     186            6 :     QString error(qrencode.readAllStandardError());
     187            3 :     if (error.trimmed().isEmpty()) {
     188              :       error = crashed
     189            4 :                   ? tr("qrencode crashed")
     190            3 :                   : tr("qrencode exited with code %1").arg(qrencode.exitCode());
     191              :     }
     192            3 :     emit statusMessage(error, 2000);
     193              :   } else {
     194            1 :     QPixmap image;
     195            1 :     image.loadFromData(output, "PNG");
     196            1 :     QDialog *popup = createQRCodePopup(image);
     197            1 :     popup->exec();
     198            1 :   }
     199           10 : }
     200              : 
     201            2 : QDialog *QtPass::createQRCodePopup(const QPixmap &image) {
     202            2 :   auto *popup = new QDialog(nullptr, Qt::Popup | Qt::FramelessWindowHint);
     203            2 :   popup->setAttribute(Qt::WA_DeleteOnClose);
     204            2 :   auto *layout = new QVBoxLayout;
     205            2 :   auto *popupLabel = new QLabel();
     206            2 :   layout->addWidget(popupLabel);
     207            2 :   popupLabel->setPixmap(image);
     208            2 :   popupLabel->setScaledContents(true);
     209            2 :   popupLabel->show();
     210            2 :   popup->setLayout(layout);
     211            2 :   popup->move(QCursor::pos());
     212            2 :   return popup;
     213              : }
        

Generated by: LCOV version 2.0-1