diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 5af84d156..d155a5ad8 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,612 +1,614 @@ # SPDX-FileCopyrightText: none # SPDX-License-Identifier: BSD-3-Clause add_subdirectory(icons) add_subdirectory(mimetypes) include_directories(${CMAKE_CURRENT_BINARY_DIR}) include_directories(${CMAKE_CURRENT_SOURCE_DIR}) if (NOT DISABLE_KWATCHGNUPG) add_subdirectory(kwatchgnupg) endif() add_subdirectory(libkleopatraclient) add_subdirectory(conf) add_subdirectory(kconf_update) if(WIN32) set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_win.cpp) set(_kleopatra_extra_SRCS selftest/registrycheck.cpp selftest/registrycheck.h utils/gnupg-registry.c utils/userinfo_win.cpp utils/windowsprocessdevice.cpp utils/windowsprocessdevice.h versioninfo.rc ) else() set(_kleopatra_extra_uiserver_SRCS uiserver/uiserver_unix.cpp) set(_kleopatra_extra_SRCS) endif() set(_kleopatra_uiserver_SRCS ${_kleopatra_extra_uiserver_SRCS} selftest/uiservercheck.cpp selftest/uiservercheck.h uiserver/assuanserverconnection.cpp uiserver/assuanserverconnection.h uiserver/createchecksumscommand.cpp uiserver/createchecksumscommand.h uiserver/decryptverifycommandemailbase.cpp uiserver/decryptverifycommandemailbase.h uiserver/decryptverifycommandfilesbase.cpp uiserver/decryptverifycommandfilesbase.h uiserver/echocommand.cpp uiserver/echocommand.h uiserver/encryptcommand.cpp uiserver/encryptcommand.h uiserver/importfilescommand.cpp uiserver/importfilescommand.h uiserver/prepencryptcommand.cpp uiserver/prepencryptcommand.h uiserver/prepsigncommand.cpp uiserver/prepsigncommand.h uiserver/selectcertificatecommand.cpp uiserver/sessiondata.cpp uiserver/sessiondata.h uiserver/signcommand.cpp uiserver/signcommand.h uiserver/signencryptfilescommand.cpp uiserver/uiserver.cpp uiserver/verifychecksumscommand.cpp uiserver/verifychecksumscommand.h ) set(_kleopatra_uiserver_extra_libs LibAssuan::LibAssuan LibGpgError::LibGpgError) if(HAVE_GPG_ERR_SOURCE_KLEO) add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_KLEO) else() add_definitions(-DGPG_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) add_definitions(-DGPGMEPP_ERR_SOURCE_DEFAULT=GPG_ERR_SOURCE_USER_1) endif() if(KF5IdentityManagement_FOUND AND KF5MailTransport_FOUND AND KF5MailTransportAkonadi_FOUND) set(_kleopatra_mail_libs KF5::IdentityManagement # Export OpenPGP keys using WKS KF5::MailTransport KF5::MailTransportAkonadi ) add_definitions(-DMAILAKONADI_ENABLED) endif() ki18n_wrap_ui(_kleopatra_uiserver_SRCS crypto/gui/signingcertificateselectionwidget.ui) set(_kleopatra_SRCS ${_kleopatra_extra_SRCS} accessibility/accessiblelink.cpp accessibility/accessiblelink_p.h accessibility/accessiblerichtextlabel.cpp accessibility/accessiblerichtextlabel_p.h accessibility/accessiblevaluelabel.cpp accessibility/accessiblevaluelabel_p.h accessibility/accessiblewidgetfactory.cpp accessibility/accessiblewidgetfactory.h commands/adduseridcommand.cpp commands/adduseridcommand.h commands/authenticatepivcardapplicationcommand.cpp commands/authenticatepivcardapplicationcommand.h commands/cardcommand.cpp commands/cardcommand.h commands/certificatetopivcardcommand.cpp commands/certificatetopivcardcommand.h commands/certifycertificatecommand.cpp commands/certifycertificatecommand.h commands/changeexpirycommand.cpp commands/changeexpirycommand.h commands/changeownertrustcommand.cpp commands/changeownertrustcommand.h commands/changepassphrasecommand.cpp commands/changepassphrasecommand.h commands/changepincommand.cpp commands/changepincommand.h commands/changeroottrustcommand.cpp commands/changeroottrustcommand.h commands/checksumcreatefilescommand.cpp commands/checksumcreatefilescommand.h commands/checksumverifyfilescommand.cpp commands/checksumverifyfilescommand.h commands/clearcrlcachecommand.cpp commands/clearcrlcachecommand.h commands/command.cpp commands/command.h commands/createcsrforcardkeycommand.cpp commands/createcsrforcardkeycommand.h commands/createopenpgpkeyfromcardkeyscommand.cpp commands/createopenpgpkeyfromcardkeyscommand.h commands/decryptverifyclipboardcommand.cpp commands/decryptverifyclipboardcommand.h commands/decryptverifyfilescommand.cpp commands/decryptverifyfilescommand.h commands/deletecertificatescommand.cpp commands/deletecertificatescommand.h commands/detailscommand.cpp commands/detailscommand.h commands/dumpcertificatecommand.cpp commands/dumpcertificatecommand.h commands/dumpcrlcachecommand.cpp commands/dumpcrlcachecommand.h commands/encryptclipboardcommand.cpp commands/encryptclipboardcommand.h commands/exportcertificatecommand.cpp commands/exportcertificatecommand.h commands/exportgroupscommand.cpp commands/exportgroupscommand.h commands/exportopenpgpcertstoservercommand.cpp commands/exportopenpgpcertstoservercommand.h commands/exportopenpgpcerttoprovidercommand.cpp commands/exportopenpgpcerttoprovidercommand.h commands/exportpaperkeycommand.cpp commands/exportpaperkeycommand.h commands/exportsecretkeycommand.cpp commands/exportsecretkeycommand.h commands/exportsecretkeycommand_old.cpp commands/exportsecretkeycommand_old.h commands/exportsecretsubkeycommand.cpp commands/exportsecretsubkeycommand.h commands/genrevokecommand.cpp commands/genrevokecommand.h commands/gnupgprocesscommand.cpp commands/gnupgprocesscommand.h commands/importcertificatefromclipboardcommand.cpp commands/importcertificatefromclipboardcommand.h commands/importcertificatefromdatacommand.cpp commands/importcertificatefromdatacommand.h commands/importcertificatefromfilecommand.cpp commands/importcertificatefromfilecommand.h commands/importcertificatefromkeyservercommand.cpp commands/importcertificatefromkeyservercommand.h commands/importcertificatefrompivcardcommand.cpp commands/importcertificatefrompivcardcommand.h commands/importcertificatescommand.cpp commands/importcertificatescommand.h commands/importcrlcommand.cpp commands/importcrlcommand.h commands/importpaperkeycommand.cpp commands/importpaperkeycommand.h commands/keytocardcommand.cpp commands/keytocardcommand.h commands/learncardkeyscommand.cpp commands/learncardkeyscommand.h commands/lookupcertificatescommand.cpp commands/lookupcertificatescommand.h commands/newcertificatesigningrequestcommand.cpp commands/newcertificatesigningrequestcommand.h commands/newopenpgpcertificatecommand.cpp commands/newopenpgpcertificatecommand.h commands/openpgpgeneratecardkeycommand.cpp commands/openpgpgeneratecardkeycommand.h commands/pivgeneratecardkeycommand.cpp commands/pivgeneratecardkeycommand.h commands/refreshcertificatecommand.cpp commands/refreshcertificatecommand.h commands/refreshopenpgpcertscommand.cpp commands/refreshopenpgpcertscommand.h commands/refreshx509certscommand.cpp commands/refreshx509certscommand.h commands/reloadkeyscommand.cpp commands/reloadkeyscommand.h commands/revokecertificationcommand.cpp commands/revokecertificationcommand.h commands/revokekeycommand.cpp commands/revokekeycommand.h commands/revokeuseridcommand.cpp commands/revokeuseridcommand.h commands/selftestcommand.cpp commands/selftestcommand.h commands/setinitialpincommand.cpp commands/setinitialpincommand.h commands/setpivcardapplicationadministrationkeycommand.cpp commands/setpivcardapplicationadministrationkeycommand.h commands/setprimaryuseridcommand.cpp commands/setprimaryuseridcommand.h commands/signclipboardcommand.cpp commands/signclipboardcommand.h commands/signencryptfilescommand.cpp commands/signencryptfilescommand.h commands/signencryptfoldercommand.cpp commands/signencryptfoldercommand.h conf/configuredialog.cpp conf/configuredialog.h conf/groupsconfigdialog.cpp conf/groupsconfigdialog.h conf/groupsconfigpage.cpp conf/groupsconfigpage.h conf/groupsconfigwidget.cpp conf/groupsconfigwidget.h crypto/autodecryptverifyfilescontroller.cpp crypto/autodecryptverifyfilescontroller.h crypto/certificateresolver.cpp crypto/certificateresolver.h crypto/checksumsutils_p.cpp crypto/checksumsutils_p.h crypto/controller.cpp crypto/controller.h crypto/createchecksumscontroller.cpp crypto/createchecksumscontroller.h crypto/decryptverifyemailcontroller.cpp crypto/decryptverifyemailcontroller.h crypto/decryptverifyfilescontroller.cpp crypto/decryptverifyfilescontroller.h crypto/decryptverifytask.cpp crypto/decryptverifytask.h crypto/encryptemailcontroller.cpp crypto/encryptemailcontroller.h crypto/encryptemailtask.cpp crypto/encryptemailtask.h crypto/gui/certificatelineedit.cpp crypto/gui/certificatelineedit.h crypto/gui/certificateselectionline.cpp crypto/gui/certificateselectionline.h crypto/gui/decryptverifyfilesdialog.cpp crypto/gui/decryptverifyfilesdialog.h crypto/gui/decryptverifyfileswizard.cpp crypto/gui/decryptverifyfileswizard.h crypto/gui/decryptverifyoperationwidget.cpp crypto/gui/decryptverifyoperationwidget.h crypto/gui/encryptemailwizard.cpp crypto/gui/encryptemailwizard.h crypto/gui/newresultpage.cpp crypto/gui/newresultpage.h crypto/gui/objectspage.cpp crypto/gui/objectspage.h crypto/gui/resolverecipientspage.cpp crypto/gui/resolverecipientspage.h crypto/gui/resultitemwidget.cpp crypto/gui/resultitemwidget.h crypto/gui/resultlistwidget.cpp crypto/gui/resultlistwidget.h crypto/gui/resultpage.cpp crypto/gui/resultpage.h crypto/gui/signemailwizard.cpp crypto/gui/signemailwizard.h crypto/gui/signencryptemailconflictdialog.cpp crypto/gui/signencryptemailconflictdialog.h crypto/gui/signencryptfileswizard.cpp crypto/gui/signencryptfileswizard.h crypto/gui/signencryptwidget.cpp crypto/gui/signencryptwidget.h crypto/gui/signencryptwizard.cpp crypto/gui/signencryptwizard.h crypto/gui/signerresolvepage.cpp crypto/gui/signerresolvepage.h crypto/gui/signingcertificateselectiondialog.cpp crypto/gui/signingcertificateselectiondialog.h crypto/gui/signingcertificateselectionwidget.cpp crypto/gui/signingcertificateselectionwidget.h crypto/gui/unknownrecipientwidget.cpp crypto/gui/unknownrecipientwidget.h crypto/gui/verifychecksumsdialog.cpp crypto/gui/verifychecksumsdialog.h crypto/gui/wizard.cpp crypto/gui/wizard.h crypto/gui/wizardpage.cpp crypto/gui/wizardpage.h crypto/newsignencryptemailcontroller.cpp crypto/newsignencryptemailcontroller.h crypto/recipient.cpp crypto/recipient.h crypto/sender.cpp crypto/sender.h crypto/signemailcontroller.cpp crypto/signemailcontroller.h crypto/signemailtask.cpp crypto/signemailtask.h crypto/signencryptfilescontroller.cpp crypto/signencryptfilescontroller.h crypto/signencrypttask.cpp crypto/signencrypttask.h crypto/task.cpp crypto/task.h crypto/taskcollection.cpp crypto/taskcollection.h crypto/verifychecksumscontroller.cpp crypto/verifychecksumscontroller.h dialogs/adduseriddialog.cpp dialogs/adduseriddialog.h dialogs/certificatedetailsdialog.cpp dialogs/certificatedetailsdialog.h dialogs/certificatedetailsinputwidget.cpp dialogs/certificatedetailsinputwidget.h dialogs/certificatedetailswidget.cpp dialogs/certificatedetailswidget.h dialogs/certificateselectiondialog.cpp dialogs/certificateselectiondialog.h dialogs/certifycertificatedialog.cpp dialogs/certifycertificatedialog.h dialogs/certifywidget.cpp dialogs/certifywidget.h dialogs/createcsrforcardkeydialog.cpp dialogs/createcsrforcardkeydialog.h dialogs/deletecertificatesdialog.cpp dialogs/deletecertificatesdialog.h dialogs/editgroupdialog.cpp dialogs/editgroupdialog.h dialogs/expirydialog.cpp dialogs/expirydialog.h dialogs/exportdialog.cpp dialogs/exportdialog.h dialogs/gencardkeydialog.cpp dialogs/gencardkeydialog.h dialogs/groupdetailsdialog.cpp dialogs/groupdetailsdialog.h dialogs/lookupcertificatesdialog.cpp dialogs/lookupcertificatesdialog.h dialogs/nameandemailwidget.cpp dialogs/nameandemailwidget.h dialogs/newopenpgpcertificatedetailsdialog.cpp dialogs/newopenpgpcertificatedetailsdialog.h dialogs/pivcardapplicationadministrationkeyinputdialog.cpp dialogs/pivcardapplicationadministrationkeyinputdialog.h dialogs/revokekeydialog.cpp dialogs/revokekeydialog.h dialogs/selftestdialog.cpp dialogs/selftestdialog.h dialogs/setinitialpindialog.cpp dialogs/setinitialpindialog.h dialogs/subkeyswidget.cpp dialogs/subkeyswidget.h dialogs/trustchainwidget.cpp dialogs/trustchainwidget.h dialogs/updatenotification.cpp dialogs/updatenotification.h dialogs/weboftrustdialog.cpp dialogs/weboftrustdialog.h dialogs/weboftrustwidget.cpp dialogs/weboftrustwidget.h interfaces/anchorprovider.h interfaces/focusfirstchild.h newcertificatewizard/advancedsettingsdialog.cpp newcertificatewizard/advancedsettingsdialog_p.h newcertificatewizard/enterdetailspage.cpp newcertificatewizard/enterdetailspage_p.h newcertificatewizard/keyalgo.cpp newcertificatewizard/keyalgo_p.h newcertificatewizard/keycreationpage.cpp newcertificatewizard/keycreationpage_p.h newcertificatewizard/listwidget.cpp newcertificatewizard/listwidget.h newcertificatewizard/newcertificatewizard.cpp newcertificatewizard/newcertificatewizard.h newcertificatewizard/resultpage.cpp newcertificatewizard/resultpage_p.h newcertificatewizard/wizardpage.cpp newcertificatewizard/wizardpage_p.h selftest/compliancecheck.cpp selftest/compliancecheck.h selftest/enginecheck.cpp selftest/enginecheck.h selftest/gpgagentcheck.cpp selftest/gpgagentcheck.h selftest/gpgconfcheck.cpp selftest/gpgconfcheck.h selftest/libkleopatrarccheck.cpp selftest/libkleopatrarccheck.h selftest/selftest.cpp selftest/selftest.h smartcard/algorithminfo.h smartcard/card.cpp smartcard/card.h smartcard/deviceinfowatcher.cpp smartcard/deviceinfowatcher.h smartcard/keypairinfo.cpp smartcard/keypairinfo.h smartcard/netkeycard.cpp smartcard/netkeycard.h smartcard/openpgpcard.cpp smartcard/openpgpcard.h smartcard/p15card.cpp smartcard/p15card.h smartcard/pivcard.cpp smartcard/pivcard.h smartcard/readerstatus.cpp smartcard/readerstatus.h smartcard/utils.cpp smartcard/utils.h utils/accessibility.cpp utils/accessibility.h utils/action_data.cpp utils/action_data.h utils/applicationstate.cpp utils/applicationstate.h utils/archivedefinition.cpp utils/archivedefinition.h utils/clipboardmenu.cpp utils/clipboardmenu.h utils/debug-helpers.cpp utils/debug-helpers.h utils/dragqueen.cpp utils/dragqueen.h utils/email.cpp utils/email.h utils/emptypassphraseprovider.cpp utils/emptypassphraseprovider.h utils/filedialog.cpp utils/filedialog.h utils/gui-helper.cpp utils/gui-helper.h utils/headerview.cpp utils/headerview.h utils/input.cpp utils/input.h utils/iodevicelogger.cpp utils/iodevicelogger.h utils/kdpipeiodevice.cpp utils/kdpipeiodevice.h utils/keyparameters.cpp utils/keyparameters.h utils/keys.cpp utils/keys.h utils/kuniqueservice.cpp utils/kuniqueservice.h utils/log.cpp utils/log.h utils/memory-helpers.h utils/multivalidator.cpp utils/multivalidator.h utils/output.cpp utils/output.h + utils/overwritedialog.cpp + utils/overwritedialog.h utils/path-helper.cpp utils/path-helper.h utils/scrollarea.cpp utils/scrollarea.h utils/systemtrayicon.cpp utils/systemtrayicon.h utils/tags.cpp utils/tags.h utils/types.cpp utils/types.h utils/userinfo.cpp utils/userinfo.h utils/validation.cpp utils/validation.h utils/writecertassuantransaction.cpp utils/writecertassuantransaction.h utils/wsastarter.cpp utils/wsastarter.h view/anchorcache.cpp view/anchorcache_p.h view/errorlabel.cpp view/errorlabel.h view/formtextinput.cpp view/formtextinput.h view/htmllabel.cpp view/htmllabel.h view/infofield.cpp view/infofield.h view/keycacheoverlay.cpp view/keycacheoverlay.h view/keylistcontroller.cpp view/keylistcontroller.h view/keytreeview.cpp view/keytreeview.h view/netkeywidget.cpp view/netkeywidget.h view/nullpinwidget.cpp view/nullpinwidget.h view/openpgpkeycardwidget.cpp view/openpgpkeycardwidget.h view/p15cardwidget.cpp view/p15cardwidget.h view/padwidget.cpp view/padwidget.h view/pgpcardwidget.cpp view/pgpcardwidget.h view/pivcardwidget.cpp view/pivcardwidget.h view/searchbar.cpp view/searchbar.h view/smartcardwidget.cpp view/smartcardwidget.h view/tabwidget.cpp view/tabwidget.h view/urllabel.cpp view/urllabel.h view/waitwidget.cpp view/waitwidget.h view/welcomewidget.cpp view/welcomewidget.h aboutdata.cpp aboutdata.h kleopatra.qrc kleopatraapplication.cpp kleopatraapplication.h main.cpp mainwindow.cpp mainwindow.h systrayicon.cpp systrayicon.h ) if(WIN32) configure_file (versioninfo.rc.in versioninfo.rc) set(_kleopatra_SRCS ${CMAKE_CURRENT_BINARY_DIR}/versioninfo.rc ${_kleopatra_SRCS}) endif() set (_kleopatra_SRCS conf/kleopageconfigdialog.cpp conf/kleopageconfigdialog.h ${_kleopatra_SRCS}) ecm_qt_declare_logging_category(_kleopatra_SRCS HEADER kleopatra_debug.h IDENTIFIER KLEOPATRA_LOG CATEGORY_NAME org.kde.pim.kleopatra DESCRIPTION "kleopatra (kleopatra)" OLD_CATEGORY_NAMES log_kleopatra EXPORT KLEOPATRA ) if(KLEO_MODEL_TEST) add_definitions(-DKLEO_MODEL_TEST) set(_kleopatra_SRCS ${_kleopatra_SRCS} models/modeltest.cpp) endif() ki18n_wrap_ui(_kleopatra_SRCS dialogs/setinitialpindialog.ui dialogs/trustchainwidget.ui newcertificatewizard/listwidget.ui ) kconfig_add_kcfg_files(_kleopatra_SRCS kcfg/emailoperationspreferences.kcfgc kcfg/fileoperationspreferences.kcfgc kcfg/settings.kcfgc kcfg/smimevalidationpreferences.kcfgc kcfg/tagspreferences.kcfgc kcfg/tooltippreferences.kcfgc ) file(GLOB ICONS_SRCS "${CMAKE_CURRENT_SOURCE_DIR}/icons/*-apps-kleopatra.png") ecm_add_app_icon(_kleopatra_SRCS ICONS ${ICONS_SRCS}) add_executable(kleopatra_bin ${_kleopatra_SRCS} ${_kleopatra_uiserver_SRCS}) # For the ConfigureDialog & KCMs target_link_libraries(kleopatra_bin kcm_kleopatra_static) #if (COMPILE_WITH_UNITY_CMAKE_SUPPORT) # set_target_properties(kleopatra_bin PROPERTIES UNITY_BUILD ON) #endif() set_target_properties(kleopatra_bin PROPERTIES OUTPUT_NAME kleopatra) if (WIN32) set(_kleopatra_platform_libs "secur32") endif () target_link_libraries(kleopatra_bin Gpgmepp ${_kleopatra_extra_libs} KF5::Libkleo KF5::Mime KF${KF_MAJOR_VERSION}::I18n KF${KF_MAJOR_VERSION}::XmlGui KF${KF_MAJOR_VERSION}::IconThemes KF${KF_MAJOR_VERSION}::WindowSystem KF${KF_MAJOR_VERSION}::CoreAddons KF${KF_MAJOR_VERSION}::ItemModels KF${KF_MAJOR_VERSION}::Crash KF${KF_MAJOR_VERSION}::Codecs ${_kleopatra_mail_libs} Qt::Network Qt::PrintSupport # Printing secret keys ${_kleopatra_uiserver_extra_libs} ${_kleopatra_dbusaddons_libs} kleopatraclientcore ${_kleopatra_platform_libs} ) if (QT_MAJOR_VERSION STREQUAL "6") target_link_libraries(kleopatra_bin QGpgmeQt6) else() target_link_libraries(kleopatra_bin QGpgme) endif() install(TARGETS kleopatra_bin ${KDE_INSTALL_TARGETS_DEFAULT_ARGS}) install( PROGRAMS data/org.kde.kleopatra.desktop data/kleopatra_import.desktop DESTINATION ${KDE_INSTALL_APPDIR} ) install(FILES data/org.kde.kleopatra.appdata.xml DESTINATION ${KDE_INSTALL_METAINFODIR}) install( PROGRAMS data/kleopatra_signencryptfiles.desktop data/kleopatra_signencryptfolders.desktop data/kleopatra_decryptverifyfiles.desktop data/kleopatra_decryptverifyfolders.desktop DESTINATION ${KDE_INSTALL_DATADIR}/kio/servicemenus ) diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp index 75f4e4d3f..0d5bbbcf6 100644 --- a/src/crypto/signencrypttask.cpp +++ b/src/crypto/signencrypttask.cpp @@ -1,812 +1,814 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencrypttask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencrypttask.h" #include #include #include #include #include #include #include #include #include #include #include #include #if QGPGME_SUPPORTS_ARCHIVE_JOBS #include #include #include #endif #include #include #include #include #include "kleopatra_debug.h" #include #include #include // for Qt::escape using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted) { return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", input.toHtmlEscaped(), outputDeleted ? QStringLiteral("%1").arg(output.toHtmlEscaped()) : output.toHtmlEscaped()); } class ErrorResult : public Task::Result { public: ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog) : Task::Result(), m_sign(sign), m_encrypt(encrypt), m_error(err), m_errString(errStr), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog) {} QString overview() const override; QString details() const override; GpgME::Error error() const override { return m_error; } QString errorString() const override { return m_errString; } VisualCode code() const override { return NeutralError; } AuditLogEntry auditLog() const override { return m_auditLog; } private: const bool m_sign; const bool m_encrypt; const Error m_error; const QString m_errString; const QString m_inputLabel; const QString m_outputLabel; const AuditLogEntry m_auditLog; }; namespace { struct LabelAndError { QString label; QString errorString; }; } class SignEncryptFilesResult : public Task::Result { public: SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_sresult(sr), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull()); } SignEncryptFilesResult(const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_eresult(er), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_eresult.isNull()); } SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_sresult(sr), m_eresult(er), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull()); } QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; private: const SigningResult m_sresult; const EncryptionResult m_eresult; const LabelAndError m_input; const LabelAndError m_output; const bool m_outputCreated; const AuditLogEntry m_auditLog; }; static QString makeSigningOverview(const Error &err) { if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed."); } return i18n("Signing succeeded."); } static QString makeResultOverview(const SigningResult &result) { return makeSigningOverview(result.error()); } static QString makeEncryptionOverview(const Error &err) { if (err.isCanceled()) { return i18n("Encryption canceled."); } if (err) { return i18n("Encryption failed."); } return i18n("Encryption succeeded."); } static QString makeResultOverview(const EncryptionResult &result) { return makeEncryptionOverview(result.error()); } static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er) { if (er.isNull() && sr.isNull()) { return QString(); } if (er.isNull()) { return makeResultOverview(sr); } if (sr.isNull()) { return makeResultOverview(er); } if (sr.error().isCanceled() || sr.error()) { return makeResultOverview(sr); } if (er.error().isCanceled() || er.error()) { return makeResultOverview(er); } return i18n("Signing and encryption succeeded."); } static QString escape(QString s) { s = s.toHtmlEscaped(); s.replace(QLatin1Char('\n'), QStringLiteral("
")); return s; } static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return QString::fromLocal8Bit(err.asString()).toHtmlEscaped(); } return QString(); } static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return QString::fromLocal8Bit(err.asString()).toHtmlEscaped(); } return i18n(" Encryption succeeded."); } } QString ErrorResult::overview() const { Q_ASSERT(m_error || m_error.isCanceled()); Q_ASSERT(m_sign || m_encrypt); const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true); const bool canceled = m_error.isCanceled(); if (m_sign && m_encrypt) { return canceled ? i18n("%1: Sign/encrypt canceled.", label) : i18n(" %1: Sign/encrypt failed.", label); } return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: %2", label, m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error)); } QString ErrorResult::details() const { return m_errString; } class SignEncryptTask::Private { friend class ::Kleo::Crypto::SignEncryptTask; SignEncryptTask *const q; public: explicit Private(SignEncryptTask *qq); private: void startSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createSignJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createEncryptJob(GpgME::Protocol proto); #if QGPGME_SUPPORTS_ARCHIVE_JOBS void startSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createEncryptArchiveJob(GpgME::Protocol proto); #endif std::shared_ptr makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog); private: void slotResult(const SigningResult &); void slotResult(const SigningResult &, const EncryptionResult &); void slotResult(const EncryptionResult &); void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &); private: std::shared_ptr input; std::shared_ptr output; QStringList inputFileNames; QString outputFileName; std::vector signers; std::vector recipients; bool sign : 1; bool encrypt : 1; bool detached : 1; bool symmetric: 1; bool clearsign: 1; bool archive : 1; QPointer job; std::shared_ptr m_overwritePolicy; }; SignEncryptTask::Private::Private(SignEncryptTask *qq) : q{qq} , sign{true} , encrypt{true} , detached{false} , clearsign{false} , archive{false} , m_overwritePolicy{new OverwritePolicy{nullptr}} { q->setAsciiArmor(true); } std::shared_ptr SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog) { return std::shared_ptr(new ErrorResult(sign, encrypt, err, errStr, q->label(), output ? output->label() : QString{}, auditLog)); } SignEncryptTask::SignEncryptTask(QObject *p) : Task(p), d(new Private(this)) { } SignEncryptTask::~SignEncryptTask() {} void SignEncryptTask::setInputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->inputFileNames = QStringList(fileName); } void SignEncryptTask::setInputFileNames(const QStringList &fileNames) { kleo_assert(!d->job); kleo_assert(!fileNames.empty()); d->inputFileNames = fileNames; } void SignEncryptTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEncryptTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEncryptTask::setOutputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->outputFileName = fileName; } void SignEncryptTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); d->signers = signers; } void SignEncryptTask::setRecipients(const std::vector &recipients) { kleo_assert(!d->job); d->recipients = recipients; } void SignEncryptTask::setOverwritePolicy(const std::shared_ptr &policy) { kleo_assert(!d->job); d->m_overwritePolicy = policy; } void SignEncryptTask::setSign(bool sign) { kleo_assert(!d->job); d->sign = sign; } void SignEncryptTask::setEncrypt(bool encrypt) { kleo_assert(!d->job); d->encrypt = encrypt; } void SignEncryptTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; } void SignEncryptTask::setEncryptSymmetric(bool symmetric) { kleo_assert(!d->job); d->symmetric = symmetric; } void SignEncryptTask::setClearsign(bool clearsign) { kleo_assert(!d->job); d->clearsign = clearsign; } void SignEncryptTask::setCreateArchive(bool archive) { kleo_assert(!d->job); d->archive = archive; } Protocol SignEncryptTask::protocol() const { if (d->sign && !d->signers.empty()) { return d->signers.front().protocol(); } if (d->encrypt || d->symmetric) { if (!d->recipients.empty()) { return d->recipients.front().protocol(); } else { return GpgME::OpenPGP; // symmetric OpenPGP encryption } } throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Cannot determine protocol for task")); } QString SignEncryptTask::label() const { if (d->input) { return d->input->label(); } else if (!d->inputFileNames.empty()) { const auto firstFile = QFileInfo{d->inputFileNames.front()}.fileName(); return d->inputFileNames.size() == 1 ? firstFile : i18nc(", ...", "%1, ...", firstFile); } return {}; } QString SignEncryptTask::tag() const { return Formatting::displayName(protocol()); } unsigned long long SignEncryptTask::inputSize() const { return d->input ? d->input->size() : 0U; } #if QGPGME_SUPPORTS_ARCHIVE_JOBS static bool archiveJobsCanBeUsed(GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); } #endif void SignEncryptTask::doStart() { kleo_assert(!d->job); if (d->sign) { kleo_assert(!d->signers.empty()); if (d->archive) { kleo_assert(!d->detached && !d->clearsign); } } if (!d->output) { d->output = Output::createFromFile(d->outputFileName, d->m_overwritePolicy); } + // release grab on policy, so that output can infer multiple outputs from use count > 1 + d->m_overwritePolicy.reset(); const auto proto = protocol(); #if QGPGME_SUPPORTS_ARCHIVE_JOBS if (d->archive && archiveJobsCanBeUsed(proto)) { d->startSignEncryptArchiveJob(proto); } else #endif { d->startSignEncryptJob(proto); } } void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto) { kleo_assert(input); if (encrypt || symmetric) { Context::EncryptionFlags flags = Context::AlwaysTrust; if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { std::unique_ptr job = createSignEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_SUPPORTS_SET_FILENAME if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } #endif job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); this->job = job.release(); } else { std::unique_ptr job = createEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_SUPPORTS_SET_FILENAME if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } #endif job->start(recipients, input->ioDevice(), output->ioDevice(), flags); this->job = job.release(); } } else if (sign) { std::unique_ptr job = createSignJob(proto); kleo_assert(job.get()); kleo_assert(! (detached && clearsign)); job->start(signers, input->ioDevice(), output->ioDevice(), detached ? GpgME::Detached : clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode); this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } void SignEncryptTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEncryptTask::Private::createSignJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(signJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signEncryptJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(signEncryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult,GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult,GpgME::EncryptionResult))); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(encryptJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(encryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } #if QGPGME_SUPPORTS_ARCHIVE_JOBS void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto) { kleo_assert(!input); const auto baseDirectory = heuristicBaseDirectory(inputFileNames); if (baseDirectory.isEmpty()) { throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n')))); } qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory; const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames); const auto relativePaths = std::vector{tempPaths.begin(), tempPaths.end()}; qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths; if (encrypt || symmetric) { Context::EncryptionFlags flags = Context::AlwaysTrust; if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { std::unique_ptr job = createSignEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(signers, recipients, relativePaths, output->ioDevice(), flags); this->job = job.release(); } else { std::unique_ptr job = createEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(recipients, relativePaths, output->ioDevice(), flags); this->job = job.release(); } } else if (sign) { std::unique_ptr job = createSignArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(signers, relativePaths, output->ioDevice()); this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } std::unique_ptr SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signArchiveJob(q->asciiArmor())); auto job = signJob.get(); kleo_assert(job); connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) { slotResult(job, signResult, EncryptionResult{}); }); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor())); auto job = signEncryptJob.get(); kleo_assert(job); connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) { slotResult(job, signResult, encryptResult); }); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptArchiveJob(q->asciiArmor())); auto job = encryptJob.get(); kleo_assert(job); connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) { slotResult(job, SigningResult{}, encryptResult); }); return encryptJob; } #endif void SignEncryptTask::Private::slotResult(const SigningResult &result) { slotResult(qobject_cast(q->sender()), result, EncryptionResult{}); } void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult) { slotResult(qobject_cast(q->sender()), sresult, eresult); } void SignEncryptTask::Private::slotResult(const EncryptionResult &result) { slotResult(qobject_cast(q->sender()), SigningResult{}, result); } void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult) { const AuditLogEntry auditLog = AuditLogEntry::fromJob(job); bool outputCreated = false; if (input && input->failed()) { output->cancel(); q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape( input->errorString())), auditLog)); return; } else if (sresult.error().code() || eresult.error().code()) { output->cancel(); } else { try { kleo_assert(!sresult.isNull() || !eresult.isNull()); output->finalize(); outputCreated = true; if (input) { input->finalize(); } } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } const LabelAndError inputInfo{q->label(), input ? input->errorString() : QString{}}; const LabelAndError outputInfo{output->label(), output->errorString()}; q->emitResult(std::shared_ptr(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog))); } QString SignEncryptFilesResult::overview() const { const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated); return files + QLatin1String(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult)); } QString SignEncryptFilesResult::details() const { return errorString(); } GpgME::Error SignEncryptFilesResult::error() const { if (m_sresult.error().code()) { return m_sresult.error(); } if (m_eresult.error().code()) { return m_eresult.error(); } return {}; } QString SignEncryptFilesResult::errorString() const { const bool sign = !m_sresult.isNull(); const bool encrypt = !m_eresult.isNull(); kleo_assert(sign || encrypt); if (sign && encrypt) { return m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) : m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString) : QString(); } return sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) : /*else*/ makeResultDetails(m_eresult, m_input.errorString, m_output.errorString); } Task::Result::VisualCode SignEncryptFilesResult::code() const { if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) { return Warning; } return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess; } AuditLogEntry SignEncryptFilesResult::auditLog() const { return m_auditLog; } #include "moc_signencrypttask.cpp" diff --git a/src/utils/output.cpp b/src/utils/output.cpp index 1ad7ea7f0..5d29ace9e 100644 --- a/src/utils/output.cpp +++ b/src/utils/output.cpp @@ -1,761 +1,817 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/output.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "output.h" #include "input_p.h" #include "detail_p.h" #include "kleo_assert.h" #include "kdpipeiodevice.h" #include "log.h" #include "cached.h" +#include "overwritedialog.h" #include +#include #include #include + #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include +#include #ifdef Q_OS_WIN # include #endif #include using namespace Kleo; using namespace Kleo::_detail; static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1; // no timeout static const int PROCESS_TERMINATE_TIMEOUT = 5 * 1000; // 5s class OverwritePolicy::Private { public: Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {} OverwritePolicy::Policy policy; QWidget *widget; }; OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy)) { } OverwritePolicy::~OverwritePolicy() {} OverwritePolicy::Policy OverwritePolicy::policy() const { return d->policy; } void OverwritePolicy::setPolicy(Policy policy) { d->policy = policy; } QWidget *OverwritePolicy::parentWidget() const { return d->widget; } namespace { class TemporaryFile : public QTemporaryFile { public: using QTemporaryFile::QTemporaryFile; void close() override { if (isOpen()) { m_oldFileName = fileName(); } QTemporaryFile::close(); } bool openNonInheritable() { if (!QTemporaryFile::open()) { return false; } #if defined(Q_OS_WIN) //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable. //The handle is then inherited by gpgsm, which prevents deletion of the temp file //in FileOutput::doFinalize() //There are no inheritable handles under wince return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0); #endif return true; } QString oldFileName() const { return m_oldFileName; } private: QString m_oldFileName; }; template struct inhibit_close : T_IODevice { explicit inhibit_close() : T_IODevice() {} template explicit inhibit_close(T1 &t1) : T_IODevice(t1) {} /* reimp */ void close() override {} void reallyClose() { T_IODevice::close(); } }; template struct redirect_close : T_IODevice { explicit redirect_close() : T_IODevice(), m_closed(false) {} template explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {} /* reimp */ void close() override { this->closeWriteChannel(); m_closed = true; } bool isClosed() const { return m_closed; } private: bool m_closed; }; class FileOutput; class OutputInput : public InputImplBase { public: OutputInput(const std::shared_ptr &output); unsigned int classification() const override { return 0; } void outputFinalized() { if (!m_ioDevice->open(QIODevice::ReadOnly)) { qCCritical(KLEOPATRA_LOG) << "Failed to open file for reading"; } } std::shared_ptr ioDevice() const override { return m_ioDevice; } unsigned long long size() const override { return 0; } private: std::shared_ptr m_output; mutable std::shared_ptr m_ioDevice = nullptr; }; class OutputImplBase : public Output { public: OutputImplBase() : Output(), m_defaultLabel(), m_customLabel(), m_errorString(), m_isFinalized(false), m_isFinalizing(false), m_cancelPending(false), m_canceled(false), m_binaryOpt(false) { } QString label() const override { return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel; } void setLabel(const QString &label) override { m_customLabel = label; } void setDefaultLabel(const QString &l) { m_defaultLabel = l; } void setBinaryOpt(bool value) override { m_binaryOpt = value; } bool binaryOpt() const override { return m_binaryOpt; } QString errorString() const override { if (m_errorString.dirty()) { m_errorString = doErrorString(); } return m_errorString; } bool isFinalized() const override { return m_isFinalized; } void finalize() override { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalized || m_isFinalizing) { return; } m_isFinalizing = true; try { doFinalize(); } catch (...) { m_isFinalizing = false; throw; } m_isFinalizing = false; m_isFinalized = true; if (m_cancelPending) { cancel(); } } void cancel() override { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalizing) { m_cancelPending = true; } else if (!m_canceled) { m_isFinalizing = true; try { doCancel(); } catch (...) {} m_isFinalizing = false; m_isFinalized = false; m_canceled = true; } } private: virtual QString doErrorString() const { if (std::shared_ptr io = ioDevice()) { return io->errorString(); } else { return i18n("No output device"); } } virtual void doFinalize() = 0; virtual void doCancel() = 0; private: QString m_defaultLabel; QString m_customLabel; mutable cached m_errorString; bool m_isFinalized : 1; bool m_isFinalizing : 1; bool m_cancelPending : 1; bool m_canceled : 1; bool m_binaryOpt : 1; }; class PipeOutput : public OutputImplBase { public: explicit PipeOutput(assuan_fd_t fd); std::shared_ptr ioDevice() const override { return m_io; } void doFinalize() override { m_io->reallyClose(); } void doCancel() override { doFinalize(); } private: std::shared_ptr< inhibit_close > m_io; }; class ProcessStdInOutput : public OutputImplBase { public: explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd); std::shared_ptr ioDevice() const override { return m_proc; } void doFinalize() override { /* Make sure the data is written in the output here. If this is not done the output will be written in small chunks trough the eventloop causing an unnecessary delay before the process has even a chance to work and finish. This delay is mainly noticeable on Windows where it can take ~30 seconds to write out a 10MB file in the 512 byte chunks gpgme serves. */ qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite() << " Bytes to be written"; while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT)); if (!m_proc->isClosed()) { m_proc->close(); } m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT); } bool failed() const override { if (!m_proc) { return false; } return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0); } void doCancel() override { m_proc->terminate(); QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill); } QString label() const override; private: QString doErrorString() const override; private: const QString m_command; const QStringList m_arguments; const std::shared_ptr< redirect_close > m_proc; }; class FileOutput : public OutputImplBase { public: explicit FileOutput(const QString &fileName, const std::shared_ptr &policy); ~FileOutput() override { qCDebug(KLEOPATRA_LOG) << this; } QString label() const override { return QFileInfo(m_fileName).fileName(); } std::shared_ptr ioDevice() const override { return m_tmpFile; } void doFinalize() override; void doCancel() override { qCDebug(KLEOPATRA_LOG) << this; } QString fileName() const override { return m_fileName; } void attachInput(const std::shared_ptr &input) { m_attachedInput = std::weak_ptr(input); } private: - bool obtainOverwritePermission(); + // returns the file name to write to or an empty string if overwriting was declined + QString obtainOverwritePermission(const QString &fileName); private: - const QString m_fileName; + QString m_fileName; std::shared_ptr< TemporaryFile > m_tmpFile; const std::shared_ptr m_policy; std::weak_ptr m_attachedInput; }; #ifndef QT_NO_CLIPBOARD class ClipboardOutput : public OutputImplBase { public: explicit ClipboardOutput(QClipboard::Mode mode); QString label() const override; std::shared_ptr ioDevice() const override { return m_buffer; } void doFinalize() override; void doCancel() override {} private: QString doErrorString() const override { return QString(); } private: const QClipboard::Mode m_mode; std::shared_ptr m_buffer; }; #endif // QT_NO_CLIPBOARD class ByteArrayOutput: public OutputImplBase { public: explicit ByteArrayOutput(QByteArray *data): m_buffer(std::shared_ptr(new QBuffer(data))) { if (!m_buffer->open(QIODevice::WriteOnly)) throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not open bytearray for writing?!")); } QString label() const override { return m_label; } void setLabel(const QString &label) override { m_label = label; } std::shared_ptr ioDevice() const override { return m_buffer; } void doFinalize() override { m_buffer->close(); } void doCancel() override { m_buffer->close(); } private: QString doErrorString() const override { return QString(); } private: QString m_label; std::shared_ptr m_buffer; }; } std::shared_ptr Output::createFromPipeDevice(assuan_fd_t fd, const QString &label) { std::shared_ptr po(new PipeOutput(fd)); po->setDefaultLabel(label); return po; } PipeOutput::PipeOutput(assuan_fd_t fd) : OutputImplBase(), m_io(new inhibit_close) { errno = 0; if (!m_io->open(fd, QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for writing", assuanFD2int(fd))); } std::shared_ptr Output::createFromFile(const QString &fileName, bool forceOverwrite) { - return createFromFile(fileName, std::shared_ptr(new OverwritePolicy(nullptr, forceOverwrite ? OverwritePolicy::Allow : OverwritePolicy::Deny))); - + return createFromFile(fileName, std::shared_ptr(new OverwritePolicy(nullptr, forceOverwrite ? OverwritePolicy::Overwrite : OverwritePolicy::Skip))); } + std::shared_ptr Output::createFromFile(const QString &fileName, const std::shared_ptr &policy) { std::shared_ptr fo(new FileOutput(fileName, policy)); qCDebug(KLEOPATRA_LOG) << fo.get(); return fo; } FileOutput::FileOutput(const QString &fileName, const std::shared_ptr &policy) : OutputImplBase(), m_fileName(fileName), m_tmpFile(new TemporaryFile(fileName)), m_policy(policy) { Q_ASSERT(m_policy); errno = 0; if (!m_tmpFile->openNonInheritable()) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not create temporary file for output \"%1\"", fileName)); } -bool FileOutput::obtainOverwritePermission() +static QString suggestFileName(const QString &fileName) +{ + const QFileInfo fileInfo{fileName}; + const QString path = fileInfo.absolutePath(); + const QString newFileName = KFileUtils::suggestName(QUrl::fromLocalFile(path), fileInfo.fileName()); + return path + QLatin1Char{'/'} + newFileName; +} + +QString FileOutput::obtainOverwritePermission(const QString &fileName) { - if (m_policy->policy() != OverwritePolicy::Ask) { - return m_policy->policy() == OverwritePolicy::Allow; + switch (m_policy->policy()) { + case OverwritePolicy::Ask: + break; + case OverwritePolicy::Overwrite: + return fileName; + case OverwritePolicy::Rename: { + return suggestFileName(fileName); } - const int sel = KMessageBox::questionTwoActionsCancel(m_policy->parentWidget(), - i18n("The file %1 already exists.\n" - "Overwrite?", - m_fileName), - i18n("Overwrite Existing File?"), - KStandardGuiItem::overwrite(), - KGuiItem(i18n("Overwrite All")), - KStandardGuiItem::cancel()); - if (sel == KMessageBox::ButtonCode::SecondaryAction) { // Overwrite All - m_policy->setPolicy(OverwritePolicy::Allow); + case OverwritePolicy::Skip: + return {}; + case OverwritePolicy::Cancel: + qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Called although user canceled operation"; + return {}; } - return sel == KMessageBox::ButtonCode::PrimaryAction || sel == KMessageBox::ButtonCode::SecondaryAction; + + qCDebug(KLEOPATRA_LOG) << __func__ << "m_policy.use_count():" << m_policy.use_count(); + OverwriteDialog::Options options = OverwriteDialog::AllowRename; + if (m_policy.use_count() > 1) { + options |= OverwriteDialog::MultipleItems | OverwriteDialog::AllowSkip; + } + OverwriteDialog dialog{m_policy->parentWidget(), + i18nc("@title:window", "File Already Exists"), + fileName, + options}; + const auto result = static_cast(dialog.exec()); + qCDebug(KLEOPATRA_LOG) << __func__ << "result:" << static_cast(result); + switch (result) { + case OverwriteDialog::Cancel: + m_policy->setPolicy(OverwritePolicy::Cancel); + return {}; + case OverwriteDialog::AutoSkip: + m_policy->setPolicy(OverwritePolicy::Skip); + [[fallthrough]]; + case OverwriteDialog::Skip: + return {}; + case OverwriteDialog::OverwriteAll: + m_policy->setPolicy(OverwritePolicy::Overwrite); + [[fallthrough]]; + case OverwriteDialog::Overwrite: + return fileName; + case OverwriteDialog::Rename: + return dialog.newFileName(); + case OverwriteDialog::AutoRename: { + m_policy->setPolicy(OverwritePolicy::Rename); + return suggestFileName(fileName); + } + default: + qCDebug(KLEOPATRA_LOG) << __func__ << "unexpected result:" << result; + }; + return {}; } void FileOutput::doFinalize() { qCDebug(KLEOPATRA_LOG) << this; struct Remover { QString file; ~Remover() { if (QFile::exists(file)) { QFile::remove(file); } } } remover; kleo_assert(m_tmpFile); if (m_tmpFile->isOpen()) { m_tmpFile->close(); } QString tmpFileName = remover.file = m_tmpFile->oldFileName(); m_tmpFile->setAutoRemove(false); QPointer guard = m_tmpFile.get(); m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/ kleo_assert(!guard); // if this triggers, we need to audit for holder of std::shared_ptrs. const QFileInfo fi(tmpFileName); bool qtbug83365_workaround = false; if (!fi.exists()) { /* QT Bug 83365 since qt 5.13 causes the filename of temporary files * in UNC path name directories (unmounted samba shares) to start with * UNC/ instead of the // that Qt can actually handle for things like * rename and remove. So if we can't find our temporary file we try * to workaround that bug. */ qCDebug(KLEOPATRA_LOG) << "failure to find " << tmpFileName; if (tmpFileName.startsWith(QLatin1String("UNC"))) { tmpFileName.replace(0, strlen("UNC"), QLatin1Char('/')); qtbug83365_workaround = true; } const QFileInfo fi2(tmpFileName); if (!fi2.exists()) { throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not find temporary file \"%1\".").arg(tmpFileName)); } } - qCDebug(KLEOPATRA_LOG) << this << " renaming " << tmpFileName << "->" << m_fileName; + qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; - if (!obtainOverwritePermission()) - throw Exception(gpg_error(GPG_ERR_CANCELED), - i18n("Overwriting declined")); - - qCDebug(KLEOPATRA_LOG) << this << "going to overwrite" << m_fileName; - - if (!QFile::remove(m_fileName)) - throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), - i18n("Could not remove file \"%1\" for overwriting.", m_fileName)); + { + const auto newFileName = obtainOverwritePermission(m_fileName); + if (newFileName.isEmpty()) { + throw Exception(gpg_error(GPG_ERR_CANCELED), + i18n("Overwriting declined")); + } + if (newFileName == m_fileName) { + qCDebug(KLEOPATRA_LOG) << this << "going to remove file for overwriting" << m_fileName; + if (!QFile::remove(m_fileName)) { + throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), + xi18nc("@info", "Could not remove file %1 for overwriting.", m_fileName)); + } + qCDebug(KLEOPATRA_LOG) << this << "removing file to overwrite succeeded"; + } else { + m_fileName = newFileName; + } + } - qCDebug(KLEOPATRA_LOG) << this << "succeeded, renaming " << tmpFileName << "->" << m_fileName; + qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } if (qtbug83365_workaround) { QFile::remove(tmpFileName); } return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n(R"(Could not rename file "%1" to "%2")", tmpFileName, m_fileName)); } std::shared_ptr Output::createFromProcessStdIn(const QString &command) { return std::shared_ptr(new ProcessStdInOutput(command, QStringList(), QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args) { return std::shared_ptr(new ProcessStdInOutput(command, args, QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd) { return std::shared_ptr(new ProcessStdInOutput(command, args, wd)); } ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd) : OutputImplBase(), m_command(cmd), m_arguments(args), m_proc(new redirect_close) { qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << '\n' << cmd << args; if (cmd.isEmpty()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified")); m_proc->setWorkingDirectory(wd.absolutePath()); m_proc->start(cmd, args); m_proc->setReadChannel(QProcess::StandardError); if (!m_proc->waitForStarted()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); } QString ProcessStdInOutput::label() const { if (!m_proc) { return OutputImplBase::label(); } // output max. 3 arguments const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' ')); if (m_arguments.size() > 3) { return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline); } else { return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline); } } QString ProcessStdInOutput::doErrorString() const { kleo_assert(m_proc); if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) { return QString(); } if (m_proc->error() == QProcess::UnknownError) return i18n("Error while running %1: %2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", m_command, m_proc->errorString()); } } #ifndef QT_NO_CLIPBOARD std::shared_ptr Output::createFromClipboard() { return std::shared_ptr(new ClipboardOutput(QClipboard::Clipboard)); } ClipboardOutput::ClipboardOutput(QClipboard::Mode mode) : OutputImplBase(), m_mode(mode), m_buffer(new QBuffer) { errno = 0; if (!m_buffer->open(QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not write to clipboard")); } QString ClipboardOutput::label() const { switch (m_mode) { case QClipboard::Clipboard: return i18n("Clipboard"); case QClipboard::FindBuffer: return i18n("Find buffer"); case QClipboard::Selection: return i18n("Selection"); } return QString(); } void ClipboardOutput::doFinalize() { if (m_buffer->isOpen()) { m_buffer->close(); } if (QClipboard *const cb = QApplication::clipboard()) { cb->setText(QString::fromUtf8(m_buffer->data())); } else throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not find clipboard")); } #endif // QT_NO_CLIPBOARD Output::~Output() {} OutputInput::OutputInput(const std::shared_ptr &output) : m_output(output) , m_ioDevice(new QFile(output->fileName())) { } std::shared_ptr Input::createFromOutput(const std::shared_ptr &output) { if (auto fo = std::dynamic_pointer_cast(output)) { auto input = std::shared_ptr(new OutputInput(fo)); fo->attachInput(input); return input; } else { return {}; } } std::shared_ptr Output::createFromByteArray(QByteArray *data, const QString &label) { auto ret = std::shared_ptr(new ByteArrayOutput(data)); ret->setLabel(label); return ret; } diff --git a/src/utils/output.h b/src/utils/output.h index 9fee4747b..8feced6f0 100644 --- a/src/utils/output.h +++ b/src/utils/output.h @@ -1,81 +1,83 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/output.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include // for assuan_fd_t #include #include #include #include class QIODevice; class QDir; class QWidget; namespace Kleo { class OverwritePolicy { public: enum Policy { - Allow, - Deny, - Ask + Ask, + Overwrite, + Rename, + Skip, + Cancel, }; explicit OverwritePolicy(QWidget *parent, Policy initialPolicy = Ask); ~OverwritePolicy(); Policy policy() const; void setPolicy(Policy); QWidget *parentWidget() const; private: class Private; kdtools::pimpl_ptr d; }; class Output { public: virtual ~Output(); virtual void setLabel(const QString &label) = 0; virtual QString label() const = 0; virtual std::shared_ptr ioDevice() const = 0; virtual QString errorString() const = 0; virtual bool isFinalized() const = 0; virtual void finalize() = 0; virtual void cancel() = 0; virtual bool binaryOpt() const = 0; virtual void setBinaryOpt(bool value) = 0; /** Whether or not the output failed. */ virtual bool failed() const { return false; } virtual QString fileName() const { return {}; } static std::shared_ptr createFromFile(const QString &fileName, const std::shared_ptr &); static std::shared_ptr createFromFile(const QString &fileName, bool forceOverwrite); static std::shared_ptr createFromPipeDevice(assuan_fd_t fd, const QString &label); static std::shared_ptr createFromProcessStdIn(const QString &command); static std::shared_ptr createFromProcessStdIn(const QString &command, const QStringList &args); static std::shared_ptr createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &workingDirectory); #ifndef QT_NO_CLIPBOARD static std::shared_ptr createFromClipboard(); #endif static std::shared_ptr createFromByteArray(QByteArray *data, const QString &label); }; } diff --git a/src/utils/overwritedialog.cpp b/src/utils/overwritedialog.cpp new file mode 100644 index 000000000..7f711280b --- /dev/null +++ b/src/utils/overwritedialog.cpp @@ -0,0 +1,311 @@ +/* utils/overwritedialog.cpp + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2023 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ + +#include + +#include "overwritedialog.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +using namespace Kleo; + +class OverwriteDialog::Private +{ + Kleo::OverwriteDialog *q; + +public: + Private(const QString &filePath, Kleo::OverwriteDialog *qq) + : q{qq} + , fileInfo{filePath} + { + } + + void setRenameBoxText(const QString &fileName); + void enableRenameButton(const QString &newName); + void suggestNewNamePressed(); + QString newFileName() const; + + void renamePressed(); + void renameAllPressed(); + void skipPressed(); + void skipAllPressed(); + void overwritePressed(); + void overwriteAllPressed(); + void cancelPressed(); + + void done(Result result) + { + q->done(static_cast(result)); + } + + QLineEdit *newNameEdit = nullptr; + QPushButton *suggestNewNameBtn = nullptr; + QPushButton *renameBtn = nullptr; + QPushButton *renameAllBtn = nullptr; + QPushButton *skipBtn = nullptr; + QPushButton *skipAllBtn = nullptr; + QPushButton *overwriteBtn = nullptr; + QPushButton *overwriteAllBtn = nullptr; + QPushButton *cancelBtn = nullptr; + QFileInfo fileInfo; +}; + +void OverwriteDialog::Private::setRenameBoxText(const QString &fileName) +{ + // sets the text in file name line edit box, selecting the filename (but not the extension if there is one). + QMimeDatabase db; + const auto extension = db.suffixForFileName(fileName); + newNameEdit->setText(fileName); + + if (!extension.isEmpty()) { + const int selectionLength = fileName.length() - extension.length() - 1; + newNameEdit->setSelection(0, selectionLength); + } else { + newNameEdit->selectAll(); + } +} + +void OverwriteDialog::Private::enableRenameButton(const QString &newName) +{ + if (!newName.isEmpty() && (newName != fileInfo.fileName())) { + renameBtn->setEnabled(true); + renameBtn->setDefault(true); + + if (renameAllBtn) { + renameAllBtn->setEnabled(false); + } + if (overwriteBtn) { + overwriteBtn->setEnabled(false); + } + if (overwriteAllBtn) { + overwriteAllBtn->setEnabled(false); + } + } else { + renameBtn->setEnabled(false); + + if (renameAllBtn) { + renameAllBtn->setEnabled(true); + } + if (overwriteBtn) { + overwriteBtn->setEnabled(true); + } + if (overwriteAllBtn) { + overwriteAllBtn->setEnabled(true); + } + } +} + +void OverwriteDialog::Private::suggestNewNamePressed() +{ + if (!newNameEdit->text().isEmpty()) { + setRenameBoxText(KFileUtils::suggestName(QUrl::fromLocalFile(fileInfo.absolutePath()), newNameEdit->text())); + } else { + setRenameBoxText(KFileUtils::suggestName(QUrl::fromLocalFile(fileInfo.absolutePath()), fileInfo.fileName())); + } +} + +QString OverwriteDialog::Private::newFileName() const +{ + return fileInfo.path() + QLatin1Char{'/'} + newNameEdit->text(); +} + +void OverwriteDialog::Private::renamePressed() +{ + if (newNameEdit->text().isEmpty()) { + return; + } + const auto fileName = newFileName(); + if (QFileInfo::exists(fileName)) { + KMessageBox::error(q, xi18nc("@info", "The file %1 already exists. Please enter a different file name.", fileName)); + return; + } + done(Result::Rename); +} + +void OverwriteDialog::Private::renameAllPressed() +{ + done(Result::AutoRename); +} + +void OverwriteDialog::Private::skipPressed() +{ + done(Result::Skip); +} + +void OverwriteDialog::Private::skipAllPressed() +{ + done(Result::AutoSkip); +} + +void OverwriteDialog::Private::overwritePressed() +{ + done(Result::Overwrite); +} + +void OverwriteDialog::Private::overwriteAllPressed() +{ + done(Result::OverwriteAll); +} + +void OverwriteDialog::Private::cancelPressed() +{ + done(Result::Cancel); +} + +OverwriteDialog::OverwriteDialog(QWidget *parent, const QString &title, const QString &fileName, Options options) + : QDialog{parent} + , d{new Private{fileName, this}} +{ + setObjectName(QStringLiteral("Kleo::OverwriteDialog")); + + setWindowTitle(title); + + auto mainLayout = new QVBoxLayout{this}; + mainLayout->addStrut(400); // makes dlg at least that wide + + mainLayout->addWidget(new QLabel{xi18nc("@info", "The file %1 already exists.", fileName), this}); + + if (options & AllowRename) { + mainLayout->addSpacing(15); + + auto label = new QLabel{i18nc("@label", "Rename:"), this}; + mainLayout->addWidget(label); + + auto hbox = new QHBoxLayout; + + d->newNameEdit = new QLineEdit{this}; + label->setBuddy(d->newNameEdit); + hbox->addWidget(d->newNameEdit); + + d->suggestNewNameBtn = new QPushButton{i18nc("@action:button", "Suggest New Name"), this}; + d->suggestNewNameBtn->setToolTip(i18nc("@info:tooltip", "Suggest a file name that does not already exist.")); + hbox->addWidget(d->suggestNewNameBtn); + + mainLayout->addLayout(hbox); + } + + mainLayout->addWidget(new KSeparator{this}); + + auto buttonLayout = new QHBoxLayout; + + if (options & AllowRename) { + d->renameBtn = new QPushButton{i18nc("@action:button", "Rename"), this}; + d->renameBtn->setToolTip(i18nc("@info:tooltip", "Save the file with the given name.")); + d->renameBtn->setIcon(QIcon::fromTheme(QStringLiteral("edit-rename"))); + d->renameBtn->setEnabled(false); + buttonLayout->addWidget(d->renameBtn); + + if (options & MultipleItems) { + d->renameAllBtn = new QPushButton{i18nc("@action:button", "Rename All"), this}; + d->renameAllBtn->setIcon(d->renameBtn->icon()); + d->renameAllBtn->setToolTip( + i18nc("@info:tooltip", "Automatically save all files that would overwrite an already existing file with a different name.")); + buttonLayout->addWidget(d->renameAllBtn); + } + } + + if ((options & AllowSkip) && (options & MultipleItems)) { + d->skipBtn = new QPushButton{i18nc("@action:button", "Skip"), this}; + d->skipBtn->setIcon(QIcon::fromTheme(QStringLiteral("go-next-skip"))); + d->skipBtn->setToolTip(i18nc("@info:tooltip", "Do not write this file, skip to the next one instead.")); + buttonLayout->addWidget(d->skipBtn); + + d->skipAllBtn = new QPushButton{i18nc("@action:button", "Skip All"), this}; + d->skipAllBtn->setIcon(d->skipBtn->icon()); + d->skipAllBtn->setToolTip(i18nc("@info:tooltip", "Do not write this file and any other files that would overwrite an already existing file.")); + buttonLayout->addWidget(d->skipAllBtn); + } + + d->overwriteBtn = new QPushButton{i18nc("@action:button", "Overwrite"), this}; + d->overwriteBtn->setIcon(KStandardGuiItem::overwrite().icon()); + d->overwriteBtn->setToolTip(i18nc("@info:tooltip", "Overwrite the existing file.")); + buttonLayout->addWidget(d->overwriteBtn); + + if (options & MultipleItems) { + d->overwriteAllBtn = new QPushButton{i18nc("@action:button", "Overwrite All"), this}; + d->overwriteAllBtn->setIcon(d->overwriteBtn->icon()); + d->overwriteAllBtn->setToolTip(i18nc("@info:tooltip", "Overwrite the existing file and any other files that already exist.")); + buttonLayout->addWidget(d->overwriteAllBtn); + } + + d->cancelBtn = new QPushButton{this}; + KGuiItem::assign(d->cancelBtn, KStandardGuiItem::cancel()); + d->cancelBtn->setDefault(true); + buttonLayout->addWidget(d->cancelBtn); + + mainLayout->addLayout(buttonLayout); + + if (d->newNameEdit) { + d->setRenameBoxText(d->fileInfo.fileName()); + connect(d->newNameEdit, &QLineEdit::textChanged, this, [this](const QString &text) { + d->enableRenameButton(text); + }); + connect(d->suggestNewNameBtn, &QAbstractButton::clicked, this, [this]() { + d->suggestNewNamePressed(); + }); + } + if (d->renameBtn) { + connect(d->renameBtn, &QAbstractButton::clicked, this, [this]() { + d->renamePressed(); + }); + } + if (d->renameAllBtn) { + connect(d->renameAllBtn, &QAbstractButton::clicked, this, [this]() { + d->renameAllPressed(); + }); + } + if (d->skipBtn) { + connect(d->skipBtn, &QAbstractButton::clicked, this, [this]() { + d->skipPressed(); + }); + connect(d->skipAllBtn, &QAbstractButton::clicked, this, [this]() { + d->skipAllPressed(); + }); + } + connect(d->overwriteBtn, &QAbstractButton::clicked, this, [this]() { + d->overwritePressed(); + }); + if (d->overwriteAllBtn) { + connect(d->overwriteAllBtn, &QAbstractButton::clicked, this, [this]() { + d->overwriteAllPressed(); + }); + } + connect(d->cancelBtn, &QAbstractButton::clicked, this, [this]() { + d->cancelPressed(); + }); + + if (d->newNameEdit) { + d->newNameEdit->setFocus(); + } + + resize(sizeHint()); +} + +OverwriteDialog::~OverwriteDialog() = default; + +QString OverwriteDialog::newFileName() const +{ + if (result() == Result::Rename) { + return d->newFileName(); + } + return {}; +} diff --git a/src/utils/overwritedialog.h b/src/utils/overwritedialog.h new file mode 100644 index 000000000..93cfbbf26 --- /dev/null +++ b/src/utils/overwritedialog.h @@ -0,0 +1,80 @@ +/* utils/overwritedialog.h + + This file is part of Kleopatra, the KDE keymanager + SPDX-FileCopyrightText: 2023 g10 Code GmbH + SPDX-FileContributor: Ingo Klöcker + + SPDX-License-Identifier: GPL-2.0-or-later +*/ +#pragma once + +#include + +#include + +namespace Kleo +{ + +/** + * @class Kleo::OverwriteDialog overwritedialog.h + * + * This dialog can be shown when you realize that a file you want to write + * already exists and you want to offer the user the choice to either Rename, + * Overwrite, or Skip. + */ +class OverwriteDialog : public QDialog +{ + Q_OBJECT +public: + /** + * @see Options + */ + enum Option { + AllowRename = 1, ///< Allow the user to enter a different file name. + AllowSkip = 2, ///< Offer a "Skip" button, to skip other files too. Requires MultipleItems. + MultipleItems = + 4, ///< Set if the current operation concerns multiple files, so it makes sense to offer buttons that apply the user's choice to all files/folders. + }; + /** + * Stores a combination of #Option values. + */ + Q_DECLARE_FLAGS(Options, Option) + + enum Result { + Cancel = 0, // = QDialog::Rejected + Overwrite = 1, + OverwriteAll = 2, + Rename = 3, + AutoRename = 4, + Skip = 5, + AutoSkip = 6, + }; + + /** + * Construct an "overwrite" dialog to let the user know that the file @p fileName is about to be overwritten. + * + * @param parent parent widget + * @param title the title for the dialog + * @param fileName the path of the file that already exists + * @param options parameters for the dialog (which buttons to show...), + */ + OverwriteDialog(QWidget *parent, const QString &title, const QString &fileName, Options options); + + ~OverwriteDialog() override; + + /** + * Returns the new file name to use if the user selected the Rename option. + * Otherwise, returns an empty string. + * + * @return the new file name or an empty string + */ + QString newFileName() const; + +private: + class Private; + std::unique_ptr const d; +}; + +Q_DECLARE_OPERATORS_FOR_FLAGS(OverwriteDialog::Options) + +}