diff --git a/CMakeLists.txt b/CMakeLists.txt index a85181803..d188c0cf1 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,225 +1,225 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause cmake_minimum_required(VERSION 3.16 FATAL_ERROR) set(RELEASE_SERVICE_VERSION_MAJOR "22") set(RELEASE_SERVICE_VERSION_MINOR "03") 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 "21") 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") project(kleopatra VERSION ${kleopatra_version}) option(DISABLE_KWATCHGNUPG "Don't build the kwatchgnupg tool [default=OFF]" OFF) # Standalone build. Find / include everything necessary. set(KF5_MIN_VERSION "5.91.0") set(KMIME_VERSION "5.19.40") -set(LIBKLEO_VERSION "5.19.51") +set(LIBKLEO_VERSION "5.19.52") set(QT_REQUIRED_VERSION "5.15.2") set(GPGME_REQUIRED_VERSION "1.15.0") set(BOOST_REQUIRED_VERSION "1.58") if (WIN32) set(KF5_WANT_VERSION "5.70.0") set(KMIME_WANT_VERSION "5.12.0") else () set(KF5_WANT_VERSION ${KF5_MIN_VERSION}) set(KMIME_WANT_VERSION ${KMIME_VERSION}) endif () 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) # Find KF5 packages find_package(KF5WidgetsAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ConfigWidgets ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5CoreAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Codecs ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5Config ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5I18n ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5IconThemes ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5ItemModels ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5XmlGui ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5WindowSystem ${KF5_WANT_VERSION} CONFIG REQUIRED) find_package(KF5DocTools ${KF5_WANT_VERSION} CONFIG) find_package(KF5Crash ${KF5_WANT_VERSION} REQUIRED) set_package_properties(KF5DocTools 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(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG) set_package_properties(KF5DBusAddons PROPERTIES DESCRIPTION "Support library to work with DBus" PURPOSE "DBus session integration" URL "https://inqlude.org/libraries/kdbusaddons.html" TYPE OPTIONAL) else() find_package(KF5DBusAddons ${KF5_WANT_VERSION} CONFIG REQUIRED) set(_kleopatra_dbusaddons_libs KF5::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.16.0") set(GPGMEPP_SUPPORTS_TRUST_SIGNATURES 1) endif() find_package(QGpgme ${GPGME_REQUIRED_VERSION} CONFIG REQUIRED) if (QGpgme_VERSION VERSION_GREATER_EQUAL "1.16.0") set(QGPGME_SUPPORTS_TRUST_SIGNATURES 1) set(QGPGME_SUPPORTS_SIGNATURE_EXPIRATION 1) endif() if (QGpgme_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() # Kdepimlibs packages find_package(KF5Libkleo ${LIBKLEO_VERSION} CONFIG REQUIRED) find_package(KF5Mime ${KMIME_WANT_VERSION} CONFIG REQUIRED) find_package(Qt${QT_MAJOR_VERSION} ${QT_REQUIRED_VERSION} CONFIG REQUIRED Widgets Test Network PrintSupport) find_package(Assuan2 REQUIRED) find_package(Boost ${BOOST_REQUIRED_VERSION} MODULE REQUIRED) find_path(Boost_TOPOLOGICAL_SORT_DIR NAMES boost/graph/topological_sort.hpp PATHS ${Boost_INCLUDE_DIRS}) if(NOT Boost_TOPOLOGICAL_SORT_DIR) message(FATAL_ERROR "The Boost Topological_sort header was NOT found. Should be part of Boost graph module.") endif() 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) include (ConfigureChecks.cmake) 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} ${Boost_INCLUDE_DIRS} ${ASSUAN2_INCLUDES} ) add_definitions(-D_ASSUAN_ONLY_GPG_ERRORS) ecm_set_disabled_deprecation_versions(QT 5.14.0 KF 5.91.0) if(CMAKE_COMPILER_IS_GNUCXX) set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-missing-braces -Wno-parentheses -Wno-ignored-qualifiers") 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(KF5DocTools_FOUND) kdoctools_install(po) add_subdirectory(doc) endif() feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index 9d5cd4416..13aebfdc7 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,657 +1,671 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class FileNameRequesterWithIcon : public QWidget { Q_OBJECT public: explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr) : QWidget(parent) { auto layout = new QHBoxLayout{this}; layout->setContentsMargins(0, 0, 0, 0); mIconLabel = new QLabel{this}; mRequester = new FileNameRequester{filter, this}; mRequester->setExistingOnly(false); layout->addWidget(mIconLabel); layout->addWidget(mRequester); setFocusPolicy(mRequester->focusPolicy()); setFocusProxy(mRequester); connect(mRequester, &FileNameRequester::fileNameChanged, this, &FileNameRequesterWithIcon::fileNameChanged); } void setIcon(const QIcon &icon) { mIconLabel->setPixmap(icon.pixmap(32, 32)); } void setFileName(const QString &name) { mRequester->setFileName(name); } QString fileName() const { return mRequester->fileName(); } void setNameFilter(const QString &nameFilter) { mRequester->setNameFilter(nameFilter); } QString nameFilter() const { return mRequester->nameFilter(); } + FileNameRequester *requester() + { + return mRequester; + } + Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *e) override { if (e->type() == QEvent::ToolTipChange) { mRequester->setToolTip(toolTip()); } return QWidget::event(e); } private: QLabel *mIconLabel; FileNameRequester *mRequester; }; class SigEncPage: public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mOutputLabel{nullptr}, mArchive(false), mUseOutputDir(false), mSingleFile{true} { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); if (!Settings{}.cmsEnabled()) { mWidget->setProtocol(GpgME::OpenPGP); } mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage", "&Sign as:")); mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage", "Encrypt for &me:")); mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage", "Encrypt for &others:")); mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage", "Encrypt with &password. Anyone you share the password with can read the data.")); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); auto outputGrp = new QGroupBox(i18nc("@title:group", "Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:")); mOutLayout->addWidget(mOutputLabel); createRequesters(mOutLayout); mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage", "Encrypt / Sign &each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info:tooltip", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) { mUseOutputDir = state; mArchive = !mUseOutputDir && !mSingleFile; updateFileWidgets(); }); vLay->addWidget(outputGrp); auto messageWidget = new KMessageWidget; messageWidget->setMessageType(KMessageWidget::Error); messageWidget->setIcon(style()->standardIcon(QStyle::SP_MessageBoxCritical, nullptr, this)); messageWidget->setText(i18n("Signing and encrypting files is not possible.")); messageWidget->setToolTip(xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); messageWidget->setCloseButtonVisible(false); messageWidget->setVisible(Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()); vLay->addWidget(messageWidget); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { return false; } return !mWidget->currentOp().isNull(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); updateCommitButton(mWidget->currentOp()); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } void setSingleFile(bool singleFile) { mSingleFile = singleFile; mArchive = !mUseOutputDir && !mSingleFile; } bool validatePage() override { if (Kleo::gnupgUsesDeVsCompliance() && !Kleo::gnupgIsDeVsCompliant()) { KMessageBox::sorry(topLevelWidget(), xi18nc("@info %1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "Sorry! You cannot use Kleopatra for signing or encrypting files " "because the GnuPG system used by Kleopatra is not %1.", Formatting::deVsString())); return false; } bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel(this, xi18nc("@info", "Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature." "This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra." "Future versions of Kleopatra are expected to also support detached signatures in this case."), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) { return true; } const auto recipientKeys = recipients(); const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys), [](const auto &k) { return k.hasSecret(); }); if (!hasSecret) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "None of the recipients you are encrypting to seems to be your own." "This means that you will not be able to decrypt the data anymore, once encrypted." "Do you want to continue, or cancel to change the recipient selection?"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } std::vector recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ std::vector signers() const { const Key k = mWidget->signKey(); if (!k.isNull()) { return {k}; } return {}; } private: void createRequesters(QBoxLayout *lay) { static const QMap icons = { { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt") }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder") } }; static const QMap toolTips = { - { SignEncryptFilesWizard::SignatureCMS, i18n("The S/MIME signature.") }, - { SignEncryptFilesWizard::SignaturePGP, i18n("The signature.") }, - { SignEncryptFilesWizard::CombinedPGP, i18n("The signed and encrypted file.") }, - { SignEncryptFilesWizard::EncryptedPGP, i18n("The encrypted file.") }, - { SignEncryptFilesWizard::EncryptedCMS, i18n("The S/MIME encrypted file.") }, - { SignEncryptFilesWizard::Directory, i18n("Output directory.") } + { SignEncryptFilesWizard::SignatureCMS, i18n("This is the filename of the S/MIME signature.") }, + { SignEncryptFilesWizard::SignaturePGP, i18n("This is the filename of the detached OpenPGP signature.") }, + { SignEncryptFilesWizard::CombinedPGP, i18n("This is the filename of the OpenPGP-signed and encrypted file.") }, + { SignEncryptFilesWizard::EncryptedPGP, i18n("This is the filename of the OpenPGP encrypted file.") }, + { SignEncryptFilesWizard::EncryptedCMS, i18n("This is the filename of the S/MIME encrypted file.") }, + { SignEncryptFilesWizard::Directory, i18n("The resulting files are written to this directory.") } + }; + static const QMap accessibleNames = { + { SignEncryptFilesWizard::SignatureCMS, i18n("S/MIME signature file") }, + { SignEncryptFilesWizard::SignaturePGP, i18n("OpenPGP signature file") }, + { SignEncryptFilesWizard::CombinedPGP, i18n("OpenPGP signed and encrypted file") }, + { SignEncryptFilesWizard::EncryptedPGP, i18n("OpenPGP encrypted file") }, + { SignEncryptFilesWizard::EncryptedCMS, i18n("S/MIME encrypted file") }, + { SignEncryptFilesWizard::Directory, i18n("Output directory") } }; static const QMap nameFiltersBinary = { { SignEncryptFilesWizard::SignatureCMS, i18n("S/MIME Signatures (*.p7s)") }, { SignEncryptFilesWizard::SignaturePGP, i18n("OpenPGP Signatures (*.sig *.pgp)") }, { SignEncryptFilesWizard::CombinedPGP, i18n("OpenPGP Files (*.gpg *.pgp)") }, { SignEncryptFilesWizard::EncryptedPGP, i18n("OpenPGP Files (*.gpg *.pgp)") }, { SignEncryptFilesWizard::EncryptedCMS, i18n("S/MIME Files (*.p7m)") }, { SignEncryptFilesWizard::Directory, {} } }; static const QMap nameFiltersAscii = { { SignEncryptFilesWizard::SignatureCMS, i18n("S/MIME Signatures (*.p7s *.pem)") }, { SignEncryptFilesWizard::SignaturePGP, i18n("OpenPGP Signatures (*.asc *.sig)") }, { SignEncryptFilesWizard::CombinedPGP, i18n("OpenPGP Files (*.asc)") }, { SignEncryptFilesWizard::EncryptedPGP, i18n("OpenPGP Files (*.asc)") }, { SignEncryptFilesWizard::EncryptedCMS, i18n("S/MIME Files (*.p7m *.pem)") }, { SignEncryptFilesWizard::Directory, {} } }; if (!mRequesters.empty()) { return; } const auto &nameFilters = FileOperationsPreferences().addASCIIArmor() ? nameFiltersAscii : nameFiltersBinary; for (auto kind : icons.keys()) { auto requesterWithIcon = new FileNameRequesterWithIcon{ kind == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this}; requesterWithIcon->setIcon(QIcon::fromTheme(icons[kind])); requesterWithIcon->setToolTip(toolTips[kind]); + requesterWithIcon->requester()->setAccessibleNameOfLineEdit(accessibleNames[kind]); requesterWithIcon->setNameFilter(nameFilters[kind]); lay->addWidget(requesterWithIcon); connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, kind](const QString &newName) { mOutNames[kind] = newName; }); mRequesters.insert(kind, requesterWithIcon); } } public: void setOutputNames(const QMap &names) { Q_ASSERT(mOutNames.isEmpty()); for (auto it = std::begin(names); it != std::end(names); ++it) { mRequesters.value(it.key())->setFileName(it.value()); } mOutNames = names; updateFileWidgets(); } QMap outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const QString &label) { if (mParent->currentPage() != this) { return; } auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { mParent->setButtonText(QWizard::CommitButton, label); if (Kleo::gnupgUsesDeVsCompliance()) { const bool de_vs = Kleo::gnupgIsDeVsCompliant() && mWidget->isDeVsAndValid(); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); btn->setStyleSheet(QStringLiteral("QPushButton { background-color: %1; }").arg(de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mParent->setLabelText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); } } else { mParent->setButtonText(QWizard::CommitButton, i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequesters.isEmpty()) { return; } const std::vector recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); const bool pgp = mWidget->encryptSymmetric() || std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::OpenPGP; }); const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::CMS; }); mOutLayout->setEnabled(false); if (cms || pgp || !sigKey.isNull()) { mPlaceholderWidget->setVisible(false); mOutputLabel->setVisible(true); mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir); auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { return !w->isHidden(); }); mOutputLabel->setBuddy(*firstNotHidden); } else { mPlaceholderWidget->setVisible(true); mOutputLabel->setVisible(false); std::for_each(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { w->setVisible(false); }); mOutputLabel->setBuddy(nullptr); } mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequesters; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; QLabel *mOutputLabel; bool mArchive; bool mUseOutputDir; bool mSingleFile; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent), mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); const bool de_vs = Kleo::gnupgUsesDeVsCompliance(); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ auto btn = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ btn->setStyleSheet(QStringLiteral("border: none")); btn->setFocusPolicy(Qt::NoFocus); } } void SignEncryptFilesWizard::setLabelText(const QString &label) { button(QWizard::CommitButton)->setToolTip(label); setButtonText(QWizard::CustomButton1, label); } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } void SignEncryptFilesWizard::setSingleFile(bool singleFile) { mSigEncPage->setSingleFile(singleFile); } std::vector SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } std::vector SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap &map) const { mSigEncPage->setOutputNames(map); } QMap SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc"