diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp index 5beb97298..0c384826b 100644 --- a/src/commands/exportsecretkeycommand.cpp +++ b/src/commands/exportsecretkeycommand.cpp @@ -1,312 +1,317 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportsecretkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "exportsecretkeycommand.h" #include "command_p.h" #include "fileoperationspreferences.h" #include "utils/filedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; namespace { QString getLastUsedExportDirectory() { KConfigGroup config{KSharedConfig::openConfig(), "ExportDialog"}; return config.readEntry("LastDirectory", QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation)); } void updateLastUsedExportDirectory(const QString &path) { KConfigGroup config{KSharedConfig::openConfig(), "ExportDialog"}; config.writeEntry("LastDirectory", QFileInfo{path}.absolutePath()); } QString openPGPCertificateFileExtension() { return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt())}; } QString cmsCertificateFileExtension() { return QLatin1String{outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM, /*usePGPFileExt=*/false)}; } QString certificateFileExtension(GpgME::Protocol protocol) { switch (protocol) { case GpgME::OpenPGP: return openPGPCertificateFileExtension(); case GpgME::CMS: return cmsCertificateFileExtension(); default: qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; return QStringLiteral("txt"); } } QString proposeFilename(const Key &key) { QString filename; auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); /* Not translated so it's better to use in tutorials etc. */ filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID); filename.replace(u'/', u'_'); return getLastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol()); } QString secretKeyFileFilters(GpgME::Protocol protocol) { switch (protocol) { case GpgME::OpenPGP: return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"}; case GpgME::CMS: return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.p12)"}; default: qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; return i18nc("description of filename filter", "All Files") + QLatin1String{" (*)"}; } } QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent) { auto filename = FileDialog::getSaveFileNameEx( parent, i18nc("@title:window", "Secret Key Backup"), QStringLiteral("imp"), proposedFilename, secretKeyFileFilters(key.protocol())); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += u'.' + certificateFileExtension(key.protocol()); } updateLastUsedExportDirectory(filename); } return filename; } QString errorCaption() { return i18nc("@title:window", "Secret Key Backup Error"); } } class ExportSecretKeyCommand::Private : public Command::Private { friend class ::ExportSecretKeyCommand; ExportSecretKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr); ~Private() override; void start(); void cancel(); private: std::unique_ptr startExportJob(const Key &key); void onExportJobResult(const Error &err, const QByteArray &keyData); void showError(const Error &err); private: QString filename; QPointer job; }; ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() { return static_cast(d.get()); } const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c) : Command::Private{qq, c} { } ExportSecretKeyCommand::Private::~Private() = default; void ExportSecretKeyCommand::Private::start() { const Key key = this->key(); if (key.isNull()) { finished(); return; } filename = requestFilename(key, proposeFilename(key), parentWidgetOrView()); if (filename.isEmpty()) { canceled(); return; } auto exportJob = startExportJob(key); if (!exportJob) { finished(); return; } job = exportJob.release(); } void ExportSecretKeyCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } std::unique_ptr ExportSecretKeyCommand::Private::startExportJob(const Key &key) { #ifdef QGPGME_SUPPORTS_SECRET_KEY_EXPORT const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive); const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); std::unique_ptr exportJob{backend->secretKeyExportJob(armor)}; Q_ASSERT(exportJob); connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) { onExportJobResult(err, keyData); }); connect(exportJob.get(), &QGpgME::Job::progress, q, &Command::progress); const GpgME::Error err = exportJob->start({QLatin1String{key.primaryFingerprint()}}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Backing up secret key...")); return exportJob; #else Q_UNUSED(key) return {}; #endif } void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) { + if (err.isCanceled()) { + finished(); + return; + } + if (err) { showError(err); finished(); return; } if (keyData.isEmpty()) { error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."), errorCaption()); finished(); return; } QFile f{filename}; if (!f.open(QIODevice::WriteOnly)) { error(xi18nc("@info", "Cannot open file %1 for writing.", filename), errorCaption()); finished(); return; } const auto bytesWritten = f.write(keyData); if (bytesWritten != keyData.size()) { error(xi18nc("@info", "Writing key to file %1 failed.", filename), errorCaption()); finished(); return; } information(i18nc("@info", "The backup of the secret key was created successfully."), i18nc("@title:window", "Secret Key Backup")); finished(); } void ExportSecretKeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the backup of the secret key:" "%1", QString::fromLocal8Bit(err.asString())), errorCaption()); } ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller) : Command{view, new Private{this, controller}} { } ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key) : Command{key, new Private{this}} { } ExportSecretKeyCommand::~ExportSecretKeyCommand() = default; void ExportSecretKeyCommand::doStart() { d->start(); } void ExportSecretKeyCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_exportsecretkeycommand.cpp"