Page Menu
Home
GnuPG
Search
Configure Global Search
Log In
Files
F34157976
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Size
34 KB
Subscribers
None
View Options
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 3b3dd5981..447a57090 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -1,298 +1,298 @@
# SPDX-FileCopyrightText: none
# SPDX-License-Identifier: BSD-3-Clause
cmake_minimum_required(VERSION 3.16 FATAL_ERROR)
set(RELEASE_SERVICE_VERSION_MAJOR "23")
set(RELEASE_SERVICE_VERSION_MINOR "11")
set(RELEASE_SERVICE_VERSION_MICRO "70")
# The RELEASE_SERVICE_VERSION is used by Gpg4win to add the Gpg4win version
if (NOT RELEASE_SERVICE_VERSION)
set(RELEASE_SERVICE_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}.${RELEASE_SERVICE_VERSION_MINOR}.${RELEASE_SERVICE_VERSION_MICRO}")
endif()
if(RELEASE_SERVICE_VERSION_MICRO LESS 10)
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}0${RELEASE_SERVICE_VERSION_MICRO}")
else()
set(KDE_APPLICATIONS_COMPACT_VERSION "${RELEASE_SERVICE_VERSION_MAJOR}${RELEASE_SERVICE_VERSION_MINOR}${RELEASE_SERVICE_VERSION_MICRO}")
endif()
set(KLEOPATRA_VERSION_MAJOR "3")
set(KLEOPATRA_VERSION_MINOR "1")
set(KLEOPATRA_VERSION_MICRO "28")
set(kleopatra_version "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}.${KDE_APPLICATIONS_COMPACT_VERSION}")
# The following is for Windows
set(kleopatra_version_win "${KLEOPATRA_VERSION_MAJOR}.${KLEOPATRA_VERSION_MINOR}.${KLEOPATRA_VERSION_MICRO}")
set(kleopatra_fileversion_win "${KLEOPATRA_VERSION_MAJOR},${KLEOPATRA_VERSION_MINOR},${KLEOPATRA_VERSION_MICRO},0")
if (NOT KLEOPATRA_DISTRIBUTION_TEXT)
# This is only used on Windows for the file attributes of Kleopatra
set(KLEOPATRA_DISTRIBUTION_TEXT "KDE")
endif()
project(kleopatra VERSION ${kleopatra_version})
option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF)
# Standalone build. Find / include everything necessary.
set(KF_MIN_VERSION "5.105.0")
set(KIDENTITYMANAGEMENT_VERSION "5.24.0")
set(KMAILTRANSPORT_VERSION "5.24.0")
set(AKONADI_MIME_VERSION "5.24.0")
set(KMIME_VERSION "5.24.0")
-set(LIBKLEO_VERSION "5.24.45")
+set(LIBKLEO_VERSION "5.24.46")
set(MIMETREEPARSER_VERSION "5.24.41")
set(QT_REQUIRED_VERSION "5.15.2")
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
endif()
set(GPGME_REQUIRED_VERSION "1.16.0")
set(LIBASSUAN_REQUIRED_VERSION "2.4.2")
set(GPG_ERROR_REQUIRED_VERSION "1.36")
if (WIN32)
set(KF5_WANT_VERSION "5.104.0")
set(KMIME_WANT_VERSION "5.12.0")
else ()
set(KF5_WANT_VERSION ${KF_MIN_VERSION})
set(KMIME_WANT_VERSION ${KMIME_VERSION})
endif ()
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(ECM ${KF5_WANT_VERSION} CONFIG REQUIRED)
set(CMAKE_MODULE_PATH ${ECM_MODULE_PATH})
set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake/modules ${CMAKE_MODULE_PATH})
include(ECMInstallIcons)
include(ECMSetupVersion)
include(ECMAddTests)
include(GenerateExportHeader)
include(ECMGenerateHeaders)
include(FeatureSummary)
include(CheckFunctionExists)
include(KDEInstallDirs)
include(KDECMakeSettings)
include(KDECompilerSettings NO_POLICY_SCOPE)
include(ECMAddAppIcon)
include(ECMQtDeclareLoggingCategory)
include(ECMDeprecationSettings)
include(KDEClangFormat)
include(KDEGitCommitHooks)
if (QT_MAJOR_VERSION STREQUAL "6")
set(QT_REQUIRED_VERSION "6.4.0")
set(KF_MIN_VERSION "5.240.0")
set(KF_MAJOR_VERSION "6")
else()
set(KF_MIN_VERSION "5.105.0")
set(KF_MAJOR_VERSION "5")
endif()
# Find KF5 packages
find_package(KF${KF_MAJOR_VERSION} ${KF5_WANT_VERSION}
REQUIRED COMPONENTS
Codecs
Config
ConfigWidgets
CoreAddons
Crash
I18n
IconThemes
ItemModels
KCMUtils
KIO
WidgetsAddons
WindowSystem
XmlGui
OPTIONAL_COMPONENTS
DocTools
)
set_package_properties(KF${KF_MAJOR_VERSION}DocTools PROPERTIES
DESCRIPTION "Documentation tools"
PURPOSE "Required to generate Kleopatra documentation."
TYPE OPTIONAL)
# Optional packages
if (WIN32)
# Only a replacement available for Windows so this
# is required on other platforms.
find_package(KF${KF_MAJOR_VERSION}DBusAddons ${KF5_WANT_VERSION} CONFIG)
set_package_properties(KF${KF_MAJOR_VERSION}DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus"
PURPOSE "DBus session integration"
URL "https://inqlude.org/libraries/kdbusaddons.html"
TYPE OPTIONAL)
else()
find_package(KF${KF_MAJOR_VERSION}DBusAddons ${KF5_WANT_VERSION} CONFIG REQUIRED)
set(_kleopatra_dbusaddons_libs KF${KF_MAJOR_VERSION}::DBusAddons)
endif()
set(HAVE_QDBUS ${Qt${QT_MAJOR_VERSION}DBus_FOUND})
find_package(Gpgmepp ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.19.0")
set(GPGMEPP_SUPPORTS_SET_CURVE 1)
endif()
if (Gpgmepp_VERSION VERSION_GREATER_EQUAL "1.19.1")
set(GPGMEPP_KEY_CANSIGN_IS_FIXED 1)
endif()
set(QGPGME_NAME "QGpgme")
find_package(${QGPGME_NAME} ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED)
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.17.0")
set(QGPGME_SUPPORTS_CHANGING_EXPIRATION_OF_COMPLETE_KEY 1)
set(QGPGME_CRYPTOCONFIGENTRY_HAS_DEFAULT_VALUE 1)
set(QGPGME_SUPPORTS_WKDLOOKUP 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_FILTER 1)
set(QGPGME_SUPPORTS_IMPORT_WITH_KEY_ORIGIN 1)
set(QGPGME_SUPPORTS_SECRET_KEY_EXPORT 1)
set(QGPGME_SUPPORTS_SECRET_SUBKEY_EXPORT 1)
set(QGPGME_SUPPORTS_RECEIVING_KEYS_BY_KEY_ID 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.18.0")
set(QGPGME_SUPPORTS_KEY_REVOCATION 1)
set(QGPGME_SUPPORTS_KEY_REFRESH 1)
set(QGPGME_SUPPORTS_SET_FILENAME 1)
set(QGPGME_SUPPORTS_SET_PRIMARY_UID 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.19.0")
set(QGPGME_SUPPORTS_DEFERRED_IMPORT_JOB 1)
set(QGPGME_SUPPORTS_ARCHIVE_JOBS 1)
set(QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.21.0")
set(QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME 1)
set(QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.21.1")
set(QGPGME_HAS_TOLOGSTRING 1)
set(QGPGME_SUPPORTS_IS_MIME 1)
endif()
if (${QGPGME_NAME}_VERSION VERSION_GREATER_EQUAL "1.22.1")
set(QGPGME_SUPPORTS_WKD_REFRESH_JOB 1)
endif()
if (QT_MAJOR_VERSION STREQUAL "6")
find_package(Qt6Core5Compat)
endif()
# Kdepimlibs packages
find_package(KPim${KF_MAJOR_VERSION}Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED)
find_package(KPim${KF_MAJOR_VERSION}IdentityManagement ${KIDENTITYMANAGEMENT_VERSION} CONFIG)
find_package(KPim${KF_MAJOR_VERSION}MailTransport ${KMAILTRANSPORT_VERSION} CONFIG)
find_package(KPim${KF_MAJOR_VERSION}AkonadiMime ${AKONADI_MIME_VERSION} CONFIG)
find_package(KPim${KF_MAJOR_VERSION}MimeTreeParserWidgets ${MIMETREEPARSER_VERSION} CONFIG REQUIRED)
find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport)
find_package(LibAssuan ${LIBASSUAN_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibAssuan PROPERTIES
TYPE REQUIRED
PURPOSE "Needed for Kleopatra to act as the GnuPG UI Server"
)
find_package(LibGpgError ${GPG_ERROR_REQUIRED_VERSION} REQUIRED)
set_package_properties(LibGpgError PROPERTIES
TYPE REQUIRED
)
set(kleopatra_release FALSE)
if(NOT kleopatra_release)
find_package(Git)
if(GIT_FOUND)
execute_process(COMMAND ${GIT_EXECUTABLE} rev-parse
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
RESULT_VARIABLE rc
ERROR_QUIET)
if(rc EQUAL 0)
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%h ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_REVISION)
string(REGEX REPLACE "\n" "" Kleopatra_WC_REVISION "${Kleopatra_WC_REVISION}")
execute_process(COMMAND ${GIT_EXECUTABLE} log -1 --oneline --format=%cI ${CMAKE_CURRENT_SOURCE_DIR}
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
OUTPUT_VARIABLE Kleopatra_WC_LAST_CHANGED_DATE)
string(REGEX REPLACE "^([0-9]+)-([0-9]+)-([0-9]+)T([0-9]+):([0-9]+):([0-9]+).*$" "\\1\\2\\3T\\4\\5\\6"
Kleopatra_WC_LAST_CHANGED_DATE "${Kleopatra_WC_LAST_CHANGED_DATE}")
set(kleopatra_version "${kleopatra_version}+git${Kleopatra_WC_LAST_CHANGED_DATE}~${Kleopatra_WC_REVISION}")
endif()
endif()
endif()
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/version-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/version-kleopatra.h)
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/config-kleopatra.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config-kleopatra.h)
include_directories(
${CMAKE_CURRENT_BINARY_DIR}
${CMAKE_CURRENT_SOURCE_DIR}
)
if (WIN32)
# On Windows, we need to use stuff deprecated since Qt 5.11, e.g. from QDesktopWidget
ecm_set_disabled_deprecation_versions(QT 5.10.0 KF 5.103.0)
else ()
ecm_set_disabled_deprecation_versions(QT 6.4 KF 5.103.0)
endif ()
if(CMAKE_COMPILER_IS_GNUCXX)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers")
endif()
if(MINGW)
# we do not care about different signedness of passed pointer arguments
add_compile_options($<$<COMPILE_LANGUAGE:C>:-Wno-pointer-sign>)
endif()
add_definitions(-DQT_NO_EMIT)
remove_definitions(-DQT_NO_FOREACH)
# Disable the use of QStringBuilder for operator+ to prevent crashes when
# returning the result of concatenating string temporaries in lambdas. We do
# this for example in some std::transform expressions.
# This is a known issue: https://bugreports.qt.io/browse/QTBUG-47066
# Alternatively, one would always have to remember to force the lambdas to
# return a QString instead of QStringBuilder, but that's just too easy to
# forget and, unfortunately, the compiler doesn't issue a warning if one forgets
# this. So, it's just too dangerous.
# One can still use QStringBuilder explicitly with the operator% if necessary.
remove_definitions(-DQT_USE_FAST_OPERATOR_PLUS)
remove_definitions(-DQT_USE_QSTRINGBUILDER)
kde_enable_exceptions()
option(USE_UNITY_CMAKE_SUPPORT "Use UNITY cmake support (speedup compile time)" OFF)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT OFF)
if (USE_UNITY_CMAKE_SUPPORT)
set(COMPILE_WITH_UNITY_CMAKE_SUPPORT ON)
endif()
add_subdirectory(pics)
add_subdirectory(src)
if(BUILD_TESTING)
add_subdirectory(tests)
add_subdirectory(autotests)
endif()
ecm_qt_install_logging_categories(
EXPORT KLEOPATRA
FILE kleopatra.categories
DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR}
)
ki18n_install(po)
if(KF${KF_MAJOR_VERSION}DocTools_FOUND)
kdoctools_install(po)
add_subdirectory(doc)
endif()
feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES)
# add clang-format target for all our real source files
file(GLOB_RECURSE ALL_CLANG_FORMAT_SOURCE_FILES *.cpp *.h *.c)
kde_clang_format(${ALL_CLANG_FORMAT_SOURCE_FILES})
kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT)
diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp
index 48332908b..18e070d26 100644
--- a/src/commands/exportcertificatecommand.cpp
+++ b/src/commands/exportcertificatecommand.cpp
@@ -1,366 +1,360 @@
/* -*- 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 <Libkleo/KeyHelpers>
#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()) {
+ if (d->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);
+ const auto keys = Kleo::partitionKeysByProtocol(d->keys());
+ const bool haveBoth = !keys.cms.empty() && !keys.openpgp.empty();
+ const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!keys.cms.empty() ? CMS : OpenPGP);
if (!d->requestFileNames(prot)) {
Q_EMIT canceled();
d->finished();
} else {
- if (!openpgp.empty()) {
- d->startExportJob(GpgME::OpenPGP, openpgp);
+ if (!keys.openpgp.empty()) {
+ d->startExportJob(GpgME::OpenPGP, keys.openpgp);
}
- if (!cms.empty()) {
- d->startExportJob(GpgME::CMS, cms);
+ if (!keys.cms.empty()) {
+ d->startExportJob(GpgME::CMS, keys.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(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/exportgroupscommand.cpp b/src/commands/exportgroupscommand.cpp
index 19f417055..1b19dd1c7 100644
--- a/src/commands/exportgroupscommand.cpp
+++ b/src/commands/exportgroupscommand.cpp
@@ -1,339 +1,334 @@
/* -*- mode: c++; c-basic-offset:4 -*-
exportgroupscommand.cpp
This file is part of Kleopatra, the KDE keymanager
SPDX-FileCopyrightText: 2021 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 "exportgroupscommand.h"
#include "utils/filedialog.h"
#include <utils/applicationstate.h>
#include <Libkleo/Algorithm>
#include <Libkleo/Formatting>
#include <Libkleo/KeyGroup>
#include <Libkleo/KeyGroupImportExport>
#include <Libkleo/KeyHelpers>
#include <KLocalizedString>
#include <KSharedConfig>
#include <QGpgME/ExportJob>
#include <QGpgME/Protocol>
#include <QFileInfo>
#include <QStandardPaths>
#include <memory>
#include <vector>
using namespace Kleo;
using namespace GpgME;
using namespace QGpgME;
namespace
{
static const QString certificateGroupFileExtension{QLatin1String{".kgrp"}};
QString proposeFilename(const std::vector<KeyGroup> &groups)
{
QString filename;
filename = ApplicationState::lastUsedExportDirectory() + QLatin1Char{'/'};
if (groups.size() == 1) {
filename += groups.front().name().replace(QLatin1Char{'/'}, QLatin1Char{'_'});
} else {
filename += i18nc("A generic filename for exported certificate groups", "certificate groups");
}
return filename + certificateGroupFileExtension;
}
QString requestFilename(QWidget *parent, const std::vector<KeyGroup> &groups)
{
const QString proposedFilename = proposeFilename(groups);
auto filename =
FileDialog::getSaveFileNameEx(parent,
i18ncp("@title:window", "Export Certificate Group", "Export Certificate Groups", groups.size()),
QStringLiteral("imp"),
proposedFilename,
i18nc("filename filter like Certificate Groups (*.foo)", "Certificate Groups (*%1)", certificateGroupFileExtension));
if (!filename.isEmpty()) {
const QFileInfo fi{filename};
if (fi.suffix().isEmpty()) {
filename += certificateGroupFileExtension;
}
ApplicationState::setLastUsedExportDirectory(filename);
}
return filename;
}
}
class ExportGroupsCommand::Private : public Command::Private
{
friend class ::ExportGroupsCommand;
ExportGroupsCommand *q_func() const
{
return static_cast<ExportGroupsCommand *>(q);
}
public:
explicit Private(ExportGroupsCommand *qq);
~Private() override;
void start();
bool confirmExport();
bool exportGroups();
bool startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys);
void onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData);
void cancelJobs();
void showError(const GpgME::Error &err);
void finishedIfLastJob(const QGpgME::Job *job);
private:
std::vector<KeyGroup> groups;
QString filename;
std::vector<QPointer<QGpgME::Job>> exportJobs;
};
ExportGroupsCommand::Private *ExportGroupsCommand::d_func()
{
return static_cast<Private *>(d.get());
}
const ExportGroupsCommand::Private *ExportGroupsCommand::d_func() const
{
return static_cast<const Private *>(d.get());
}
#define d d_func()
#define q q_func()
ExportGroupsCommand::Private::Private(ExportGroupsCommand *qq)
: Command::Private(qq)
{
}
ExportGroupsCommand::Private::~Private() = default;
void ExportGroupsCommand::Private::start()
{
if (groups.empty()) {
finished();
return;
}
if (!confirmExport()) {
canceled();
return;
}
filename = requestFilename(parentWidgetOrView(), groups);
if (filename.isEmpty()) {
canceled();
return;
}
const auto groupKeys = std::accumulate(std::begin(groups), std::end(groups), KeyGroup::Keys{}, [](auto allKeys, const auto &group) {
const auto keys = group.keys();
allKeys.insert(std::begin(keys), std::end(keys));
return allKeys;
});
-
- std::vector<Key> openpgpKeys;
- std::vector<Key> cmsKeys;
- std::partition_copy(std::begin(groupKeys), std::end(groupKeys), std::back_inserter(openpgpKeys), std::back_inserter(cmsKeys), [](const GpgME::Key &key) {
- return key.protocol() == GpgME::OpenPGP;
- });
+ const auto keys = Kleo::partitionKeysByProtocol(groupKeys);
// remove/overwrite existing file
if (QFile::exists(filename) && !QFile::remove(filename)) {
error(xi18n("Cannot overwrite existing <filename>%1</filename>.", filename), i18nc("@title:window", "Export Failed"));
finished();
return;
}
if (!exportGroups()) {
finished();
return;
}
- if (!openpgpKeys.empty()) {
- if (!startExportJob(GpgME::OpenPGP, openpgpKeys)) {
+ if (!keys.openpgp.empty()) {
+ if (!startExportJob(GpgME::OpenPGP, keys.openpgp)) {
finished();
return;
}
}
- if (!cmsKeys.empty()) {
- if (!startExportJob(GpgME::CMS, cmsKeys)) {
+ if (!keys.cms.empty()) {
+ if (!startExportJob(GpgME::CMS, keys.cms)) {
finishedIfLastJob(nullptr);
}
}
}
bool ExportGroupsCommand::Private::confirmExport()
{
auto notFullyCertifiedGroups = std::accumulate(groups.cbegin(), groups.cend(), QStringList{}, [](auto groupNames, const auto &group) {
const bool allOpenPGPKeysAreCertifiedByUser = Kleo::all_of(group.keys(), [](const Key &key) {
// we only check the primary user ID of OpenPGP keys because currently group certification only certifies the primary user ID
return key.protocol() != GpgME::OpenPGP || Kleo::userIDIsCertifiedByUser(key.userID(0));
});
if (!allOpenPGPKeysAreCertifiedByUser) {
groupNames.push_back(group.name());
}
return groupNames;
});
if (!notFullyCertifiedGroups.empty()) {
if (groups.size() == 1) {
const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(),
xi18nc("@info",
"<para>You haven't certified all OpenPGP certificates in this group.</para>"
"<para>Do you want to continue the export?</para>"),
i18nc("@title:window", "Confirm Group Export"),
KGuiItem{i18nc("@action:button", "Export Group")},
KStandardGuiItem::cancel());
return answer == KMessageBox::PrimaryAction;
} else {
std::sort(notFullyCertifiedGroups.begin(), notFullyCertifiedGroups.end());
const auto answer =
KMessageBox::questionTwoActionsList(parentWidgetOrView(),
xi18nc("@info",
"<para>You haven't certified all OpenPGP certificates in the groups listed below.</para>"
"<para>Do you want to continue the export?</para>"),
notFullyCertifiedGroups,
i18nc("@title:window", "Confirm Group Export"),
KGuiItem{i18nc("@action:button", "Export Groups")},
KStandardGuiItem::cancel());
return answer == KMessageBox::PrimaryAction;
}
}
return true;
}
bool ExportGroupsCommand::Private::exportGroups()
{
const auto result = writeKeyGroups(filename, groups);
if (result != WriteKeyGroups::Success) {
error(xi18n("Writing groups to file <filename>%1</filename> failed.", filename), i18nc("@title:window", "Export Failed"));
}
return result == WriteKeyGroups::Success;
}
bool ExportGroupsCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector<Key> &keys)
{
const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime();
Q_ASSERT(backend);
std::unique_ptr<ExportJob> jobOwner(backend->publicKeyExportJob(/*armor=*/true));
auto job = jobOwner.get();
Q_ASSERT(job);
connect(job, &ExportJob::result, q, [this, job](const GpgME::Error &err, const QByteArray &keyData) {
onExportJobResult(job, err, keyData);
});
#if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS
connect(job, &QGpgME::Job::jobProgress, q, &Command::progress);
#else
connect(job, &QGpgME::Job::progress, q, [this](const QString &, int current, int total) {
Q_EMIT q->progress(current, total);
});
#endif
const GpgME::Error err = job->start(Kleo::getFingerprints(keys));
if (err) {
showError(err);
return false;
}
Q_EMIT q->info(i18n("Exporting certificate groups..."));
exportJobs.push_back(jobOwner.release());
return true;
}
void ExportGroupsCommand::Private::onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData)
{
Q_ASSERT(Kleo::contains(exportJobs, job));
if (err) {
showError(err);
finishedIfLastJob(job);
return;
}
QFile f{filename};
if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) {
error(xi18n("Cannot open file <filename>%1</filename> for writing.", filename), i18nc("@title:window", "Export Failed"));
finishedIfLastJob(job);
return;
}
const auto bytesWritten = f.write(keyData);
if (bytesWritten != keyData.size()) {
error(xi18n("Writing certificates to file <filename>%1</filename> failed.", filename), i18nc("@title:window", "Export Failed"));
}
finishedIfLastJob(job);
}
void ExportGroupsCommand::Private::showError(const GpgME::Error &err)
{
error(xi18n("<para>An error occurred during the export:</para>"
"<para><message>%1</message></para>",
Formatting::errorAsString(err)),
i18nc("@title:window", "Export Failed"));
}
void ExportGroupsCommand::Private::finishedIfLastJob(const QGpgME::Job *job)
{
if (job) {
exportJobs.erase(std::remove(exportJobs.begin(), exportJobs.end(), job), exportJobs.end());
}
if (exportJobs.size() == 0) {
finished();
}
}
void ExportGroupsCommand::Private::cancelJobs()
{
std::for_each(std::cbegin(exportJobs), std::cend(exportJobs), [](const auto &job) {
if (job) {
job->slotCancel();
}
});
exportJobs.clear();
}
ExportGroupsCommand::ExportGroupsCommand(const std::vector<KeyGroup> &groups)
: Command{new Private{this}}
{
d->groups = groups;
}
ExportGroupsCommand::~ExportGroupsCommand() = default;
void ExportGroupsCommand::doStart()
{
d->start();
}
void ExportGroupsCommand::doCancel()
{
d->cancelJobs();
}
#undef d
#undef q
#include "moc_exportgroupscommand.cpp"
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Thu, Dec 11, 7:51 AM (6 h, 26 m)
Storage Engine
local-disk
Storage Format
Raw Data
Storage Handle
30/34/3498fe44b819574b0691f66124f3
Attached To
rKLEOPATRA Kleopatra
Event Timeline
Log In to Comment