diff --git a/src/commands/clearcrlcachecommand.cpp b/src/commands/clearcrlcachecommand.cpp index 1ba2a8d9..33c76f0e 100644 --- a/src/commands/clearcrlcachecommand.cpp +++ b/src/commands/clearcrlcachecommand.cpp @@ -1,89 +1,89 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/clearcrlcachecommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "clearcrlcachecommand.h" #include using namespace Kleo; using namespace Kleo::Commands; ClearCrlCacheCommand::ClearCrlCacheCommand(KeyListController *c) : GnuPGProcessCommand(c) { } ClearCrlCacheCommand::ClearCrlCacheCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } ClearCrlCacheCommand::~ClearCrlCacheCommand() {} QStringList ClearCrlCacheCommand::arguments() const { return QStringList() << QStringLiteral("dirmngr") << QStringLiteral("--flush"); //return QStringList() << "gpgsm" << "--call-dirmngr" << "flush"; } QString ClearCrlCacheCommand::errorCaption() const { return i18n("Clear CRL Cache Error"); } QString ClearCrlCacheCommand::successCaption() const { return i18n("Clear CRL Cache Finished"); } QString ClearCrlCacheCommand::crashExitMessage(const QStringList &args) const { return i18n("The DirMngr process that tried to clear the CRL cache " "ended prematurely because of an unexpected error. " - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ClearCrlCacheCommand::errorExitMessage(const QStringList &args) const { return i18n("An error occurred while trying to clear the CRL cache. " "The output from %1 was:\n%2", args[0], errorString()); } QString ClearCrlCacheCommand::successMessage(const QStringList &) const { return i18n("CRL cache cleared successfully."); } diff --git a/src/commands/exportopenpgpcertstoservercommand.cpp b/src/commands/exportopenpgpcertstoservercommand.cpp index 4c924395..704559f4 100644 --- a/src/commands/exportopenpgpcertstoservercommand.cpp +++ b/src/commands/exportopenpgpcertstoservercommand.cpp @@ -1,158 +1,158 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportopenpgpcertstoservercommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "exportopenpgpcertstoservercommand.h" #include "command_p.h" #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; static bool haveKeyserverConfigured() { const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig(); if (!config) { return false; } const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver")); return entry && !entry->stringValue().isEmpty(); } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(KeyListController *c) : GnuPGProcessCommand(c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const Key &key) : GnuPGProcessCommand(key) { } ExportOpenPGPCertsToServerCommand::~ExportOpenPGPCertsToServerCommand() {} bool ExportOpenPGPCertsToServerCommand::preStartHook(QWidget *parent) const { if (!haveKeyserverConfigured()) if (KMessageBox::warningContinueCancel(parent, xi18nc("@info", "No OpenPGP directory services have been configured." "Since none is configured, Kleopatra will use " "keys.gnupg.net as the server to export to." "You can configure OpenPGP directory servers in Kleopatra's " "configuration dialog." "Do you want to continue with keys.gnupg.net " "as the server to export to?"), i18nc("@title:window", "OpenPGP Certificate Export"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-export-openpgp-missing-keyserver")) != KMessageBox::Continue) { return false; } return KMessageBox::warningContinueCancel(parent, xi18nc("@info", "When OpenPGP certificates have been exported to a public directory server, " "it is nearly impossible to remove them again." "Before exporting your certificate to a public directory server, make sure that you " "have created a revocation certificate so you can revoke the certificate if needed later." "Are you sure you want to continue?"), i18nc("@title:window", "OpenPGP Certificate Export"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-export-openpgp-nonrevocable")) == KMessageBox::Continue; } QStringList ExportOpenPGPCertsToServerCommand::arguments() const { QStringList result; result << gpgPath(); if (!haveKeyserverConfigured()) { result << QStringLiteral("--keyserver") << QStringLiteral("keys.gnupg.net"); } result << QStringLiteral("--send-keys"); Q_FOREACH (const Key &key, d->keys()) { result << QLatin1String(key.primaryFingerprint()); } return result; } QString ExportOpenPGPCertsToServerCommand::errorCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Error"); } QString ExportOpenPGPCertsToServerCommand::successCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Finished"); } QString ExportOpenPGPCertsToServerCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to export OpenPGP certificates " "ended prematurely because of an unexpected error." - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ExportOpenPGPCertsToServerCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to export OpenPGP certificates. " "The output from %1 was: %2", args[0], errorString()); } QString ExportOpenPGPCertsToServerCommand::successMessage(const QStringList &) const { return i18nc("@info", "OpenPGP certificates exported successfully."); } diff --git a/src/commands/exportpaperkeycommand.cpp b/src/commands/exportpaperkeycommand.cpp index 1dfb9c2b..907aae16 100644 --- a/src/commands/exportpaperkeycommand.cpp +++ b/src/commands/exportpaperkeycommand.cpp @@ -1,138 +1,138 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportpaperkeycommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2016 Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "exportpaperkeycommand.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include "command_p.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ExportPaperKeyCommand::ExportPaperKeyCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c), mParent(v) { connect(&mPkProc, static_cast(&QProcess::finished), this, &ExportPaperKeyCommand::pkProcFinished); mPkProc.setProgram(paperKeyInstallPath()); mPkProc.setArguments(QStringList() << QStringLiteral("--output-type=base16")); process()->setStandardOutputProcess(&mPkProc); qCDebug(KLEOPATRA_LOG) << "Starting PaperKey process."; mPkProc.start(); } QStringList ExportPaperKeyCommand::arguments() const { const Key key = d->key(); QStringList result; result << gpgPath() << QStringLiteral("--batch"); result << QStringLiteral("--export-secret-key"); result << QLatin1String(key.primaryFingerprint()); return result; } bool ExportPaperKeyCommand::preStartHook(QWidget *parent) const { if (paperKeyInstallPath().isNull()) { KMessageBox::sorry(parent, xi18nc("@info", "Kleopatra uses " "PaperKey to create a minimized and" " printable version of your secret key." "Please make sure it is installed."), i18nc("@title", "Failed to find PaperKey executable.")); return false; } return true; } void ExportPaperKeyCommand::pkProcFinished(int code, QProcess::ExitStatus status) { qCDebug(KLEOPATRA_LOG) << "Paperkey export finished: " << code << "status: " << status; QPrinter printer; const Key key = d->key(); printer.setDocName(QStringLiteral("0x%1-sec").arg(key.shortKeyID())); QPrintDialog printDialog(&printer, mParent); printDialog.setWindowTitle(i18nc("@title", "Print secret key")); if (printDialog.exec() != QDialog::Accepted) { qCDebug(KLEOPATRA_LOG) << "Printing aborted."; return; } QTextDocument doc(mPkProc.readAllStandardOutput()); doc.setDefaultFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); doc.print(&printer); } QString ExportPaperKeyCommand::errorCaption() const { return i18nc("@title:window", "Error printing secret key"); } QString ExportPaperKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to export the secret key " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", - args.join(QStringLiteral(" "))); + args.join(QLatin1Char(' '))); } QString ExportPaperKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to export the secret key. " "The output from %1 was: %2", args[0], errorString()); } diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp index 7b9592c6..cb96aa6f 100644 --- a/src/commands/exportsecretkeycommand.cpp +++ b/src/commands/exportsecretkeycommand.cpp @@ -1,165 +1,165 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportsecretkeycommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "exportsecretkeycommand.h" #include "command_p.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace GpgME; ExportSecretKeyCommand::ExportSecretKeyCommand(KeyListController *c) : GnuPGProcessCommand(c) { } ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } ExportSecretKeyCommand::ExportSecretKeyCommand(const Key &key) : GnuPGProcessCommand(key) { } ExportSecretKeyCommand::~ExportSecretKeyCommand() {} void ExportSecretKeyCommand::setFileName(const QString &fileName) { m_filename = fileName; } void ExportSecretKeyCommand::setPassphraseCharset(const QByteArray &charset) { m_charset = charset; } void ExportSecretKeyCommand::setUseArmor(bool armor) { m_armor = armor; } bool ExportSecretKeyCommand::preStartHook(QWidget *parent) const { if (!m_filename.isEmpty()) { return true; } ExportSecretKeyDialog dlg(parent); dlg.setKey(d->key()); if (!dlg.exec()) { return false; } m_filename = dlg.fileName(); m_armor = dlg.useArmor(); m_charset = dlg.charset(); return true; } QStringList ExportSecretKeyCommand::arguments() const { const Key key = d->key(); QStringList result; if (key.protocol() == OpenPGP) { result << gpgPath() << QStringLiteral("--batch"); } else { result << gpgSmPath(); } result << QStringLiteral("--output") << m_filename; if (m_armor) { result << QStringLiteral("--armor"); } if (key.protocol() == CMS && !m_charset.isEmpty()) { result << QStringLiteral("--p12-charset") << QLatin1String(m_charset); } if (key.protocol() == OpenPGP) { result << QStringLiteral("--export-secret-key"); } else { result << QStringLiteral("--export-secret-key-p12"); } result << QLatin1String(key.primaryFingerprint()); return result; } QString ExportSecretKeyCommand::errorCaption() const { return i18nc("@title:window", "Secret Key Export Error"); } QString ExportSecretKeyCommand::successCaption() const { return i18nc("@title:window", "Secret Key Export Finished"); } QString ExportSecretKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG or GpgSM process that tried to export the secret key " "ended prematurely because of an unexpected error." - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ExportSecretKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to export the secret key. " "The output from %1 was: %2", args[0], errorString()); } QString ExportSecretKeyCommand::successMessage(const QStringList &) const { return i18nc("@info", "Secret key successfully exported."); } diff --git a/src/commands/gnupgprocesscommand.cpp b/src/commands/gnupgprocesscommand.cpp index c5e0ecb4..af93faa8 100644 --- a/src/commands/gnupgprocesscommand.cpp +++ b/src/commands/gnupgprocesscommand.cpp @@ -1,394 +1,394 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/gnupgprocesscommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "gnupgprocesscommand.h" #include "command_p.h" #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds using namespace Kleo; using namespace Kleo::Commands; namespace { class OutputDialog : public QDialog { Q_OBJECT public: explicit OutputDialog(QWidget *parent = nullptr) : QDialog(parent), vlay(this), logTextWidget(this), buttonBox(QDialogButtonBox::Cancel | QDialogButtonBox::Close, Qt::Horizontal, this) { KDAB_SET_OBJECT_NAME(vlay); KDAB_SET_OBJECT_NAME(logTextWidget); KDAB_SET_OBJECT_NAME(buttonBox); logTextWidget.setMinimumVisibleLines(20); logTextWidget.setMinimumVisibleColumns(80); vlay.addWidget(&logTextWidget, 1); vlay.addWidget(&buttonBox); connect(closeButton(), &QAbstractButton::clicked, this, &QWidget::close); connect(cancelButton(), &QAbstractButton::clicked, this, &OutputDialog::slotCancelClicked); } Q_SIGNALS: void cancelRequested(); public Q_SLOTS: void message(const QString &s) { logTextWidget.message(s); } void setComplete(bool complete) { cancelButton()->setVisible(!complete); } private Q_SLOTS: void slotCancelClicked() { cancelButton()->hide(); Q_EMIT cancelRequested(); } private: QAbstractButton *closeButton() const { return buttonBox.button(QDialogButtonBox::Close); } QAbstractButton *cancelButton() const { return buttonBox.button(QDialogButtonBox::Cancel); } private: QVBoxLayout vlay; KDLogTextWidget logTextWidget; QDialogButtonBox buttonBox; }; } class GnuPGProcessCommand::Private : Command::Private { friend class ::Kleo::Commands::GnuPGProcessCommand; GnuPGProcessCommand *q_func() const { return static_cast(q); } public: explicit Private(GnuPGProcessCommand *qq, KeyListController *c); ~Private(); private: void init(); void ensureDialogCreated() { if (!showsOutputWindow) { return; } if (!dialog) { dialog = new OutputDialog; dialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(dialog); connect(dialog.data(), &OutputDialog::cancelRequested, q, &Command::cancel); dialog->setWindowTitle(i18n("Subprocess Diagnostics")); } } void ensureDialogVisible() { if (!showsOutputWindow) { return; } ensureDialogCreated(); if (dialog->isVisible()) { dialog->raise(); } else { dialog->show(); } #ifdef Q_OS_WIN KWindowSystem::forceActiveWindow(dialog->winId()); #endif } void message(const QString &msg) { if (dialog) { dialog->message(msg); } else { qCDebug(KLEOPATRA_LOG) << msg; } } private: void slotProcessFinished(int, QProcess::ExitStatus); void slotProcessReadyReadStandardError(); private: QProcess process; QPointer dialog; QStringList arguments; QByteArray errorBuffer; bool ignoresSuccessOrFailure; bool showsOutputWindow; bool canceled; }; GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() { return static_cast(d.get()); } const GnuPGProcessCommand::Private *GnuPGProcessCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() GnuPGProcessCommand::Private::Private(GnuPGProcessCommand *qq, KeyListController *c) : Command::Private(qq, c), process(), dialog(), errorBuffer(), ignoresSuccessOrFailure(false), showsOutputWindow(false), canceled(false) { process.setReadChannel(QProcess::StandardError); } GnuPGProcessCommand::Private::~Private() {} GnuPGProcessCommand::GnuPGProcessCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } GnuPGProcessCommand::GnuPGProcessCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } GnuPGProcessCommand::GnuPGProcessCommand(const GpgME::Key &key) : Command(key, new Private(this, 0)) { d->init(); } void GnuPGProcessCommand::Private::init() { connect(&process, SIGNAL(finished(int,QProcess::ExitStatus)), q, SLOT(slotProcessFinished(int,QProcess::ExitStatus))); connect(&process, SIGNAL(readyReadStandardError()), q, SLOT(slotProcessReadyReadStandardError())); } GnuPGProcessCommand::~GnuPGProcessCommand() {} QDialog *GnuPGProcessCommand::dialog() const { return d->dialog; } bool GnuPGProcessCommand::preStartHook(QWidget *) const { return true; } void GnuPGProcessCommand::postSuccessHook(QWidget *) { } void GnuPGProcessCommand::doStart() { if (!preStartHook(d->parentWidgetOrView())) { d->finished(); return; } d->arguments = arguments(); d->process.setProgram(d->arguments.takeFirst()); d->process.setArguments(d->arguments); d->process.start(); if (!d->process.waitForStarted()) { d->error(i18n("Unable to start process %1. " "Please check your installation.", d->arguments[0]), errorCaption()); d->finished(); } else { d->ensureDialogVisible(); - d->message(i18n("Starting %1...", d->arguments.join(QStringLiteral(" ")))); + d->message(i18n("Starting %1...", d->arguments.join(QLatin1Char(' ')))); } } void GnuPGProcessCommand::doCancel() { d->canceled = true; if (d->process.state() != QProcess::NotRunning) { d->process.terminate(); QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill); } } void GnuPGProcessCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status) { if (!canceled) { if (status == QProcess::CrashExit) { error(q->crashExitMessage(arguments), q->errorCaption()); } else if (ignoresSuccessOrFailure) { if (dialog) { message(i18n("Process finished")); } else { ; } } else if (code) { error(q->errorExitMessage(arguments), q->errorCaption()); } else { q->postSuccessHook(parentWidgetOrView()); const QString successMessage = q->successMessage(arguments); if (!successMessage.isNull()) { if (dialog) { message(successMessage); } else { information(successMessage, q->successCaption()); } } } } if (dialog) { dialog->setComplete(true); } finished(); } void GnuPGProcessCommand::Private::slotProcessReadyReadStandardError() { while (process.canReadLine()) { QByteArray ba = process.readLine(); errorBuffer += ba; while (ba.endsWith('\n') || ba.endsWith('\r')) { ba.chop(1); } message(QString::fromLocal8Bit(ba.constData(), ba.size())); } } QString GnuPGProcessCommand::errorString() const { return QString::fromLocal8Bit(d->errorBuffer); } void GnuPGProcessCommand::setIgnoresSuccessOrFailure(bool ignores) { d->ignoresSuccessOrFailure = ignores; } bool GnuPGProcessCommand::ignoresSuccessOrFailure() const { return d->ignoresSuccessOrFailure; } void GnuPGProcessCommand::setShowsOutputWindow(bool show) { if (show == d->showsOutputWindow) { return; } d->showsOutputWindow = show; if (show) { d->ensureDialogCreated(); } else { if (d->dialog) { d->dialog->deleteLater(); } d->dialog = 0; } } bool GnuPGProcessCommand::showsOutputWindow() const { return d->showsOutputWindow; } QProcess *GnuPGProcessCommand::process() { return &d->process; } QString GnuPGProcessCommand::successCaption() const { return QString(); } QString GnuPGProcessCommand::successMessage(const QStringList &args) const { Q_UNUSED(args); return QString(); } #undef d #undef q #include "moc_gnupgprocesscommand.cpp" #include "gnupgprocesscommand.moc" diff --git a/src/commands/learncardkeyscommand.cpp b/src/commands/learncardkeyscommand.cpp index ff7cd0f2..af2d362c 100644 --- a/src/commands/learncardkeyscommand.cpp +++ b/src/commands/learncardkeyscommand.cpp @@ -1,105 +1,105 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/learncardkeyscommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "learncardkeyscommand.h" #include "command_p.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; LearnCardKeysCommand::LearnCardKeysCommand(GpgME::Protocol proto) : GnuPGProcessCommand(0), m_protocol(proto) { setIgnoresSuccessOrFailure(true); setShowsOutputWindow(true); connect(this, &Command::finished, SmartCard::ReaderStatus::mutableInstance(), &SmartCard::ReaderStatus::updateStatus); } LearnCardKeysCommand::~LearnCardKeysCommand() {} Protocol LearnCardKeysCommand::protocol() const { return m_protocol; } QStringList LearnCardKeysCommand::arguments() const { if (protocol() == OpenPGP) { return QStringList() << gpgPath() << QStringLiteral("--batch") << QStringLiteral("--card-status") << QStringLiteral("-v"); } else { return QStringList() << gpgSmPath() << QStringLiteral("--learn-card") << QStringLiteral("-v"); } } QString LearnCardKeysCommand::errorCaption() const { return i18n("Error Learning SmartCard"); } QString LearnCardKeysCommand::successCaption() const { return i18n("Finished Learning SmartCard"); } QString LearnCardKeysCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG or GpgSM process that tried to learn the smart card " "ended prematurely because of an unexpected error." - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString LearnCardKeysCommand::errorExitMessage(const QStringList &) const { // unused, since we setIgnoresSuccessOrFailure(true) return QString(); } QString LearnCardKeysCommand::successMessage(const QStringList &) const { // unused, since we setIgnoresSuccessOrFailure(true) return QString(); } diff --git a/src/commands/refreshopenpgpcertscommand.cpp b/src/commands/refreshopenpgpcertscommand.cpp index a7aa1ca7..fb900bef 100644 --- a/src/commands/refreshopenpgpcertscommand.cpp +++ b/src/commands/refreshopenpgpcertscommand.cpp @@ -1,149 +1,149 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/refreshopenpgpcertscommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "refreshopenpgpcertscommand.h" #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace QGpgME; static bool haveKeyserverConfigured() { const QGpgME::CryptoConfig *const config = QGpgME::cryptoConfig(); if (!config) { return false; } const QGpgME::CryptoConfigEntry *const entry = config->entry(QStringLiteral("gpg"), QStringLiteral("Keyserver"), QStringLiteral("keyserver")); return entry && !entry->stringValue().isEmpty(); } RefreshOpenPGPCertsCommand::RefreshOpenPGPCertsCommand(KeyListController *c) : GnuPGProcessCommand(c) { setShowsOutputWindow(true); } RefreshOpenPGPCertsCommand::RefreshOpenPGPCertsCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { setShowsOutputWindow(true); } RefreshOpenPGPCertsCommand::~RefreshOpenPGPCertsCommand() {} bool RefreshOpenPGPCertsCommand::preStartHook(QWidget *parent) const { if (!haveKeyserverConfigured()) if (KMessageBox::warningContinueCancel(parent, xi18nc("@info", "No OpenPGP directory services have been configured." "If not all of the certificates carry the name of their preferred " "certificate server (few do), a fallback server is needed to fetch from." "Since none is configured, Kleopatra will use " "keys.gnupg.net as the fallback." "You can configure OpenPGP directory servers in Kleopatra's " "configuration dialog." "Do you want to continue with keys.gnupg.net " "as fallback server?"), i18nc("@title:window", "OpenPGP Certificate Refresh"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-refresh-openpgp-missing-keyserver")) != KMessageBox::Continue) { return false; } return KMessageBox::warningContinueCancel(parent, xi18nc("@info", "Refreshing OpenPGP certificates implies downloading all certificates anew, " "to check if any of them have been revoked in the meantime." "This can put a severe strain on your own as well as other people's network " "connections, and can take up to an hour or more to complete, depending on " "your network connection, and the number of certificates to check. " "Are you sure you want to continue?"), i18nc("@title:window", "OpenPGP Certificate Refresh"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-refresh-openpgp-expensive")) == KMessageBox::Continue; } QStringList RefreshOpenPGPCertsCommand::arguments() const { QStringList result; result << gpgPath(); if (!haveKeyserverConfigured()) { result << QStringLiteral("--keyserver") << QStringLiteral("keys.gnupg.net"); } result << QStringLiteral("--refresh-keys"); return result; } QString RefreshOpenPGPCertsCommand::errorCaption() const { return i18nc("@title:window", "OpenPGP Certificate Refresh Error"); } QString RefreshOpenPGPCertsCommand::successCaption() const { return i18nc("@title:window", "OpenPGP Certificate Refresh Finished"); } QString RefreshOpenPGPCertsCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to refresh OpenPGP certificates " "ended prematurely because of an unexpected error." - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString RefreshOpenPGPCertsCommand::errorExitMessage(const QStringList &args) const { return i18nc("@info", "An error occurred while trying to refresh OpenPGP certificates. " "The output from %1 was: %2", - args[0], errorString().replace(QStringLiteral("\n"), QStringLiteral("
"))); + args[0], errorString().replace(QLatin1Char('\n'), QStringLiteral("
"))); } QString RefreshOpenPGPCertsCommand::successMessage(const QStringList &) const { return i18nc("@info", "OpenPGP certificates refreshed successfully."); // ### --check-trustdb } diff --git a/src/commands/refreshx509certscommand.cpp b/src/commands/refreshx509certscommand.cpp index bf98e43a..e91d6d65 100644 --- a/src/commands/refreshx509certscommand.cpp +++ b/src/commands/refreshx509certscommand.cpp @@ -1,110 +1,110 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/refreshx509certscommand.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "refreshx509certscommand.h" #include #include #include using namespace Kleo; using namespace Kleo::Commands; RefreshX509CertsCommand::RefreshX509CertsCommand(KeyListController *c) : GnuPGProcessCommand(c) { } RefreshX509CertsCommand::RefreshX509CertsCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } RefreshX509CertsCommand::~RefreshX509CertsCommand() {} bool RefreshX509CertsCommand::preStartHook(QWidget *parent) const { return KMessageBox::warningContinueCancel(parent, xi18nc("@info", "Refreshing X.509 certificates implies downloading CRLs for all certificates, " "even if they might otherwise still be valid." "This can put a severe strain on your own as well as other people's network " "connections, and can take up to an hour or more to complete, depending on " "your network connection, and the number of certificates to check. " "Are you sure you want to continue?"), i18nc("@title:window", "X.509 Certificate Refresh"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-refresh-x509-expensive")) == KMessageBox::Continue; } QStringList RefreshX509CertsCommand::arguments() const { return QStringList() << gpgSmPath() << QStringLiteral("-k") << QStringLiteral("--with-validation") << QStringLiteral("--force-crl-refresh") << QStringLiteral("--enable-crl-checks"); } QString RefreshX509CertsCommand::errorCaption() const { return i18nc("@title:window", "X.509 Certificate Refresh Error"); } QString RefreshX509CertsCommand::successCaption() const { return i18nc("@title:window", "X.509 Certificate Refresh Finished"); } QString RefreshX509CertsCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GpgSM process that tried to refresh X.509 certificates " "ended prematurely because of an unexpected error." - "Please check the output of %1 for details.", args.join(QStringLiteral(" "))); + "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString RefreshX509CertsCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to refresh X.509 certificates." "The output from %1 was: %2", - args[0], errorString().replace(QStringLiteral("\n"), QStringLiteral("
"))); + args[0], errorString().replace(QLatin1Char('\n'), QStringLiteral("
"))); } QString RefreshX509CertsCommand::successMessage(const QStringList &) const { return i18nc("@info", "X.509 certificates refreshed successfully."); } diff --git a/src/crypto/createchecksumscontroller.cpp b/src/crypto/createchecksumscontroller.cpp index 58ab1817..53d976df 100644 --- a/src/crypto/createchecksumscontroller.cpp +++ b/src/crypto/createchecksumscontroller.cpp @@ -1,737 +1,737 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/createchecksumscontroller.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2010 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "createchecksumscontroller.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; namespace { class ResultDialog : public QDialog { Q_OBJECT public: ResultDialog(const QStringList &created, const QStringList &errors, QWidget *parent = nullptr, Qt::WindowFlags f = 0) : QDialog(parent, f), createdLB(created.empty() ? i18nc("@info", "No checksum files have been created.") : i18nc("@info", "These checksum files have been successfully created:"), this), createdLW(this), errorsLB(errors.empty() ? i18nc("@info", "There were no errors.") : i18nc("@info", "The following errors were encountered:"), this), errorsLW(this), buttonBox(QDialogButtonBox::Ok, Qt::Horizontal, this), vlay(this) { KDAB_SET_OBJECT_NAME(createdLB); KDAB_SET_OBJECT_NAME(createdLW); KDAB_SET_OBJECT_NAME(errorsLB); KDAB_SET_OBJECT_NAME(errorsLW); KDAB_SET_OBJECT_NAME(buttonBox); KDAB_SET_OBJECT_NAME(vlay); createdLW.addItems(created); QRect r; for (int i = 0; i < created.size(); ++i) { r = r.united(createdLW.visualRect(createdLW.model()->index(0, i))); } createdLW.setMinimumWidth(qMin(1024, r.width() + 4 * createdLW.frameWidth())); errorsLW.addItems(errors); vlay.addWidget(&createdLB); vlay.addWidget(&createdLW, 1); vlay.addWidget(&errorsLB); vlay.addWidget(&errorsLW, 1); vlay.addWidget(&buttonBox); if (created.empty()) { createdLW.hide(); } if (errors.empty()) { errorsLW.hide(); } connect(&buttonBox, &QDialogButtonBox::accepted, this, &ResultDialog::accept); connect(&buttonBox, &QDialogButtonBox::rejected, this, &ResultDialog::reject); readConfig(); } ~ResultDialog() { writeConfig(); } void readConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "ResultDialog"); const QSize size = dialog.readEntry("Size", QSize(600, 400)); if (size.isValid()) { resize(size); } } void writeConfig() { KConfigGroup dialog(KSharedConfig::openConfig(), "ResultDialog"); dialog.writeEntry("Size", size()); dialog.sync(); } private: QLabel createdLB; QListWidget createdLW; QLabel errorsLB; QListWidget errorsLW; QDialogButtonBox buttonBox; QVBoxLayout vlay; }; } #ifdef Q_OS_UNIX static const bool HAVE_UNIX = true; #else static const bool HAVE_UNIX = false; #endif static const Qt::CaseSensitivity fs_cs = HAVE_UNIX ? Qt::CaseSensitive : Qt::CaseInsensitive; // can we use QAbstractFileEngine::caseSensitive()? static QStringList fs_sort(QStringList l) { std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return l; } static QStringList fs_intersect(QStringList l1, QStringList l2) { fs_sort(l1); fs_sort(l2); QStringList result; std::set_intersection(l1.begin(), l1.end(), l2.begin(), l2.end(), std::back_inserter(result), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return result; } static QList get_patterns(const std::vector< std::shared_ptr > &checksumDefinitions) { QList result; Q_FOREACH (const std::shared_ptr &cd, checksumDefinitions) if (cd) Q_FOREACH (const QString &pattern, cd->patterns()) { result.push_back(QRegExp(pattern, fs_cs)); } return result; } namespace { struct matches_any : std::unary_function { const QList m_regexps; explicit matches_any(const QList ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::any_of(m_regexps.cbegin(), m_regexps.cend(), [s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; } class CreateChecksumsController::Private : public QThread { Q_OBJECT friend class ::Kleo::Crypto::CreateChecksumsController; CreateChecksumsController *const q; public: explicit Private(CreateChecksumsController *qq); ~Private(); Q_SIGNALS: void progress(int, int, const QString &); private: void slotOperationFinished() { #ifndef QT_NO_PROGRESSDIALOG if (progressDialog) { progressDialog->setValue(progressDialog->maximum()); progressDialog->close(); } #endif // QT_NO_PROGRESSDIALOG ResultDialog *const dlg = new ResultDialog(created, errors); dlg->setAttribute(Qt::WA_DeleteOnClose); q->bringToForeground(dlg); if (!errors.empty()) q->setLastError(gpg_error(GPG_ERR_GENERAL), - errors.join(QStringLiteral("\n"))); + errors.join(QLatin1Char('\n'))); q->emitDoneOrError(); } void slotProgress(int current, int total, const QString &what) { qCDebug(KLEOPATRA_LOG) << "progress: " << current << "/" << total << ": " << qPrintable(what); #ifndef QT_NO_PROGRESSDIALOG if (!progressDialog) { return; } progressDialog->setMaximum(total); progressDialog->setValue(current); progressDialog->setLabelText(what); #endif // QT_NO_PROGRESSDIALOG } private: void run() Q_DECL_OVERRIDE; private: #ifndef QT_NO_PROGRESSDIALOG QPointer progressDialog; #endif mutable QMutex mutex; const std::vector< std::shared_ptr > checksumDefinitions; std::shared_ptr checksumDefinition; QStringList files; QStringList errors, created; bool allowAddition; volatile bool canceled; }; CreateChecksumsController::Private::Private(CreateChecksumsController *qq) : q(qq), #ifndef QT_NO_PROGRESSDIALOG progressDialog(), #endif mutex(), checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()), checksumDefinition(ChecksumDefinition::getDefaultChecksumDefinition(checksumDefinitions)), files(), errors(), created(), allowAddition(false), canceled(false) { connect(this, SIGNAL(progress(int,int,QString)), q, SLOT(slotProgress(int,int,QString))); connect(this, &Private::progress, q, &Controller::progress); connect(this, SIGNAL(finished()), q, SLOT(slotOperationFinished())); } CreateChecksumsController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } CreateChecksumsController::CreateChecksumsController(QObject *p) : Controller(p), d(new Private(this)) { } CreateChecksumsController::CreateChecksumsController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } CreateChecksumsController::~CreateChecksumsController() { qCDebug(KLEOPATRA_LOG); } void CreateChecksumsController::setFiles(const QStringList &files) { kleo_assert(!d->isRunning()); kleo_assert(!files.empty()); const QList patterns = get_patterns(d->checksumDefinitions); if (!std::all_of(files.cbegin(), files.cend(), matches_any(patterns)) && !std::none_of(files.cbegin(), files.cend(), matches_any(patterns))) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Create Checksums: input files must be either all checksum files or all files to be checksummed, not a mixture of both.")); } const QMutexLocker locker(&d->mutex); d->files = files; } void CreateChecksumsController::setAllowAddition(bool allow) { kleo_assert(!d->isRunning()); const QMutexLocker locker(&d->mutex); d->allowAddition = allow; } bool CreateChecksumsController::allowAddition() const { const QMutexLocker locker(&d->mutex); return d->allowAddition; } void CreateChecksumsController::start() { { const QMutexLocker locker(&d->mutex); #ifndef QT_NO_PROGRESSDIALOG d->progressDialog = new QProgressDialog(i18n("Initializing..."), i18n("Cancel"), 0, 0); applyWindowID(d->progressDialog); d->progressDialog->setAttribute(Qt::WA_DeleteOnClose); d->progressDialog->setMinimumDuration(1000); d->progressDialog->setWindowTitle(i18nc("@title:window", "Create Checksum Progress")); connect(d->progressDialog.data(), &QProgressDialog::canceled, this, &CreateChecksumsController::cancel); #endif // QT_NO_PROGRESSDIALOG d->canceled = false; d->errors.clear(); d->created.clear(); } d->start(); } void CreateChecksumsController::cancel() { qCDebug(KLEOPATRA_LOG); const QMutexLocker locker(&d->mutex); d->canceled = true; } namespace { struct Dir { QDir dir; QString sumFile; QStringList inputFiles; quint64 totalSize; std::shared_ptr checksumDefinition; }; } static QStringList remove_checksum_files(QStringList l, const QList &rxs) { QStringList::iterator end = l.end(); Q_FOREACH (const QRegExp &rx, rxs) { end = std::remove_if(l.begin(), end, [rx](const QString &str) { return rx.exactMatch(str); }); } l.erase(end, l.end()); return l; } namespace { struct File { QString name; QByteArray checksum; bool binary; }; } static QString decode(const QString &encoded) { QString decoded; decoded.reserve(encoded.size()); bool shift = false; Q_FOREACH (QChar ch, encoded) if (shift) { switch (ch.toLatin1()) { case '\\': decoded += QLatin1Char('\\'); break; case 'n': decoded += QLatin1Char('\n'); break; default: qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')"; decoded += ch; break; } shift = false; } else { if (ch == QLatin1Char('\\')) { shift = true; } else { decoded += ch; } } return decoded; } static std::vector parse_sum_file(const QString &fileName) { std::vector files; QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { QTextStream s(&f); QRegExp rx(QLatin1String("(\\?)([a-f0-9A-F]+) ([ *])([^\n]+)\n*")); while (!s.atEnd()) { const QString line = s.readLine(); if (rx.exactMatch(line)) { assert(!rx.cap(4).endsWith(QLatin1Char('\n'))); const File file = { rx.cap(1) == QLatin1String("\\") ? decode(rx.cap(4)) : rx.cap(4), rx.cap(2).toLatin1(), rx.cap(3) == QLatin1String("*"), }; files.push_back(file); } } } return files; } static quint64 aggregate_size(const QDir &dir, const QStringList &files) { quint64 n = 0; Q_FOREACH (const QString &file, files) { n += QFileInfo(dir.absoluteFilePath(file)).size(); } return n; } static std::shared_ptr filename2definition(const QString &fileName, const std::vector< std::shared_ptr > &checksumDefinitions) { Q_FOREACH (const std::shared_ptr &cd, checksumDefinitions) if (cd) Q_FOREACH (const QString &pattern, cd->patterns()) if (QRegExp(pattern, fs_cs).exactMatch(fileName)) { return cd; } return std::shared_ptr(); } static std::vector find_dirs_by_sum_files(const QStringList &files, bool allowAddition, const std::function &progress, const std::vector< std::shared_ptr > &checksumDefinitions) { const QList patterns = get_patterns(checksumDefinitions); std::vector dirs; dirs.reserve(files.size()); int i = 0; Q_FOREACH (const QString &file, files) { const QFileInfo fi(file); const QDir dir = fi.dir(); const QStringList entries = remove_checksum_files(dir.entryList(QDir::Files), patterns); QStringList inputFiles; if (allowAddition) { inputFiles = entries; } else { const std::vector parsed = parse_sum_file(fi.absoluteFilePath()); QStringList oldInputFiles; oldInputFiles.reserve(parsed.size()); std::transform(parsed.cbegin(), parsed.cend(), std::back_inserter(oldInputFiles), std::mem_fn(&File::name)); inputFiles = fs_intersect(oldInputFiles, entries); } const Dir item = { dir, fi.fileName(), inputFiles, aggregate_size(dir, inputFiles), filename2definition(fi.fileName(), checksumDefinitions) }; dirs.push_back(item); if (progress) { progress(++i); } } return dirs; } namespace { struct less_dir : std::binary_function { bool operator()(const QDir &lhs, const QDir &rhs) const { return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0; } }; } static std::vector find_dirs_by_input_files(const QStringList &files, const std::shared_ptr &checksumDefinition, bool allowAddition, const std::function &progress, const std::vector< std::shared_ptr > &checksumDefinitions) { Q_UNUSED(allowAddition); if (!checksumDefinition) { return std::vector(); } const QList patterns = get_patterns(checksumDefinitions); std::map dirs2files; // Step 1: sort files by the dir they're contained in: std::deque inputs(files.begin(), files.end()); int i = 0; while (!inputs.empty()) { const QString file = inputs.front(); inputs.pop_front(); const QFileInfo fi(file); if (fi.isDir()) { QDir dir(file); dirs2files[ dir ] = remove_checksum_files(dir.entryList(QDir::Files), patterns); const auto entryList = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); std::transform(entryList.cbegin(), entryList.cend(), std::inserter(inputs, inputs.begin()), [&dir](const QString &entry) { return dir.absoluteFilePath(entry); }); } else { dirs2files[fi.dir()].push_back(file); } if (progress) { progress(++i); } } // Step 2: convert into vector: std::vector dirs; dirs.reserve(dirs2files.size()); for (std::map::const_iterator it = dirs2files.begin(), end = dirs2files.end(); it != end; ++it) { const QStringList inputFiles = remove_checksum_files(it->second, patterns); if (inputFiles.empty()) { continue; } const Dir dir = { it->first, checksumDefinition->outputFileName(), inputFiles, aggregate_size(it->first, inputFiles), checksumDefinition }; dirs.push_back(dir); if (progress) { progress(++i); } } return dirs; } static QString process(const Dir &dir, bool *fatal) { const QString absFilePath = dir.dir.absoluteFilePath(dir.sumFile); QTemporaryFile out; QProcess p; if (!out.open()) { return QStringLiteral("Faile to open Temporary file."); } p.setWorkingDirectory(dir.dir.absolutePath()); p.setStandardOutputFile(out.fileName()); const QString program = dir.checksumDefinition->createCommand(); dir.checksumDefinition->startCreateCommand(&p, dir.inputFiles); p.waitForFinished(); qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode(); if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { if (fatal && p.error() == QProcess::FailedToStart) { *fatal = true; } if (p.error() == QProcess::UnknownError) return i18n("Error while running %1: %2", program, QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", program, p.errorString()); } } QFileInfo fi(absFilePath); if (!(fi.exists() && !QFile::remove(absFilePath)) && QFile::rename(out.fileName(), absFilePath)) { out.setAutoRemove(false); return QString(); } return xi18n("Failed to overwrite %1.", dir.sumFile); } namespace { static QDebug operator<<(QDebug s, const Dir &dir) { return s << "Dir(" << dir.dir << "->" << dir.sumFile << "<-(" << dir.totalSize << ')' << dir.inputFiles << ")\n"; } } void CreateChecksumsController::Private::run() { QMutexLocker locker(&mutex); const QStringList files = this->files; const std::vector< std::shared_ptr > checksumDefinitions = this->checksumDefinitions; const std::shared_ptr checksumDefinition = this->checksumDefinition; const bool allowAddition = this->allowAddition; locker.unlock(); QStringList errors; QStringList created; if (!checksumDefinition) { errors.push_back(i18n("No checksum programs defined.")); locker.relock(); this->errors = errors; return; } else { qCDebug(KLEOPATRA_LOG) << "using checksum-definition" << checksumDefinition->id(); } // // Step 1: build a list of work to do (no progress): // const QString scanning = i18n("Scanning directories..."); Q_EMIT progress(0, 0, scanning); const bool haveSumFiles = std::all_of(files.cbegin(), files.cend(), matches_any(get_patterns(checksumDefinitions))); const auto progressCb = [this, &scanning](int c) { Q_EMIT progress(c, 0, scanning); }; const std::vector dirs = haveSumFiles ? find_dirs_by_sum_files(files, allowAddition, progressCb, checksumDefinitions) : find_dirs_by_input_files(files, checksumDefinition, allowAddition, progressCb, checksumDefinitions); Q_FOREACH (const Dir &dir, dirs) { qCDebug(KLEOPATRA_LOG) << dir; } if (!canceled) { Q_EMIT progress(0, 0, i18n("Calculating total size...")); const quint64 total = kdtools::accumulate_transform(dirs.cbegin(), dirs.cend(), std::mem_fn(&Dir::totalSize), Q_UINT64_C(0)); if (!canceled) { // // Step 2: perform work (with progress reporting): // // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...) const quint64 factor = total / std::numeric_limits::max() + 1; quint64 done = 0; Q_FOREACH (const Dir &dir, dirs) { Q_EMIT progress(done / factor, total / factor, i18n("Checksumming (%2) in %1", dir.checksumDefinition->label(), dir.dir.path())); bool fatal = false; const QString error = process(dir, &fatal); if (!error.isEmpty()) { errors.push_back(error); } else { created.push_back(dir.dir.absoluteFilePath(dir.sumFile)); } done += dir.totalSize; if (fatal || canceled) { break; } } Q_EMIT progress(done / factor, total / factor, i18n("Done.")); } } locker.relock(); this->errors = errors; this->created = created; // mutex unlocked by QMutexLocker } #include "moc_createchecksumscontroller.cpp" #include "createchecksumscontroller.moc" diff --git a/src/crypto/signemailtask.cpp b/src/crypto/signemailtask.cpp index 8831eecf..89344a3c 100644 --- a/src/crypto/signemailtask.cpp +++ b/src/crypto/signemailtask.cpp @@ -1,307 +1,307 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signemailtask.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "signemailtask.h" #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { class SignEMailResult : public Task::Result { const SigningResult m_result; const AuditLog m_auditLog; public: explicit SignEMailResult(const SigningResult &r, const AuditLog &auditLog) : Task::Result(), m_result(r), m_auditLog(auditLog) {} QString overview() const Q_DECL_OVERRIDE; QString details() const Q_DECL_OVERRIDE; int errorCode() const Q_DECL_OVERRIDE; QString errorString() const Q_DECL_OVERRIDE; VisualCode code() const Q_DECL_OVERRIDE; AuditLog auditLog() const Q_DECL_OVERRIDE; }; QString makeResultString(const SigningResult &res) { const Error err = res.error(); if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed: %1", QString::fromLocal8Bit(err.asString()).toHtmlEscaped()); } return i18n("Signing succeeded."); } } class SignEMailTask::Private { friend class ::Kleo::Crypto::SignEMailTask; SignEMailTask *const q; public: explicit Private(SignEMailTask *qq); private: std::unique_ptr createJob(GpgME::Protocol proto); private: void slotResult(const SigningResult &); private: std::shared_ptr input; std::shared_ptr output; std::vector signers; bool detached; bool clearsign; QString micAlg; QPointer job; }; SignEMailTask::Private::Private(SignEMailTask *qq) : q(qq), input(), output(), signers(), detached(false), clearsign(false), micAlg(), job(0) { } SignEMailTask::SignEMailTask(QObject *p) : Task(p), d(new Private(this)) { } SignEMailTask::~SignEMailTask() {} void SignEMailTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEMailTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEMailTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); kleo_assert(!signers.empty()); kleo_assert(std::none_of(signers.cbegin(), signers.cend(), std::mem_fn(&Key::isNull))); d->signers = signers; } void SignEMailTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; d->clearsign = false; } void SignEMailTask::setClearsign(bool clear) { kleo_assert(!d->job); d->clearsign = clear; d->detached = false; } Protocol SignEMailTask::protocol() const { kleo_assert(!d->signers.empty()); return d->signers.front().protocol(); } QString SignEMailTask::label() const { return d->input ? d->input->label() : QString(); } unsigned long long SignEMailTask::inputSize() const { return d->input ? d->input->size() : 0; } void SignEMailTask::doStart() { kleo_assert(!d->job); kleo_assert(d->input); kleo_assert(d->output); kleo_assert(!d->signers.empty()); d->micAlg.clear(); std::unique_ptr job = d->createJob(protocol()); kleo_assert(job.get()); job->start(d->signers, d->input->ioDevice(), d->output->ioDevice(), d->clearsign ? GpgME::Clearsigned : d->detached ? GpgME::Detached : GpgME::NormalSignatureMode); d->job = job.release(); } void SignEMailTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEMailTask::Private::createJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt(); std::unique_ptr signJob(backend->signJob(/*armor=*/ shouldArmor, /*textmode=*/false)); kleo_assert(signJob.get()); if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) { signJob->setOutputIsBase64Encoded(true); } connect(signJob.get(), SIGNAL(progress(QString,int,int)), q, SLOT(setProgress(QString,int,int))); connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } static QString collect_micalgs(const GpgME::SigningResult &result, GpgME::Protocol proto) { const std::vector css = result.createdSignatures(); QStringList micalgs; std::transform(css.begin(), css.end(), std::back_inserter(micalgs), [](const GpgME::CreatedSignature &sig) { return QString::fromLatin1(sig.hashAlgorithmAsString()).toLower(); }); if (proto == GpgME::OpenPGP) for (QStringList::iterator it = micalgs.begin(), end = micalgs.end(); it != end; ++it) { it->prepend(QStringLiteral("pgp-")); } micalgs.sort(); micalgs.erase(std::unique(micalgs.begin(), micalgs.end()), micalgs.end()); - return micalgs.join(QStringLiteral(",")); + return micalgs.join(QLatin1Char(',')); } void SignEMailTask::Private::slotResult(const SigningResult &result) { const QGpgME::Job *const job = qobject_cast(q->sender()); if (result.error().code()) { output->cancel(); } else { output->finalize(); micAlg = collect_micalgs(result, q->protocol()); } q->emitResult(std::shared_ptr(new SignEMailResult(result, AuditLog::fromJob(job)))); } QString SignEMailTask::micAlg() const { return d->micAlg; } QString SignEMailResult::overview() const { return makeOverview(makeResultString(m_result)); } QString SignEMailResult::details() const { return QString(); } int SignEMailResult::errorCode() const { return m_result.error().encodedError(); } QString SignEMailResult::errorString() const { return hasError() ? makeResultString(m_result) : QString(); } Task::Result::VisualCode SignEMailResult::code() const { if (m_result.error().isCanceled()) { return Warning; } return m_result.error().code() ? NeutralError : NeutralSuccess; } AuditLog SignEMailResult::auditLog() const { return m_auditLog; } #include "moc_signemailtask.cpp" diff --git a/src/crypto/verifychecksumscontroller.cpp b/src/crypto/verifychecksumscontroller.cpp index ece8f866..2a286fbe 100644 --- a/src/crypto/verifychecksumscontroller.cpp +++ b/src/crypto/verifychecksumscontroller.cpp @@ -1,708 +1,708 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/verifychecksumscontroller.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2010 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "verifychecksumscontroller.h" #ifndef QT_NO_DIRMODEL #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; #ifdef Q_OS_UNIX static const bool HAVE_UNIX = true; #else static const bool HAVE_UNIX = false; #endif static const QLatin1String CHECKSUM_DEFINITION_ID_ENTRY("checksum-definition-id"); static const Qt::CaseSensitivity fs_cs = HAVE_UNIX ? Qt::CaseSensitive : Qt::CaseInsensitive; // can we use QAbstractFileEngine::caseSensitive()? #if 0 static QStringList fs_sort(QStringList l) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return l; } static QStringList fs_intersect(QStringList l1, QStringList l2) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; fs_sort(l1); fs_sort(l2); QStringList result; std::set_intersection(l1.begin(), l1.end(), l2.begin(), l2.end(), std::back_inserter(result), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, fs_cs) < 0; }); return result; } #endif static QList get_patterns(const std::vector< std::shared_ptr > &checksumDefinitions) { QList result; Q_FOREACH (const std::shared_ptr &cd, checksumDefinitions) if (cd) Q_FOREACH (const QString &pattern, cd->patterns()) { result.push_back(QRegExp(pattern, fs_cs)); } return result; } namespace { struct matches_any : std::unary_function { const QList m_regexps; explicit matches_any(const QList ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::any_of(m_regexps.cbegin(), m_regexps.cend(), [&s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; struct matches_none_of : std::unary_function { const QList m_regexps; explicit matches_none_of(const QList ®exps) : m_regexps(regexps) {} bool operator()(const QString &s) const { return std::none_of(m_regexps.cbegin(), m_regexps.cend(), [&s](const QRegExp &rx) { return rx.exactMatch(s); }); } }; } class VerifyChecksumsController::Private : public QThread { Q_OBJECT friend class ::Kleo::Crypto::VerifyChecksumsController; VerifyChecksumsController *const q; public: explicit Private(VerifyChecksumsController *qq); ~Private(); Q_SIGNALS: void baseDirectories(const QStringList &); void progress(int, int, const QString &); void status(const QString &file, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status); private: void slotOperationFinished() { if (dialog) { dialog->setProgress(100, 100); dialog->setErrors(errors); } if (!errors.empty()) q->setLastError(gpg_error(GPG_ERR_GENERAL), - errors.join(QStringLiteral("\n"))); + errors.join(QLatin1Char('\n'))); q->emitDoneOrError(); } private: void run() Q_DECL_OVERRIDE; private: QPointer dialog; mutable QMutex mutex; const std::vector< std::shared_ptr > checksumDefinitions; QStringList files; QStringList errors; volatile bool canceled; }; VerifyChecksumsController::Private::Private(VerifyChecksumsController *qq) : q(qq), dialog(), mutex(), checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()), files(), errors(), canceled(false) { connect(this, &Private::progress, q, &Controller::progress); connect(this, SIGNAL(finished()), q, SLOT(slotOperationFinished())); } VerifyChecksumsController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } VerifyChecksumsController::VerifyChecksumsController(QObject *p) : Controller(p), d(new Private(this)) { } VerifyChecksumsController::VerifyChecksumsController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } VerifyChecksumsController::~VerifyChecksumsController() { qCDebug(KLEOPATRA_LOG); } void VerifyChecksumsController::setFiles(const QStringList &files) { kleo_assert(!d->isRunning()); kleo_assert(!files.empty()); const QMutexLocker locker(&d->mutex); d->files = files; } void VerifyChecksumsController::start() { { const QMutexLocker locker(&d->mutex); d->dialog = new VerifyChecksumsDialog; d->dialog->setAttribute(Qt::WA_DeleteOnClose); d->dialog->setWindowTitle(i18nc("@title:window", "Verify Checksum Results")); connect(d->dialog.data(), &VerifyChecksumsDialog::canceled, this, &VerifyChecksumsController::cancel); connect(d.get(), &Private::baseDirectories, d->dialog.data(), &VerifyChecksumsDialog::setBaseDirectories); connect(d.get(), &Private::progress, d->dialog.data(), &VerifyChecksumsDialog::setProgress); connect(d.get(), &Private::status, d->dialog.data(), &VerifyChecksumsDialog::setStatus); d->canceled = false; d->errors.clear(); } d->start(); d->dialog->show(); } void VerifyChecksumsController::cancel() { qCDebug(KLEOPATRA_LOG); const QMutexLocker locker(&d->mutex); d->canceled = true; } namespace { struct SumFile { QDir dir; QString sumFile; quint64 totalSize; std::shared_ptr checksumDefinition; }; } static QStringList filter_checksum_files(QStringList l, const QList &rxs) { l.erase(std::remove_if(l.begin(), l.end(), matches_none_of(rxs)), l.end()); return l; } namespace { struct File { QString name; QByteArray checksum; bool binary; }; } static QString decode(const QString &encoded) { QString decoded; decoded.reserve(encoded.size()); bool shift = false; Q_FOREACH (const QChar ch, encoded) if (shift) { switch (ch.toLatin1()) { case '\\': decoded += QLatin1Char('\\'); break; case 'n': decoded += QLatin1Char('\n'); break; default: qCDebug(KLEOPATRA_LOG) << "invalid escape sequence" << '\\' << ch << "(interpreted as '" << ch << "')"; decoded += ch; break; } shift = false; } else { if (ch == QLatin1Char('\\')) { shift = true; } else { decoded += ch; } } return decoded; } static std::vector parse_sum_file(const QString &fileName) { std::vector files; QFile f(fileName); if (f.open(QIODevice::ReadOnly)) { QTextStream s(&f); QRegExp rx(QLatin1String("(\\?)([a-f0-9A-F]+) ([ *])([^\n]+)\n*")); while (!s.atEnd()) { const QString line = s.readLine(); if (rx.exactMatch(line)) { assert(!rx.cap(4).endsWith(QLatin1Char('\n'))); const File file = { rx.cap(1) == QLatin1String("\\") ? decode(rx.cap(4)) : rx.cap(4), rx.cap(2).toLatin1(), rx.cap(3) == QLatin1String("*"), }; files.push_back(file); } } } return files; } static quint64 aggregate_size(const QDir &dir, const QStringList &files) { quint64 n = 0; Q_FOREACH (const QString &file, files) { n += QFileInfo(dir.absoluteFilePath(file)).size(); } return n; } static std::shared_ptr filename2definition(const QString &fileName, const std::vector< std::shared_ptr > &checksumDefinitions) { Q_FOREACH (const std::shared_ptr &cd, checksumDefinitions) if (cd) Q_FOREACH (const QString &pattern, cd->patterns()) if (QRegExp(pattern, fs_cs).exactMatch(fileName)) { return cd; } return std::shared_ptr(); } namespace { struct less_dir : std::binary_function { bool operator()(const QDir &lhs, const QDir &rhs) const { return QString::compare(lhs.absolutePath(), rhs.absolutePath(), fs_cs) < 0; } }; struct less_file : std::binary_function { bool operator()(const QString &lhs, const QString &rhs) const { return QString::compare(lhs, rhs, fs_cs) < 0; } }; struct sumfile_contains_file : std::unary_function { const QDir dir; const QString fileName; sumfile_contains_file(const QDir &dir_, const QString &fileName_) : dir(dir_), fileName(fileName_) {} bool operator()(const QString &sumFile) const { const std::vector files = parse_sum_file(dir.absoluteFilePath(sumFile)); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << files.size() << " files listed in " << qPrintable(dir.absoluteFilePath(sumFile)); Q_FOREACH (const File &file, files) { const bool isSameFileName = (QString::compare(file.name, fileName, fs_cs) == 0); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: " << qPrintable(file.name) << " == " << qPrintable(fileName) << " ? " << isSameFileName; if (isSameFileName) { return true; } } return false; } }; } // IF is_dir(file) // add all sumfiles \in dir(file) // inputs.prepend( all dirs \in dir(file) ) // ELSE IF is_sum_file(file) // add // ELSE IF \exists sumfile in dir(file) \where sumfile \contains file // add sumfile // ELSE // error: no checksum found for "file" static QStringList find_base_directiories(const QStringList &files) { // Step 1: find base dirs: std::set dirs; Q_FOREACH (const QString &file, files) { const QFileInfo fi(file); const QDir dir = fi.isDir() ? QDir(file) : fi.dir(); dirs.insert(dir); } // Step 1a: collapse direct child directories bool changed; do { changed = false; std::set::iterator it = dirs.begin(); while (it != dirs.end()) { QDir dir = *it; if (dir.cdUp() && dirs.count(dir)) { dirs.erase(it++); changed = true; } else { ++it; } } } while (changed); QStringList rv; rv.reserve(dirs.size()); std::transform(dirs.cbegin(), dirs.cend(), std::back_inserter(rv), std::mem_fn(&QDir::absolutePath)); return rv; } static std::vector find_sums_by_input_files(const QStringList &files, QStringList &errors, const std::function &progress, const std::vector< std::shared_ptr > &checksumDefinitions) { const QList patterns = get_patterns(checksumDefinitions); const matches_any is_sum_file(patterns); std::map, less_dir> dirs2sums; // Step 1: find the sumfiles we need to check: std::deque inputs(files.begin(), files.end()); int i = 0; while (!inputs.empty()) { const QString file = inputs.front(); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: considering " << qPrintable(file); inputs.pop_front(); const QFileInfo fi(file); const QString fileName = fi.fileName(); if (fi.isDir()) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a directory"; QDir dir(file); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() << " sum files: " << qPrintable(sumfiles.join(QStringLiteral(", "))); dirs2sums[ dir ].insert(sumfiles.begin(), sumfiles.end()); const QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << dirs.size() << " subdirs, prepending"; std::transform(dirs.cbegin(), dirs.cend(), std::inserter(inputs, inputs.begin()), [&dir](const QString &path) { return dir.absoluteFilePath(path); }); } else if (is_sum_file(fileName)) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a sum file"; dirs2sums[fi.dir()].insert(fileName); } else { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's something else; checking whether we'll find a sumfile for it..."; const QDir dir = fi.dir(); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() << " potential sumfiles: " << qPrintable(sumfiles.join(QStringLiteral(", "))); const auto it = std::find_if(sumfiles.cbegin(), sumfiles.cend(), sumfile_contains_file(dir, fileName)); if (it == sumfiles.end()) { errors.push_back(i18n("Cannot find checksums file for file %1", file)); } else { dirs2sums[dir].insert(*it); } } if (progress) { progress(++i); } } // Step 2: convert into vector: std::vector sumfiles; sumfiles.reserve(dirs2sums.size()); for (std::map, less_dir>::const_iterator it = dirs2sums.begin(), end = dirs2sums.end(); it != end; ++it) { if (it->second.empty()) { continue; } const QDir &dir = it->first; Q_FOREACH (const QString &sumFileName, it->second) { const std::vector summedfiles = parse_sum_file(dir.absoluteFilePath(sumFileName)); QStringList files; files.reserve(summedfiles.size()); std::transform(summedfiles.cbegin(), summedfiles.cend(), std::back_inserter(files), std::mem_fn(&File::name)); const SumFile sumFile = { it->first, sumFileName, aggregate_size(it->first, files), filename2definition(sumFileName, checksumDefinitions), }; sumfiles.push_back(sumFile); } if (progress) { progress(++i); } } return sumfiles; } static QStringList c_lang_environment() { QStringList env = QProcess::systemEnvironment(); env.erase(std::remove_if(env.begin(), env.end(), [](const QString &str) { return QRegExp(QLatin1String("^LANG=.*"), fs_cs).exactMatch(str); }), env.end()); env.push_back(QStringLiteral("LANG=C")); return env; } static const struct { const char *string; VerifyChecksumsDialog::Status status; } statusStrings[] = { { "OK", VerifyChecksumsDialog::OK }, { "FAILED", VerifyChecksumsDialog::Failed }, }; static const size_t numStatusStrings = sizeof statusStrings / sizeof * statusStrings; static VerifyChecksumsDialog::Status string2status(const QByteArray &str) { for (unsigned int i = 0; i < numStatusStrings; ++i) if (str == statusStrings[i].string) { return statusStrings[i].status; } return VerifyChecksumsDialog::Unknown; } static QString process(const SumFile &sumFile, bool *fatal, const QStringList &env, const std::function &status) { QProcess p; p.setEnvironment(env); p.setWorkingDirectory(sumFile.dir.absolutePath()); p.setReadChannel(QProcess::StandardOutput); const QString absFilePath = sumFile.dir.absoluteFilePath(sumFile.sumFile); const QString program = sumFile.checksumDefinition->verifyCommand(); sumFile.checksumDefinition->startVerifyCommand(&p, QStringList(absFilePath)); QByteArray remainder; // used for filenames with newlines in them while (p.state() != QProcess::NotRunning) { p.waitForReadyRead(); while (p.canReadLine()) { const QByteArray line = p.readLine(); const int colonIdx = line.lastIndexOf(':'); if (colonIdx < 0) { remainder += line; // no colon -> probably filename with a newline continue; } const QString file = QFile::decodeName(remainder + line.left(colonIdx)); remainder.clear(); const VerifyChecksumsDialog::Status result = string2status(line.mid(colonIdx + 1).trimmed()); status(sumFile.dir.absoluteFilePath(file), result); } } qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode(); if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { if (fatal && p.error() == QProcess::FailedToStart) { *fatal = true; } if (p.error() == QProcess::UnknownError) return i18n("Error while running %1: %2", program, QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", program, p.errorString()); } } return QString(); } namespace { static QDebug operator<<(QDebug s, const SumFile &sum) { return s << "SumFile(" << sum.dir << "->" << sum.sumFile << "<-(" << sum.totalSize << ')' << ")\n"; } } void VerifyChecksumsController::Private::run() { QMutexLocker locker(&mutex); const QStringList files = this->files; const std::vector< std::shared_ptr > checksumDefinitions = this->checksumDefinitions; locker.unlock(); QStringList errors; // // Step 0: find base directories: // Q_EMIT baseDirectories(find_base_directiories(files)); // // Step 1: build a list of work to do (no progress): // const QString scanning = i18n("Scanning directories..."); Q_EMIT progress(0, 0, scanning); const auto progressCb = [this, scanning](int arg) { Q_EMIT progress(arg, 0, scanning); }; const auto statusCb = [this](const QString &str, VerifyChecksumsDialog::Status st) { Q_EMIT status(str, st); }; const std::vector sumfiles = find_sums_by_input_files(files, errors, progressCb, checksumDefinitions); Q_FOREACH (const SumFile &sumfile, sumfiles) { qCDebug(KLEOPATRA_LOG) << sumfile; } if (!canceled) { Q_EMIT progress(0, 0, i18n("Calculating total size...")); const quint64 total = kdtools::accumulate_transform(sumfiles.cbegin(), sumfiles.cend(), std::mem_fn(&SumFile::totalSize), Q_UINT64_C(0)); if (!canceled) { // // Step 2: perform work (with progress reporting): // const QStringList env = c_lang_environment(); // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...) const quint64 factor = total / std::numeric_limits::max() + 1; quint64 done = 0; Q_FOREACH (const SumFile &sumFile, sumfiles) { Q_EMIT progress(done / factor, total / factor, i18n("Verifying checksums (%2) in %1", sumFile.checksumDefinition->label(), sumFile.dir.path())); bool fatal = false; const QString error = process(sumFile, &fatal, env, statusCb); if (!error.isEmpty()) { errors.push_back(error); } done += sumFile.totalSize; if (fatal || canceled) { break; } } Q_EMIT progress(done / factor, total / factor, i18n("Done.")); } } locker.relock(); this->errors = errors; // mutex unlocked by QMutexLocker } #include "moc_verifychecksumscontroller.cpp" #include "verifychecksumscontroller.moc" #endif // QT_NO_DIRMODEL diff --git a/src/dialogs/adduseriddialog.cpp b/src/dialogs/adduseriddialog.cpp index 944e8985..3663fd9d 100644 --- a/src/dialogs/adduseriddialog.cpp +++ b/src/dialogs/adduseriddialog.cpp @@ -1,360 +1,360 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/adduseriddialog.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "adduseriddialog.h" #include "ui_adduseriddialog.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include using namespace Kleo; using namespace Kleo::Dialogs; namespace { struct Line { QString attr; QString label; QString regex; QLineEdit *edit; }; } static QString pgpLabel(const QString &attr) { if (attr == QLatin1String("NAME")) { return i18n("Name"); } if (attr == QLatin1String("COMMENT")) { return i18n("Comment"); } if (attr == QLatin1String("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = /*pgp ?*/ pgpLabel(attr) /*: Kleo::DNAttributeMapper::instance()->name2label( attr )*/; if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required) { assert(l); assert(row >= 0); assert(row < l->rowCount()); QLabel *lb = qobject_cast(l->itemAtPosition(row, 0)->widget()); assert(lb); QLineEdit *le = qobject_cast(l->itemAtPosition(row, 1)->widget()); assert(le); QLabel *reqLB = qobject_cast(l->itemAtPosition(row, 2)->widget()); assert(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); delete le->validator(); if (validator) { if (!validator->parent()) { validator->setParent(le); } le->setValidator(validator); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } class AddUserIDDialog::Private { friend class ::Kleo::Dialogs::AddUserIDDialog; AddUserIDDialog *const q; public: explicit Private(AddUserIDDialog *qq) : q(qq), ui(q) { } private: void slotUserIDChanged(); private: bool isComplete() const; private: struct UI : public Ui_AddUserIDDialog { QVector lineList; explicit UI(AddUserIDDialog *qq) : Ui_AddUserIDDialog() { setupUi(qq); // ### this code is mostly the same as the one in // ### newcertificatewizard. Find some time to factor them // ### into a single copy. // hide the stuff nameLB->hide(); nameLE->hide(); nameRequiredLB->hide(); emailLB->hide(); emailLE->hide(); emailRequiredLB->hide(); commentLB->hide(); commentLE->hide(); commentRequiredLB->hide(); // set errorLB to have a fixed height of two lines: errorLB->setText(QStringLiteral("2
1")); errorLB->setFixedHeight(errorLB->minimumSizeHint().height()); errorLB->clear(); const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QStringList attrOrder = config.readEntry("OpenPGPAttributeOrder", QStringList() << QStringLiteral("NAME!") << QStringLiteral("EMAIL!") << QStringLiteral("COMMENT")); QMap lines; Q_FOREACH (const QString &rawKey, attrOrder) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = config.readEntry(attr); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, true)); const QString regex = config.readEntry(attr + QLatin1String("_regex")); int row; QValidator *validator = 0; if (attr == QLatin1String("EMAIL")) { validator = regex.isEmpty() ? Validation::email() : Validation::email(QRegExp(regex)); row = row_index_of(emailLE, gridLayout); } else if (attr == QLatin1String("NAME")) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(QRegExp(regex)); row = row_index_of(nameLE, gridLayout); } else if (attr == QLatin1String("COMMENT")) { validator = regex.isEmpty() ? Validation::pgpComment() : Validation::pgpComment(QRegExp(regex)); row = row_index_of(commentLE, gridLayout); } else { continue; } QLineEdit *le = adjust_row(gridLayout, row, label, preset, validator, readonly, required); const Line line = { key, label, regex, le }; lines[row] = line; } std::copy(lines.begin(), lines.end(), std::back_inserter(lineList)); } QPushButton *okPB() const { return buttonBox->button(QDialogButtonBox::Ok); } } ui; }; AddUserIDDialog::AddUserIDDialog(QWidget *p) : QDialog(p), d(new Private(this)) { } AddUserIDDialog::~AddUserIDDialog() {} void AddUserIDDialog::setName(const QString &name) { d->ui.nameLE->setText(name); } QString AddUserIDDialog::name() const { return d->ui.nameLE->text().trimmed(); } void AddUserIDDialog::setEmail(const QString &email) { d->ui.emailLE->setText(email); } QString AddUserIDDialog::email() const { return d->ui.emailLE->text().trimmed(); } void AddUserIDDialog::setComment(const QString &comment) { d->ui.commentLE->setText(comment); } QString AddUserIDDialog::comment() const { return d->ui.commentLE->text().trimmed(); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return !v || v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QVector &list, QString &error) { Q_FOREACH (const Line &line, list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is required, but empty.", line.label); } else error = xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is incomplete.", line.label); } else error = xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is invalid.", line.label); } else error = xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", line.label, line.regex); return false; } qCDebug(KLEOPATRA_LOG) << "ok"; } return true; } bool AddUserIDDialog::Private::isComplete() const { QString error; const bool ok = requirementsAreMet(ui.lineList, error); ui.errorLB->setText(error); return ok; } void AddUserIDDialog::Private::slotUserIDChanged() { ui.okPB()->setEnabled(isComplete()); const QString name = q->name(); const QString email = q->email(); const QString comment = q->comment(); QStringList parts; if (!name.isEmpty()) { parts.push_back(name); } if (!comment.isEmpty()) { parts.push_back(QLatin1Char('(') + comment + QLatin1Char(')')); } if (!email.isEmpty()) { parts.push_back(QLatin1Char('<') + email + QLatin1Char('>')); } - ui.resultLB->setText(parts.join(QStringLiteral(" "))); + ui.resultLB->setText(parts.join(QLatin1Char(' '))); } #include "moc_adduseriddialog.cpp" diff --git a/src/libkleopatraclient/gui/certificaterequester.cpp b/src/libkleopatraclient/gui/certificaterequester.cpp index 9426e06f..be2a8384 100644 --- a/src/libkleopatraclient/gui/certificaterequester.cpp +++ b/src/libkleopatraclient/gui/certificaterequester.cpp @@ -1,266 +1,266 @@ /* -*- mode: c++; c-basic-offset:4 -*- gui/certificaterequester.h This file is part of KleopatraClient, the Kleopatra interface library Copyright (c) 2008 Klarälvdalens Datakonsult AB KleopatraClient is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. KleopatraClient is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "certificaterequester.h" #include #include #include #include #include #include #include #include using namespace KleopatraClientCopy; using namespace KleopatraClientCopy::Gui; class CertificateRequester::Private { friend class ::KleopatraClientCopy::Gui::CertificateRequester; CertificateRequester *const q; public: explicit Private(CertificateRequester *qq) : q(qq), selectedCertificates(), command(), multipleCertificatesAllowed(false), onlySigningCertificatesAllowed(false), onlyEncryptionCertificatesAllowed(false), onlyOpenPGPCertificatesAllowed(false), onlyX509CertificatesAllowed(false), onlySecretKeysAllowed(false), ui(q) { } private: void updateLineEdit() { - ui.lineEdit.setText(selectedCertificates.join(QStringLiteral(" "))); + ui.lineEdit.setText(selectedCertificates.join(QLatin1Char(' '))); } void createCommand() { std::unique_ptr cmd(new SelectCertificateCommand); cmd->setMultipleCertificatesAllowed(multipleCertificatesAllowed); cmd->setOnlySigningCertificatesAllowed(onlySigningCertificatesAllowed); cmd->setOnlyEncryptionCertificatesAllowed(onlyEncryptionCertificatesAllowed); cmd->setOnlyOpenPGPCertificatesAllowed(onlyOpenPGPCertificatesAllowed); cmd->setOnlyX509CertificatesAllowed(onlyX509CertificatesAllowed); cmd->setOnlySecretKeysAllowed(onlySecretKeysAllowed); cmd->setSelectedCertificates(selectedCertificates); if (const QWidget *const window = q->window()) { cmd->setParentWId(window->effectiveWinId()); } connect(cmd.get(), SIGNAL(finished()), q, SLOT(slotCommandFinished())); command = cmd.release(); } void slotButtonClicked(); void slotCommandFinished(); private: QStringList selectedCertificates; QPointer command; bool multipleCertificatesAllowed : 1; bool onlySigningCertificatesAllowed : 1; bool onlyEncryptionCertificatesAllowed : 1; bool onlyOpenPGPCertificatesAllowed : 1; bool onlyX509CertificatesAllowed : 1; bool onlySecretKeysAllowed : 1; struct Ui { QLineEdit lineEdit; QPushButton button; QHBoxLayout hlay; explicit Ui(CertificateRequester *qq) : lineEdit(qq), button(i18n("Change..."), qq), hlay(qq) { lineEdit.setObjectName(QStringLiteral("lineEdit")); button.setObjectName(QStringLiteral("button")); hlay.setObjectName(QStringLiteral("hlay")); hlay.addWidget(&lineEdit, 1); hlay.addWidget(&button); lineEdit.setReadOnly(true); connect(&button, SIGNAL(clicked()), qq, SLOT(slotButtonClicked())); } } ui; }; CertificateRequester::CertificateRequester(QWidget *p, Qt::WindowFlags f) : QWidget(p, f), d(new Private(this)) { } CertificateRequester::~CertificateRequester() { delete d; d = 0; } void CertificateRequester::setMultipleCertificatesAllowed(bool allow) { if (allow == d->multipleCertificatesAllowed) { return; } d->multipleCertificatesAllowed = allow; } bool CertificateRequester::multipleCertificatesAllowed() const { return d->multipleCertificatesAllowed; } void CertificateRequester::setOnlySigningCertificatesAllowed(bool allow) { if (allow == d->onlySigningCertificatesAllowed) { return; } d->onlySigningCertificatesAllowed = allow; } bool CertificateRequester::onlySigningCertificatesAllowed() const { return d->onlySigningCertificatesAllowed; } void CertificateRequester::setOnlyEncryptionCertificatesAllowed(bool allow) { if (allow == d->onlyEncryptionCertificatesAllowed) { return; } d->onlyEncryptionCertificatesAllowed = allow; } bool CertificateRequester::onlyEncryptionCertificatesAllowed() const { return d->onlyEncryptionCertificatesAllowed; } void CertificateRequester::setOnlyOpenPGPCertificatesAllowed(bool allow) { if (allow == d->onlyOpenPGPCertificatesAllowed) { return; } d->onlyOpenPGPCertificatesAllowed = allow; } bool CertificateRequester::onlyOpenPGPCertificatesAllowed() const { return d->onlyOpenPGPCertificatesAllowed; } void CertificateRequester::setOnlyX509CertificatesAllowed(bool allow) { if (allow == d->onlyX509CertificatesAllowed) { return; } d->onlyX509CertificatesAllowed = allow; } bool CertificateRequester::onlyX509CertificatesAllowed() const { return d->onlyX509CertificatesAllowed; } void CertificateRequester::setOnlySecretKeysAllowed(bool allow) { if (allow == d->onlySecretKeysAllowed) { return; } d->onlySecretKeysAllowed = allow; } bool CertificateRequester::onlySecretKeysAllowed() const { return d->onlySecretKeysAllowed; } void CertificateRequester::setSelectedCertificates(const QStringList &certs) { if (certs == d->selectedCertificates) { return; } d->selectedCertificates = certs; d->updateLineEdit(); /*emit*/ selectedCertificatesChanged(certs); } QStringList CertificateRequester::selectedCertificates() const { return d->selectedCertificates; } void CertificateRequester::setSelectedCertificate(const QString &cert) { setSelectedCertificates(QStringList(cert)); } QString CertificateRequester::selectedCertificate() const { return d->selectedCertificates.empty() ? QString() : d->selectedCertificates.front(); } void CertificateRequester::Private::slotButtonClicked() { if (command) { return; } createCommand(); command->start(); ui.button.setEnabled(false); } void CertificateRequester::Private::slotCommandFinished() { if (command->wasCanceled()) /* do nothing */; else if (command->error()) QMessageBox::information(q, i18n("Kleopatra Error"), i18n("There was an error while connecting to Kleopatra: %1") .arg(command->errorString())); else { q->setSelectedCertificates(command->selectedCertificates()); } ui.button.setEnabled(true); delete command; } #include "moc_certificaterequester.cpp" diff --git a/src/newcertificatewizard/newcertificatewizard.cpp b/src/newcertificatewizard/newcertificatewizard.cpp index e9f0e19e..7c7ef3b8 100644 --- a/src/newcertificatewizard/newcertificatewizard.cpp +++ b/src/newcertificatewizard/newcertificatewizard.cpp @@ -1,1862 +1,1862 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/newcertificatewizard.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 2016 Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "newcertificatewizard.h" #include "ui_chooseprotocolpage.h" #include "ui_enterdetailspage.h" #include "ui_overviewpage.h" #include "ui_keycreationpage.h" #include "ui_resultpage.h" #include "ui_advancedsettingsdialog.h" #include #include #include #include #include #include "utils/gnupg-helper.h" #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace Kleo::Commands; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames { { QStringLiteral("brainpoolP256r1") }, { QStringLiteral("brainpoolP384r1") }, { QStringLiteral("brainpoolP512r1") }, { QStringLiteral("NIST P-256") }, { QStringLiteral("NIST P-384") }, { QStringLiteral("NIST P-521") }, }; static void set_tab_order(const QList &wl) { kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), &QWidget::setTabOrder); } enum KeyAlgo { RSA, DSA, ELG, ECDSA, ECDH }; static bool is_algo(Subkey::PubkeyAlgo algo, KeyAlgo what) { switch (algo) { case Subkey::AlgoRSA: case Subkey::AlgoRSA_E: case Subkey::AlgoRSA_S: return what == RSA; case Subkey::AlgoELG_E: case Subkey::AlgoELG: return what == ELG; case Subkey::AlgoDSA: return what == DSA; case Subkey::AlgoECDSA: return what == ECDSA; case Subkey::AlgoECDH: return what == ECDH; default: break; } return false; } static bool is_rsa(unsigned int algo) { return is_algo(static_cast(algo), RSA); } static bool is_dsa(unsigned int algo) { return is_algo(static_cast(algo), DSA); } static bool is_elg(unsigned int algo) { return is_algo(static_cast(algo), ELG); } static bool is_ecdsa(unsigned int algo) { return is_algo(static_cast(algo), ECDSA); } static bool is_ecdh(unsigned int algo) { return is_algo(static_cast(algo), ECDH); } static void force_set_checked(QAbstractButton *b, bool on) { // work around Qt bug (tested: 4.1.4, 4.2.3, 4.3.4) const bool autoExclusive = b->autoExclusive(); b->setAutoExclusive(false); b->setChecked(b->isEnabled() && on); b->setAutoExclusive(autoExclusive); } static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast(strength)); if (idx < 0) { qCWarning(KLEOPATRA_LOG) << "keysize " << strength << " not allowed"; } cb->setCurrentIndex(idx); } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findData(curve); if (idx < 0) { // Can't happen as we don't have them configurable. qCWarning(KLEOPATRA_LOG) << "curve " << curve << " not allowed"; } cb->setCurrentIndex(idx); } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } Q_DECLARE_METATYPE(GpgME::Subkey::PubkeyAlgo); namespace Kleo { namespace NewCertificateUi { class WizardPage : public QWizardPage { Q_OBJECT protected: explicit WizardPage(QWidget *parent = nullptr) : QWizardPage(parent) {} NewCertificateWizard *wizard() const { assert(static_cast(QWizardPage::wizard()) == qobject_cast(QWizardPage::wizard())); return static_cast(QWizardPage::wizard()); } QAbstractButton *button(QWizard::WizardButton button) const { return QWizardPage::wizard() ? QWizardPage::wizard()->button(button) : 0; } bool isButtonVisible(QWizard::WizardButton button) const { if (const QAbstractButton *const b = this->button(button)) { return b->isVisible(); } else { return false; } } QDir tmpDir() const; protected Q_SLOTS: void setButtonVisible(QWizard::WizardButton button, bool visible) { if (QAbstractButton *const b = this->button(button)) { b->setVisible(visible); } } protected: #define FIELD(type, name) type name() const { return field( QLatin1String(#name) ).value(); } FIELD(bool, pgp) FIELD(bool, signingAllowed) FIELD(bool, encryptionAllowed) FIELD(bool, certificationAllowed) FIELD(bool, authenticationAllowed) FIELD(QString, name) FIELD(QString, email) FIELD(QString, comment) FIELD(QString, dn) FIELD(Subkey::PubkeyAlgo, keyType) FIELD(int, keyStrength) FIELD(QString, keyCurve) FIELD(Subkey::PubkeyAlgo, subkeyType) FIELD(int, subkeyStrength) FIELD(QString, subkeyCurve) FIELD(QDate, expiryDate) FIELD(QStringList, additionalUserIDs) FIELD(QStringList, additionalEMailAddresses) FIELD(QStringList, dnsNames) FIELD(QStringList, uris) FIELD(QString, url) FIELD(QString, error) FIELD(QString, result) FIELD(QString, fingerprint) #undef FIELD }; } // namespace NewCertificateUi } // namespace Kleo using namespace Kleo::NewCertificateUi; namespace { class AdvancedSettingsDialog : public QDialog { Q_OBJECT Q_PROPERTY(QStringList additionalUserIDs READ additionalUserIDs WRITE setAdditionalUserIDs) Q_PROPERTY(QStringList additionalEMailAddresses READ additionalEMailAddresses WRITE setAdditionalEMailAddresses) Q_PROPERTY(QStringList dnsNames READ dnsNames WRITE setDnsNames) Q_PROPERTY(QStringList uris READ uris WRITE setUris) Q_PROPERTY(uint keyStrength READ keyStrength WRITE setKeyStrength) Q_PROPERTY(Subkey::PubkeyAlgo keyType READ keyType WRITE setKeyType) Q_PROPERTY(QString keyCurve READ keyCurve WRITE setKeyCurve) Q_PROPERTY(uint subkeyStrength READ subkeyStrength WRITE setSubkeyStrength) Q_PROPERTY(QString subkeyCurve READ keyCurve WRITE setSubkeyCurve) Q_PROPERTY(Subkey::PubkeyAlgo subkeyType READ subkeyType WRITE setSubkeyType) Q_PROPERTY(bool signingAllowed READ signingAllowed WRITE setSigningAllowed) Q_PROPERTY(bool encryptionAllowed READ encryptionAllowed WRITE setEncryptionAllowed) Q_PROPERTY(bool certificationAllowed READ certificationAllowed WRITE setCertificationAllowed) Q_PROPERTY(bool authenticationAllowed READ authenticationAllowed WRITE setAuthenticationAllowed) Q_PROPERTY(QDate expiryDate READ expiryDate WRITE setExpiryDate) public: explicit AdvancedSettingsDialog(QWidget *parent = nullptr) : QDialog(parent), protocol(UnknownProtocol), pgpDefaultAlgorithm(Subkey::AlgoELG_E), cmsDefaultAlgorithm(Subkey::AlgoRSA), keyTypeImmutable(false), ui(), mECCSupported(engineIsVersion(2, 1, 0)) { qRegisterMetaType("Subkey::PubkeyAlgo"); ui.setupUi(this); const QDate today = QDate::currentDate(); ui.expiryDE->setMinimumDate(today); ui.expiryDE->setDate(today.addYears(2)); ui.emailLW->setDefaultValue(i18n("new email")); ui.dnsLW->setDefaultValue(i18n("new dns name")); ui.uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); } void setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaultKeyType(); } void setAdditionalUserIDs(const QStringList &items) { ui.uidLW->setItems(items); } QStringList additionalUserIDs() const { return ui.uidLW->items(); } void setAdditionalEMailAddresses(const QStringList &items) { ui.emailLW->setItems(items); } QStringList additionalEMailAddresses() const { return ui.emailLW->items(); } void setDnsNames(const QStringList &items) { ui.dnsLW->setItems(items); } QStringList dnsNames() const { return ui.dnsLW->items(); } void setUris(const QStringList &items) { ui.uriLW->setItems(items); } QStringList uris() const { return ui.uriLW->items(); } void setKeyStrength(unsigned int strength) { set_keysize(ui.rsaKeyStrengthCB, strength); set_keysize(ui.dsaKeyStrengthCB, strength); } unsigned int keyStrength() const { return ui.dsaRB->isChecked() ? get_keysize(ui.dsaKeyStrengthCB) : ui.rsaRB->isChecked() ? get_keysize(ui.rsaKeyStrengthCB) : 0; } void setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui.rsaRB : is_dsa(algo) ? ui.dsaRB : is_ecdsa(algo) ? ui.ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo keyType() const { return ui.dsaRB->isChecked() ? Subkey::AlgoDSA : ui.rsaRB->isChecked() ? Subkey::AlgoRSA : ui.ecdsaRB->isChecked() ? Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void setKeyCurve(const QString &curve) { set_curve(ui.ecdsaKeyCurvesCB, curve); } QString keyCurve() const { return get_curve(ui.ecdsaKeyCurvesCB); } void setSubkeyType(Subkey::PubkeyAlgo algo) { ui.elgCB->setChecked(is_elg(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); ui.ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo subkeyType() const { if (ui.elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui.rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui.ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void setSubkeyCurve(const QString &curve) { set_curve(ui.ecdhKeyCurvesCB, curve); } QString subkeyCurve() const { return get_curve(ui.ecdhKeyCurvesCB); } void setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui.rsaKeyStrengthSubCB, strength); } else { set_keysize(ui.elgKeyStrengthCB, strength); } } unsigned int subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui.rsaKeyStrengthSubCB); } return get_keysize(ui.elgKeyStrengthCB); } void setSigningAllowed(bool on) { ui.signingCB->setChecked(on); } bool signingAllowed() const { return ui.signingCB->isChecked(); } void setEncryptionAllowed(bool on) { ui.encryptionCB->setChecked(on); } bool encryptionAllowed() const { return ui.encryptionCB->isChecked(); } void setCertificationAllowed(bool on) { ui.certificationCB->setChecked(on); } bool certificationAllowed() const { return ui.certificationCB->isChecked(); } void setAuthenticationAllowed(bool on) { ui.authenticationCB->setChecked(on); } bool authenticationAllowed() const { return ui.authenticationCB->isChecked(); } void setExpiryDate(const QDate &date) { if (date.isValid()) { ui.expiryDE->setDate(date); } else { ui.expiryCB->setChecked(false); } } QDate expiryDate() const { return ui.expiryCB->isChecked() ? ui.expiryDE->date() : QDate(); } Q_SIGNALS: void changed(); private Q_SLOTS: void slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { if (!keyTypeImmutable) { ui.elgCB->setEnabled(is_dsa(algo)); ui.rsaSubCB->setEnabled(is_rsa(algo)); ui.ecdhCB->setEnabled(is_ecdsa(algo)); if (sender() == ui.dsaRB || sender() == ui.rsaRB || sender() == ui.ecdsaRB) { ui.elgCB->setChecked(is_dsa(algo)); ui.ecdhCB->setChecked(is_ecdsa(algo)); ui.rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui.encryptionCB->setEnabled(true); ui.encryptionCB->setChecked(true); ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui.encryptionCB->setEnabled(false); if (is_elg(sk_algo)) { ui.encryptionCB->setChecked(true); } else { ui.encryptionCB->setChecked(false); } } else if (is_ecdsa(algo)) { ui.signingCB->setEnabled(true); ui.signingCB->setChecked(true); ui.authenticationCB->setEnabled(true); ui.encryptionCB->setEnabled(false); ui.encryptionCB->setChecked(is_ecdh(sk_algo)); } } } else { //assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } private: void fillKeySizeComboBoxen(); void loadDefaultKeyType(); void updateWidgetVisibility(); private: GpgME::Protocol protocol; unsigned int pgpDefaultAlgorithm; unsigned int cmsDefaultAlgorithm; bool keyTypeImmutable; Ui_AdvancedSettingsDialog ui; bool mECCSupported; }; class ChooseProtocolPage : public WizardPage { Q_OBJECT public: explicit ChooseProtocolPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), ui() { ui.setupUi(this); registerField(QStringLiteral("pgp"), ui.pgpCLB); } void setProtocol(Protocol proto) { if (proto == OpenPGP) { ui.pgpCLB->setChecked(true); } else if (proto == CMS) { ui.x509CLB->setChecked(true); } else { force_set_checked(ui.pgpCLB, false); force_set_checked(ui.x509CLB, false); } } Protocol protocol() const { return ui.pgpCLB->isChecked() ? OpenPGP : ui.x509CLB->isChecked() ? CMS : UnknownProtocol; } void initializePage() Q_DECL_OVERRIDE { if (!initialized) { connect(ui.pgpCLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); connect(ui.x509CLB, &QAbstractButton::clicked, wizard(), &QWizard::next, Qt::QueuedConnection); } initialized = true; } bool isComplete() const Q_DECL_OVERRIDE { return protocol() != UnknownProtocol; } private: bool initialized : 1; Ui_ChooseProtocolPage ui; }; struct Line { QString attr; QString label; QString regex; QLineEdit *edit; }; class EnterDetailsPage : public WizardPage { Q_OBJECT public: explicit EnterDetailsPage(QWidget *p = nullptr) : WizardPage(p), dialog(this), ui() { ui.setupUi(this); // set errorLB to have a fixed height of two lines: ui.errorLB->setText(QStringLiteral("2
1")); ui.errorLB->setFixedHeight(ui.errorLB->minimumSizeHint().height()); ui.errorLB->clear(); connect(ui.resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); // The email doesn't necessarily show up in ui.resultLE: connect(ui.emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); connect(ui.addEmailToDnCB, &QAbstractButton::toggled, this, &EnterDetailsPage::slotUpdateResultLabel); registerDialogPropertiesAsFields(); registerField(QStringLiteral("dn"), ui.resultLE); registerField(QStringLiteral("name"), ui.nameLE); registerField(QStringLiteral("email"), ui.emailLE); registerField(QStringLiteral("comment"), ui.commentLE); updateForm(); } bool isComplete() const Q_DECL_OVERRIDE; void initializePage() Q_DECL_OVERRIDE { updateForm(); dialog.setProtocol(pgp() ? OpenPGP : CMS); } void cleanupPage() Q_DECL_OVERRIDE { saveValues(); } private: void updateForm(); void clearForm(); void saveValues(); void registerDialogPropertiesAsFields(); private: QString pgpUserID() const; QString cmsDN() const; private Q_SLOTS: void slotAdvancedSettingsClicked(); void slotUpdateResultLabel() { ui.resultLE->setText(pgp() ? pgpUserID() : cmsDN()); } private: QVector lineList; QList dynamicWidgets; QMap savedValues; AdvancedSettingsDialog dialog; Ui_EnterDetailsPage ui; }; class OverviewPage : public WizardPage { Q_OBJECT public: explicit OverviewPage(QWidget *p = nullptr) : WizardPage(p), ui() { ui.setupUi(this); setCommitPage(true); setButtonText(QWizard::CommitButton, i18nc("@action", "Create Key")); } void initializePage() Q_DECL_OVERRIDE { slotShowDetails(); } private Q_SLOTS: void slotShowDetails() { ui.textBrowser->setHtml(i18nFormatGnupgKeyParms(ui.showAllDetailsCB->isChecked())); } private: QStringList i18nKeyUsages() const; QStringList i18nSubkeyUsages() const; QStringList i18nCombinedKeyUsages() const; QString i18nFormatGnupgKeyParms(bool details) const; private: Ui_OverviewPage ui; }; class KeyCreationPage : public WizardPage { Q_OBJECT public: explicit KeyCreationPage(QWidget *p = nullptr) : WizardPage(p), ui() { ui.setupUi(this); } bool isComplete() const Q_DECL_OVERRIDE { return !job; } void initializePage() Q_DECL_OVERRIDE { startJob(); } private: void startJob() { const auto proto = pgp() ? QGpgME::openpgp() : QGpgME::smime(); if (!proto) { return; } QGpgME::KeyGenerationJob *const j = proto->keyGenerationJob(); if (!j) { return; } connect(j, SIGNAL(result(GpgME::KeyGenerationResult,QByteArray,QString)), this, SLOT(slotResult(GpgME::KeyGenerationResult,QByteArray,QString))); if (const Error err = j->start(createGnupgKeyParms())) setField(QStringLiteral("error"), i18n("Could not start certificate creation: %1", QString::fromLocal8Bit(err.asString()))); else { job = j; } } QStringList keyUsages() const; QStringList subkeyUsages() const; QString createGnupgKeyParms() const; private Q_SLOTS: void slotResult(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog) { Q_UNUSED(auditLog); if (result.error().code()) { setField(QStringLiteral("error"), result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not create certificate: %1", QString::fromLocal8Bit(result.error().asString()))); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else if (pgp()) { setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), i18n("Certificate created successfully.\n" "Fingerprint: %1", QLatin1String(result.fingerprint()))); } else { QFile file(tmpDir().absoluteFilePath(QStringLiteral("request.p10"))); if (!file.open(QIODevice::WriteOnly)) { setField(QStringLiteral("error"), i18n("Could not write output file %1: %2", file.fileName(), file.errorString())); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else { file.write(request); setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QUrl::fromLocalFile(file.fileName()).toString()); setField(QStringLiteral("result"), i18n("Certificate created successfully.")); } } setField(QStringLiteral("fingerprint"), QString::fromLatin1(result.fingerprint())); job = 0; Q_EMIT completeChanged(); QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } private: QPointer job; Ui_KeyCreationPage ui; }; class ResultPage : public WizardPage { Q_OBJECT public: explicit ResultPage(QWidget *p = nullptr) : WizardPage(p), initialized(false), successfullyCreatedSigningCertificate(false), successfullyCreatedEncryptionCertificate(false), ui() { ui.setupUi(this); ui.dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui.errorTB, "plainText"); registerField(QStringLiteral("result"), ui.resultTB, "plainText"); registerField(QStringLiteral("url"), ui.dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... QLineEdit *le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } void initializePage() Q_DECL_OVERRIDE { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui.resultTB ->setVisible(!error); ui.errorTB ->setVisible(error); ui.dragQueen ->setVisible(!error &&!pgp()); ui.restartWizardPB ->setVisible(error); ui.nextStepsGB ->setVisible(!error); ui.saveRequestToFilePB ->setVisible(!pgp()); ui.makeBackupPB ->setVisible(pgp()); ui.createRevocationRequestPB->setVisible(pgp() &&false); // not implemented ui.sendCertificateByEMailPB ->setVisible(pgp()); ui.sendRequestByEMailPB ->setVisible(!pgp()); ui.uploadToKeyserverPB ->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui.createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate &&!successfullyCreatedSigningCertificate); ui.createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate &&!successfullyCreatedEncryptionCertificate); setButtonVisible(QWizard::CancelButton, error); if (!initialized) connect(ui.restartWizardPB, &QAbstractButton::clicked, wizard(), &QWizard::restart); initialized = true; } void cleanupPage() Q_DECL_OVERRIDE { setButtonVisible(QWizard::CancelButton, true); } bool isError() const { return !ui.errorTB->toPlainText().isEmpty(); } bool isComplete() const Q_DECL_OVERRIDE { return !isError(); } private: Key key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } private Q_SLOTS: void slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { fileName += QLatin1String(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file %1 " "to file %2: %3", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", fileName), i18nc("@title", "Request Saved")); } void slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QUrl(url()).toLocalFile()); // attachment } void slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } ExportCertificateCommand *cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = 0; if (fileName.isEmpty()) { return; } invokeMailer(QString(), // to i18n("My new OpenPGP certificate"), // subject i18n("Please find attached my new OpenPGP certificate."), // body fileName); } QByteArray ol_quote(QByteArray str) { #ifdef Q_OS_WIN return "\"\"" + str.replace('"', "\\\"") + "\"\""; //return '"' + str.replace( '"', "\\\"" ) + '"'; #else return str; #endif } void invokeMailer(const QString &to, const QString &subject, const QString &body, const QString &attachment) { qCDebug(KLEOPATRA_LOG) << "to:" << to << "subject:" << subject << "body:" << body << "attachment:" << attachment; // RFC 2368 says body's linebreaks need to be encoded as // "%0D%0A", so normalize body to CRLF: //body.replace(QLatin1Char('\n'), QStringLiteral("\r\n")).remove(QStringLiteral("\r\r")); QUrlQuery query; query.addQueryItem(QStringLiteral("subject"), subject); query.addQueryItem(QStringLiteral("body"), body); if (!attachment.isEmpty()) { query.addQueryItem(QStringLiteral("attach"), attachment); } QUrl url; url.setScheme(QStringLiteral("mailto")); url.setQuery(query); qCDebug(KLEOPATRA_LOG) << "openUrl" << url; QDesktopServices::openUrl(url); KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then drag the Kleopatra icon and drop it on the message compose window of your mail client." "If that does not work, either, save the request to a file, and then attach that."), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void slotCreateRevocationRequest() { } void slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } private: void toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel( this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); // restart and skip to Overview Page: wizard()->restart(); for (int i = wizard()->currentId(); i < NewCertificateWizard::OverviewPageId; ++i) { wizard()->next(); } } private: bool initialized : 1; bool successfullyCreatedSigningCertificate : 1; bool successfullyCreatedEncryptionCertificate : 1; QPointer exportCertificateCommand; Ui_ResultPage ui; }; } class NewCertificateWizard::Private { friend class ::Kleo::NewCertificateWizard; friend class ::Kleo::NewCertificateUi::WizardPage; NewCertificateWizard *const q; public: explicit Private(NewCertificateWizard *qq) : q(qq), tmp(QDir::temp().absoluteFilePath(QStringLiteral("kleo-"))), ui(q) { q->setWindowTitle(i18nc("@title", "Certificate Creation Wizard")); } private: QTemporaryDir tmp; struct Ui { ChooseProtocolPage chooseProtocolPage; EnterDetailsPage enterDetailsPage; OverviewPage overviewPage; KeyCreationPage keyCreationPage; ResultPage resultPage; explicit Ui(NewCertificateWizard *q) : chooseProtocolPage(q), enterDetailsPage(q), overviewPage(q), keyCreationPage(q), resultPage(q) { KDAB_SET_OBJECT_NAME(chooseProtocolPage); KDAB_SET_OBJECT_NAME(enterDetailsPage); KDAB_SET_OBJECT_NAME(overviewPage); KDAB_SET_OBJECT_NAME(keyCreationPage); KDAB_SET_OBJECT_NAME(resultPage); q->setOptions(DisabledBackButtonOnLastPage); q->setPage(ChooseProtocolPageId, &chooseProtocolPage); q->setPage(EnterDetailsPageId, &enterDetailsPage); q->setPage(OverviewPageId, &overviewPage); q->setPage(KeyCreationPageId, &keyCreationPage); q->setPage(ResultPageId, &resultPage); q->setStartId(ChooseProtocolPageId); } } ui; }; NewCertificateWizard::NewCertificateWizard(QWidget *p) : QWizard(p), d(new Private(this)) { } NewCertificateWizard::~NewCertificateWizard() {} void NewCertificateWizard::setProtocol(Protocol proto) { d->ui.chooseProtocolPage.setProtocol(proto); setStartId(proto == UnknownProtocol ? ChooseProtocolPageId : EnterDetailsPageId); } Protocol NewCertificateWizard::protocol() const { return d->ui.chooseProtocolPage.protocol(); } static QString pgpLabel(const QString &attr) { if (attr == QLatin1String("NAME")) { return i18n("Name"); } if (attr == QLatin1String("COMMENT")) { return i18n("Comment"); } if (attr == QLatin1String("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = pgp ? pgpLabel(attr) : Kleo::DNAttributeMapper::instance()->name2label(attr); if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } #if 0 //Not used anywhere static QString attributeLabelWithColor(const QString &attr, bool pgp) { const QString result = attributeLabel(attr, pgp); if (result.isEmpty()) { return QString(); } else { return result + ':'; } } #endif static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } static const char *oidForAttributeName(const QString &attr) { QByteArray attrUtf8 = attr.toUtf8(); for (unsigned int i = 0; i < numOidMaps; ++i) if (qstricmp(attrUtf8, oidmap[i].name) == 0) { return oidmap[i].oid; } return nullptr; } QDir WizardPage::tmpDir() const { return wizard() ? QDir(wizard()->d->tmp.path()) : QDir::home(); } void EnterDetailsPage::registerDialogPropertiesAsFields() { const QMetaObject *const mo = dialog.metaObject(); for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) { const QMetaProperty mp = mo->property(i); if (mp.isValid()) { registerField(QLatin1String(mp.name()), &dialog, mp.name(), SIGNAL(accepted())); } } } void EnterDetailsPage::saveValues() { Q_FOREACH (const Line &line, lineList) { savedValues[ attributeFromKey(line.attr) ] = line.edit->text().trimmed(); } } void EnterDetailsPage::clearForm() { qDeleteAll(dynamicWidgets); dynamicWidgets.clear(); lineList.clear(); ui.nameLE->hide(); ui.nameLE->clear(); ui.nameLB->hide(); ui.nameRequiredLB->hide(); ui.emailLE->hide(); ui.emailLE->clear(); ui.emailLB->hide(); ui.emailRequiredLB->hide(); ui.commentLE->hide(); ui.commentLE->clear(); ui.commentLB->hide(); ui.commentRequiredLB->hide(); ui.addEmailToDnCB->hide(); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit *adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, QValidator *validator, bool readonly, bool required) { assert(l); assert(row >= 0); assert(row < l->rowCount()); QLabel *lb = qobject_cast(l->itemAtPosition(row, 0)->widget()); assert(lb); QLineEdit *le = qobject_cast(l->itemAtPosition(row, 1)->widget()); assert(le); lb->setBuddy(le); // For better accessibility QLabel *reqLB = qobject_cast(l->itemAtPosition(row, 2)->widget()); assert(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); delete le->validator(); if (validator) { if (!validator->parent()) { validator->setParent(le); } le->setValidator(validator); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } static int add_row(QGridLayout *l, QList *wl) { assert(l); assert(wl); const int row = l->rowCount(); QWidget *w1, *w2, *w3; l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0); l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1); l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2); wl->push_back(w1); wl->push_back(w2); wl->push_back(w3); return row; } void EnterDetailsPage::updateForm() { clearForm(); const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); QStringList attrOrder = config.readEntry(pgp() ? "OpenPGPAttributeOrder" : "DNAttributeOrder", QStringList()); if (attrOrder.empty()) { if (pgp()) { attrOrder << QStringLiteral("NAME!") << QStringLiteral("EMAIL!") << QStringLiteral("COMMENT"); } else { attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O!") << QStringLiteral("C!") << QStringLiteral("EMAIL!"); } } QList widgets; widgets.push_back(ui.nameLE); widgets.push_back(ui.emailLE); widgets.push_back(ui.commentLE); QMap lines; Q_FOREACH (const QString &rawKey, attrOrder) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = savedValues.value(attr, config.readEntry(attr, QString())); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, pgp())); const QString regex = config.readEntry(attr + QLatin1String("_regex")); int row; bool known = true; QValidator *validator = 0; if (attr == QLatin1String("EMAIL")) { row = row_index_of(ui.emailLE, ui.gridLayout); validator = regex.isEmpty() ? Validation::email() : Validation::email(QRegExp(regex)); if (!pgp()) { ui.addEmailToDnCB->show(); } } else if (attr == QLatin1String("NAME") || attr == QLatin1String("CN")) { if ((pgp() && attr == QLatin1String("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { continue; } if (pgp()) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(QRegExp(regex)); } row = row_index_of(ui.nameLE, ui.gridLayout); } else if (attr == QLatin1String("COMMENT")) { if (!pgp()) { continue; } validator = regex.isEmpty() ? Validation::pgpComment() : Validation::pgpComment(QRegExp(regex)); row = row_index_of(ui.commentLE, ui.gridLayout); } else { known = false; row = add_row(ui.gridLayout, &dynamicWidgets); } if (!validator && !regex.isEmpty()) { validator = new QRegExpValidator(QRegExp(regex), 0); } QLineEdit *le = adjust_row(ui.gridLayout, row, label, preset, validator, readonly, required); const Line line = { key, label, regex, le }; lines[row] = line; if (!known) { widgets.push_back(le); } // don't connect twice: disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); } // create lineList in visual order, so requirementsAreMet() // complains from top to bottom: lineList.reserve(lines.count()); std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList)); widgets.push_back(ui.resultLE); widgets.push_back(ui.addEmailToDnCB); widgets.push_back(ui.advancedPB); set_tab_order(widgets); } QString EnterDetailsPage::cmsDN() const { DN dn; for (QVector::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) { const QString text = it->edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(it->attr); if (attr == QLatin1String("EMAIL") && !ui.addEmailToDnCB->isChecked()) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } QString EnterDetailsPage::pgpUserID() const { return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui.nameLE->text().trimmed(), ui.emailLE->text().trimmed(), ui.commentLE->text().trimmed()); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QVector &list, QString &error) { Q_FOREACH (const Line &line, list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << key << "\" against \"" << le->text() << "\":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is required, but empty.", line.label); } else error = xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is incomplete.", line.label); } else error = xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is invalid.", line.label); } else error = xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", line.label, line.regex); return false; } qCDebug(KLEOPATRA_LOG) << "ok"; } return true; } bool EnterDetailsPage::isComplete() const { QString error; const bool ok = requirementsAreMet(lineList, error); ui.errorLB->setText(error); return ok; } void EnterDetailsPage::slotAdvancedSettingsClicked() { dialog.exec(); } QStringList KeyCreationPage::keyUsages() const { QStringList usages; if (signingAllowed()) { usages << QStringLiteral("sign"); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usages << QStringLiteral("encrypt"); } if (0) // not needed in pgp (implied) and not supported in cms if (certificationAllowed()) { usages << QStringLiteral("certify"); } if (authenticationAllowed()) { usages << QStringLiteral("auth"); } return usages; } QStringList OverviewPage::i18nKeyUsages() const { QStringList usages; if (signingAllowed()) { usages << i18n("Sign"); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usages << i18n("Encrypt"); } if (0) // not needed in pgp (implied) and not supported in cms if (certificationAllowed()) { usages << i18n("Certify"); } if (authenticationAllowed()) { usages << i18n("Authenticate"); } return usages; } QStringList KeyCreationPage::subkeyUsages() const { QStringList usages; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { assert(subkeyType()); usages << QStringLiteral("encrypt"); } return usages; } QStringList OverviewPage::i18nSubkeyUsages() const { QStringList usages; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { assert(subkeyType()); usages << i18n("Encrypt"); } return usages; } QStringList OverviewPage::i18nCombinedKeyUsages() const { return i18nSubkeyUsages() + i18nKeyUsages(); } namespace { template struct Row { QString key; T value; Row(const QString &k, const T &v) : key(k), value(v) {} }; template QTextStream &operator<<(QTextStream &s, const Row &row) { if (row.key.isEmpty()) { return s; } else { return s << "" << row.key << "" << row.value << ""; } } } QString OverviewPage::i18nFormatGnupgKeyParms(bool details) const { QString result; QTextStream s(&result); s << ""; if (pgp()) { s << Row< >(i18n("Name:"), name()); } s << Row< >(i18n("Email Address:"), email()); if (pgp()) { if (!comment().isEmpty()) { s << Row< >(i18n("Comment:"), comment()); } } else { s << Row< >(i18n("Subject-DN:"), DN(dn()).dn(QStringLiteral(",
"))); } if (details) { s << Row< >(i18n("Key Type:"), QLatin1String(Subkey::publicKeyAlgorithmAsString(keyType()))); if (is_ecdsa(keyType())) { s << Row< >(i18n("Key Curve:"), keyCurve()); } else if (const unsigned int strength = keyStrength()) { s << Row< >(i18n("Key Strength:"), i18np("1 bit", "%1 bits", strength)); } else { s << Row< >(i18n("Key Strength:"), i18n("default")); } s << Row< >(i18n("Certificate Usage:"), i18nCombinedKeyUsages().join(i18nc("separator for key usages", ", "))); if (const Subkey::PubkeyAlgo subkey = subkeyType()) { s << Row< >(i18n("Subkey Type:"), QLatin1String(Subkey::publicKeyAlgorithmAsString(subkey))); if (is_ecdh(subkeyType())) { s << Row< >(i18n("Key Curve:"), subkeyCurve()); } else if (const unsigned int strength = subkeyStrength()) { s << Row< >(i18n("Subkey Strength:"), i18np("1 bit", "%1 bits", strength)); } else { s << Row< >(i18n("Subkey Strength:"), i18n("default")); } s << Row< >(i18n("Subkey Usage:"), i18nSubkeyUsages().join(i18nc("separator for key usages", ", "))); } } if (pgp() && details && expiryDate().isValid()) { s << Row< >(i18n("Valid Until:"), QLocale().toString(expiryDate())); } if (!pgp() && details) { Q_FOREACH (const QString &email, additionalEMailAddresses()) { s << Row< >(i18n("Add. Email Address:"), email); } Q_FOREACH (const QString &dns, dnsNames()) { s << Row< >(i18n("DNS Name:"), dns); } Q_FOREACH (const QString &uri, uris()) { s << Row< >(i18n("URI:"), uri); } } return result; } static QString encode_dns(const QString &dns) { return QLatin1String(QUrl::toAce(dns)); } static QString encode_email(const QString &email) { const int at = email.lastIndexOf(QLatin1Char('@')); if (at < 0) { return email; } return email.left(at + 1) + encode_dns(email.mid(at + 1)); } QString KeyCreationPage::createGnupgKeyParms() const { QString result; QTextStream s(&result); s << "" << endl; if (pgp()) { s << "%ask-passphrase" << endl; } s << "key-type: " << Subkey::publicKeyAlgorithmAsString(keyType()) << endl; if (is_ecdsa(keyType())) { s << "key-curve: " << keyCurve() << endl; } else if (const unsigned int strength = keyStrength()) { s << "key-length: " << strength << endl; } - s << "key-usage: " << keyUsages().join(QStringLiteral(" ")) << endl; + s << "key-usage: " << keyUsages().join(QLatin1Char(' ')) << endl; if (const Subkey::PubkeyAlgo subkey = subkeyType()) { s << "subkey-type: " << Subkey::publicKeyAlgorithmAsString(subkey) << endl; if (is_ecdh(subkeyType())) { s << "subkey-curve: " << subkeyCurve() << endl; } else if (const unsigned int strength = subkeyStrength()) { s << "subkey-length: " << strength << endl; } - s << "subkey-usage: " << subkeyUsages().join(QStringLiteral(" ")) << endl; + s << "subkey-usage: " << subkeyUsages().join(QLatin1Char(' ')) << endl; } if (pgp() && expiryDate().isValid()) { s << "expire-date: " << expiryDate().toString(Qt::ISODate) << endl; } if (pgp()) { s << "name-real: " << name() << endl; s << "name-email: " << email() << endl; if (!comment().isEmpty()) { s << "name-comment: " << comment() << endl; } } else { s << "name-dn: " << dn() << endl; s << "name-email: " << encode_email(email()) << endl; Q_FOREACH (const QString &email, additionalEMailAddresses()) { s << "name-email: " << encode_email(email) << endl; } Q_FOREACH (const QString &dns, dnsNames()) { s << "name-dns: " << encode_dns(dns) << endl; } Q_FOREACH (const QString &uri, uris()) { s << "name-uri: " << uri << endl; } } s << "" << endl; qCDebug(KLEOPATRA_LOG) << '\n' << result; return result; } static void fill_combobox(QComboBox &cb, const QList &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? sizes[i] < 0 ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit; default)", "%2 (%1 bits; default)", -sizes[i], labels[i].trimmed()) : i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", sizes[i], labels[i].trimmed()) : sizes[i] < 0 ? i18ncp("%1: key size in bits", "1 bit (default)", "%1 bits (default)", -sizes[i]) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", sizes[i]), std::abs(sizes[i])); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QList rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << 1536 << -2048 << 3072 << 4096); const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << 1536 << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui.rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui.dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui.elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); ui.ecdhKeyCurvesCB->addItems(curveNames); ui.ecdsaKeyCurvesCB->addItems(curveNames); } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), "CertificateCreationWizard"); const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); } else { if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); updateWidgetVisibility(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui.tabWidget->indexOf(ui.personalTab) != -1) { ui.tabWidget->removeTab(ui.tabWidget->indexOf(ui.personalTab)); } } else { if (ui.tabWidget->indexOf(ui.personalTab) == -1) { ui.tabWidget->addTab(ui.personalTab, tr2i18n("Personal Details", 0)); } } ui.uidGB->setVisible(protocol == OpenPGP); ui.uidGB->setEnabled(false); ui.uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one User ID is not yet implemented.")); ui.emailGB->setVisible(protocol == CMS); ui.dnsGB->setVisible(protocol == CMS); ui.uriGB->setVisible(protocol == CMS); ui.ecdhCB->setVisible(mECCSupported); ui.ecdhKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaKeyCurvesCB->setVisible(mECCSupported); ui.ecdsaRB->setVisible(mECCSupported); // Technical Details Page if (keyTypeImmutable) { ui.rsaRB->setEnabled(false); ui.rsaSubCB->setEnabled(false); ui.dsaRB->setEnabled(false); ui.elgCB->setEnabled(false); ui.ecdsaRB->setEnabled(false); ui.ecdhCB->setEnabled(false); } else { ui.rsaRB->setEnabled(true); ui.rsaSubCB->setEnabled(protocol == OpenPGP); ui.dsaRB->setEnabled(protocol == OpenPGP); ui.elgCB->setEnabled(protocol == OpenPGP); ui.ecdsaRB->setEnabled(protocol == OpenPGP); ui.ecdhCB->setEnabled(protocol == OpenPGP); } ui.certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui.authenticationCB->setVisible(protocol == OpenPGP); if (protocol == OpenPGP) { // pgp keys must have certify capability ui.certificationCB->setChecked(true); ui.certificationCB->setEnabled(false); } if (protocol == CMS) { ui.encryptionCB->setEnabled(true); ui.rsaSubCB->setChecked(false); } ui.expiryDE->setVisible(protocol == OpenPGP); ui.expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } #include "newcertificatewizard.moc" diff --git a/src/selftest/gpgconfcheck.cpp b/src/selftest/gpgconfcheck.cpp index 0283063a..6ecc7709 100644 --- a/src/selftest/gpgconfcheck.cpp +++ b/src/selftest/gpgconfcheck.cpp @@ -1,164 +1,164 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/gpgconfcheck.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "gpgconfcheck.h" #include "implementation_p.h" #include #include #include "kleopatra_debug.h" #include #include #include #include using namespace Kleo; using namespace Kleo::_detail; namespace { class GpgConfCheck : public SelfTestImplementation { QString m_component; public: explicit GpgConfCheck(const char *component) : SelfTestImplementation(i18nc("@title", "%1 Configuration Check", component && * component ? QLatin1String(component) : QLatin1String("gpgconf"))), m_component(QLatin1String(component)) { runTest(); } QStringList arguments() const { if (m_component.isEmpty()) { return QStringList() << QStringLiteral("--check-config"); } else { return QStringList() << QStringLiteral("--check-options") << m_component; } } bool canRun() { if (!ensureEngineVersion(GpgME::GpgConfEngine, 2, 0, 10)) { return false; } if (!m_component.isEmpty()) { return true; } QProcess gpgconf; gpgconf.setReadChannel(QProcess::StandardOutput); gpgconf.start(gpgConfPath(), QStringList() << QStringLiteral("--list-dirs"), QIODevice::ReadOnly); gpgconf.waitForFinished(); if (gpgconf.exitStatus() != QProcess::NormalExit || gpgconf.exitCode() != 0) { qCDebug(KLEOPATRA_LOG) << "GpgConfCheck: \"gpgconf --list-dirs\" gives error, disabling"; return false; } const QList lines = gpgconf.readAll().split('\n'); Q_FOREACH (const QByteArray &line, lines) if (line.startsWith("sysconfdir:")) //krazy:exclude=strings try { return QDir(QFile::decodeName(hexdecode(line.mid(strlen("sysconfdir:"))))).exists(QStringLiteral("gpgconf.conf")); } catch (...) { return false; } qCDebug(KLEOPATRA_LOG) << "GpgConfCheck: \"gpgconf --list-dirs\" has no sysconfdir entry"; return false; } void runTest() { if (!canRun()) { if (!m_skipped) { m_passed = true; } return; } QProcess process; process.setProcessChannelMode(QProcess::MergedChannels); process.start(gpgConfPath(), arguments(), QIODevice::ReadOnly); process.waitForFinished(); const QString output = QString::fromUtf8(process.readAll()); const QString message = process.exitStatus() == QProcess::CrashExit ? i18n("The process terminated prematurely") : process.errorString(); if (process.exitStatus() != QProcess::NormalExit || process.error() != QProcess::UnknownError) { m_passed = false; m_error = i18nc("self-test did not pass", "Failed"); m_explaination = i18n("There was an error executing the GnuPG configuration self-check for %2:\n" " %1\n" "You might want to execute \"gpgconf %3\" on the command line.\n", - message, m_component.isEmpty() ? QStringLiteral("GnuPG") : m_component, arguments().join(QStringLiteral(" "))); + message, m_component.isEmpty() ? QStringLiteral("GnuPG") : m_component, arguments().join(QLatin1Char(' '))); if (!output.trimmed().isEmpty()) { m_explaination += QLatin1Char('\n') + i18n("Diagnostics:") + QLatin1Char('\n') + output; } m_proposedFix.clear(); } else if (process.exitCode()) { m_passed = false; m_error = i18nc("self-check did not pass", "Failed"); m_explaination = !output.trimmed().isEmpty() ? i18nc("Self-test did not pass", "The GnuPG configuration self-check failed.\n" "\n" "Error code: %1\n" "Diagnostics:", process.exitCode()) + QLatin1Char('\n') + output : i18nc("self-check did not pass", "The GnuPG configuration self-check failed with error code %1.\n" "No output was received.", process.exitCode()); m_proposedFix.clear(); } else { m_passed = true; } } }; } std::shared_ptr Kleo::makeGpgConfCheckConfigurationSelfTest(const char *component) { return std::shared_ptr(new GpgConfCheck(component)); } diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp index 79ec4901..0ad37fd5 100644 --- a/src/uiserver/assuanserverconnection.cpp +++ b/src/uiserver/assuanserverconnection.cpp @@ -1,1716 +1,1716 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/assuanserverconnection.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef QT_NO_CAST_TO_ASCII # define QT_NO_CAST_TO_ASCII #endif #ifndef QT_NO_CAST_FROM_ASCII # define QT_NO_CAST_FROM_ASCII #endif #include #include #include "assuanserverconnection.h" #include "assuancommand.h" #include "sessiondata.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef __GLIBCXX__ # include // for is_sorted #endif #ifdef Q_OS_WIN32 # include # include #else # include # include #endif using namespace Kleo; static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan... //static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0; static const int FOR_READING = 0; static const unsigned int MAX_ACTIVE_FDS = 32; #ifdef HAVE_ASSUAN2 static void my_assuan_release(assuan_context_t ctx) { if (ctx) { assuan_release(ctx); } } #endif // std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server: typedef std::shared_ptr::type> AssuanContextBase; struct AssuanContext : AssuanContextBase { AssuanContext() : AssuanContextBase() {} #ifndef HAVE_ASSUAN2 explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &assuan_deinit_server) {} #else explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) {} #endif #ifndef HAVE_ASSUAN2 void reset(assuan_context_t ctx = 0) { AssuanContextBase::reset(ctx, &assuan_deinit_server); } #else void reset(assuan_context_t ctx = 0) { AssuanContextBase::reset(ctx, &my_assuan_release); } #endif }; static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg) { return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg)); } static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const std::string &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.c_str()); } static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData()); } static std::map upcase_option(const char *option, std::map options) { std::string value; bool value_found = false; std::map::iterator it = options.begin(); while (it != options.end()) if (qstricmp(it->first.c_str(), option) == 0) { value = it->second; options.erase(it++); value_found = true; } else { ++it; } if (value_found) { options[option] = value; } return options; } static std::map parse_commandline(const char *line) { std::map result; if (line) { const char *begin = line; const char *lastEQ = nullptr; while (*line) { if (*line == ' ' || *line == '\t') { if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[ std::string(begin, line - begin) ] = std::string(); } } begin = line + 1; } else if (*line == '=') { if (line == begin) throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("No option name given")); else { lastEQ = line; } } ++line; } if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[ std::string(begin, lastEQ - begin) ] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[ begin ] = std::string(); } } } return result; } static WId wid_from_string(const QString &winIdStr, bool *ok = 0) { return static_cast(winIdStr.toULongLong(ok, 16)); } static void apply_window_id(QWidget *widget, const QString &winIdStr) { if (!widget || winIdStr.isEmpty()) { return; } bool ok = false; const WId wid = wid_from_string(winIdStr, &ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number"; return; } if (QWidget *pw = QWidget::find(wid)) { widget->setParent(pw, widget->windowFlags()); } else { KWindowSystem::setMainWindow(widget, wid); } } // // // AssuanServerConnection: // // class AssuanServerConnection::Private : public QObject { Q_OBJECT friend class ::Kleo::AssuanServerConnection; friend class ::Kleo::AssuanCommandFactory; friend class ::Kleo::AssuanCommand; AssuanServerConnection *const q; public: Private(assuan_fd_t fd_, const std::vector< std::shared_ptr > &factories_, AssuanServerConnection *qq); ~Private(); Q_SIGNALS: void startKeyManager(); public Q_SLOTS: void slotReadActivity(int) { assert(ctx); #ifndef HAVE_ASSUAN2 if (const int err = assuan_process_next(ctx.get())) { #else int done = false; if (const int err = assuan_process_next(ctx.get(), &done) || done) { #endif //if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) { topHalfDeletion(); if (nohupedCommands.empty()) { bottomHalfDeletion(); } //} else { //assuan_process_done( ctx.get(), err ); //return; //} } } int startCommandBottomHalf(); private: void nohupDone(AssuanCommand *cmd) { const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(), [cmd](const std::shared_ptr &other) { return other.get() == cmd; }); assert(it != nohupedCommands.end()); nohupedCommands.erase(it); if (nohupedCommands.empty() && closed) { bottomHalfDeletion(); } } void commandDone(AssuanCommand *cmd) { if (!cmd || cmd != currentCommand.get()) { return; } currentCommand.reset(); } void topHalfDeletion() { if (currentCommand) { currentCommand->canceled(); } if (fd != ASSUAN_INVALID_FD) { #if defined(Q_OS_WIN32) CloseHandle(fd); #else ::close(fd); #endif } notifiers.clear(); closed = true; } void bottomHalfDeletion() { if (sessionId) { SessionDataHandler::instance()->exitSession(sessionId); } cleanup(); const QPointer that = this; Q_EMIT q->closed(q); if (that) { // still there q->deleteLater(); } } private: #ifndef HAVE_ASSUAN2 static void reset_handler(assuan_context_t ctx_) { #else static gpg_error_t reset_handler(assuan_context_t ctx_, char *) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); conn.reset(); #ifdef HAVE_ASSUAN2 return 0; #endif } #ifndef HAVE_ASSUAN2 static int option_handler(assuan_context_t ctx_, const char *key, const char *value) { #else static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (key && key[0] == '-' && key[1] == '-') { key += 2; // skip "--" } conn.options[key] = QString::fromUtf8(value); return 0; //return gpg_error( GPG_ERR_UNKNOWN_OPTION ); } #ifndef HAVE_ASSUAN2 static int session_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t session_handler(assuan_context_t ctx_, char *line) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); const QString str = QString::fromUtf8(line); QRegExp rx(QLatin1String("(\\d+)(?:\\s+(.*))?")); if (!rx.exactMatch(str)) { static const QString errorString = i18n("Parse error"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } bool ok = false; if (const qulonglong id = rx.cap(1).toULongLong(&ok)) { if (ok && id <= std::numeric_limits::max()) { SessionDataHandler::instance()->enterSession(id); conn.sessionId = id; } else { static const QString errorString = i18n("Parse error: numeric session id too large"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } } if (!rx.cap(2).isEmpty()) { conn.sessionTitle = rx.cap(2); } qCDebug(KLEOPATRA_LOG) << "session_handler: " << "id=" << static_cast(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle); return assuan_process_done(ctx_, 0); } #ifndef HAVE_ASSUAN2 static int capabilities_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line) { #endif if (!QByteArray(line).trimmed().isEmpty()) { static const QString errorString = i18n("CAPABILITIES does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } static const char capabilities[] = "SENDER=info\n" "RECIPIENT=info\n" "SESSION\n" ; return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1)); } #ifndef HAVE_ASSUAN2 static int getinfo_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (qstrcmp(line, "version") == 0) { static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING; return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1)); } QByteArray ba; if (qstrcmp(line, "pid") == 0) { ba = QByteArray::number(mygetpid()); } else if (qstrcmp(line, "options") == 0) { ba = conn.dumpOptions(); } else if (qstrcmp(line, "x-mementos") == 0) { ba = conn.dumpMementos(); } else if (qstrcmp(line, "senders") == 0) { ba = conn.dumpSenders(); } else if (qstrcmp(line, "recipients") == 0) { ba = conn.dumpRecipients(); } else if (qstrcmp(line, "x-files") == 0) { ba = conn.dumpFiles(); } else { static const QString errorString = i18n("Unknown value for WHAT"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size())); } #ifndef HAVE_ASSUAN2 static int start_keymanager_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_KEYMANAGER does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startKeyManagerRequested(); return assuan_process_done(ctx_, 0); } #ifndef HAVE_ASSUAN2 static int start_confdialog_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_CONFDIALOG does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startConfigDialogRequested(); return assuan_process_done(ctx_, 0); } template struct Input_or_Output : std::conditional {}; // format: TAG (FD|FD=\d+|FILE=...) template #ifndef HAVE_ASSUAN2 static int IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { #else static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); char *binOpt = strstr(line_, "--binary"); if (binOpt && !in) { /* Note there is also --armor and --base64 allowed but we don't need * to parse those because they are default. * We remove it here so that it is not parsed as an Option.*/ memset(binOpt, ' ', 8); } try { /*const*/ std::map options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_))); if (options.size() < 1 || options.size() > 2) { throw gpg_error(GPG_ERR_ASS_SYNTAX); } std::shared_ptr< typename Input_or_Output::type > io; if (options.count("FD")) { if (options.count("FILE")) { throw gpg_error(GPG_ERR_CONFLICT); } assuan_fd_t fd = ASSUAN_INVALID_FD; const std::string fdstr = options["FD"]; if (fdstr.empty()) { if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) { throw err; } } else { #if defined(Q_OS_WIN32) fd = (assuan_fd_t)std::stoi(fdstr); #else fd = std::stoi(fdstr); #endif } io = Input_or_Output::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString()); options.erase("FD"); } else if (options.count("FILE")) { if (options.count("FD")) { throw gpg_error(GPG_ERR_CONFLICT); } const QString filePath = QFile::decodeName(options["FILE"].c_str()); if (filePath.isEmpty()) { throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path")); } const QFileInfo fi(filePath); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.isFile()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE")); } else { io = Input_or_Output::type::createFromFile(fi.absoluteFilePath(), true); } options.erase("FILE"); } else { throw gpg_error(GPG_ERR_ASS_PARAMETER); } if (options.size()) { throw gpg_error(GPG_ERR_UNKNOWN_OPTION); } (conn.*which).push_back(io); if (binOpt && !in) { Output *out = reinterpret_cast (io.get()); out->setBinaryOpt(true); qCDebug(KLEOPATRA_LOG) << "Configured output for binary data"; } qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label(); return assuan_process_done(conn.ctx.get(), 0); } catch (const GpgME::Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str()); } catch (const std::exception &) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX)); } catch (const gpg_error_t e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught"); } } #ifndef HAVE_ASSUAN2 static int input_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t input_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::inputs); } #ifndef HAVE_ASSUAN2 static int output_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t output_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::outputs); } #ifndef HAVE_ASSUAN2 static int message_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t message_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::messages); } #ifndef HAVE_ASSUAN2 static int file_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t file_handler(assuan_context_t ctx_, char *line) { #endif assert(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); try { const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str())); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.exists()) { throw gpg_error(GPG_ERR_ENOENT); } if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) { throw gpg_error(GPG_ERR_EPERM); } conn.files.push_back(fi.absoluteFilePath()); return assuan_process_done(conn.ctx.get(), 0); } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData()); } catch (const gpg_error_t e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData()); } } static bool parse_informative(const char *&begin, GpgME::Protocol &protocol) { protocol = GpgME::UnknownProtocol; bool informative = false; const char *pos = begin; while (true) { while (*pos == ' ' || *pos == '\t') { ++pos; } if (qstrnicmp(pos, "--info", strlen("--info")) == 0) { informative = true; pos += strlen("--info"); if (*pos == '=') { ++pos; break; } } else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) { pos += strlen("--protocol="); if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) { protocol = GpgME::OpenPGP; pos += strlen("OpenPGP"); } else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) { protocol = GpgME::CMS; pos += strlen("CMS"); } else { ; } } else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) { pos += 3; while (*pos == ' ' || *pos == '\t') { ++pos; } break; } else { break; } } begin = pos; return informative; } template #ifndef HAVE_ASSUAN2 static int recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { #else static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { #endif assert(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); if (!line || !*line) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG)); } const char *begin = line; const char *const end = begin + qstrlen(line); GpgME::Protocol proto = GpgME::UnknownProtocol; const bool informative = parse_informative(begin, proto); if (!(conn.*mp).empty() && informative != (conn.*info)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_CONFLICT), i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData()); KMime::Types::Mailbox mb; if (!KMime::HeaderParsing::parseMailbox(begin, end, mb)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData()); if (begin != end) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData()); (conn.*info) = informative; (conn.*mp).push_back(mb); const QString email = mb.addrSpec().asString(); (void)assuan_write_line(conn.ctx.get(), qPrintable(QString().sprintf("# ok, parsed as \"%s\"", qPrintable(email)))); if (sender && !informative) { return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN"); } else { return assuan_process_done(ctx, 0); } } #ifndef HAVE_ASSUAN2 static int recipient_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t recipient_handler(assuan_context_t ctx, char *line) { #endif return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line); } #ifndef HAVE_ASSUAN2 static int sender_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t sender_handler(assuan_context_t ctx, char *line) { #endif return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true); } QByteArray dumpOptions() const { QByteArray result; for (std::map::const_iterator it = options.begin(), end = options.end(); it != end; ++it) { result += it->first.c_str() + it->second.toString().toUtf8() + '\n'; } return result; } static QByteArray dumpStringList(const QStringList &sl) { - return sl.join(QStringLiteral("\n")).toUtf8(); + return sl.join(QLatin1Char('\n')).toUtf8(); } template static QByteArray dumpStringList(const T_container &c) { QStringList sl; std::copy(c.begin(), c.end(), std::back_inserter(sl)); return dumpStringList(sl); } template static QByteArray dumpMailboxes(const T_container &c) { QStringList sl; std::transform(c.begin(), c.end(), std::back_inserter(sl), [](typename T_container::const_reference val) { return val.prettyAddress(); }); return dumpStringList(sl); } QByteArray dumpSenders() const { return dumpMailboxes(senders); } QByteArray dumpRecipients() const { return dumpMailboxes(recipients); } QByteArray dumpMementos() const { QByteArray result; for (std::map< QByteArray, std::shared_ptr >::const_iterator it = mementos.begin(), end = mementos.end(); it != end; ++it) { char buf[2 + 2 * sizeof(void *) + 2]; sprintf(buf, "0x%p\n", (void *)it->second.get()); buf[sizeof(buf) - 1] = '\0'; result += it->first + QByteArray::fromRawData(buf, sizeof buf); } return result; } QByteArray dumpFiles() const { QStringList rv; rv.reserve(files.size()); std::copy(files.cbegin(), files.cend(), std::back_inserter(rv)); return dumpStringList(rv); } void cleanup(); void reset() { options.clear(); senders.clear(); informativeSenders = false; recipients.clear(); informativeRecipients = false; sessionTitle.clear(); sessionId = 0; mementos.clear(); files.clear(); std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize)); inputs.clear(); std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize)); outputs.clear(); std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize)); messages.clear(); bias = GpgME::UnknownProtocol; } assuan_fd_t fd; AssuanContext ctx; bool closed : 1; bool cryptoCommandsEnabled : 1; bool commandWaitingForCryptoCommandsEnabled : 1; bool currentCommandIsNohup : 1; bool informativeSenders; // address taken, so no : 1 bool informativeRecipients; // address taken, so no : 1 GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; std::vector< std::shared_ptr > notifiers; std::vector< std::shared_ptr > factories; // sorted: _detail::ByName std::shared_ptr currentCommand; std::vector< std::shared_ptr > nohupedCommands; std::map options; std::vector senders, recipients; std::vector< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > outputs; std::vector files; std::map< QByteArray, std::shared_ptr > mementos; }; void AssuanServerConnection::Private::cleanup() { assert(nohupedCommands.empty()); reset(); currentCommand.reset(); currentCommandIsNohup = false; commandWaitingForCryptoCommandsEnabled = false; notifiers.clear(); ctx.reset(); fd = ASSUAN_INVALID_FD; } AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector< std::shared_ptr > &factories_, AssuanServerConnection *qq) : QObject(), q(qq), fd(fd_), closed(false), cryptoCommandsEnabled(false), commandWaitingForCryptoCommandsEnabled(false), currentCommandIsNohup(false), informativeSenders(false), informativeRecipients(false), bias(GpgME::UnknownProtocol), sessionId(0), factories(factories_) { #ifdef __GLIBCXX__ assert(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName())); #endif if (fd == ASSUAN_INVALID_FD) { throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext"); } #ifndef HAVE_ASSUAN2 assuan_context_t naked_ctx = 0; if (const gpg_error_t err = assuan_init_socket_server_ext(&naked_ctx, fd, INIT_SOCKET_FLAGS)) #else { assuan_context_t naked_ctx = 0; if (const gpg_error_t err = assuan_new(&naked_ctx)) { throw Exception(err, "assuan_new"); } ctx.reset(naked_ctx); } if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS)) #endif throw Exception(err, "assuan_init_socket_server_ext"); #ifndef HAVE_ASSUAN2 ctx.reset(naked_ctx); naked_ctx = 0; #endif // for callbacks, associate the context with this connection: assuan_set_pointer(ctx.get(), this); FILE *const logFile = Log::instance()->logFile(); assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr); // register FDs with the event loop: assuan_fd_t fds[MAX_ACTIVE_FDS]; const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS); assert(numFDs != -1); // == 1 if (!numFDs || fds[0] != fd) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } notifiers.reserve(notifiers.size() + numFDs); for (int i = 0; i < numFDs; ++i) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } // register our INPUT/OUTPUT/MESSGAE/FILE handlers: #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, "")) #endif throw Exception(err, "register \"INPUT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, "")) #endif throw Exception(err, "register \"MESSAGE\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, "")) #endif throw Exception(err, "register \"OUTPUT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, "")) #endif throw Exception(err, "register \"FILE\" handler"); // register user-defined commands: Q_FOREACH (std::shared_ptr fac, factories) #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler())) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), "")) #endif throw Exception(err, std::string("register \"") + fac->name() + "\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, "")) #endif throw Exception(err, "register \"GETINFO\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, "")) #endif throw Exception(err, "register \"START_KEYMANAGER\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, "")) #endif throw Exception(err, "register \"START_CONFDIALOG\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, "")) #endif throw Exception(err, "register \"RECIPIENT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, "")) #endif throw Exception(err, "register \"SENDER\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, "")) #endif throw Exception(err, "register \"SESSION\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, "")) #endif throw Exception(err, "register \"CAPABILITIES\" handler"); assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve"); //assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" ) // some notifiers we're interested in: if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) { throw Exception(err, "register reset notify"); } if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) { throw Exception(err, "register option handler"); } // and last, we need to call assuan_accept, which doesn't block // (d/t INIT_SOCKET_FLAGS), but performs vital connection // establishing handling: if (const gpg_error_t err = assuan_accept(ctx.get())) { throw Exception(err, "assuan_accept"); } } AssuanServerConnection::Private::~Private() { cleanup(); } AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector< std::shared_ptr > &factories, QObject *p) : QObject(p), d(new Private(fd, factories, this)) { } AssuanServerConnection::~AssuanServerConnection() {} void AssuanServerConnection::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; if (d->commandWaitingForCryptoCommandsEnabled) { QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf); } } // // // AssuanCommand: // // namespace Kleo { class InquiryHandler : public QObject { Q_OBJECT public: #if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) explicit InquiryHandler(const char *keyword_, QObject *p = 0) : QObject(p), # if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) buffer(0), buflen(0), # endif keyword(keyword_) { } # if defined(HAVE_ASSUAN2) || defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) # ifndef HAVE_ASSUAN2 static int handler(void *cb_data, int rc, unsigned char *buffer, size_t buflen) # else static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen) # endif { assert(cb_data); InquiryHandler *this_ = static_cast(cb_data); Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(buffer), buflen), this_->keyword); std::free(buffer); delete this_; return 0; } # else static int handler(void *cb_data, int rc) { assert(cb_data); InquiryHandler *this_ = static_cast(cb_data); Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(this_->buffer), this_->buflen), this_->keyword); std::free(this_->buffer); delete this_; return 0; } # endif private: #if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) friend class ::Kleo::AssuanCommand; unsigned char *buffer; size_t buflen; #endif const char *keyword; #endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) Q_SIGNALS: void signal(int rc, const QByteArray &data, const QByteArray &keyword); }; } // namespace Kleo class AssuanCommand::Private { public: Private() : informativeRecipients(false), informativeSenders(false), bias(GpgME::UnknownProtocol), done(false), nohup(false) { } std::map options; std::vector< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > outputs; std::vector files; std::vector recipients, senders; bool informativeRecipients, informativeSenders; GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; QByteArray utf8ErrorKeepAlive; AssuanContext ctx; bool done; bool nohup; }; AssuanCommand::AssuanCommand() : d(new Private) { } AssuanCommand::~AssuanCommand() { } int AssuanCommand::start() { try { if (const int err = doStart()) if (!d->done) { done(err); } return 0; } catch (const Exception &e) { if (!d->done) { done(e.error_code(), e.message()); } return 0; } catch (const GpgME::Exception &e) { if (!d->done) { done(e.error(), QString::fromLocal8Bit(e.message().c_str())); } return 0; } catch (const std::exception &e) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what()))); } return 0; } catch (...) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers.")); } return 0; } } void AssuanCommand::canceled() { d->done = true; doCanceled(); } // static int AssuanCommand::makeError(int code) { return makeGnuPGError(code); } bool AssuanCommand::hasOption(const char *opt) const { return d->options.count(opt); } QVariant AssuanCommand::option(const char *opt) const { const std::map::const_iterator it = d->options.find(opt); if (it == d->options.end()) { return QVariant(); } else { return it->second; } } const std::map &AssuanCommand::options() const { return d->options; } namespace { template std::vector keys(const std::map &map) { std::vector result; result.resize(map.size()); for (typename std::map::const_iterator it = map.begin(), end = map.end(); it != end; ++it) { result.push_back(it->first); } return result; } } const std::map< QByteArray, std::shared_ptr > &AssuanCommand::mementos() const { // oh, hack :( assert(assuan_get_pointer(d->ctx.get())); const AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); return conn.mementos; } bool AssuanCommand::hasMemento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag); } else { return mementos().count(tag); } } std::shared_ptr AssuanCommand::memento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { const std::shared_ptr sdh = SessionDataHandler::instance(); const std::shared_ptr sd = sdh->sessionData(id); const std::map< QByteArray, std::shared_ptr >::const_iterator it = sd->mementos.find(tag); if (it != sd->mementos.end()) { return it->second; } } const std::map< QByteArray, std::shared_ptr >::const_iterator it = mementos().find(tag); if (it == mementos().end()) { return std::shared_ptr(); } else { return it->second; } } QByteArray AssuanCommand::registerMemento(const std::shared_ptr &mem) { const QByteArray tag = QByteArray::number(reinterpret_cast(mem.get()), 36); return registerMemento(tag, mem); } QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr &mem) { // oh, hack :( assert(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem; } else { conn.mementos[tag] = mem; } return tag; } void AssuanCommand::removeMemento(const QByteArray &tag) { // oh, hack :( assert(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); conn.mementos.erase(tag); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag); } } const std::vector< std::shared_ptr > &AssuanCommand::inputs() const { return d->inputs; } const std::vector< std::shared_ptr > &AssuanCommand::messages() const { return d->messages; } const std::vector< std::shared_ptr > &AssuanCommand::outputs() const { return d->outputs; } QStringList AssuanCommand::fileNames() const { QStringList rv; rv.reserve(d->files.size()); std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv)); return rv; } unsigned int AssuanCommand::numFiles() const { return d->files.size(); } void AssuanCommand::sendStatus(const char *keyword, const QString &text) { sendStatusEncoded(keyword, text.toUtf8().constData()); } void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text) { if (d->nohup) { return; } if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) { throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword))); } } void AssuanCommand::sendData(const QByteArray &data, bool moreToCome) { if (d->nohup) { return; } if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) { throw Exception(err, i18n("Cannot send data")); } if (!moreToCome) if (const gpg_error_t err = assuan_send_data(d->ctx.get(), 0, 0)) { // flush throw Exception(err, i18n("Cannot flush data")); } } int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize) { assert(keyword); assert(receiver); assert(slot); if (d->nohup) { return makeError(GPG_ERR_INV_OP); } #if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) std::unique_ptr ih(new InquiryHandler(keyword, receiver)); receiver->connect(ih.get(), SIGNAL(signal(int,QByteArray,QByteArray)), slot); if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword, # if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) &ih->buffer, &ih->buflen, # endif maxSize, InquiryHandler::handler, ih.get())) { return err; } ih.release(); return 0; #else return makeError(GPG_ERR_NOT_SUPPORTED); // libassuan too old #endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) } void AssuanCommand::done(const GpgME::Error &err, const QString &details) { if (d->ctx && !d->done && !details.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Error: " << details; d->utf8ErrorKeepAlive = details.toUtf8(); if (!d->nohup) { assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData()); } } done(err); } void AssuanCommand::done(const GpgME::Error &err) { if (!d->ctx) { qCDebug(KLEOPATRA_LOG) << err.asString() << ": called with NULL ctx."; return; } if (d->done) { qCDebug(KLEOPATRA_LOG) << err.asString() << ": called twice!"; return; } d->done = true; std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize)); std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize)); std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize)); d->messages.clear(); d->inputs.clear(); d->outputs.clear(); d->files.clear(); // oh, hack :( assert(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (d->nohup) { conn.nohupDone(this); return; } const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError()); if (gpg_err_code(rc) != GPG_ERR_NO_ERROR) qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)", static_cast(rc), gpg_strerror(rc)); d->utf8ErrorKeepAlive.clear(); conn.commandDone(this); } void AssuanCommand::setNohup(bool nohup) { d->nohup = nohup; } bool AssuanCommand::isNohup() const { return d->nohup; } bool AssuanCommand::isDone() const { return d->done; } QString AssuanCommand::sessionTitle() const { return d->sessionTitle; } unsigned int AssuanCommand::sessionId() const { return d->sessionId; } bool AssuanCommand::informativeSenders() const { return d->informativeSenders; } bool AssuanCommand::informativeRecipients() const { return d->informativeRecipients; } const std::vector &AssuanCommand::recipients() const { return d->recipients; } const std::vector &AssuanCommand::senders() const { return d->senders; } #ifndef HAVE_ASSUAN2 int AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { #else gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { #endif assert(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); try { const std::vector< std::shared_ptr >::const_iterator it = std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName()); kleo_assert(it != conn.factories.end()); kleo_assert(*it); kleo_assert(qstricmp((*it)->name(), commandName) == 0); const std::shared_ptr cmd = (*it)->create(); kleo_assert(cmd); cmd->d->ctx = conn.ctx; cmd->d->options = conn.options; cmd->d->inputs.swap(conn.inputs); kleo_assert(conn.inputs.empty()); cmd->d->messages.swap(conn.messages); kleo_assert(conn.messages.empty()); cmd->d->outputs.swap(conn.outputs); kleo_assert(conn.outputs.empty()); cmd->d->files.swap(conn.files); kleo_assert(conn.files.empty()); cmd->d->senders.swap(conn.senders); kleo_assert(conn.senders.empty()); cmd->d->recipients.swap(conn.recipients); kleo_assert(conn.recipients.empty()); cmd->d->informativeRecipients = conn.informativeRecipients; cmd->d->informativeSenders = conn.informativeSenders; cmd->d->bias = conn.bias; cmd->d->sessionTitle = conn.sessionTitle; cmd->d->sessionId = conn.sessionId; const std::map cmdline_options = parse_commandline(line); for (std::map::const_iterator it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) { cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str()); } bool nohup = false; if (cmd->d->options.count("nohup")) { if (!cmd->d->options["nohup"].toString().isEmpty()) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument"); } nohup = true; cmd->d->options.erase("nohup"); } conn.currentCommand = cmd; conn.currentCommandIsNohup = nohup; QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf); return 0; } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } int AssuanServerConnection::Private::startCommandBottomHalf() { commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled; if (!cryptoCommandsEnabled) { return 0; } const std::shared_ptr cmd = currentCommand; if (!cmd) { return 0; } currentCommand.reset(); const bool nohup = currentCommandIsNohup; currentCommandIsNohup = false; try { if (const int err = cmd->start()) { if (cmd->isDone()) { return err; } else { return assuan_process_done(ctx.get(), err); } } if (cmd->isDone()) { return 0; } if (nohup) { cmd->setNohup(true); nohupedCommands.push_back(cmd); return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end."); } else { currentCommand = cmd; return 0; } } catch (const Exception &e) { return assuan_process_done_msg(ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } // // // AssuanCommand convenience methods // // /*! Checks the \c --mode parameter. \returns The parameter as an AssuanCommand::Mode enum value. If no \c --mode was given, or it's value wasn't recognized, throws an Kleo::Exception. */ AssuanCommand::Mode AssuanCommand::checkMode() const { if (!hasOption("mode")) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing")); } const QString modeString = option("mode").toString().toLower(); if (modeString == QLatin1String("filemanager")) { return FileManager; } if (modeString == QLatin1String("email")) { return EMail; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString)); } /*! Checks the \c --protocol parameter. \returns The parameter as a GpgME::Protocol enum value. If \c --protocol was given, but has an invalid value, throws an Kleo::Exception. If no \c --protocol was given, checks the connection bias, if available, otherwise, in FileManager mode, returns GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an Kleo::Exception instead. */ GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const { if (!hasOption("protocol")) if (d->bias != GpgME::UnknownProtocol) { return d->bias; } else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing")); } else { return GpgME::UnknownProtocol; } else if (mode == AssuanCommand::FileManager) { throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here")); } const QString protocolString = option("protocol").toString().toLower(); if (protocolString == QLatin1String("openpgp")) { return GpgME::OpenPGP; } if (protocolString == QLatin1String("cms")) { return GpgME::CMS; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString)); } void AssuanCommand::doApplyWindowID(QWidget *widget) const { if (!widget || !hasOption("window-id")) { return; } apply_window_id(widget, option("window-id").toString()); } WId AssuanCommand::parentWId() const { return wid_from_string(option("window-id").toString()); } #include "assuanserverconnection.moc" diff --git a/src/utils/archivedefinition.cpp b/src/utils/archivedefinition.cpp index d8e20b21..86a2f46d 100644 --- a/src/utils/archivedefinition.cpp +++ b/src/utils/archivedefinition.cpp @@ -1,418 +1,418 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/archivedefinition.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2009, 2010 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "archivedefinition.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; static QMutex installPathMutex; Q_GLOBAL_STATIC(QString, _installPath) QString ArchiveDefinition::installPath() { const QMutexLocker locker(&installPathMutex); QString *const ip = _installPath(); if (ip->isEmpty()) { if (QCoreApplication::instance()) { *ip = QCoreApplication::applicationDirPath(); } else { qCWarning(KLEOPATRA_LOG) << "called before QCoreApplication was constructed"; } } return *ip; } void ArchiveDefinition::setInstallPath(const QString &ip) { const QMutexLocker locker(&installPathMutex); *_installPath() = ip; } // Archive Definition #N groups static const QLatin1String ID_ENTRY("id"); static const QLatin1String NAME_ENTRY("Name"); static const QLatin1String PACK_COMMAND_ENTRY("pack-command"); static const QLatin1String PACK_COMMAND_OPENPGP_ENTRY("pack-command-openpgp"); static const QLatin1String PACK_COMMAND_CMS_ENTRY("pack-command-cms"); static const QLatin1String UNPACK_COMMAND_ENTRY("unpack-command"); static const QLatin1String UNPACK_COMMAND_OPENPGP_ENTRY("unpack-command-openpgp"); static const QLatin1String UNPACK_COMMAND_CMS_ENTRY("unpack-command-cms"); static const QLatin1String EXTENSIONS_ENTRY("extensions"); static const QLatin1String EXTENSIONS_OPENPGP_ENTRY("extensions-openpgp"); static const QLatin1String EXTENSIONS_CMS_ENTRY("extensions-cms"); static const QLatin1String FILE_PLACEHOLDER("%f"); static const QLatin1String INSTALLPATH_PLACEHOLDER("%I"); static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|"); static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|'); namespace { class ArchiveDefinitionError : public Kleo::Exception { const QString m_id; public: ArchiveDefinitionError(const QString &id, const QString &message) : Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in archive definition %1: %2", id, message), MessageOnly), m_id(id) { } ~ArchiveDefinitionError() throw() {} const QString &archiveDefinitionId() const { return m_id; } }; } static QString try_extensions(const QString &path) { static const char exts[][4] = { "", "exe", "bat", "bin", "cmd", }; static const size_t numExts = sizeof exts / sizeof * exts; for (unsigned int i = 0; i < numExts; ++i) { const QFileInfo fi(path + QLatin1Char('.') + QLatin1String(exts[i])); if (fi.exists()) { return fi.filePath(); } } return QString(); } static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ArchiveDefinition::ArgumentPassingMethod *method, bool parseFilePlaceholder) { assert(prefix); assert(suffix); assert(method); KShell::Errors errors; QStringList l; if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) { *method = ArchiveDefinition::NullSeparatedInputFile; cmdline.remove(0, 2); } else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) { *method = ArchiveDefinition::NewlineSeparatedInputFile; cmdline.remove(0, 1); } else { *method = ArchiveDefinition::CommandLine; } if (*method != ArchiveDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) { throw ArchiveDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand)); } cmdline.replace(FILE_PLACEHOLDER, QLatin1String("__files_go_here__")) .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__")); l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors); l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER); if (l.indexOf(QRegExp(QLatin1String(".*__path_goes_here__.*"))) >= 0) { l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ArchiveDefinition::installPath()); } if (errors == KShell::BadQuoting) { throw ArchiveDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand)); } if (errors == KShell::FoundMeta) { throw ArchiveDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand)); } qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << l; if (l.empty()) { throw ArchiveDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand)); } const QFileInfo fi1(l.front()); if (fi1.isAbsolute()) { *command = try_extensions(l.front()); } else { *command = QStandardPaths::findExecutable(fi1.fileName()); } if (command->isEmpty()) { throw ArchiveDefinitionError(id, i18n("'%1' empty or not found", whichCommand)); } if (parseFilePlaceholder) { const int idx1 = l.indexOf(FILE_PLACEHOLDER); if (idx1 < 0) { // none -> append *prefix = l.mid(1); } else { *prefix = l.mid(1, idx1 - 1); *suffix = l.mid(idx1 + 1); } } else { *prefix = l.mid(1); } switch (*method) { case ArchiveDefinition::CommandLine: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix; break; case ArchiveDefinition::NewlineSeparatedInputFile: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << "find | " << *command << *prefix; break; case ArchiveDefinition::NullSeparatedInputFile: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << "find -print0 | " << *command << *prefix; break; case ArchiveDefinition::NumArgumentPassingMethods: assert(!"Should not happen"); break; } } namespace { class KConfigBasedArchiveDefinition : public ArchiveDefinition { public: explicit KConfigBasedArchiveDefinition(const KConfigGroup &group) : ArchiveDefinition(group.readEntryUntranslated(ID_ENTRY), group.readEntry(NAME_ENTRY)) { if (id().isEmpty()) { throw ArchiveDefinitionError(group.name(), i18n("'%1' entry is empty/missing", ID_ENTRY)); } QStringList extensions; QString extensionsKey; // extensions(-openpgp) if (group.hasKey(EXTENSIONS_OPENPGP_ENTRY)) { extensionsKey = EXTENSIONS_OPENPGP_ENTRY; } else { extensionsKey = EXTENSIONS_ENTRY; } extensions = group.readEntry(extensionsKey, QStringList()); if (extensions.empty()) { throw ArchiveDefinitionError(id(), i18n("'%1' entry is empty/missing", extensionsKey)); } setExtensions(OpenPGP, extensions); // extensions(-cms) if (group.hasKey(EXTENSIONS_CMS_ENTRY)) { extensionsKey = EXTENSIONS_CMS_ENTRY; } else { extensionsKey = EXTENSIONS_ENTRY; } extensions = group.readEntry(extensionsKey, QStringList()); if (extensions.empty()) { throw ArchiveDefinitionError(id(), i18n("'%1' entry is empty/missing", extensionsKey)); } setExtensions(CMS, extensions); ArgumentPassingMethod method; // pack-command(-openpgp) if (group.hasKey(PACK_COMMAND_OPENPGP_ENTRY)) parse_command(group.readEntry(PACK_COMMAND_OPENPGP_ENTRY), id(), PACK_COMMAND_OPENPGP_ENTRY, &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true); else parse_command(group.readEntry(PACK_COMMAND_ENTRY), id(), PACK_COMMAND_ENTRY, &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true); setPackCommandArgumentPassingMethod(OpenPGP, method); // pack-command(-cms) if (group.hasKey(PACK_COMMAND_CMS_ENTRY)) parse_command(group.readEntry(PACK_COMMAND_CMS_ENTRY), id(), PACK_COMMAND_CMS_ENTRY, &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true); else parse_command(group.readEntry(PACK_COMMAND_ENTRY), id(), PACK_COMMAND_ENTRY, &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true); setPackCommandArgumentPassingMethod(CMS, method); QStringList dummy; // unpack-command(-openpgp) if (group.hasKey(UNPACK_COMMAND_OPENPGP_ENTRY)) parse_command(group.readEntry(UNPACK_COMMAND_OPENPGP_ENTRY), id(), UNPACK_COMMAND_OPENPGP_ENTRY, &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false); else parse_command(group.readEntry(UNPACK_COMMAND_ENTRY), id(), UNPACK_COMMAND_ENTRY, &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false); if (method != CommandLine) { throw ArchiveDefinitionError(id(), i18n("cannot use argument passing on standard input for unpack-command")); } // unpack-command(-cms) if (group.hasKey(UNPACK_COMMAND_CMS_ENTRY)) parse_command(group.readEntry(UNPACK_COMMAND_CMS_ENTRY), id(), UNPACK_COMMAND_CMS_ENTRY, &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false); else parse_command(group.readEntry(UNPACK_COMMAND_ENTRY), id(), UNPACK_COMMAND_ENTRY, &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false); if (method != CommandLine) { throw ArchiveDefinitionError(id(), i18n("cannot use argument passing on standard input for unpack-command")); } } private: QString doGetPackCommand(GpgME::Protocol p) const Q_DECL_OVERRIDE { return m_packCommand[p]; } QString doGetUnpackCommand(GpgME::Protocol p) const Q_DECL_OVERRIDE { return m_unpackCommand[p]; } QStringList doGetPackArguments(GpgME::Protocol p, const QStringList &files) const Q_DECL_OVERRIDE { return m_packPrefixArguments[p] + files + m_packPostfixArguments[p]; } QStringList doGetUnpackArguments(GpgME::Protocol p, const QString &file) const Q_DECL_OVERRIDE { QStringList copy = m_unpackArguments[p]; copy.replaceInStrings(FILE_PLACEHOLDER, file); return copy; } private: QString m_packCommand[2], m_unpackCommand[2]; QStringList m_packPrefixArguments[2], m_packPostfixArguments[2]; QStringList m_unpackArguments[2]; }; } ArchiveDefinition::ArchiveDefinition(const QString &id, const QString &label) : m_id(id), m_label(label) { m_packCommandMethod[GpgME::OpenPGP] = m_packCommandMethod[GpgME::CMS] = CommandLine; } ArchiveDefinition::~ArchiveDefinition() {} static QByteArray make_input(const QStringList &files, char sep) { QByteArray result; Q_FOREACH (const QString &file, files) { result += QFile::encodeName(file); result += sep; } return result; } std::shared_ptr ArchiveDefinition::createInputFromPackCommand(GpgME::Protocol p, const QStringList &files) const { checkProtocol(p); const QString base = heuristicBaseDirectory(files); if (base.isEmpty()) { - throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", files.join(QStringLiteral("\n")))); + throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", files.join(QLatin1Char('\n')))); } qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << files << ") ->" << base; const QStringList relative = makeRelativeTo(base, files); qCDebug(KLEOPATRA_LOG) << "relative" << relative; switch (m_packCommandMethod[p]) { case CommandLine: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, relative), QDir(base)); case NewlineSeparatedInputFile: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, QStringList()), QDir(base), make_input(relative, '\n')); case NullSeparatedInputFile: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, QStringList()), QDir(base), make_input(relative, '\0')); case NumArgumentPassingMethods: assert(!"Should not happen"); } return std::shared_ptr(); // make compiler happy } std::shared_ptr ArchiveDefinition::createOutputFromUnpackCommand(GpgME::Protocol p, const QString &file, const QDir &wd) const { checkProtocol(p); const QFileInfo fi(file); return Output::createFromProcessStdIn(doGetUnpackCommand(p), doGetUnpackArguments(p, fi.absoluteFilePath()), wd); } // static std::vector< std::shared_ptr > ArchiveDefinition::getArchiveDefinitions() { QStringList errors; return getArchiveDefinitions(errors); } // static std::vector< std::shared_ptr > ArchiveDefinition::getArchiveDefinitions(QStringList &errors) { std::vector< std::shared_ptr > result; KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc")); const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #"))); result.reserve(groups.size()); Q_FOREACH (const QString &group, groups) try { const std::shared_ptr ad(new KConfigBasedArchiveDefinition(KConfigGroup(config, group))); result.push_back(ad); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << e.what(); errors.push_back(QString::fromLocal8Bit(e.what())); } catch (...) { errors.push_back(i18n("Caught unknown exception in group %1", group)); } return result; } void ArchiveDefinition::checkProtocol(GpgME::Protocol p) const { kleo_assert(p == GpgME::OpenPGP || p == GpgME::CMS); } diff --git a/src/utils/input.cpp b/src/utils/input.cpp index 8ef68c02..1964a786 100644 --- a/src/utils/input.cpp +++ b/src/utils/input.cpp @@ -1,450 +1,450 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/input.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "input.h" #include "detail_p.h" #include "kdpipeiodevice.h" #include "log.h" #include "kleo_assert.h" #include "cached.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class Process : public QProcess { public: explicit Process(QObject *parent = nullptr) : QProcess(parent) {} void close() Q_DECL_OVERRIDE { closeReadChannel(StandardOutput); } }; } namespace { class InputImplBase : public Input { public: InputImplBase() : Input(), m_customLabel(), m_defaultLabel() {} QString label() const Q_DECL_OVERRIDE { return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel; } void setDefaultLabel(const QString &l) { m_defaultLabel = l; } void setLabel(const QString &l) Q_DECL_OVERRIDE { m_customLabel = l; } QString errorString() const Q_DECL_OVERRIDE { if (m_errorString.dirty()) { m_errorString = doErrorString(); } return m_errorString; } private: virtual QString doErrorString() const { if (const std::shared_ptr io = ioDevice()) { return io->errorString(); } else { return i18n("No input device"); } } private: QString m_customLabel; QString m_defaultLabel; mutable cached m_errorString; }; class PipeInput : public InputImplBase { public: explicit PipeInput(assuan_fd_t fd); std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_io; } unsigned int classification() const Q_DECL_OVERRIDE; unsigned long long size() const Q_DECL_OVERRIDE { return 0; } private: std::shared_ptr m_io; }; class ProcessStdOutInput : public InputImplBase { public: explicit ProcessStdOutInput(const QString &cmd, const QStringList &args, const QDir &wd, const QByteArray &stdin_ = QByteArray()); std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_proc; } unsigned int classification() const Q_DECL_OVERRIDE { return 0U; // plain text } unsigned long long size() const Q_DECL_OVERRIDE { return 0; } QString label() const Q_DECL_OVERRIDE; private: QString doErrorString() const Q_DECL_OVERRIDE; private: const QString m_command; const QStringList m_arguments; const std::shared_ptr m_proc; }; class FileInput : public InputImplBase { public: explicit FileInput(const QString &fileName); explicit FileInput(const std::shared_ptr &file); QString label() const Q_DECL_OVERRIDE { return m_io ? QFileInfo(m_fileName).fileName() : InputImplBase::label(); } std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_io; } unsigned int classification() const Q_DECL_OVERRIDE; unsigned long long size() const Q_DECL_OVERRIDE { return QFileInfo(m_fileName).size(); } private: std::shared_ptr m_io; QString m_fileName; }; #ifndef QT_NO_CLIPBOARD class ClipboardInput : public Input { public: explicit ClipboardInput(QClipboard::Mode mode); void setLabel(const QString &label) Q_DECL_OVERRIDE; QString label() const Q_DECL_OVERRIDE; std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_buffer; } unsigned int classification() const Q_DECL_OVERRIDE; unsigned long long size() const Q_DECL_OVERRIDE { return m_buffer ? m_buffer->buffer().size() : 0; } QString errorString() const Q_DECL_OVERRIDE { return QString(); } private: const QClipboard::Mode m_mode; std::shared_ptr m_buffer; }; #endif // QT_NO_CLIPBOARD } std::shared_ptr Input::createFromPipeDevice(assuan_fd_t fd, const QString &label) { std::shared_ptr po(new PipeInput(fd)); po->setDefaultLabel(label); return po; } PipeInput::PipeInput(assuan_fd_t fd) : InputImplBase(), m_io() { std::shared_ptr kdp(new KDPipeIODevice); errno = 0; if (!kdp->open(fd, QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for reading", _detail::assuanFD2int(fd))); m_io = Log::instance()->createIOLogger(kdp, QStringLiteral("pipe-input"), Log::Read); } unsigned int PipeInput::classification() const { notImplemented(); return 0; } std::shared_ptr Input::createFromFile(const QString &fileName, bool) { return std::shared_ptr(new FileInput(fileName)); } std::shared_ptr Input::createFromFile(const std::shared_ptr &file) { return std::shared_ptr(new FileInput(file)); } FileInput::FileInput(const QString &fileName) : InputImplBase(), m_io(), m_fileName(fileName) { std::shared_ptr file(new QFile(fileName)); errno = 0; if (!file->open(QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open file \"%1\" for reading", fileName)); m_io = Log::instance()->createIOLogger(file, QStringLiteral("file-in"), Log::Read); } FileInput::FileInput(const std::shared_ptr &file) : InputImplBase(), m_io(), m_fileName(file->fileName()) { kleo_assert(file); errno = 0; if (file->isOpen() && !file->isReadable()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("File \"%1\" is already open, but not for reading", file->fileName())); if (!file->isOpen() && !file->open(QIODevice::ReadOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open file \"%1\" for reading", m_fileName)); m_io = Log::instance()->createIOLogger(file, QStringLiteral("file-in"), Log::Read); } unsigned int FileInput::classification() const { return classify(m_fileName); } std::shared_ptr Input::createFromProcessStdOut(const QString &command) { return std::shared_ptr(new ProcessStdOutInput(command, QStringList(), QDir::current())); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args) { return std::shared_ptr(new ProcessStdOutInput(command, args, QDir::current())); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QDir &wd) { return std::shared_ptr(new ProcessStdOutInput(command, args, wd)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, QStringList(), QDir::current(), stdin_)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, args, QDir::current(), stdin_)); } std::shared_ptr Input::createFromProcessStdOut(const QString &command, const QStringList &args, const QDir &wd, const QByteArray &stdin_) { return std::shared_ptr(new ProcessStdOutInput(command, args, wd, stdin_)); } namespace { struct Outputter { const QByteArray &data; explicit Outputter(const QByteArray &data) : data(data) {} }; static QDebug operator<<(QDebug s, const Outputter &o) { if (const quint64 size = o.data.size()) { s << " << (" << size << "bytes)"; } return s; } } ProcessStdOutInput::ProcessStdOutInput(const QString &cmd, const QStringList &args, const QDir &wd, const QByteArray &stdin_) : InputImplBase(), m_command(cmd), m_arguments(args), m_proc(new Process) { const QIODevice::OpenMode openMode = stdin_.isEmpty() ? QIODevice::ReadOnly : QIODevice::ReadWrite; qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << endl << cmd << args << Outputter(stdin_); if (cmd.isEmpty()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified")); m_proc->setWorkingDirectory(wd.absolutePath()); m_proc->start(cmd, args, openMode); if (!m_proc->waitForStarted()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); if (!stdin_.isEmpty()) { if (m_proc->write(stdin_) != stdin_.size()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Failed to write input to %1 process: %2", cmd, m_proc->errorString())); m_proc->closeWriteChannel(); } } QString ProcessStdOutInput::label() const { if (!m_proc) { return InputImplBase::label(); } // output max. 3 arguments - const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QStringLiteral(" ")); + const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' ')); if (m_arguments.size() > 3) { return i18nc("e.g. \"Output of tar xf - file1 ...\"", "Output of %1 ...", cmdline); } else { return i18nc("e.g. \"Output of tar xf - file\"", "Output of %1", cmdline); } } QString ProcessStdOutInput::doErrorString() const { kleo_assert(m_proc); if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) { return QString(); } if (m_proc->error() == QProcess::UnknownError) return i18n("Error while running %1:\n%2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", m_command, m_proc->errorString()); } } #ifndef QT_NO_CLIPBOARD std::shared_ptr Input::createFromClipboard() { return std::shared_ptr(new ClipboardInput(QClipboard::Clipboard)); } static QByteArray dataFromClipboard(QClipboard::Mode mode) { Q_UNUSED(mode); if (QClipboard *const cb = QApplication::clipboard()) { return cb->text().toUtf8(); } else { return QByteArray(); } } ClipboardInput::ClipboardInput(QClipboard::Mode mode) : Input(), m_mode(mode), m_buffer(new QBuffer) { m_buffer->setData(dataFromClipboard(mode)); if (!m_buffer->open(QIODevice::ReadOnly)) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not open clipboard for reading")); } void ClipboardInput::setLabel(const QString &) { notImplemented(); } QString ClipboardInput::label() const { switch (m_mode) { case QClipboard::Clipboard: return i18n("Clipboard contents"); case QClipboard::FindBuffer: return i18n("FindBuffer contents"); case QClipboard::Selection: return i18n("Current selection"); }; return QString(); } unsigned int ClipboardInput::classification() const { return classifyContent(m_buffer->data()); } #endif // QT_NO_CLIPBOARD Input::~Input() {} void Input::finalize() { if (const std::shared_ptr io = ioDevice()) if (io->isOpen()) { io->close(); } } diff --git a/src/utils/kdlogtextwidget.cpp b/src/utils/kdlogtextwidget.cpp index 16e0a3bc..01596794 100644 --- a/src/utils/kdlogtextwidget.cpp +++ b/src/utils/kdlogtextwidget.cpp @@ -1,646 +1,646 @@ /**************************************************************************** ** Copyright (C) 2001-2010 Klaralvdalens Datakonsult AB. All rights reserved. ** ** This file is part of the KD Tools library. ** ** Licensees holding valid commercial KD Tools licenses may use this file in ** accordance with the KD Tools Commercial License Agreement provided with ** the Software. ** ** ** This file may be distributed and/or modified under the terms of the ** GNU Lesser General Public License version 2 and version 3 as published by the ** Free Software Foundation and appearing in the file LICENSE.LGPL included. ** ** This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE ** WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. ** ** Contact info@kdab.com if any conditions of this licensing are not ** clear to you. ** **********************************************************************/ #include #include "kdlogtextwidget.h" #include #include #include #include #include #include #include #include /*! \class KDLogTextWidget \brief A high-speed text display widget. This widget provides very fast display of large amounts of line-oriented text, as commonly found in application log viewers. The feature set and implementation are optimized for frequent appends. You can set initial text using setLines(), and append lines with calls to message(). You can limit the number of lines kept in the view using setHistorySize(). Text formatting is currently limited to per-line text color, but is expected to be enhanced on client request in upcoming versions. You can pass the color to use to calls to message(). */ class KDLogTextWidget::Private { friend class ::KDLogTextWidget; KDLogTextWidget *const q; public: explicit Private(KDLogTextWidget *qq); ~Private(); void updateCache() const; void triggerTimer() { if (!timer.isActive()) { timer.start(500, q); } } void addPendingLines(); void enforceHistorySize(); void updateScrollRanges(); QPair visibleLines(int top, int bottom) { return qMakePair(qMax(0, lineByYCoordinate(top)), qMax(0, 1 + lineByYCoordinate(bottom))); } int lineByYCoordinate(int x) const; QPoint scrollOffset() const; QRect lineRect(int idx) const { assert(!cache.dirty); return QRect(0, idx * cache.fontMetrics.lineSpacing, cache.dimensions.longestLineLength, cache.fontMetrics.lineSpacing - 1); } struct Style { QColor color; friend inline uint qHash(const Style &style) { return qHash(style.color.rgba()); } bool operator==(const Style &other) const { return this->color.rgba() == other.color.rgba(); } bool operator<(const Style &other) const { return this->color.rgba() < other.color.rgba(); } }; struct LineItem { QString text; unsigned int styleID; }; unsigned int findOrAddStyle(const Style &style); private: QHash styleByID; QHash idByStyle; QVector lines, pendingLines; unsigned int historySize; unsigned int minimumVisibleLines; unsigned int minimumVisibleColumns; bool alternatingRowColors; QBasicTimer timer; mutable struct Cache { enum { Dimensions = 1, FontMetrics = 2, All = FontMetrics | Dimensions }; Cache() : dirty(All) {} int dirty; struct { int lineSpacing; int ascent; int averageCharWidth; QVector lineWidths; } fontMetrics; struct { int indexOfLongestLine; int longestLineLength; } dimensions; } cache; }; /*! Constructor. Creates an empty KDLogTextWidget. */ KDLogTextWidget::KDLogTextWidget(QWidget *parent_) : QAbstractScrollArea(parent_), d(new Private(this)) { } /*! Destructor. */ KDLogTextWidget::~KDLogTextWidget() {} /*! \property KDLogTextWidget::historySize Specifies the maximum number of lines this widget will hold before dropping old lines. The default is INT_MAX (ie. essentially unlimited). Get this property's value using %historySize(), and set it with %setHistorySize(). */ void KDLogTextWidget::setHistorySize(unsigned int hs) { if (hs == d->historySize) { return; } d->historySize = hs; d->enforceHistorySize(); d->updateScrollRanges(); viewport()->update(); } unsigned int KDLogTextWidget::historySize() const { return d->historySize; } /*! \property KDLogTextWidget::text Contains the current %text as a single string. Equivalent to \code lines().join( "\n" ) \endcode */ QString KDLogTextWidget::text() const { - return lines().join(QStringLiteral("\n")); + return lines().join(QLatin1Char('\n')); } /*! \property KDLogTextWidget::lines Contains the current %text as a string list. The default empty. Get this property's value using %lines(), and set it with %setLines(). */ void KDLogTextWidget::setLines(const QStringList &l) { clear(); Q_FOREACH (const QString &s, l) { message(s); } } QStringList KDLogTextWidget::lines() const { QStringList result; Q_FOREACH (const Private::LineItem &li, d->lines) { result.push_back(li.text); } Q_FOREACH (const Private::LineItem &li, d->pendingLines) { result.push_back(li.text); } return result; } /*! \property KDLogTextWidget::minimumVisibleLines Specifies the number of lines that should be visible at any one time. The default is 1 (one). Get this property's value using %minimumVisibleLines(), and set it using %setMinimumVisibleLines(). */ void KDLogTextWidget::setMinimumVisibleLines(unsigned int num) { if (num == d->minimumVisibleLines) { return; } d->minimumVisibleLines = num; updateGeometry(); } unsigned int KDLogTextWidget::minimumVisibleLines() const { return d->minimumVisibleLines; } /*! \property KDLogTextWidget::minimumVisibleColumns Specifies the number of columns that should be visible at any one time. The default is 1 (one). The width is calculated using QFontMetrics::averageCharWidth(), if that is available. Otherwise, the width of \c M is used. Get this property's value using %minimumVisibleColumns(), and set it using %setMinimumVisibleColumns(). */ void KDLogTextWidget::setMinimumVisibleColumns(unsigned int num) { if (num == d->minimumVisibleColumns) { return; } d->minimumVisibleColumns = num; updateGeometry(); } unsigned int KDLogTextWidget::minimumVisibleColumns() const { return d->minimumVisibleColumns; } /*! \property KDLogTextWidget::alternatingRowColors Specifies whether the background should be drawn using row-alternating colors. The default is \c false. Get this property's value using %alternatingRowColors(), and set it using %setAlternatingRowColors(). */ void KDLogTextWidget::setAlternatingRowColors(bool on) { if (on == d->alternatingRowColors) { return; } d->alternatingRowColors = on; update(); } bool KDLogTextWidget::alternatingRowColors() const { return d->alternatingRowColors; } QSize KDLogTextWidget::minimumSizeHint() const { d->updateCache(); const QSize base = QAbstractScrollArea::minimumSizeHint(); const QSize view(d->minimumVisibleColumns * d->cache.fontMetrics.averageCharWidth, d->minimumVisibleLines * d->cache.fontMetrics.lineSpacing); const QSize scrollbars(verticalScrollBar() ? verticalScrollBar()->minimumSizeHint().width() : 0, horizontalScrollBar() ? horizontalScrollBar()->minimumSizeHint().height() : 0); return base + view + scrollbars; } QSize KDLogTextWidget::sizeHint() const { if (d->minimumVisibleLines > 1 || d->minimumVisibleColumns > 1) { return minimumSizeHint(); } else { return 2 * minimumSizeHint(); } } /*! Clears the text. \post lines().empty() == true */ void KDLogTextWidget::clear() { d->timer.stop(); d->lines.clear(); d->pendingLines.clear(); d->styleByID.clear(); d->idByStyle.clear(); d->cache.dirty = Private::Cache::All; viewport()->update(); } /*! Appends \a str to the view, highlighting the line in \a color. \post lines().back() == str (modulo trailing whitespace and contained newlines) */ void KDLogTextWidget::message(const QString &str, const QColor &color) { const Private::Style s = { color }; const Private::LineItem li = { str, d->findOrAddStyle(s) }; d->pendingLines.push_back(li); d->triggerTimer(); } /*! \overload Uses the default text color set in this widget's palette. */ void KDLogTextWidget::message(const QString &str) { const Private::LineItem li = { str, 0 }; d->pendingLines.push_back(li); d->triggerTimer(); } void KDLogTextWidget::paintEvent(QPaintEvent *e) { d->updateCache(); QPainter p(viewport()); p.translate(-d->scrollOffset()); const QRect visible = p.matrix().inverted().mapRect(e->rect()); const QPair visibleLines = d->visibleLines(visible.top(), visible.bottom()); assert(visibleLines.first <= visibleLines.second); const Private::Style defaultStyle = { p.pen().color() }; const Private::Cache &cache = d->cache; p.setPen(Qt::NoPen); p.setBrush(palette().base()); if (d->alternatingRowColors) { p.drawRect(visible); #if 0 // leaves garbage for (unsigned int i = visibleLines.first % 2 ? visibleLines.first + 1 : visibleLines.first, end = visibleLines.second; i < end; i += 2) { p.drawRect(d->lineRect(i)); } if (visibleLines.second >= 0) { const int lastY = d->lineRect(visibleLines.second - 1).y(); if (lastY < visible.bottom()) { p.drawRect(0, lastY + 1, cache.dimensions.longestLineLength, visible.bottom() - lastY); } } #endif p.setBrush(palette().alternateBase()); for (unsigned int i = visibleLines.first % 2 ? visibleLines.first : visibleLines.first + 1, end = visibleLines.second; i < end; i += 2) { p.drawRect(d->lineRect(i)); } } else { p.drawRect(visible); } // ### unused optimization: paint lines by styles to minimise pen changes. for (unsigned int i = visibleLines.first, end = visibleLines.second; i != end; ++i) { const Private::LineItem &li = d->lines[i]; assert(!li.styleID || d->styleByID.contains(li.styleID)); const Private::Style &st = li.styleID ? d->styleByID[li.styleID] : defaultStyle; p.setPen(st.color); p.drawText(0, i * cache.fontMetrics.lineSpacing + cache.fontMetrics.ascent, li.text); } } void KDLogTextWidget::timerEvent(QTimerEvent *e) { if (e->timerId() == d->timer.timerId()) { d->addPendingLines(); d->timer.stop(); } else { QAbstractScrollArea::timerEvent(e); } } void KDLogTextWidget::changeEvent(QEvent *e) { QAbstractScrollArea::changeEvent(e); d->cache.dirty |= Private::Cache::FontMetrics; } void KDLogTextWidget::resizeEvent(QResizeEvent *) { d->updateScrollRanges(); } KDLogTextWidget::Private::Private(KDLogTextWidget *qq) : q(qq), styleByID(), idByStyle(), lines(), pendingLines(), historySize(0xFFFFFFFF), minimumVisibleLines(1), minimumVisibleColumns(1), alternatingRowColors(false), timer(), cache() { // PENDING(marc) find all the magic flags we need here... QWidget *const vp = qq->viewport(); vp->setBackgroundRole(QPalette::Base); vp->setAttribute(Qt::WA_StaticContents); vp->setAttribute(Qt::WA_NoSystemBackground); #ifndef QT_NO_CURSOR vp->setCursor(Qt::IBeamCursor); #endif } KDLogTextWidget::Private::~Private() {} void KDLogTextWidget::Private::updateCache() const { if (cache.dirty >= Cache::FontMetrics) { const QFontMetrics &fm = q->fontMetrics(); cache.fontMetrics.lineSpacing = fm.lineSpacing(); cache.fontMetrics.ascent = fm.ascent(); cache.fontMetrics.averageCharWidth = fm.averageCharWidth(); QVector &lw = cache.fontMetrics.lineWidths; lw.clear(); lw.reserve(lines.size()); Q_FOREACH (const LineItem &li, lines) { lw.push_back(fm.width(li.text)); } } if (cache.dirty >= Cache::Dimensions) { const QVector &lw = cache.fontMetrics.lineWidths; const QVector::const_iterator it = std::max_element(lw.begin(), lw.end()); if (it == lw.end()) { cache.dimensions.indexOfLongestLine = -1; cache.dimensions.longestLineLength = 0; } else { cache.dimensions.indexOfLongestLine = it - lw.begin(); cache.dimensions.longestLineLength = *it; } } cache.dirty = false; } unsigned int KDLogTextWidget::Private::findOrAddStyle(const Style &s) { if (idByStyle.contains(s)) { const unsigned int id = idByStyle[s]; assert(styleByID.contains(id)); assert(styleByID[id] == s); return id; } else { static unsigned int nextID = 0; // remember, 0 is reserved const unsigned int id = ++nextID; idByStyle.insert(s, id); styleByID.insert(id, s); return id; } } void KDLogTextWidget::Private::enforceHistorySize() { const size_t numLimes = lines.size(); if (numLimes <= historySize) { return; } const int remove = numLimes - historySize; lines.erase(lines.begin(), lines.begin() + remove); // can't quickly update the dimensions if the fontMetrics aren't uptodate. if (cache.dirty & Cache::FontMetrics) { cache.dirty |= Cache::Dimensions; return; } QVector &lw = cache.fontMetrics.lineWidths; assert(lw.size() > remove); lw.erase(lw.begin(), lw.begin() + remove); if (cache.dirty & Cache::Dimensions) { return; } if (cache.dimensions.indexOfLongestLine >= remove) { cache.dimensions.indexOfLongestLine -= remove; } else { cache.dirty |= Cache::Dimensions; } } static void set_scrollbar_properties(QScrollBar &sb, int document, int viewport, int singleStep, Qt::Orientation o) { const int min = 0; const int max = std::max(0, document - viewport); const int value = sb.value(); const bool wasAtEnd = value == sb.maximum(); sb.setRange(min, max); sb.setPageStep(viewport); sb.setSingleStep(singleStep); sb.setValue(o == Qt::Vertical && wasAtEnd ? sb.maximum() : value); } void KDLogTextWidget::Private::updateScrollRanges() { updateCache(); if (QScrollBar *const sb = q->verticalScrollBar()) { const int document = lines.size() * cache.fontMetrics.lineSpacing; const int viewport = q->viewport()->height(); const int singleStep = cache.fontMetrics.lineSpacing; set_scrollbar_properties(*sb, document, viewport, singleStep, Qt::Vertical); } if (QScrollBar *const sb = q->horizontalScrollBar()) { const int document = cache.dimensions.longestLineLength; const int viewport = q->viewport()->width(); const int singleStep = cache.fontMetrics.lineSpacing; // rather randomly chosen set_scrollbar_properties(*sb, document, viewport, singleStep, Qt::Horizontal); } } void KDLogTextWidget::Private::addPendingLines() { if (pendingLines.empty()) { return; } const unsigned int oldNumLines = lines.size(); lines += pendingLines; // if the cache isn't dirty, we can quickly update it without // invalidation: if (!cache.dirty) { // update fontMetrics: const QFontMetrics &fm = q->fontMetrics(); QVector plw; plw.reserve(pendingLines.size()); Q_FOREACH (const LineItem &li, pendingLines) { plw.push_back(fm.width(li.text)); } // update dimensions: const QVector::const_iterator it = std::max_element(plw.constBegin(), plw.constEnd()); if (*it >= cache.dimensions.longestLineLength) { cache.dimensions.longestLineLength = *it; cache.dimensions.indexOfLongestLine = oldNumLines + (it - plw.constBegin()); } } pendingLines.clear(); enforceHistorySize(); updateScrollRanges(); q->viewport()->update(); } int KDLogTextWidget::Private::lineByYCoordinate(int y) const { updateCache(); if (cache.fontMetrics.lineSpacing == 0) { return -1; } const int raw = y / cache.fontMetrics.lineSpacing; if (raw < 0) { return -1; } if (raw >= lines.size()) { return lines.size() - 1; } return raw; } static int get_scrollbar_offset(const QScrollBar *sb) { return sb ? sb->value() : 0; } QPoint KDLogTextWidget::Private::scrollOffset() const { return QPoint(get_scrollbar_offset(q->horizontalScrollBar()), get_scrollbar_offset(q->verticalScrollBar())); } diff --git a/src/utils/output.cpp b/src/utils/output.cpp index e59fc673..33de1381 100644 --- a/src/utils/output.cpp +++ b/src/utils/output.cpp @@ -1,629 +1,629 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/output.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2007 Klarälvdalens Datakonsult AB Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "output.h" #include "detail_p.h" #include "kleo_assert.h" #include "kdpipeiodevice.h" #include "log.h" #include "cached.h" #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN # include #endif #include using namespace Kleo; using namespace Kleo::_detail; static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1; // no timeout static const int PROCESS_TERMINATE_TIMEOUT = 5 * 1000; // 5s class OverwritePolicy::Private { public: Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {} OverwritePolicy::Policy policy; QWidget *widget; }; OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy)) { } OverwritePolicy::~OverwritePolicy() {} OverwritePolicy::Policy OverwritePolicy::policy() const { return d->policy; } void OverwritePolicy::setPolicy(Policy policy) { d->policy = policy; } QWidget *OverwritePolicy::parentWidget() const { return d->widget; } namespace { class TemporaryFile : public QTemporaryFile { public: explicit TemporaryFile() : QTemporaryFile() {} explicit TemporaryFile(const QString &templateName) : QTemporaryFile(templateName) {} explicit TemporaryFile(QObject *parent) : QTemporaryFile(parent) {} explicit TemporaryFile(const QString &templateName, QObject *parent) : QTemporaryFile(templateName, parent) {} void close() Q_DECL_OVERRIDE { if (isOpen()) { m_oldFileName = fileName(); } QTemporaryFile::close(); } bool openNonInheritable() { if (!QTemporaryFile::open()) { return false; } #if defined(Q_OS_WIN) //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable. //The handle is then inherited by gpgsm, which prevents deletion of the temp file //in FileOutput::doFinalize() //There are no inheritable handles under wince return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0); #endif return true; } QString oldFileName() const { return m_oldFileName; } private: QString m_oldFileName; }; template struct inhibit_close : T_IODevice { explicit inhibit_close() : T_IODevice() {} template explicit inhibit_close(T1 &t1) : T_IODevice(t1) {} /* reimp */ void close() {} void reallyClose() { T_IODevice::close(); } }; template struct redirect_close : T_IODevice { explicit redirect_close() : T_IODevice(), m_closed(false) {} template explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {} /* reimp */ void close() { this->closeWriteChannel(); m_closed = true; } bool isClosed() const { return m_closed; } private: bool m_closed; }; class OutputImplBase : public Output { public: OutputImplBase() : Output(), m_defaultLabel(), m_customLabel(), m_errorString(), m_isFinalized(false), m_isFinalizing(false), m_cancelPending(false), m_canceled(false), m_binaryOpt(false) { } QString label() const Q_DECL_OVERRIDE { return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel; } void setLabel(const QString &label) Q_DECL_OVERRIDE { m_customLabel = label; } void setDefaultLabel(const QString &l) { m_defaultLabel = l; } void setBinaryOpt(bool value) Q_DECL_OVERRIDE { m_binaryOpt = value; } bool binaryOpt() const Q_DECL_OVERRIDE { return m_binaryOpt; } QString errorString() const Q_DECL_OVERRIDE { if (m_errorString.dirty()) { m_errorString = doErrorString(); } return m_errorString; } bool isFinalized() const Q_DECL_OVERRIDE { return m_isFinalized; } void finalize() Q_DECL_OVERRIDE { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalized || m_isFinalizing) { return; } m_isFinalizing = true; try { doFinalize(); } catch (...) { m_isFinalizing = false; throw; } m_isFinalizing = false; m_isFinalized = true; if (m_cancelPending) { cancel(); } } void cancel() Q_DECL_OVERRIDE { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalizing) { m_cancelPending = true; } else if (!m_canceled) { m_isFinalizing = true; try { doCancel(); } catch (...) {} m_isFinalizing = false; m_isFinalized = true; m_canceled = true; } } private: virtual QString doErrorString() const { if (std::shared_ptr io = ioDevice()) { return io->errorString(); } else { return i18n("No output device"); } } virtual void doFinalize() = 0; virtual void doCancel() = 0; private: QString m_defaultLabel; QString m_customLabel; mutable cached m_errorString; bool m_isFinalized : 1; bool m_isFinalizing : 1; bool m_cancelPending : 1; bool m_canceled : 1; bool m_binaryOpt : 1; }; class PipeOutput : public OutputImplBase { public: explicit PipeOutput(assuan_fd_t fd); std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_io; } void doFinalize() Q_DECL_OVERRIDE { m_io->reallyClose(); } void doCancel() Q_DECL_OVERRIDE { doFinalize(); } private: std::shared_ptr< inhibit_close > m_io; }; class ProcessStdInOutput : public OutputImplBase { public: explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd); std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_proc; } void doFinalize() Q_DECL_OVERRIDE { /* Make sure the data is written in the output here. If this is not done the output will be written in small chunks trough the eventloop causing an uncessary delay before the process has even a chance to work and finish. This delay is mainly noticabe on Windows where it can take ~30 seconds to write out a 10MB file in the 512 byte chunks gpgme serves. */ qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite() << " Bytes to be written"; while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT)); if (!m_proc->isClosed()) { m_proc->close(); } m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT); } void doCancel() Q_DECL_OVERRIDE { m_proc->terminate(); QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill); } QString label() const Q_DECL_OVERRIDE; private: QString doErrorString() const Q_DECL_OVERRIDE; private: const QString m_command; const QStringList m_arguments; const std::shared_ptr< redirect_close > m_proc; }; class FileOutput : public OutputImplBase { public: explicit FileOutput(const QString &fileName, const std::shared_ptr &policy); ~FileOutput() { qCDebug(KLEOPATRA_LOG) << this; } QString label() const Q_DECL_OVERRIDE { return QFileInfo(m_fileName).fileName(); } std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_tmpFile; } void doFinalize() Q_DECL_OVERRIDE; void doCancel() Q_DECL_OVERRIDE { qCDebug(KLEOPATRA_LOG) << this; } private: bool obtainOverwritePermission(); private: const QString m_fileName; std::shared_ptr< TemporaryFile > m_tmpFile; const std::shared_ptr m_policy; }; #ifndef QT_NO_CLIPBOARD class ClipboardOutput : public OutputImplBase { public: explicit ClipboardOutput(QClipboard::Mode mode); QString label() const Q_DECL_OVERRIDE; std::shared_ptr ioDevice() const Q_DECL_OVERRIDE { return m_buffer; } void doFinalize() Q_DECL_OVERRIDE; void doCancel() Q_DECL_OVERRIDE {} private: QString doErrorString() const Q_DECL_OVERRIDE { return QString(); } private: const QClipboard::Mode m_mode; std::shared_ptr m_buffer; }; #endif // QT_NO_CLIPBOARD } std::shared_ptr Output::createFromPipeDevice(assuan_fd_t fd, const QString &label) { std::shared_ptr po(new PipeOutput(fd)); po->setDefaultLabel(label); return po; } PipeOutput::PipeOutput(assuan_fd_t fd) : OutputImplBase(), m_io(new inhibit_close) { errno = 0; if (!m_io->open(fd, QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for writing", assuanFD2int(fd))); } std::shared_ptr Output::createFromFile(const QString &fileName, bool forceOverwrite) { return createFromFile(fileName, std::shared_ptr(new OverwritePolicy(0, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny))); } std::shared_ptr Output::createFromFile(const QString &fileName, const std::shared_ptr &policy) { std::shared_ptr fo(new FileOutput(fileName, policy)); qCDebug(KLEOPATRA_LOG) << fo.get(); return fo; } FileOutput::FileOutput(const QString &fileName, const std::shared_ptr &policy) : OutputImplBase(), m_fileName(fileName), m_tmpFile(new TemporaryFile(fileName)), m_policy(policy) { assert(m_policy); errno = 0; if (!m_tmpFile->openNonInheritable()) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not create temporary file for output \"%1\"", fileName)); } bool FileOutput::obtainOverwritePermission() { if (m_policy->policy() != OverwritePolicy::Ask) { return m_policy->policy() == OverwritePolicy::Allow; } const int sel = KMessageBox::questionYesNoCancel(m_policy->parentWidget(), i18n("The file %1 already exists.\n" "Overwrite?", m_fileName), i18n("Overwrite Existing File?"), KStandardGuiItem::overwrite(), KGuiItem(i18n("Overwrite All")), KStandardGuiItem::cancel()); if (sel == KMessageBox::No) { //Overwrite All m_policy->setPolicy(OverwritePolicy::Allow); } return sel == KMessageBox::Yes || sel == KMessageBox::No; } void FileOutput::doFinalize() { qCDebug(KLEOPATRA_LOG) << this; struct Remover { QString file; ~Remover() { if (QFile::exists(file)) { QFile::remove(file); } } } remover; kleo_assert(m_tmpFile); if (m_tmpFile->isOpen()) { m_tmpFile->close(); } const QString tmpFileName = remover.file = m_tmpFile->oldFileName(); m_tmpFile->setAutoRemove(false); QPointer guard = m_tmpFile.get(); m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/ kleo_assert(!guard); // if this triggers, we need to audit for holder of std::shared_ptrs. qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; if (!obtainOverwritePermission()) throw Exception(gpg_error(GPG_ERR_CANCELED), i18n("Overwriting declined")); qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName; if (!QFile::remove(m_fileName)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not remove file \"%1\" for overwriting.", m_fileName)); qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not rename file \"%1\" to \"%2\"", tmpFileName, m_fileName)); } std::shared_ptr Output::createFromProcessStdIn(const QString &command) { return std::shared_ptr(new ProcessStdInOutput(command, QStringList(), QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args) { return std::shared_ptr(new ProcessStdInOutput(command, args, QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd) { return std::shared_ptr(new ProcessStdInOutput(command, args, wd)); } ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd) : OutputImplBase(), m_command(cmd), m_arguments(args), m_proc(new redirect_close) { qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << endl << cmd << args; if (cmd.isEmpty()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified")); m_proc->setWorkingDirectory(wd.absolutePath()); m_proc->start(cmd, args); m_proc->setReadChannel(QProcess::StandardError); if (!m_proc->waitForStarted()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); } QString ProcessStdInOutput::label() const { if (!m_proc) { return OutputImplBase::label(); } // output max. 3 arguments - const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QStringLiteral(" ")); + const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' ')); if (m_arguments.size() > 3) { return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline); } else { return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline); } } QString ProcessStdInOutput::doErrorString() const { kleo_assert(m_proc); if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) { return QString(); } if (m_proc->error() == QProcess::UnknownError) return i18n("Error while running %1: %2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", m_command, m_proc->errorString()); } } #ifndef QT_NO_CLIPBOARD std::shared_ptr Output::createFromClipboard() { return std::shared_ptr(new ClipboardOutput(QClipboard::Clipboard)); } ClipboardOutput::ClipboardOutput(QClipboard::Mode mode) : OutputImplBase(), m_mode(mode), m_buffer(new QBuffer) { errno = 0; if (!m_buffer->open(QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not write to clipboard")); } QString ClipboardOutput::label() const { switch (m_mode) { case QClipboard::Clipboard: return i18n("Clipboard"); case QClipboard::FindBuffer: return i18n("Find buffer"); case QClipboard::Selection: return i18n("Selection"); } return QString(); } void ClipboardOutput::doFinalize() { if (m_buffer->isOpen()) { m_buffer->close(); } if (QClipboard *const cb = QApplication::clipboard()) { cb->setText(QString::fromUtf8(m_buffer->data())); } else throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not find clipboard")); } #endif // QT_NO_CLIPBOARD Output::~Output() {}