File indexing completed on 2024-06-23 05:13:39

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/dumpcertificatecommand.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "dumpcertificatecommand.h"
0013 
0014 #include "command_p.h"
0015 
0016 #include <Libkleo/GnuPG>
0017 
0018 #include <gpgme++/key.h>
0019 
0020 #include <KLocalizedString>
0021 #include <KMessageBox>
0022 #include <KProcess>
0023 #include <KStandardGuiItem>
0024 #include <QPushButton>
0025 
0026 #include <QByteArray>
0027 #include <QFontDatabase>
0028 #include <QHBoxLayout>
0029 #include <QPointer>
0030 #include <QString>
0031 #include <QTextEdit>
0032 #include <QTimer>
0033 #include <QVBoxLayout>
0034 
0035 static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
0036 
0037 namespace
0038 {
0039 class DumpCertificateDialog : public QDialog
0040 {
0041     Q_OBJECT
0042 public:
0043     explicit DumpCertificateDialog(QWidget *parent = nullptr)
0044         : QDialog(parent)
0045         , ui(this)
0046     {
0047         resize(600, 500);
0048     }
0049 
0050 Q_SIGNALS:
0051     void updateRequested();
0052 
0053 public Q_SLOTS:
0054     void append(const QString &line)
0055     {
0056         ui.logTextWidget.append(line);
0057         ui.logTextWidget.ensureCursorVisible();
0058     }
0059     void clear()
0060     {
0061         ui.logTextWidget.clear();
0062     }
0063 
0064 private:
0065     struct Ui {
0066         QTextEdit logTextWidget;
0067         QPushButton updateButton, closeButton;
0068         QVBoxLayout vlay;
0069         QHBoxLayout hlay;
0070 
0071         explicit Ui(DumpCertificateDialog *q)
0072             : logTextWidget(q)
0073             , updateButton(i18nc("@action:button Update the log text widget", "&Update"), q)
0074             , closeButton(q)
0075             , vlay(q)
0076             , hlay()
0077         {
0078             KGuiItem::assign(&closeButton, KStandardGuiItem::close());
0079             KDAB_SET_OBJECT_NAME(logTextWidget);
0080             KDAB_SET_OBJECT_NAME(updateButton);
0081             KDAB_SET_OBJECT_NAME(closeButton);
0082             KDAB_SET_OBJECT_NAME(vlay);
0083             KDAB_SET_OBJECT_NAME(hlay);
0084 
0085             logTextWidget.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
0086             logTextWidget.setReadOnly(true);
0087             logTextWidget.setWordWrapMode(QTextOption::NoWrap);
0088 
0089             vlay.addWidget(&logTextWidget, 1);
0090             vlay.addLayout(&hlay);
0091 
0092             hlay.addWidget(&updateButton);
0093             hlay.addStretch(1);
0094             hlay.addWidget(&closeButton);
0095 
0096             connect(&updateButton, &QAbstractButton::clicked, q, &DumpCertificateDialog::updateRequested);
0097             connect(&closeButton, &QAbstractButton::clicked, q, &QWidget::close);
0098         }
0099     } ui;
0100 };
0101 }
0102 
0103 using namespace Kleo;
0104 using namespace Kleo::Commands;
0105 
0106 static QByteArray chomped(QByteArray ba)
0107 {
0108     while (ba.endsWith('\n') || ba.endsWith('\r')) {
0109         ba.chop(1);
0110     }
0111     return ba;
0112 }
0113 
0114 class DumpCertificateCommand::Private : Command::Private
0115 {
0116     friend class ::Kleo::Commands::DumpCertificateCommand;
0117     DumpCertificateCommand *q_func() const
0118     {
0119         return static_cast<DumpCertificateCommand *>(q);
0120     }
0121 
0122 public:
0123     explicit Private(DumpCertificateCommand *qq, KeyListController *c);
0124     ~Private() override;
0125 
0126     QString errorString() const
0127     {
0128         return QString::fromLocal8Bit(errorBuffer);
0129     }
0130 
0131 private:
0132     void init();
0133     void refreshView();
0134 
0135 private:
0136     void slotProcessFinished(int, QProcess::ExitStatus);
0137 
0138     void slotProcessReadyReadStandardOutput()
0139     {
0140         while (process.canReadLine()) {
0141             const QString line = Kleo::stringFromGpgOutput(chomped(process.readLine()));
0142             if (dialog) {
0143                 dialog->append(line);
0144             }
0145             outputBuffer.push_back(line);
0146         }
0147     }
0148 
0149     void slotProcessReadyReadStandardError()
0150     {
0151         errorBuffer += process.readAllStandardError();
0152     }
0153 
0154     void slotUpdateRequested()
0155     {
0156         if (process.state() == QProcess::NotRunning) {
0157             refreshView();
0158         }
0159     }
0160 
0161     void slotDialogDestroyed()
0162     {
0163         dialog = nullptr;
0164         if (process.state() != QProcess::NotRunning) {
0165             q->cancel();
0166         } else {
0167             finished();
0168         }
0169     }
0170 
0171 private:
0172     QPointer<DumpCertificateDialog> dialog;
0173     KProcess process;
0174     QByteArray errorBuffer;
0175     QStringList outputBuffer;
0176     bool useDialog;
0177     bool canceled;
0178 };
0179 
0180 DumpCertificateCommand::Private *DumpCertificateCommand::d_func()
0181 {
0182     return static_cast<Private *>(d.get());
0183 }
0184 const DumpCertificateCommand::Private *DumpCertificateCommand::d_func() const
0185 {
0186     return static_cast<const Private *>(d.get());
0187 }
0188 
0189 #define d d_func()
0190 #define q q_func()
0191 
0192 DumpCertificateCommand::Private::Private(DumpCertificateCommand *qq, KeyListController *c)
0193     : Command::Private(qq, c)
0194     , process()
0195     , errorBuffer()
0196     , outputBuffer()
0197     , useDialog(true)
0198     , canceled(false)
0199 {
0200     process.setOutputChannelMode(KProcess::SeparateChannels);
0201     process.setReadChannel(KProcess::StandardOutput);
0202 }
0203 
0204 DumpCertificateCommand::Private::~Private()
0205 {
0206     if (dialog && !dialog->isVisible()) {
0207         delete dialog;
0208     }
0209 }
0210 
0211 DumpCertificateCommand::DumpCertificateCommand(KeyListController *c)
0212     : Command(new Private(this, c))
0213 {
0214     d->init();
0215 }
0216 
0217 DumpCertificateCommand::DumpCertificateCommand(QAbstractItemView *v, KeyListController *c)
0218     : Command(v, new Private(this, c))
0219 {
0220     d->init();
0221 }
0222 
0223 DumpCertificateCommand::DumpCertificateCommand(const GpgME::Key &k)
0224     : Command(k, new Private(this, nullptr))
0225 {
0226     d->init();
0227 }
0228 
0229 void DumpCertificateCommand::Private::init()
0230 {
0231     connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) {
0232         slotProcessFinished(exitCode, status);
0233     });
0234     connect(&process, &QProcess::readyReadStandardError, q, [this]() {
0235         slotProcessReadyReadStandardError();
0236     });
0237     connect(&process, &QProcess::readyReadStandardOutput, q, [this] {
0238         slotProcessReadyReadStandardOutput();
0239     });
0240 
0241     if (!key().isNull()) {
0242         process << gpgSmPath() << QStringLiteral("--dump-cert") << QLatin1StringView(key().primaryFingerprint());
0243     }
0244 }
0245 
0246 DumpCertificateCommand::~DumpCertificateCommand()
0247 {
0248 }
0249 
0250 void DumpCertificateCommand::setUseDialog(bool use)
0251 {
0252     d->useDialog = use;
0253 }
0254 
0255 bool DumpCertificateCommand::useDialog() const
0256 {
0257     return d->useDialog;
0258 }
0259 
0260 QStringList DumpCertificateCommand::output() const
0261 {
0262     return d->outputBuffer;
0263 }
0264 
0265 void DumpCertificateCommand::doStart()
0266 {
0267     const std::vector<GpgME::Key> keys = d->keys();
0268     if (keys.size() != 1 || keys.front().protocol() != GpgME::CMS) {
0269         d->finished();
0270         return;
0271     }
0272 
0273     if (d->useDialog) {
0274         d->dialog = new DumpCertificateDialog;
0275         d->applyWindowID(d->dialog);
0276         d->dialog->setAttribute(Qt::WA_DeleteOnClose);
0277         d->dialog->setWindowTitle(i18nc("@title:window", "Certificate Dump"));
0278 
0279         connect(d->dialog, &DumpCertificateDialog::updateRequested, this, [this]() {
0280             d->slotUpdateRequested();
0281         });
0282         connect(d->dialog, &QObject::destroyed, this, [this]() {
0283             d->slotDialogDestroyed();
0284         });
0285     }
0286 
0287     d->refreshView();
0288 }
0289 
0290 void DumpCertificateCommand::Private::refreshView()
0291 {
0292     if (dialog) {
0293         dialog->clear();
0294     }
0295     errorBuffer.clear();
0296     outputBuffer.clear();
0297 
0298     process.start();
0299 
0300     if (process.waitForStarted()) {
0301         if (dialog) {
0302             dialog->show();
0303         }
0304     } else {
0305         KMessageBox::error(dialog ? static_cast<QWidget *>(dialog) : parentWidgetOrView(),
0306                            i18n("Unable to start process gpgsm. "
0307                                 "Please check your installation."),
0308                            i18n("Dump Certificate Error"));
0309         finished();
0310     }
0311 }
0312 
0313 void DumpCertificateCommand::doCancel()
0314 {
0315     d->canceled = true;
0316     if (d->process.state() != QProcess::NotRunning) {
0317         d->process.terminate();
0318         QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
0319     }
0320     if (d->dialog) {
0321         d->dialog->close();
0322     }
0323     d->dialog = nullptr;
0324 }
0325 
0326 void DumpCertificateCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
0327 {
0328     if (!canceled) {
0329         if (status == QProcess::CrashExit)
0330             KMessageBox::error(dialog,
0331                                i18n("The GpgSM process that tried to dump the certificate "
0332                                     "ended prematurely because of an unexpected error. "
0333                                     "Please check the output of gpgsm --dump-cert %1 for details.",
0334                                     QLatin1StringView(key().primaryFingerprint())),
0335                                i18nc("@title:window", "Dump Certificate Error"));
0336         else if (code)
0337             KMessageBox::error(dialog,
0338                                i18n("An error occurred while trying to dump the certificate. "
0339                                     "The output from GpgSM was:\n%1",
0340                                     errorString()),
0341                                i18nc("@title:window", "Dump Certificate Error"));
0342     }
0343     if (!useDialog) {
0344         slotDialogDestroyed();
0345     }
0346 }
0347 
0348 #undef d
0349 #undef q
0350 
0351 #include "dumpcertificatecommand.moc"
0352 #include "moc_dumpcertificatecommand.cpp"