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"