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

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     commands/gnupgprocesscommand.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 "gnupgprocesscommand.h"
0013 
0014 #include "command_p.h"
0015 
0016 #include <Libkleo/GnuPG>
0017 
0018 #include "kleopatra_debug.h"
0019 #include <KLocalizedString>
0020 #include <KWindowSystem>
0021 
0022 #include <QByteArray>
0023 #include <QDialog>
0024 #include <QDialogButtonBox>
0025 #include <QPointer>
0026 #include <QProcess>
0027 #include <QPushButton>
0028 #include <QString>
0029 #include <QTextEdit>
0030 #include <QTimer>
0031 #include <QVBoxLayout>
0032 
0033 static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds
0034 
0035 using namespace Kleo;
0036 using namespace Kleo::Commands;
0037 
0038 namespace
0039 {
0040 
0041 class OutputDialog : public QDialog
0042 {
0043     Q_OBJECT
0044 public:
0045     explicit OutputDialog(QWidget *parent = nullptr)
0046         : QDialog(parent)
0047         , vlay(this)
0048         , logTextWidget(this)
0049         , buttonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close, Qt::Horizontal, this)
0050     {
0051         KDAB_SET_OBJECT_NAME(vlay);
0052         KDAB_SET_OBJECT_NAME(logTextWidget);
0053         KDAB_SET_OBJECT_NAME(buttonBox);
0054 
0055         logTextWidget.setReadOnly(true);
0056 
0057         vlay.addWidget(&logTextWidget, 1);
0058         vlay.addWidget(&buttonBox);
0059 
0060         connect(closeButton(), &QAbstractButton::clicked, this, &QWidget::close);
0061         connect(cancelButton(), &QAbstractButton::clicked, this, &OutputDialog::slotCancelClicked);
0062 
0063         resize(600, 500);
0064     }
0065 
0066 Q_SIGNALS:
0067     void cancelRequested();
0068 
0069 public Q_SLOTS:
0070     void message(const QString &s)
0071     {
0072         logTextWidget.append(s);
0073         logTextWidget.ensureCursorVisible();
0074     }
0075     void setComplete(bool complete)
0076     {
0077         cancelButton()->setVisible(!complete);
0078     }
0079 
0080 private Q_SLOTS:
0081     void slotCancelClicked()
0082     {
0083         cancelButton()->hide();
0084         Q_EMIT cancelRequested();
0085     }
0086 
0087 private:
0088     QAbstractButton *closeButton() const
0089     {
0090         return buttonBox.button(QDialogButtonBox::Close);
0091     }
0092     QAbstractButton *cancelButton() const
0093     {
0094         return buttonBox.button(QDialogButtonBox::Cancel);
0095     }
0096 
0097 private:
0098     QVBoxLayout vlay;
0099     QTextEdit logTextWidget;
0100     QDialogButtonBox buttonBox;
0101 };
0102 
0103 }
0104 
0105 class GnuPGProcessCommand::Private : Command::Private
0106 {
0107     friend class ::Kleo::Commands::GnuPGProcessCommand;
0108     GnuPGProcessCommand *q_func() const
0109     {
0110         return static_cast<GnuPGProcessCommand *>(q);
0111     }
0112 
0113 public:
0114     explicit Private(GnuPGProcessCommand *qq, KeyListController *c);
0115     ~Private() override;
0116 
0117 private:
0118     void init();
0119     void ensureDialogCreated()
0120     {
0121         if (!showsOutputWindow) {
0122             return;
0123         }
0124         if (!dialog) {
0125             dialog = new OutputDialog;
0126             dialog->setAttribute(Qt::WA_DeleteOnClose);
0127             applyWindowID(dialog);
0128             connect(dialog.data(), &OutputDialog::cancelRequested, q, &Command::cancel);
0129             dialog->setWindowTitle(i18nc("@title:window", "Subprocess Diagnostics"));
0130         }
0131     }
0132     void ensureDialogVisible()
0133     {
0134         if (!showsOutputWindow) {
0135             return;
0136         }
0137         ensureDialogCreated();
0138         if (dialog->isVisible()) {
0139             dialog->raise();
0140         } else {
0141             dialog->show();
0142         }
0143     }
0144     void message(const QString &msg)
0145     {
0146         if (dialog) {
0147             dialog->message(msg);
0148         } else {
0149             qCDebug(KLEOPATRA_LOG) << msg;
0150         }
0151     }
0152 
0153 private:
0154     void slotProcessFinished(int, QProcess::ExitStatus);
0155     void slotProcessReadyReadStandardError();
0156 
0157 private:
0158     QProcess process;
0159     QPointer<OutputDialog> dialog;
0160     QStringList arguments;
0161     QByteArray errorBuffer;
0162     bool ignoresSuccessOrFailure;
0163     bool showsOutputWindow;
0164     bool canceled;
0165 };
0166 
0167 GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func()
0168 {
0169     return static_cast<Private *>(d.get());
0170 }
0171 const GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() const
0172 {
0173     return static_cast<const Private *>(d.get());
0174 }
0175 
0176 #define d d_func()
0177 #define q q_func()
0178 
0179 GnuPGProcessCommand::Private::Private(GnuPGProcessCommand *qq, KeyListController *c)
0180     : Command::Private(qq, c)
0181     , process()
0182     , dialog()
0183     , errorBuffer()
0184     , ignoresSuccessOrFailure(false)
0185     , showsOutputWindow(false)
0186     , canceled(false)
0187 {
0188     process.setReadChannel(QProcess::StandardError);
0189 }
0190 
0191 GnuPGProcessCommand::Private::~Private()
0192 {
0193 }
0194 
0195 GnuPGProcessCommand::GnuPGProcessCommand(KeyListController *c)
0196     : Command(new Private(this, c))
0197 {
0198     d->init();
0199 }
0200 
0201 GnuPGProcessCommand::GnuPGProcessCommand(QAbstractItemView *v, KeyListController *c)
0202     : Command(v, new Private(this, c))
0203 {
0204     d->init();
0205 }
0206 
0207 GnuPGProcessCommand::GnuPGProcessCommand(const GpgME::Key &key)
0208     : Command(key, new Private(this, nullptr))
0209 {
0210     d->init();
0211 }
0212 
0213 GnuPGProcessCommand::GnuPGProcessCommand(const std::vector<GpgME::Key> &keys)
0214     : Command(keys, new Private(this, nullptr))
0215 {
0216     d->init();
0217 }
0218 
0219 void GnuPGProcessCommand::Private::init()
0220 {
0221     connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) {
0222         slotProcessFinished(exitCode, status);
0223     });
0224     q->m_procReadyReadStdErrConnection = connect(&process, &QProcess::readyReadStandardError, q, [this]() {
0225         slotProcessReadyReadStandardError();
0226     });
0227 }
0228 
0229 GnuPGProcessCommand::~GnuPGProcessCommand()
0230 {
0231 }
0232 
0233 QDialog *GnuPGProcessCommand::dialog() const
0234 {
0235     return d->dialog;
0236 }
0237 
0238 bool GnuPGProcessCommand::preStartHook(QWidget *) const
0239 {
0240     return true;
0241 }
0242 
0243 void GnuPGProcessCommand::postSuccessHook(QWidget *)
0244 {
0245 }
0246 
0247 void GnuPGProcessCommand::doStart()
0248 {
0249     if (!preStartHook(d->parentWidgetOrView())) {
0250         d->finished();
0251         return;
0252     }
0253 
0254     d->arguments = arguments();
0255 
0256     d->process.setProgram(d->arguments.takeFirst());
0257 
0258     d->process.setArguments(d->arguments);
0259 
0260     // Historically code using this expects arguments first to be the program.
0261     d->arguments.prepend(d->process.program());
0262 
0263     d->process.start();
0264 
0265     if (!d->process.waitForStarted()) {
0266         d->error(i18n("Unable to start process %1. "
0267                       "Please check your installation.",
0268                       d->arguments[0]),
0269                  errorCaption());
0270         d->finished();
0271     } else {
0272         d->ensureDialogVisible();
0273         d->message(i18n("Starting %1...", d->arguments.join(QLatin1Char(' '))));
0274     }
0275 }
0276 
0277 void GnuPGProcessCommand::doCancel()
0278 {
0279     d->canceled = true;
0280     if (d->process.state() != QProcess::NotRunning) {
0281         d->process.terminate();
0282         QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill);
0283     }
0284 }
0285 
0286 void GnuPGProcessCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status)
0287 {
0288     if (!canceled) {
0289         if (status == QProcess::CrashExit) {
0290             const QString msg = q->crashExitMessage(arguments);
0291             if (!msg.isEmpty()) {
0292                 error(msg, q->errorCaption());
0293             }
0294         } else if (ignoresSuccessOrFailure) {
0295             if (dialog) {
0296                 message(i18n("Process finished"));
0297             } else {
0298                 ;
0299             }
0300         } else if (code) {
0301             const QString msg = q->errorExitMessage(arguments);
0302             if (!msg.isEmpty()) {
0303                 error(q->errorExitMessage(arguments), q->errorCaption());
0304             }
0305         } else {
0306             q->postSuccessHook(parentWidgetOrView());
0307             const QString successMessage = q->successMessage(arguments);
0308             if (!successMessage.isNull()) {
0309                 if (dialog) {
0310                     message(successMessage);
0311                 } else {
0312                     information(successMessage, q->successCaption());
0313                 }
0314             }
0315         }
0316     }
0317 
0318     if (dialog) {
0319         dialog->setComplete(true);
0320     }
0321     finished();
0322 }
0323 
0324 void GnuPGProcessCommand::Private::slotProcessReadyReadStandardError()
0325 {
0326     auto ba = process.readAllStandardError();
0327     errorBuffer += ba;
0328     while (ba.endsWith('\n') || ba.endsWith('\r')) {
0329         ba.chop(1);
0330     }
0331     message(Kleo::stringFromGpgOutput(ba));
0332 }
0333 
0334 QString GnuPGProcessCommand::errorString() const
0335 {
0336     return Kleo::stringFromGpgOutput(d->errorBuffer);
0337 }
0338 
0339 void GnuPGProcessCommand::setIgnoresSuccessOrFailure(bool ignores)
0340 {
0341     d->ignoresSuccessOrFailure = ignores;
0342 }
0343 
0344 bool GnuPGProcessCommand::ignoresSuccessOrFailure() const
0345 {
0346     return d->ignoresSuccessOrFailure;
0347 }
0348 
0349 void GnuPGProcessCommand::setShowsOutputWindow(bool show)
0350 {
0351     if (show == d->showsOutputWindow) {
0352         return;
0353     }
0354     d->showsOutputWindow = show;
0355     if (show) {
0356         d->ensureDialogCreated();
0357     } else {
0358         if (d->dialog) {
0359             d->dialog->deleteLater();
0360         }
0361         d->dialog = nullptr;
0362     }
0363 }
0364 
0365 bool GnuPGProcessCommand::showsOutputWindow() const
0366 {
0367     return d->showsOutputWindow;
0368 }
0369 
0370 QProcess *GnuPGProcessCommand::process()
0371 {
0372     return &d->process;
0373 }
0374 
0375 QString GnuPGProcessCommand::successCaption() const
0376 {
0377     return QString();
0378 }
0379 
0380 QString GnuPGProcessCommand::successMessage(const QStringList &args) const
0381 {
0382     Q_UNUSED(args)
0383     return QString();
0384 }
0385 
0386 #undef d
0387 #undef q
0388 
0389 #include "gnupgprocesscommand.moc"
0390 #include "moc_gnupgprocesscommand.cpp"