Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F23020796
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
66 KB
Subscribers
None
View Options
diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp
index 09194fa49..48332908b 100644
--- a/src/commands/exportcertificatecommand.cpp
+++ b/src/commands/exportcertificatecommand.cpp
@@ -1,366 +1,366 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportcertificatecommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2021 g10 Code GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportcertificatecommand.h"
#include "fileoperationspreferences.h"
#include "command_p.h"
#include <utils/applicationstate.h>
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QSaveFile>
#include <QFileInfo>
#include <QMap>
#include <QPointer>
#include <algorithm>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
class ExportCertificateCommand::Private : public Command::Private
{
friend class ::ExportCertificateCommand;
ExportCertificateCommand *q_func() const
{
return static_cast<ExportCertificateCommand *>(q);
}
public:
explicit Private(ExportCertificateCommand *qq, KeyListController *c);
~Private() override;
void startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void cancelJobs();
void exportResult(const GpgME::Error &, const QByteArray &);
void showError(const GpgME::Error &error);
bool requestFileNames(GpgME::Protocol prot);
void finishedIfLastJob();
private:
QMap<GpgME::Protocol, QString> fileNames;
uint jobsPending = 0;
QMap<QObject *, QString> outFileForSender;
QPointer<ExportJob> cmsJob;
QPointer<ExportJob> pgpJob;
};
ExportCertificateCommand::Private *ExportCertificateCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c)
: Command::Private(qq, c)
{
}
ExportCertificateCommand::Private::~Private()
{
}
ExportCertificateCommand::ExportCertificateCommand(KeyListController *p)
: Command(new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p)
: Command(v, new Private(this, p))
{
}
ExportCertificateCommand::ExportCertificateCommand(const Key &key)
: Command(key, new Private(this, nullptr))
{
}
ExportCertificateCommand::~ExportCertificateCommand()
{
}
void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[OpenPGP] = fileName;
}
}
QString ExportCertificateCommand::openPGPFileName() const
{
return d->fileNames[OpenPGP];
}
void ExportCertificateCommand::setX509FileName(const QString &fileName)
{
if (!d->jobsPending) {
d->fileNames[CMS] = fileName;
}
}
QString ExportCertificateCommand::x509FileName() const
{
return d->fileNames[CMS];
}
void ExportCertificateCommand::doStart()
{
std::vector<Key> keys = d->keys();
if (keys.empty()) {
return;
}
const auto firstCms = std::partition(keys.begin(), keys.end(), [](const GpgME::Key &key) {
return key.protocol() != GpgME::CMS;
});
std::vector<Key> openpgp, cms;
std::copy(keys.begin(), firstCms, std::back_inserter(openpgp));
std::copy(firstCms, keys.end(), std::back_inserter(cms));
Q_ASSERT(!openpgp.empty() || !cms.empty());
const bool haveBoth = !cms.empty() && !openpgp.empty();
const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!cms.empty() ? CMS : OpenPGP);
if (!d->requestFileNames(prot)) {
Q_EMIT canceled();
d->finished();
} else {
if (!openpgp.empty()) {
d->startExportJob(GpgME::OpenPGP, openpgp);
}
if (!cms.empty()) {
d->startExportJob(GpgME::CMS, cms);
}
}
}
bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol)
{
if (protocol == UnknownProtocol) {
if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) {
return true;
}
/* Unknown protocol ask for first PGP Export file name */
if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) {
return false;
}
/* And then for CMS */
return requestFileNames(GpgME::CMS);
}
if (!fileNames[protocol].isEmpty()) {
return true;
}
const auto lastDir = ApplicationState::lastUsedExportDirectory();
QString proposedFileName = lastDir + QLatin1Char('/');
if (keys().size() == 1) {
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
const auto key = keys().front();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
const auto asciiArmoredCertificateClass = (protocol == OpenPGP ? Class::OpenPGP : Class::CMS) | Class::Ascii | Class::Certificate;
/* Not translated so it's better to use in tutorials etc. */
proposedFileName += QStringLiteral("%1_%2_public.%3")
.arg(name)
.arg(Formatting::prettyKeyID(key.shortKeyID()))
- .arg(QString::fromLatin1(outputFileExtension(asciiArmoredCertificateClass, usePGPFileExt)));
+ .arg(outputFileExtension(asciiArmoredCertificateClass, usePGPFileExt));
}
if (protocol == GpgME::CMS) {
if (!fileNames[GpgME::OpenPGP].isEmpty()) {
/* If the user has already selected a PGP file name then use that as basis
* for a proposal for the S/MIME file. */
proposedFileName = fileNames[GpgME::OpenPGP];
const int idx = proposedFileName.size() - 4;
if (proposedFileName.endsWith(QLatin1String(".asc"))) {
proposedFileName.replace(idx, 4, QLatin1String(".pem"));
}
if (proposedFileName.endsWith(QLatin1String(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) {
proposedFileName.replace(idx, 4, QLatin1String(".der"));
}
}
}
if (proposedFileName.isEmpty()) {
proposedFileName = lastDir;
proposedFileName += i18nc("A generic filename for exported certificates", "certificates");
proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(),
i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)),
QStringLiteral("imp"),
proposedFileName,
protocol == GpgME::OpenPGP ? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)")
: i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)"));
if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) {
KMessageBox::error(parentWidgetOrView(), i18n("You have to select different filenames for different protocols."), i18n("Export Error"));
return false;
}
const QFileInfo fi(fname);
if (fi.suffix().isEmpty()) {
fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem");
}
fileNames[protocol] = fname;
ApplicationState::setLastUsedExportDirectory(fi.absolutePath());
return !fname.isEmpty();
}
void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
Q_ASSERT(protocol != GpgME::UnknownProtocol);
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
const QString fileName = fileNames[protocol];
const bool binary = protocol == GpgME::OpenPGP
? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive)
: fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive);
std::unique_ptr<ExportJob> job(backend->publicKeyExportJob(!binary));
Q_ASSERT(job.get());
connect(job.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &result, const QByteArray &keyData) {
exportResult(result, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(job.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
QStringList fingerprints;
fingerprints.reserve(keys.size());
for (const Key &i : keys) {
fingerprints << QLatin1String(i.primaryFingerprint());
}
const GpgME::Error err = job->start(fingerprints);
if (err) {
showError(err);
finished();
return;
}
Q_EMIT q->info(i18n("Exporting certificates..."));
++jobsPending;
const QPointer<ExportJob> exportJob(job.release());
outFileForSender[exportJob.data()] = fileName;
(protocol == CMS ? cmsJob : pgpJob) = exportJob;
}
void ExportCertificateCommand::Private::showError(const GpgME::Error &err)
{
Q_ASSERT(err);
const QString msg = i18n(
"<qt><p>An error occurred while trying to export "
"the certificate:</p>"
"<p><b>%1</b></p></qt>",
Formatting::errorAsString(err));
error(msg, i18n("Certificate Export Failed"));
}
void ExportCertificateCommand::doCancel()
{
d->cancelJobs();
}
void ExportCertificateCommand::Private::finishedIfLastJob()
{
if (jobsPending <= 0) {
finished();
}
}
static bool write_complete(QIODevice &iod, const QByteArray &data)
{
qint64 total = 0;
qint64 toWrite = data.size();
while (total < toWrite) {
const qint64 written = iod.write(data.data() + total, toWrite);
if (written < 0) {
return false;
}
total += written;
toWrite -= written;
}
return true;
}
void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data)
{
Q_ASSERT(jobsPending > 0);
--jobsPending;
Q_ASSERT(outFileForSender.contains(q->sender()));
const QString outFile = outFileForSender[q->sender()];
if (err) {
showError(err);
finishedIfLastJob();
return;
}
QSaveFile savefile(outFile);
// TODO: use KIO
const QString writeErrorMsg = i18n("Could not write to file %1.", outFile);
const QString errorCaption = i18n("Certificate Export Failed");
if (!savefile.open(QIODevice::WriteOnly)) {
error(writeErrorMsg, errorCaption);
finishedIfLastJob();
return;
}
if (!write_complete(savefile, data) || !savefile.commit()) {
error(writeErrorMsg, errorCaption);
}
finishedIfLastJob();
}
void ExportCertificateCommand::Private::cancelJobs()
{
if (cmsJob) {
cmsJob->slotCancel();
}
if (pgpJob) {
pgpJob->slotCancel();
}
}
#undef d
#undef q
#include "moc_exportcertificatecommand.cpp"
diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp
index a4ccd9fbc..3901d018c 100644
--- a/src/commands/exportsecretkeycommand.cpp
+++ b/src/commands/exportsecretkeycommand.cpp
@@ -1,310 +1,310 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "command_p.h"
#include "exportsecretkeycommand.h"
#include "fileoperationspreferences.h"
#include "utils/filedialog.h"
#include <utils/applicationstate.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <QFileInfo>
#include <QStandardPaths>
#include <gpgme++/context.h>
#include <algorithm>
#include <memory>
#include <vector>
#include <kleopatra_debug.h>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace
{
QString openPGPCertificateFileExtension()
{
- return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt())};
+ return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt());
}
QString cmsCertificateFileExtension()
{
- return QLatin1String{outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM,
- /*usePGPFileExt=*/false)};
+ return 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 ApplicationState::lastUsedExportDirectory() + 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());
}
ApplicationState::setLastUsedExportDirectory(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<ExportSecretKeyCommand *>(q);
}
public:
explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr<QGpgME::ExportJob> startExportJob(const Key &key);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
QString filename;
QPointer<QGpgME::ExportJob> job;
};
ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const
{
return static_cast<const Private *>(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<QGpgME::ExportJob> ExportSecretKeyCommand::Private::startExportJob(const Key &key)
{
#if 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<QGpgME::ExportJob> exportJob{backend->secretKeyExportJob(armor)};
Q_ASSERT(exportJob);
if (key.protocol() == GpgME::CMS) {
exportJob->setExportFlags(GpgME::Context::ExportPKCS12);
}
connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(err, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(exportJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
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 <filename>%1</filename> for writing.", filename), errorCaption());
finished();
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18nc("@info", "Writing key to file <filename>%1</filename> 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",
"<para>An error occurred during the backup of the secret key:</para>"
"<para><message>%1</message></para>",
Formatting::errorAsString(err)),
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"
diff --git a/src/commands/exportsecretkeycommand_old.cpp b/src/commands/exportsecretkeycommand_old.cpp
index cbb5daca4..43407b84b 100644
--- a/src/commands/exportsecretkeycommand_old.cpp
+++ b/src/commands/exportsecretkeycommand_old.cpp
@@ -1,174 +1,174 @@
/* -*- mode: c++; c-basic-offset:4 -*-
commands/exportsecretkeycommand_old.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "exportsecretkeycommand_old.h"
#include "fileoperationspreferences.h"
#include "command_p.h"
#include <Libkleo/GnuPG>
#include <utils/filedialog.h>
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <gpgme++/key.h>
#include <KLocalizedString>
#include <QFile>
#include <QProcess>
using namespace Kleo;
using namespace Kleo::Commands;
using namespace GpgME;
namespace Kleo::Commands::Compat
{
ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *v, KeyListController *c)
: GnuPGProcessCommand(v, c)
{
}
ExportSecretKeyCommand::ExportSecretKeyCommand(const Key &key)
: GnuPGProcessCommand(key)
{
}
ExportSecretKeyCommand::~ExportSecretKeyCommand()
{
}
bool ExportSecretKeyCommand::preStartHook(QWidget *parent) const
{
if (!m_filename.isEmpty()) {
return true;
}
const auto key = d->key();
const auto protocol = key.protocol();
QString proposedFileName;
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
auto name = Formatting::prettyName(key);
if (name.isEmpty()) {
name = Formatting::prettyEMail(key);
}
/* Not translated so it's better to use in tutorials etc. */
- proposedFileName = QStringLiteral("%1_%2_SECRET.%3")
- .arg(name)
- .arg(Formatting::prettyKeyID(key.shortKeyID()))
- .arg(QString::fromLatin1(outputFileExtension(protocol == OpenPGP ? Class::OpenPGP | Class::Ascii | Class::Certificate
- : Class::CMS | Class::Binary | Class::ExportedPSM,
- usePGPFileExt)));
+ proposedFileName =
+ QStringLiteral("%1_%2_SECRET.%3")
+ .arg(name)
+ .arg(Formatting::prettyKeyID(key.shortKeyID()))
+ .arg(outputFileExtension(protocol == OpenPGP ? Class::OpenPGP | Class::Ascii | Class::Certificate : Class::CMS | Class::Binary | Class::ExportedPSM,
+ usePGPFileExt));
m_filename = FileDialog::getSaveFileNameEx(parent ? parent : d->parentWidgetOrView(),
i18n("Backup Secret Key"),
QStringLiteral("imp"),
proposedFileName,
protocol == GpgME::OpenPGP ? i18n("Secret Key Files") + QLatin1String(" (*.asc *.gpg *.pgp)")
: i18n("Secret Key Files") + QLatin1String(" (*.p12)"));
m_armor = m_filename.endsWith(QLatin1String(".asc"));
return !m_filename.isEmpty();
}
QStringList ExportSecretKeyCommand::arguments() const
{
const Key key = d->key();
QStringList result;
if (key.protocol() == OpenPGP) {
result << gpgPath() << QStringLiteral("--batch");
} else {
result << gpgSmPath();
}
result << QStringLiteral("--yes") << QStringLiteral("--output") << QStringLiteral("-");
if (m_armor) {
result << QStringLiteral("--armor");
}
if (key.protocol() == CMS) {
result << QStringLiteral("--p12-charset") << QStringLiteral("utf-8");
}
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",
"<para>The GPG or GpgSM process that tried to export the secret key "
"ended prematurely because of an unexpected error.</para>"
"<para>Please check the output of <icode>%1</icode> for details.</para>",
args.join(QLatin1Char(' ')));
}
QString ExportSecretKeyCommand::errorExitMessage(const QStringList &args) const
{
return xi18nc("@info",
"<para>An error occurred while trying to export the secret key.</para> "
"<para>The output from <command>%1</command> was: <message>%2</message></para>",
args[0],
errorString());
}
QString ExportSecretKeyCommand::successMessage(const QStringList &) const
{
if (mHasError) {
return QString();
}
return i18nc("@info", "Secret key successfully exported.");
}
void ExportSecretKeyCommand::postSuccessHook(QWidget *)
{
Q_ASSERT(process());
const auto data = process()->readAllStandardOutput();
if (!data.size()) {
d->error(i18nc("@info", "Possibly bad passphrase given."), errorCaption());
mHasError = true;
return;
}
QFile file(m_filename);
/* The filedialog already asked for replace ok. */
file.open(QIODevice::ReadWrite | QIODevice::Truncate);
if (file.write(data) != data.size()) {
d->error(i18nc("@info", "Failed to write data."), errorCaption());
mHasError = true;
}
file.close();
}
}
diff --git a/src/commands/exportsecretsubkeycommand.cpp b/src/commands/exportsecretsubkeycommand.cpp
index 8dfb0ae9e..91a7466e5 100644
--- a/src/commands/exportsecretsubkeycommand.cpp
+++ b/src/commands/exportsecretsubkeycommand.cpp
@@ -1,294 +1,294 @@
/* -*- 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 <dev@ingo-kloecker.de>
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "command_p.h"
#include "exportsecretsubkeycommand.h"
#include "fileoperationspreferences.h"
#include <utils/applicationstate.h>
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
#include "utils/filedialog.h"
#endif
#include <Libkleo/Classify>
#include <Libkleo/Formatting>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <QFileInfo>
#include <QStandardPaths>
#include <algorithm>
#include <memory>
#include <vector>
using namespace Kleo;
using namespace GpgME;
namespace
{
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
QString openPGPCertificateFileExtension()
{
- return QLatin1String{outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt())};
+ return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt());
}
QString proposeFilename(const std::vector<Subkey> &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(name, shortKeyID, shortSubkeyID, usage);
} else {
filename = i18nc("Generic filename for exported subkeys", "subkeys");
}
filename.replace(u'/', u'_');
return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension();
}
QString requestFilename(const std::vector<Subkey> &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();
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
template<typename SubkeyContainer>
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<ExportSecretSubkeyCommand *>(q);
}
public:
explicit Private(ExportSecretSubkeyCommand *qq);
~Private() override;
void start();
void cancel();
private:
std::unique_ptr<QGpgME::ExportJob> startExportJob(const std::vector<Subkey> &subkeys);
void onExportJobResult(const Error &err, const QByteArray &keyData);
void showError(const Error &err);
private:
std::vector<Subkey> subkeys;
QString filename;
QPointer<QGpgME::ExportJob> job;
};
ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const
{
return static_cast<const Private *>(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()
{
#if 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<QGpgME::ExportJob> ExportSecretSubkeyCommand::Private::startExportJob(const std::vector<Subkey> &subkeys)
{
#if QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT
const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive);
std::unique_ptr<QGpgME::ExportJob> 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);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(exportJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
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 (err.isCanceled()) {
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 <filename>%1</filename> 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 <filename>%2</filename> failed.",
"Writing subkeys to file <filename>%2</filename> 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",
"<para>An error occurred during the export:</para>"
"<para><message>%1</message></para>",
Formatting::errorAsString(err)),
i18nc("@title:window", "Export Failed"));
}
ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector<GpgME::Subkey> &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"
diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp
index 31a314e95..0224a4327 100644
--- a/src/crypto/signencryptfilescontroller.cpp
+++ b/src/crypto/signencryptfilescontroller.cpp
@@ -1,790 +1,791 @@
/* -*- mode: c++; c-basic-offset:4 -*-
crypto/signencryptfilescontroller.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB
SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik
SPDX-FileContributor: Intevation GmbH
SPDX-License-Identifier: GPL-2.0-or-later
*/
#include <config-kleopatra.h>
#include "signencryptfilescontroller.h"
#include "certificateresolver.h"
#include "signencrypttask.h"
#include "crypto/gui/signencryptfileswizard.h"
#include "crypto/taskcollection.h"
#include "fileoperationspreferences.h"
#include "utils/archivedefinition.h"
#include "utils/input.h"
#include "utils/kleo_assert.h"
#include "utils/output.h"
#include "utils/path-helper.h"
#include <Libkleo/Classify>
#include <Libkleo/KleoException>
#include "kleopatra_debug.h"
#include <KLocalizedString>
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
#include <QGpgME/SignEncryptArchiveJob>
#endif
#include <QDir>
#include <QFileInfo>
#include <QPointer>
#include <QTimer>
using namespace Kleo;
using namespace Kleo::Crypto;
using namespace GpgME;
using namespace KMime::Types;
class SignEncryptFilesController::Private
{
friend class ::Kleo::Crypto::SignEncryptFilesController;
SignEncryptFilesController *const q;
public:
explicit Private(SignEncryptFilesController *qq);
~Private();
private:
void slotWizardOperationPrepared();
void slotWizardCanceled();
private:
void ensureWizardCreated();
void ensureWizardVisible();
void updateWizardMode();
void cancelAllTasks();
void reportError(int err, const QString &details)
{
q->setLastError(err, details);
q->emitDoneOrError();
}
void schedule();
std::shared_ptr<SignEncryptTask> takeRunnable(GpgME::Protocol proto);
static void assertValidOperation(unsigned int);
static QString titleForOperation(unsigned int op);
private:
std::vector<std::shared_ptr<SignEncryptTask>> runnable, completed;
std::shared_ptr<SignEncryptTask> cms, openpgp;
QPointer<SignEncryptFilesWizard> wizard;
QStringList files;
unsigned int operation;
Protocol protocol;
};
SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq)
: q(qq)
, runnable()
, cms()
, openpgp()
, wizard()
, files()
, operation(SignAllowed | EncryptAllowed | ArchiveAllowed)
, protocol(UnknownProtocol)
{
}
SignEncryptFilesController::Private::~Private()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
}
QString SignEncryptFilesController::Private::titleForOperation(unsigned int op)
{
const bool signDisallowed = (op & SignMask) == SignDisallowed;
const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed;
const bool archiveSelected = (op & ArchiveMask) == ArchiveForced;
kleo_assert(!signDisallowed || !encryptDisallowed);
if (!signDisallowed && encryptDisallowed) {
if (archiveSelected) {
return i18n("Archive and Sign Files");
} else {
return i18n("Sign Files");
}
}
if (signDisallowed && !encryptDisallowed) {
if (archiveSelected) {
return i18n("Archive and Encrypt Files");
} else {
return i18n("Encrypt Files");
}
}
if (archiveSelected) {
return i18n("Archive and Sign/Encrypt Files");
} else {
return i18n("Sign/Encrypt Files");
}
}
SignEncryptFilesController::SignEncryptFilesController(QObject *p)
: Controller(p)
, d(new Private(this))
{
}
SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *p)
: Controller(ctx, p)
, d(new Private(this))
{
}
SignEncryptFilesController::~SignEncryptFilesController()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
if (d->wizard && !d->wizard->isVisible()) {
delete d->wizard;
}
// d->wizard->close(); ### ?
}
void SignEncryptFilesController::setProtocol(Protocol proto)
{
kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto);
d->protocol = proto;
d->ensureWizardCreated();
}
Protocol SignEncryptFilesController::protocol() const
{
return d->protocol;
}
// static
void SignEncryptFilesController::Private::assertValidOperation(unsigned int op)
{
kleo_assert((op & SignMask) == SignDisallowed || //
(op & SignMask) == SignAllowed || //
(op & SignMask) == SignSelected);
kleo_assert((op & EncryptMask) == EncryptDisallowed || //
(op & EncryptMask) == EncryptAllowed || //
(op & EncryptMask) == EncryptSelected);
kleo_assert((op & ArchiveMask) == ArchiveDisallowed || //
(op & ArchiveMask) == ArchiveAllowed || //
(op & ArchiveMask) == ArchiveForced);
kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0);
}
void SignEncryptFilesController::setOperationMode(unsigned int mode)
{
Private::assertValidOperation(mode);
d->operation = mode;
d->updateWizardMode();
}
void SignEncryptFilesController::Private::updateWizardMode()
{
if (!wizard) {
return;
}
wizard->setWindowTitle(titleForOperation(operation));
const unsigned int signOp = (operation & SignMask);
const unsigned int encrOp = (operation & EncryptMask);
const unsigned int archOp = (operation & ArchiveMask);
if (signOp == SignDisallowed) {
wizard->setSigningUserMutable(false);
wizard->setSigningPreset(false);
} else {
wizard->setSigningUserMutable(true);
wizard->setSigningPreset(signOp == SignSelected);
}
if (encrOp == EncryptDisallowed) {
wizard->setEncryptionPreset(false);
wizard->setEncryptionUserMutable(false);
} else {
wizard->setEncryptionUserMutable(true);
wizard->setEncryptionPreset(encrOp == EncryptSelected);
}
wizard->setArchiveForced(archOp == ArchiveForced);
wizard->setArchiveMutable(archOp == ArchiveAllowed);
}
unsigned int SignEncryptFilesController::operationMode() const
{
return d->operation;
}
-static const char *extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached)
+static QString extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached)
{
unsigned int cls = pgp ? Class::OpenPGP : Class::CMS;
if (encrypt) {
cls |= Class::CipherText;
} else if (sign) {
cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature;
}
cls |= ascii ? Class::Ascii : Class::Binary;
const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt();
- if (const char *const ext = outputFileExtension(cls, usePGPFileExt)) {
+ const auto ext = outputFileExtension(cls, usePGPFileExt);
+ if (!ext.isEmpty()) {
return ext;
} else {
- return "out";
+ return QStringLiteral("out");
}
}
static std::shared_ptr<ArchiveDefinition> getDefaultAd()
{
const std::vector<std::shared_ptr<ArchiveDefinition>> ads = ArchiveDefinition::getArchiveDefinitions();
Q_ASSERT(!ads.empty());
std::shared_ptr<ArchiveDefinition> ad = ads.front();
const FileOperationsPreferences prefs;
const QString archiveCmd = prefs.archiveCommand();
auto it = std::find_if(ads.cbegin(), ads.cend(), [&archiveCmd](const std::shared_ptr<ArchiveDefinition> &toCheck) {
return toCheck->id() == archiveCmd;
});
if (it != ads.cend()) {
ad = *it;
}
return ad;
}
static QMap<int, QString> buildOutputNames(const QStringList &files, const bool archive)
{
QMap<int, QString> nameMap;
// Build the default names for the wizard.
QString baseNameCms;
QString baseNamePgp;
const QFileInfo firstFile(files.first());
if (archive) {
QString baseName = files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName();
baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(baseName);
const auto ad = getDefaultAd();
baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.');
baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.');
} else {
baseNameCms = baseNamePgp = files.first() + QLatin1Char('.');
}
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
- nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + QString::fromLatin1(extension(false, true, false, ascii, true)));
- nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + QString::fromLatin1(extension(false, false, true, ascii, false)));
- nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + QString::fromLatin1(extension(true, true, true, ascii, false)));
- nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + QString::fromLatin1(extension(true, false, true, ascii, false)));
- nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + QString::fromLatin1(extension(true, true, false, ascii, true)));
+ nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + extension(false, true, false, ascii, true));
+ nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + extension(false, false, true, ascii, false));
+ nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + extension(true, true, true, ascii, false));
+ nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + extension(true, false, true, ascii, false));
+ nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + extension(true, true, false, ascii, true));
nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files));
return nameMap;
}
static QMap<int, QString> buildOutputNamesForDir(const QString &file, const QMap<int, QString> &orig)
{
QMap<int, QString> ret;
const QString dir = orig.value(SignEncryptFilesWizard::Directory);
if (dir.isEmpty()) {
return orig;
}
// Build the default names for the wizard.
const QFileInfo fi(file);
const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.');
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
- ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + QString::fromLatin1(extension(false, true, false, ascii, true)));
- ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + QString::fromLatin1(extension(false, false, true, ascii, false)));
- ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + QString::fromLatin1(extension(true, true, true, ascii, false)));
- ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + QString::fromLatin1(extension(true, false, true, ascii, false)));
- ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + QString::fromLatin1(extension(true, true, false, ascii, true)));
+ ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + extension(false, true, false, ascii, true));
+ ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + extension(false, false, true, ascii, false));
+ ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + extension(true, true, true, ascii, false));
+ ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + extension(true, false, true, ascii, false));
+ ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + extension(true, true, false, ascii, true));
return ret;
}
// strips all trailing slashes from the filename, but keeps filename "/"
static QString stripTrailingSlashes(const QString &fileName)
{
if (fileName.size() < 2 || !fileName.endsWith(QLatin1Char('/'))) {
return fileName;
}
auto tmp = QStringView{fileName}.chopped(1);
while (tmp.size() > 1 && tmp.endsWith(QLatin1Char('/'))) {
tmp.chop(1);
}
return tmp.toString();
}
static QStringList stripTrailingSlashesForAll(const QStringList &fileNames)
{
QStringList result;
result.reserve(fileNames.size());
std::transform(fileNames.begin(), fileNames.end(), std::back_inserter(result), &stripTrailingSlashes);
return result;
}
void SignEncryptFilesController::setFiles(const QStringList &files)
{
kleo_assert(!files.empty());
d->files = stripTrailingSlashesForAll(files);
bool archive = false;
if (d->files.size() > 1) {
setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed);
archive = true;
}
for (const auto &file : d->files) {
if (QFileInfo(file).isDir()) {
setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced);
archive = true;
break;
}
}
d->ensureWizardCreated();
d->wizard->setSingleFile(!archive);
d->wizard->setOutputNames(buildOutputNames(d->files, archive));
}
void SignEncryptFilesController::Private::slotWizardCanceled()
{
qCDebug(KLEOPATRA_LOG) << q << __func__;
q->cancel();
reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel"));
}
void SignEncryptFilesController::start()
{
d->ensureWizardVisible();
}
static std::shared_ptr<SignEncryptTask> createSignEncryptTaskForFileInfo(const QFileInfo &fi,
bool ascii,
const std::vector<Key> &recipients,
const std::vector<Key> &signers,
const QString &outputName,
bool symmetric)
{
const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask);
Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric);
task->setAsciiArmor(ascii);
if (!signers.empty()) {
task->setSign(true);
task->setSigners(signers);
task->setDetachedSignature(true);
} else {
task->setSign(false);
}
if (!recipients.empty()) {
task->setEncrypt(true);
task->setRecipients(recipients);
task->setDetachedSignature(false);
} else {
task->setEncrypt(false);
}
task->setEncryptSymmetric(symmetric);
const QString input = fi.absoluteFilePath();
task->setInputFileName(input);
task->setInput(Input::createFromFile(input));
task->setOutputFileName(outputName);
return task;
}
static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol)
{
#if QGPGME_SUPPORTS_ARCHIVE_JOBS
return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported();
#else
return false;
#endif
}
static std::shared_ptr<SignEncryptTask> createArchiveSignEncryptTaskForFiles(const QStringList &files,
const std::shared_ptr<ArchiveDefinition> &ad,
bool pgp,
bool ascii,
const std::vector<Key> &recipients,
const std::vector<Key> &signers,
const QString &outputName,
bool symmetric)
{
const std::shared_ptr<SignEncryptTask> task(new SignEncryptTask);
task->setCreateArchive(true);
task->setEncryptSymmetric(symmetric);
Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric);
task->setAsciiArmor(ascii);
if (!signers.empty()) {
task->setSign(true);
task->setSigners(signers);
task->setDetachedSignature(false);
} else {
task->setSign(false);
}
if (!recipients.empty()) {
task->setEncrypt(true);
task->setRecipients(recipients);
} else {
task->setEncrypt(false);
}
const Protocol proto = pgp ? OpenPGP : CMS;
task->setInputFileNames(files);
if (!archiveJobsCanBeUsed(proto)) {
// use legacy archive creation
kleo_assert(ad);
task->setInput(ad->createInputFromPackCommand(proto, files));
}
task->setOutputFileName(outputName);
return task;
}
static std::vector<std::shared_ptr<SignEncryptTask>> createSignEncryptTasksForFileInfo(const QFileInfo &fi,
bool ascii,
const std::vector<Key> &pgpRecipients,
const std::vector<Key> &pgpSigners,
const std::vector<Key> &cmsRecipients,
const std::vector<Key> &cmsSigners,
const QMap<int, QString> &outputNames,
bool symmetric)
{
std::vector<std::shared_ptr<SignEncryptTask>> result;
const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty();
const bool cms = !cmsSigners.empty() || !cmsRecipients.empty();
result.reserve(pgp + cms);
if (pgp || symmetric) {
// Symmetric encryption is only supported for PGP
int outKind = 0;
if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) {
outKind = SignEncryptFilesWizard::CombinedPGP;
} else if (!pgpRecipients.empty() || symmetric) {
outKind = SignEncryptFilesWizard::EncryptedPGP;
} else {
outKind = SignEncryptFilesWizard::SignaturePGP;
}
result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric));
}
if (cms) {
// There is no combined sign / encrypt in gpgsm so we create one sign task
// and one encrypt task. Which leaves us with the age old dilemma, encrypt
// then sign, or sign then encrypt. Ugly.
if (!cmsSigners.empty()) {
result.push_back(
createSignEncryptTaskForFileInfo(fi, ascii, std::vector<Key>(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false));
}
if (!cmsRecipients.empty()) {
result.push_back(
createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients, std::vector<Key>(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false));
}
}
return result;
}
static std::vector<std::shared_ptr<SignEncryptTask>> createArchiveSignEncryptTasksForFiles(const QStringList &files,
const std::shared_ptr<ArchiveDefinition> &ad,
bool ascii,
const std::vector<Key> &pgpRecipients,
const std::vector<Key> &pgpSigners,
const std::vector<Key> &cmsRecipients,
const std::vector<Key> &cmsSigners,
const QMap<int, QString> &outputNames,
bool symmetric)
{
std::vector<std::shared_ptr<SignEncryptTask>> result;
const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty();
const bool cms = !cmsSigners.empty() || !cmsRecipients.empty();
result.reserve(pgp + cms);
if (pgp || symmetric) {
int outKind = 0;
if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) {
outKind = SignEncryptFilesWizard::CombinedPGP;
} else if (!pgpRecipients.empty() || symmetric) {
outKind = SignEncryptFilesWizard::EncryptedPGP;
} else {
outKind = SignEncryptFilesWizard::SignaturePGP;
}
result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric));
}
if (cms) {
if (!cmsSigners.empty()) {
result.push_back(createArchiveSignEncryptTaskForFiles(files,
ad,
false,
ascii,
std::vector<Key>(),
cmsSigners,
outputNames[SignEncryptFilesWizard::SignatureCMS],
false));
}
if (!cmsRecipients.empty()) {
result.push_back(createArchiveSignEncryptTaskForFiles(files,
ad,
false,
ascii,
cmsRecipients,
std::vector<Key>(),
outputNames[SignEncryptFilesWizard::EncryptedCMS],
false));
}
}
return result;
}
namespace
{
static auto resolveFileNameConflicts(const std::vector<std::shared_ptr<SignEncryptTask>> &tasks, QWidget *parent)
{
std::vector<std::shared_ptr<SignEncryptTask>> resolvedTasks;
OverwritePolicy overwritePolicy{parent, tasks.size() > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}};
for (auto &task : tasks) {
// by default, do not overwrite existing files
task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Skip));
const auto outputFileName = task->outputFileName();
if (QFile::exists(outputFileName)) {
const auto newFileName = overwritePolicy.obtainOverwritePermission(outputFileName);
if (newFileName.isEmpty()) {
if (overwritePolicy.policy() == OverwritePolicy::Cancel) {
resolvedTasks.clear();
break;
}
// else Skip -> do not add task to the final task list
continue;
} else if (newFileName != outputFileName) {
task->setOutputFileName(newFileName);
} else {
task->setOverwritePolicy(std::make_shared<OverwritePolicy>(OverwritePolicy::Overwrite));
}
}
resolvedTasks.push_back(task);
}
return resolvedTasks;
}
}
void SignEncryptFilesController::Private::slotWizardOperationPrepared()
{
try {
kleo_assert(wizard);
kleo_assert(!files.empty());
const bool archive = ((wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) //
|| ((operation & ArchiveMask) == ArchiveForced));
const std::vector<Key> recipients = wizard->resolvedRecipients();
const std::vector<Key> signers = wizard->resolvedSigners();
const FileOperationsPreferences prefs;
const bool ascii = prefs.addASCIIArmor();
std::vector<Key> pgpRecipients, cmsRecipients, pgpSigners, cmsSigners;
for (const Key &k : recipients) {
if (k.protocol() == GpgME::OpenPGP) {
pgpRecipients.push_back(k);
} else {
cmsRecipients.push_back(k);
}
}
for (const Key &k : signers) {
if (k.protocol() == GpgME::OpenPGP) {
pgpSigners.push_back(k);
} else {
cmsSigners.push_back(k);
}
}
std::vector<std::shared_ptr<SignEncryptTask>> tasks;
if (!archive) {
tasks.reserve(files.size());
}
if (archive) {
tasks = createArchiveSignEncryptTasksForFiles(files,
getDefaultAd(),
ascii,
pgpRecipients,
pgpSigners,
cmsRecipients,
cmsSigners,
wizard->outputNames(),
wizard->encryptSymmetric());
} else {
for (const QString &file : std::as_const(files)) {
const std::vector<std::shared_ptr<SignEncryptTask>> created =
createSignEncryptTasksForFileInfo(QFileInfo(file),
ascii,
pgpRecipients,
pgpSigners,
cmsRecipients,
cmsSigners,
buildOutputNamesForDir(file, wizard->outputNames()),
wizard->encryptSymmetric());
tasks.insert(tasks.end(), created.begin(), created.end());
}
}
tasks = resolveFileNameConflicts(tasks, wizard);
if (tasks.empty()) {
q->cancel();
return;
}
kleo_assert(runnable.empty());
runnable.swap(tasks);
for (const auto &task : std::as_const(runnable)) {
q->connectTask(task);
}
std::shared_ptr<TaskCollection> coll(new TaskCollection);
std::vector<std::shared_ptr<Task>> tmp;
std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp));
coll->setTasks(tmp);
wizard->setTaskCollection(coll);
QTimer::singleShot(0, q, SLOT(schedule()));
} catch (const Kleo::Exception &e) {
reportError(e.error().encodedError(), e.message());
} catch (const std::exception &e) {
reportError(
gpg_error(GPG_ERR_UNEXPECTED),
i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1", QString::fromLocal8Bit(e.what())));
} catch (...) {
reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared"));
}
}
void SignEncryptFilesController::Private::schedule()
{
if (!cms)
if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(CMS)) {
t->start();
cms = t;
}
if (!openpgp)
if (const std::shared_ptr<SignEncryptTask> t = takeRunnable(OpenPGP)) {
t->start();
openpgp = t;
}
if (!cms && !openpgp) {
kleo_assert(runnable.empty());
q->emitDoneOrError();
}
}
std::shared_ptr<SignEncryptTask> SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto)
{
const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr<Task> &task) {
return task->protocol() == proto;
});
if (it == runnable.end()) {
return std::shared_ptr<SignEncryptTask>();
}
const std::shared_ptr<SignEncryptTask> result = *it;
runnable.erase(it);
return result;
}
void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
{
Q_UNUSED(result)
Q_ASSERT(task);
// We could just delete the tasks here, but we can't use
// Qt::QueuedConnection here (we need sender()) and other slots
// might not yet have executed. Therefore, we push completed tasks
// into a burial container
if (task == d->cms.get()) {
d->completed.push_back(d->cms);
d->cms.reset();
} else if (task == d->openpgp.get()) {
d->completed.push_back(d->openpgp);
d->openpgp.reset();
}
QTimer::singleShot(0, this, SLOT(schedule()));
}
void SignEncryptFilesController::cancel()
{
qCDebug(KLEOPATRA_LOG) << this << __func__;
try {
if (d->wizard) {
d->wizard->close();
}
d->cancelAllTasks();
} catch (const std::exception &e) {
qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
}
}
void SignEncryptFilesController::Private::cancelAllTasks()
{
// we just kill all runnable tasks - this will not result in
// signal emissions.
runnable.clear();
// a cancel() will result in a call to
if (cms) {
cms->cancel();
}
if (openpgp) {
openpgp->cancel();
}
}
void SignEncryptFilesController::Private::ensureWizardCreated()
{
if (wizard) {
return;
}
std::unique_ptr<SignEncryptFilesWizard> w(new SignEncryptFilesWizard);
w->setAttribute(Qt::WA_DeleteOnClose);
connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection);
connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection);
wizard = w.release();
updateWizardMode();
}
void SignEncryptFilesController::Private::ensureWizardVisible()
{
ensureWizardCreated();
q->bringToForeground(wizard);
}
#include "moc_signencryptfilescontroller.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Mon, May 12, 6:40 PM (1 d, 21 h)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
b3/00/a58038784ae8cffb37fd7bac5b5b
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment