diff --git a/src/commands/exportsecretsubkeycommand.cpp b/src/commands/exportsecretsubkeycommand.cpp index 168d62c9e..6c4ab376e 100644 --- a/src/commands/exportsecretsubkeycommand.cpp +++ b/src/commands/exportsecretsubkeycommand.cpp @@ -1,296 +1,303 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportsecretsubkeycommand.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 "exportsecretsubkeycommand.h" #include "command_p.h" #include "fileoperationspreferences.h" #include "utils/filedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { #ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 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 proposeFilename(const std::vector &subkeys) { QString filename; if (subkeys.size() == 1) { const auto subkey = subkeys.front(); const auto key = subkey.parent(); auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData()); const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"}); /* Not translated so it's better to use in tutorials etc. */ - filename += QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg( + filename = QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg( name, shortKeyID, shortSubkeyID, usage); } else { - filename += i18nc("Generic filename for exported subkeys", "subkeys"); + filename = i18nc("Generic filename for exported subkeys", "subkeys"); } filename.replace(u'/', u'_'); return getLastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension(); } QString requestFilename(const std::vector &subkeys, const QString &proposedFilename, QWidget *parent) { auto filename = FileDialog::getSaveFileNameEx( parent, i18ncp("@title:window", "Export Subkey", "Export Subkeys", subkeys.size()), QStringLiteral("imp"), proposedFilename, i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"}); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += u'.' + openPGPCertificateFileExtension(); } updateLastUsedExportDirectory(filename); } return filename; } template QStringList getSubkeyFingerprints(const SubkeyContainer &subkeys) { QStringList fingerprints; fingerprints.reserve(subkeys.size()); std::transform(std::begin(subkeys), std::end(subkeys), std::back_inserter(fingerprints), [](const auto &subkey) { return QLatin1String{subkey.fingerprint()} + u'!'; }); return fingerprints; } #endif } class ExportSecretSubkeyCommand::Private : public Command::Private { friend class ::ExportSecretSubkeyCommand; ExportSecretSubkeyCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportSecretSubkeyCommand *qq); ~Private() override; void start(); void cancel(); private: std::unique_ptr startExportJob(const std::vector &subkeys); void onExportJobResult(const Error &err, const QByteArray &keyData); void showError(const Error &err); private: std::vector subkeys; QString filename; QPointer job; }; ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() { return static_cast(d.get()); } const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportSecretSubkeyCommand::Private::Private(ExportSecretSubkeyCommand *qq) : Command::Private{qq} { } ExportSecretSubkeyCommand::Private::~Private() = default; void ExportSecretSubkeyCommand::Private::start() { #ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT if (subkeys.empty()) { finished(); return; } filename = requestFilename(subkeys, proposeFilename(subkeys), parentWidgetOrView()); if (filename.isEmpty()) { canceled(); return; } auto exportJob = startExportJob(subkeys); if (!exportJob) { finished(); return; } job = exportJob.release(); #else Q_ASSERT(!"This command is not supported by the backend it was compiled against"); finished(); return; #endif } void ExportSecretSubkeyCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } std::unique_ptr ExportSecretSubkeyCommand::Private::startExportJob(const std::vector &subkeys) { #ifdef QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive); std::unique_ptr exportJob{QGpgME::openpgp()->secretSubkeyExportJob(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(getSubkeyFingerprints(subkeys)); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Exporting subkeys...")); return exportJob; #else Q_UNUSED(subkeys) return {}; #endif } void ExportSecretSubkeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) { if (err) { showError(err); finished(); return; } if (keyData.isEmpty()) { error(i18nc("@info", "The result of the export is empty."), i18nc("@title:window", "Export Failed")); finished(); return; } QFile f{filename}; if (!f.open(QIODevice::WriteOnly)) { error(xi18nc("@info", "Cannot open file %1 for writing.", filename), i18nc("@title:window", "Export Failed")); finished(); return; } const auto bytesWritten = f.write(keyData); if (bytesWritten != keyData.size()) { error(xi18ncp("@info", - "Writing subkey to file %1 failed.", - "Writing subkeys to file %1 failed.", + "Writing subkey to file %2 failed.", + "Writing subkeys to file %2 failed.", subkeys.size(), filename), i18nc("@title:window", "Export Failed")); + finished(); + return; } + information(i18ncp("@info", + "The subkey was exported successfully.", + "%1 subkeys were exported successfully.", + subkeys.size()), + i18nc("@title:window", "Secret Key Backup")); finished(); } void ExportSecretSubkeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the export:" "%1", QString::fromLocal8Bit(err.asString())), i18nc("@title:window", "Export Failed")); } ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector &subkeys) : Command{new Private{this}} { d->subkeys = subkeys; } ExportSecretSubkeyCommand::~ExportSecretSubkeyCommand() = default; void ExportSecretSubkeyCommand::doStart() { d->start(); } void ExportSecretSubkeyCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_exportsecretsubkeycommand.cpp"