diff --git a/autotests/kuniqueservicetest.cpp b/autotests/kuniqueservicetest.cpp index 0f54d1f61..14532dc8d 100644 --- a/autotests/kuniqueservicetest.cpp +++ b/autotests/kuniqueservicetest.cpp @@ -1,165 +1,165 @@ /* This file is part of Kleopatra SPDX-FileCopyrightText: 2016 Intevation GmbH It is based on libkdbus kdbusservicetest which is: SPDX-FileCopyrightText: 1999 Waldo Bastian SPDX-FileCopyrightText: 2011 David Faure SPDX-FileCopyrightText: 2011 Kevin Ottens SPDX-License-Identifier: LGPL-2.0-only */ /* The main modification in this test is that every activateRequested * call needs to set the exit code to signal the application it's done. */ #include #include #include #include #include #include #include #include "utils/kuniqueservice.h" #include #include using namespace std::chrono_literals; class TestObject : public QObject { Q_OBJECT public: TestObject(KUniqueService *service) : m_proc(nullptr) , m_callCount(0) , m_service(service) { } ~TestObject() override { if (m_proc) { m_proc->waitForFinished(); } } int callCount() const { return m_callCount; } private Q_SLOTS: void slotActivateRequested(const QStringList &args, const QString &workingDirectory) { Q_UNUSED(workingDirectory) qDebug() << "Application executed with args" << args; ++m_callCount; if (m_callCount == 1) { Q_ASSERT(args.count() == 1); - Q_ASSERT(args.at(0) == QLatin1String("dummy call")); + Q_ASSERT(args.at(0) == QLatin1StringView("dummy call")); m_service->setExitValue(0); } else if (m_callCount == 2) { Q_ASSERT(args.count() == 2); - Q_ASSERT(args.at(1) == QLatin1String("bad call")); + Q_ASSERT(args.at(1) == QLatin1StringView("bad call")); m_service->setExitValue(4); } else if (m_callCount == 3) { Q_ASSERT(args.count() == 3); - Q_ASSERT(args.at(1) == QLatin1String("real call")); - Q_ASSERT(args.at(2) == QLatin1String("second arg")); + Q_ASSERT(args.at(1) == QLatin1StringView("real call")); + Q_ASSERT(args.at(2) == QLatin1StringView("second arg")); m_service->setExitValue(0); // OK, all done, quit QCoreApplication::instance()->quit(); } } void slotProcessFinished(int exitCode, QProcess::ExitStatus exitStatus) { Q_UNUSED(exitStatus) qDebug() << "Process exited with code" << exitCode; m_proc = nullptr; if (m_callCount == 2) { Q_ASSERT(exitCode == 4); secondCall(); } } void firstCall() { QStringList args; args << QStringLiteral("bad call"); executeNewChild(args); } void secondCall() { QStringList args; args << QStringLiteral("real call") << QStringLiteral("second arg"); executeNewChild(args); } private: void executeNewChild(const QStringList &args) { // Duplicated from kglobalsettingstest.cpp - make a shared helper method? m_proc = new QProcess(this); connect(m_proc, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotProcessFinished(int, QProcess::ExitStatus))); QString appName = QStringLiteral("kuniqueservicetest"); #ifdef Q_OS_WIN appName += QStringLiteral(".exe"); #else if (QFile::exists(appName + QStringLiteral(".shell"))) { appName = QStringLiteral("./") + appName + QStringLiteral(".shell"); } else if (QFile::exists(QCoreApplication::applicationFilePath())) { appName = QCoreApplication::applicationFilePath(); } else { Q_ASSERT(QFile::exists(appName)); appName = QStringLiteral("./") + appName; } #endif qDebug() << "about to run" << appName << args; m_proc->start(appName, args); } QProcess *m_proc; int m_callCount; KUniqueService *m_service; }; int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QCoreApplication::setApplicationName(QStringLiteral("kuniqueservicetest")); QCoreApplication::setOrganizationDomain(QStringLiteral("kde.org")); KUniqueService service; TestObject testObject(&service); QObject::connect(&service, SIGNAL(activateRequested(QStringList, QString)), &testObject, SLOT(slotActivateRequested(QStringList, QString))); // Testcase for the problem coming from the old fork-on-startup solution: // the "Activate" D-Bus call would time out if the app took too much time // to be ready. // printf("Sleeping.\n"); // sleep(200); QStringList args; args << QStringLiteral("dummy call"); QMetaObject::invokeMethod(&service, "activateRequested", Qt::QueuedConnection, Q_ARG(QStringList, args), Q_ARG(QString, QDir::currentPath())); QTimer::singleShot(400ms, &testObject, SLOT(firstCall())); qDebug() << "Running."; a.exec(); qDebug() << "Terminating."; Q_ASSERT(testObject.callCount() == 3); const bool ok = testObject.callCount() == 3; return ok ? 0 : 1; } #include "kuniqueservicetest.moc" diff --git a/src/aboutdata.cpp b/src/aboutdata.cpp index 739ef67b9..917447dbb 100644 --- a/src/aboutdata.cpp +++ b/src/aboutdata.cpp @@ -1,154 +1,154 @@ /* aboutdata.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include #include "aboutdata.h" #include "kleopatraapplication.h" #include #include #include #include #include #include #include "kleopatra_debug.h" /* Path to GnuPGs signing keys relative to the GnuPG installation */ #ifndef GNUPG_DISTSIGKEY_RELPATH #define GNUPG_DISTSIGKEY_RELPATH "/../share/gnupg/distsigkey.gpg" #endif /* Path to a VERSION file relative to QCoreApplication::applicationDirPath */ #ifndef VERSION_RELPATH #define VERSION_RELPATH "/../VERSION" #endif static const char kleopatra_version[] = KLEOPATRA_VERSION_STRING; struct about_data { const KLazyLocalizedString name; const KLazyLocalizedString desc; const char *email; const char *web; }; static const about_data authors[] = { {kli18n("Andre Heinecke"), kli18n("Current Maintainer"), "aheinecke@gnupg.org", nullptr}, {kli18n("Marc Mutz"), kli18n("Former Maintainer"), "mutz@kde.org", nullptr}, {kli18n("Steffen Hansen"), kli18n("Former Maintainer"), "hansen@kde.org", nullptr}, {kli18n("Matthias Kalle Dalheimer"), kli18n("Original Author"), "kalle@kde.org", nullptr}, }; static const about_data credits[] = { {kli18n("David Faure"), kli18n("Backend configuration framework, KIO integration"), "faure@kde.org", nullptr}, {kli18n("Michel Boyer de la Giroday"), kli18n("Key-state dependent colors and fonts in the certificates list"), "michel@klaralvdalens-datakonsult.se", nullptr}, {kli18n("Thomas Moenicke"), kli18n("Artwork"), "tm@php-qt.org", nullptr}, {kli18n("Frank Osterfeld"), kli18n("Resident gpgme/win wrangler, UI Server commands and dialogs"), "osterfeld@kde.org", nullptr}, {kli18n("Karl-Heinz Zimmer"), kli18n("DN display ordering support, infrastructure"), "khz@kde.org", nullptr}, {kli18n("Laurent Montel"), kli18n("Qt5 port, general code maintenance"), "montel@kde.org", nullptr}, }; static void updateAboutDataFromSettings(KAboutData *about, const QSettings *settings) { if (!about || !settings) { return; } about->setDisplayName(settings->value(QStringLiteral("displayName"), about->displayName()).toString()); about->setProductName(settings->value(QStringLiteral("productName"), about->productName()).toByteArray()); about->setComponentName(settings->value(QStringLiteral("componentName"), about->componentName()).toString()); about->setShortDescription(settings->value(QStringLiteral("shortDescription"), about->shortDescription()).toString()); about->setHomepage(settings->value(QStringLiteral("homepage"), about->homepage()).toString()); about->setBugAddress(settings->value(QStringLiteral("bugAddress"), about->bugAddress()).toByteArray()); about->setVersion(settings->value(QStringLiteral("version"), about->version()).toByteArray()); about->setOtherText(settings->value(QStringLiteral("otherText"), about->otherText()).toString()); about->setCopyrightStatement(settings->value(QStringLiteral("copyrightStatement"), about->copyrightStatement()).toString()); about->setDesktopFileName(settings->value(QStringLiteral("desktopFileName"), about->desktopFileName()).toString()); } // Extend the about data with the used GnuPG Version since this can // make a big difference with regards to the available features. static void loadBackendVersions() { auto thread = QThread::create([]() { STARTUP_TIMING << "Checking backend versions"; const auto backendVersions = Kleo::backendVersionInfo(); STARTUP_TIMING << "backend versions checked"; if (!backendVersions.empty()) { QMetaObject::invokeMethod(qApp, [backendVersions]() { auto about = KAboutData::applicationData(); about.setOtherText(i18nc("Preceeds a list of applications/libraries used by Kleopatra", "Uses:") // - + QLatin1String{"
  • "} // - + backendVersions.join(QLatin1String{"
  • "}) // - + QLatin1String{"
"} // + + QLatin1StringView{"
  • "} // + + backendVersions.join(QLatin1StringView{"
  • "}) // + + QLatin1StringView{"
"} // + about.otherText()); KAboutData::setApplicationData(about); }); } }); thread->start(); } // This code is mostly for Gpg4win and GnuPG VS-Desktop so that they // can put in their own about data information. static void loadCustomAboutData(KAboutData *about) { const QStringList searchPaths = {Kleo::gnupgInstallPath()}; const QString versionFile = QCoreApplication::applicationDirPath() + QStringLiteral(VERSION_RELPATH); const QString distSigKeys = Kleo::gnupgInstallPath() + QStringLiteral(GNUPG_DISTSIGKEY_RELPATH); STARTUP_TIMING << "Starting version info check"; bool valid = Kleo::gpgvVerify(versionFile, QString(), distSigKeys, searchPaths); STARTUP_TIMING << "Version info checked"; if (valid) { qCDebug(KLEOPATRA_LOG) << "Found valid VERSION file. Updating about data."; auto settings = std::make_shared(versionFile, QSettings::IniFormat); settings->beginGroup(QStringLiteral("Kleopatra")); updateAboutDataFromSettings(about, settings.get()); KleopatraApplication::instance()->setDistributionSettings(settings); } loadBackendVersions(); } AboutData::AboutData() : KAboutData(QStringLiteral("kleopatra"), i18n("Kleopatra"), - QLatin1String(kleopatra_version), + QLatin1StringView(kleopatra_version), i18n("Certificate Manager and Unified Crypto GUI"), KAboutLicense::GPL, i18n("(c) 2002 Steffen\u00A0Hansen, Matthias\u00A0Kalle\u00A0Dalheimer, Klar\u00E4lvdalens\u00A0Datakonsult\u00A0AB\n" "(c) 2004, 2007, 2008, 2009 Marc\u00A0Mutz, Klar\u00E4lvdalens\u00A0Datakonsult\u00A0AB") // + QLatin1Char('\n') // + i18n("(c) 2016-2018 Intevation GmbH") // + QLatin1Char('\n') // + i18n("(c) 2010-%1 The Kleopatra developers, g10 Code GmbH", QStringLiteral("2024"))) { using ::authors; using ::credits; for (unsigned int i = 0; i < sizeof authors / sizeof *authors; ++i) { addAuthor(KLocalizedString(authors[i].name).toString(), KLocalizedString(authors[i].desc).toString(), - QLatin1String(authors[i].email), - QLatin1String(authors[i].web)); + QLatin1StringView(authors[i].email), + QLatin1StringView(authors[i].web)); } for (unsigned int i = 0; i < sizeof credits / sizeof *credits; ++i) { addCredit(KLocalizedString(credits[i].name).toString(), KLocalizedString(credits[i].desc).toString(), - QLatin1String(credits[i].email), - QLatin1String(credits[i].web)); + QLatin1StringView(credits[i].email), + QLatin1StringView(credits[i].web)); } loadCustomAboutData(this); } diff --git a/src/accessibility/accessiblewidgetfactory.cpp b/src/accessibility/accessiblewidgetfactory.cpp index 1fe36adea..9bf2c6086 100644 --- a/src/accessibility/accessiblewidgetfactory.cpp +++ b/src/accessibility/accessiblewidgetfactory.cpp @@ -1,36 +1,36 @@ /* accessibility/accessiblewidgetfactory.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "accessiblewidgetfactory.h" #include "accessiblerichtextlabel_p.h" #include "accessiblevaluelabel_p.h" #include "utils/accessibility.h" #include "view/htmllabel.h" #include "view/urllabel.h" QAccessibleInterface *Kleo::accessibleWidgetFactory(const QString &classname, QObject *object) { QAccessibleInterface *iface = nullptr; if (!object || !object->isWidgetType()) return iface; QWidget *widget = static_cast(object); if (classname == QString::fromLatin1(Kleo::HtmlLabel::staticMetaObject.className()) || classname == QString::fromLatin1(Kleo::UrlLabel::staticMetaObject.className())) { iface = new AccessibleRichTextLabel{widget}; - } else if (classname == QLatin1String("QLabel") && Kleo::representAsAccessibleValueWidget(widget)) { + } else if (classname == QLatin1StringView("QLabel") && Kleo::representAsAccessibleValueWidget(widget)) { iface = new AccessibleValueLabel{widget}; } return iface; } diff --git a/src/commands/certifygroupcommand.cpp b/src/commands/certifygroupcommand.cpp index 1fab287a1..e3021f2ea 100644 --- a/src/commands/certifygroupcommand.cpp +++ b/src/commands/certifygroupcommand.cpp @@ -1,359 +1,359 @@ /* commands/certifygroupcommand.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 "certifygroupcommand.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; namespace { struct CertificationResultData { std::vector userIds; GpgME::Error error; }; } class CertifyGroupCommand::Private : public Command::Private { friend class ::Kleo::CertifyGroupCommand; CertifyGroupCommand *q_func() const { return static_cast(q); } public: explicit Private(CertifyGroupCommand *qq); ~Private() override; void start(); private: void showDialog(); void certifyCertificates(); void setUpProgressDialog(int numberOfKeysToCertify); void startNextCertification(); void createJob(); void slotResult(const Error &err); void wrapUp(); private: KeyGroup group; std::vector certificates; QPointer dialog; QPointer progressDialog; std::vector userIdsToCertify; struct { Key certificationKey; QDate expirationDate; QString tags; bool exportable = false; bool sendToServer = false; } certificationOptions; struct { std::vector userIds; } jobData; QPointer job; std::vector results; }; CertifyGroupCommand::Private *CertifyGroupCommand::d_func() { return static_cast(d.get()); } const CertifyGroupCommand::Private *CertifyGroupCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() CertifyGroupCommand::Private::Private(CertifyGroupCommand *qq) : Command::Private(qq) { } CertifyGroupCommand::Private::~Private() = default; void CertifyGroupCommand::Private::start() { if (!group.isNull()) { const auto &groupKeys = group.keys(); certificates = std::vector(groupKeys.begin(), groupKeys.end()); } if (certificates.empty()) { finished(); return; } if (!allKeysHaveProtocol(certificates, GpgME::OpenPGP)) { const auto title = i18nc("@title:window", "Group Cannot Be Certified"); const auto message = i18nc("@info", "This group contains S/MIME certificates which cannot be certified."); information(message, title); finished(); return; } showDialog(); } void CertifyGroupCommand::Private::showDialog() { dialog = new CertifyCertificateDialog; dialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(dialog); connect(dialog, &QDialog::accepted, q, [this]() { certifyCertificates(); }); connect(dialog, &QDialog::rejected, q, [this]() { canceled(); }); if (!group.isNull()) { dialog->setGroupName(group.name()); } dialog->setCertificatesToCertify(certificates); dialog->show(); } void CertifyGroupCommand::Private::certifyCertificates() { userIdsToCertify = dialog->selectedUserIDs(); if (userIdsToCertify.empty()) { canceled(); return; } certificationOptions.certificationKey = dialog->selectedSecretKey(); certificationOptions.expirationDate = dialog->expirationDate(); certificationOptions.tags = dialog->tags(); certificationOptions.exportable = dialog->exportableCertificationSelected(); certificationOptions.sendToServer = dialog->sendToServer(); setUpProgressDialog(userIdsToCertify.size()); startNextCertification(); } void CertifyGroupCommand::Private::setUpProgressDialog(int numberOfKeysToCertify) { if (progressDialog) { return; } progressDialog = new QProgressDialog{parentWidgetOrView()}; progressDialog->setAttribute(Qt::WA_DeleteOnClose); progressDialog->setModal(true); progressDialog->setWindowTitle(i18nc("@title:window", "Certify Certificates")); progressDialog->setLabelText(i18nc("@info:progress", "Certifying certificates ...")); progressDialog->setMinimumDuration(1000); progressDialog->setMaximum(numberOfKeysToCertify); progressDialog->setValue(0); connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel); connect(q, &Command::finished, progressDialog, [this]() { progressDialog->accept(); }); } void CertifyGroupCommand::Private::startNextCertification() { Q_ASSERT(!userIdsToCertify.empty()); const auto nextKey = userIdsToCertify.front().parent(); // for now we only deal with primary user IDs jobData.userIds = {userIdsToCertify.front()}; userIdsToCertify.erase(userIdsToCertify.begin()); const std::vector userIdIndexes = {0}; createJob(); job->setUserIDsToSign(userIdIndexes); if (const Error err = job->start(nextKey)) { QMetaObject::invokeMethod( q, [this, err]() { slotResult(err); }, Qt::QueuedConnection); } } void CertifyGroupCommand::Private::createJob() { Q_ASSERT(!job); std::unique_ptr newJob{QGpgME::openpgp()->signKeyJob()}; newJob->setDupeOk(true); newJob->setSigningKey(certificationOptions.certificationKey); newJob->setExportable(certificationOptions.exportable); if (!certificationOptions.tags.isEmpty()) { // do not set an empty remark to avoid an empty signature notation (GnuPG bug T5142) newJob->setRemark(certificationOptions.tags); } if (!certificationOptions.expirationDate.isNull()) { newJob->setExpirationDate(certificationOptions.expirationDate); } connect(newJob.get(), &QGpgME::SignKeyJob::result, q, [this](const GpgME::Error &result) { slotResult(result); }); job = newJob.release(); } void CertifyGroupCommand::Private::slotResult(const Error &err) { results.push_back({ jobData.userIds, err, }); progressDialog->setValue(results.size()); if (err.isCanceled()) { finished(); } else if (err && (results.size() == 1) // && ((err.sourceID() == GPG_ERR_SOURCE_PINENTRY) // || (err.code() == GPG_ERR_BAD_PASSPHRASE))) { // abort the certification process on certain errors during the first // certification, e.g. bad passphrase or pinentry timeout, where it's // better to restart the whole process instead of continuing with the // next certificate error(xi18nc("@info", "The certification of the certificates failed." "Error: %1", Formatting::errorAsString(results.front().error))); finished(); } else if (!userIdsToCertify.empty()) { job.clear(); jobData.userIds.clear(); startNextCertification(); } else { wrapUp(); } } static QString resultSummary(const std::vector &results) { Q_ASSERT(!results.empty()); const int totalCount = results.size(); const int successCount = Kleo::count_if(results, [](const auto &result) { return !result.error; }); if (successCount == totalCount) { return i18nc("@info", "All certificates were certified successfully."); } if (successCount == 0) { // we assume that all attempted certifications failed for the same reason return xi18nc("@info", "The certification of all certificates failed." "Error: %1", Formatting::errorAsString(results.front().error)); } return i18ncp("@info", // "1 of %2 certificates was certified successfully.", "%1 of %2 certificates were certified successfully.", successCount, totalCount); } void CertifyGroupCommand::Private::wrapUp() { Q_ASSERT(userIdsToCertify.empty()); Q_ASSERT(!results.empty()); const int successCount = Kleo::count_if(results, [](const auto &result) { return !result.error; }); const bool sendToServer = (successCount > 0) && certificationOptions.exportable && certificationOptions.sendToServer; - QString message = QLatin1String{"

"} + resultSummary(results) + QLatin1String{"

"}; + QString message = QLatin1StringView{"

"} + resultSummary(results) + QLatin1String{"

"}; if (sendToServer) { message += i18nc("@info", "

Next the certified certificates will be uploaded to the configured certificate directory.

"); } const auto failedUserIdsInfo = std::accumulate(results.cbegin(), results.cend(), QStringList{}, [](auto failedUserIds, const auto &result) { if (result.error) { failedUserIds.push_back(i18nc("A user ID (an error description)", "%1 (%2)", Formatting::formatForComboBox(result.userIds.front().parent()), Formatting::errorAsString(result.error))); } return failedUserIds; }); if (successCount > 0) { if (failedUserIdsInfo.size() > 0) { message += i18nc("@info", "

Certifying the following certificates failed:

"); } informationList(message, failedUserIdsInfo, i18nc("@title:window", "Certification Completed")); } else { error(message); } if (sendToServer) { const auto certificatesToSendToServer = std::accumulate(results.cbegin(), results.cend(), std::vector{}, [](auto keys, const auto &result) { if (!result.error) { keys.push_back(result.userIds.front().parent()); } return keys; }); const auto cmd = new ExportOpenPGPCertsToServerCommand(certificatesToSendToServer); cmd->start(); } if (!certificationOptions.tags.isEmpty()) { Tags::enableTags(); } finished(); } CertifyGroupCommand::CertifyGroupCommand(const KeyGroup &group) : Command{new Private{this}} { d->group = group; } CertifyGroupCommand::~CertifyGroupCommand() = default; void CertifyGroupCommand::doStart() { d->start(); } void CertifyGroupCommand::doCancel() { if (d->dialog) { d->dialog->close(); } if (d->job) { d->job->slotCancel(); } } #undef d #undef q #include "moc_certifygroupcommand.cpp" diff --git a/src/commands/createcsrforcardkeycommand.cpp b/src/commands/createcsrforcardkeycommand.cpp index b36203c8b..025ab29e7 100644 --- a/src/commands/createcsrforcardkeycommand.cpp +++ b/src/commands/createcsrforcardkeycommand.cpp @@ -1,307 +1,307 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/createcsrforcardkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "createcsrforcardkeycommand.h" #include "cardcommand_p.h" #include "dialogs/createcsrforcardkeydialog.h" #include "smartcard/netkeycard.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "utils/filedialog.h" #include "utils/keyparameters.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace Kleo::SmartCard; using namespace GpgME; using namespace QGpgME; class CreateCSRForCardKeyCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::CreateCSRForCardKeyCommand; CreateCSRForCardKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(CreateCSRForCardKeyCommand *qq, const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent); ~Private() override; private: void start(); void slotDialogAccepted(); void slotDialogRejected(); void slotResult(const KeyGenerationResult &result, const QByteArray &request); QUrl saveRequest(const QByteArray &request); void ensureDialogCreated(); private: std::string appName; std::string keyRef; KeyUsage keyUsage; QPointer dialog; }; CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() { return static_cast(d.get()); } const CreateCSRForCardKeyCommand::Private *CreateCSRForCardKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() CreateCSRForCardKeyCommand::Private::Private(CreateCSRForCardKeyCommand *qq, const std::string &keyRef_, const std::string &serialNumber, const std::string &appName_, QWidget *parent) : CardCommand::Private(qq, serialNumber, parent) , appName(appName_) , keyRef(keyRef_) { } CreateCSRForCardKeyCommand::Private::~Private() { } namespace { KeyUsage getKeyUsage(const KeyPairInfo &keyInfo) { // note: gpgsm does not support creating CSRs for authentication certificates KeyUsage usage; if (keyInfo.canCertify()) { usage.setCanCertify(true); } if (keyInfo.canSign()) { usage.setCanSign(true); } if (keyInfo.canEncrypt()) { usage.setCanEncrypt(true); } return usage; } } void CreateCSRForCardKeyCommand::Private::start() { if (appName != NetKeyCard::AppName && appName != OpenPGPCard::AppName && appName != PIVCard::AppName) { qCWarning(KLEOPATRA_LOG) << "CreateCSRForCardKeyCommand does not support card application" << QString::fromStdString(appName); finished(); return; } const auto card = ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the smartcard with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } const KeyPairInfo &keyInfo = card->keyInfo(keyRef); keyUsage = getKeyUsage(keyInfo); ensureDialogCreated(); dialog->setWindowTitle(i18nc("@title:window", "Certificate Details")); if (!card->cardHolder().isEmpty()) { dialog->setName(card->cardHolder()); } dialog->show(); } void CreateCSRForCardKeyCommand::Private::slotDialogAccepted() { const Error err = ReaderStatus::switchCardAndApp(serialNumber(), appName); if (err) { finished(); return; } const auto backend = smime(); if (!backend) { finished(); return; } KeyGenerationJob *const job = backend->keyGenerationJob(); if (!job) { finished(); return; } Job::context(job)->setArmor(true); connect(job, &KeyGenerationJob::result, q, [this](const GpgME::KeyGenerationResult &result, const QByteArray &pubKeyData) { slotResult(result, pubKeyData); }); KeyParameters keyParameters(KeyParameters::CMS); keyParameters.setCardKeyRef(QString::fromStdString(keyRef)); keyParameters.setKeyUsage(keyUsage); keyParameters.setDN(dialog->dn()); keyParameters.setEmail(dialog->email()); if (const Error err = job->start(keyParameters.toString())) { error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(err))); finished(); } } void CreateCSRForCardKeyCommand::Private::slotDialogRejected() { canceled(); } void CreateCSRForCardKeyCommand::Private::slotResult(const KeyGenerationResult &result, const QByteArray &request) { if (result.error().isCanceled()) { // do nothing } else if (result.error()) { error(i18nc("@info", "Creating a CSR for the card key failed:\n%1", Formatting::errorAsString(result.error()))); } else { const QUrl url = saveRequest(request); if (!url.isEmpty()) { information(xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", url.toLocalFile()), i18nc("@title", "Request Saved")); } } finished(); } namespace { struct SaveToFileResult { QUrl url; QString errorMessage; }; SaveToFileResult saveRequestToFile(const QString &filename, const QByteArray &request, QIODevice::OpenMode mode) { QFile file(filename); if (file.open(mode)) { const auto bytesWritten = file.write(request); if (bytesWritten < request.size()) { return {QUrl(), file.errorString()}; } return {QUrl::fromLocalFile(file.fileName()), QString()}; } return {QUrl(), file.errorString()}; } } QUrl CreateCSRForCardKeyCommand::Private::saveRequest(const QByteArray &request) { - const QString proposedFilename = QLatin1String("request_%1.p10").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_HHmmss"))); + const QString proposedFilename = QLatin1StringView("request_%1.p10").arg(QDateTime::currentDateTime().toString(QStringLiteral("yyyy-MM-dd_HHmmss"))); while (true) { const QString filePath = FileDialog::getSaveFileNameEx(parentWidgetOrView(), i18nc("@title", "Save Request"), QStringLiteral("save_csr"), proposedFilename, i18n("PKCS#10 Requests (*.p10)")); if (filePath.isEmpty()) { // user canceled the dialog return QUrl(); } const auto result = saveRequestToFile(filePath, request, QIODevice::NewOnly); if (result.url.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Writing request to file" << filePath << "failed:" << result.errorMessage; error(xi18nc("@info", "Saving the request failed.%1", result.errorMessage), i18nc("@title", "Error Saving Request")); } else { return result.url; } } } void CreateCSRForCardKeyCommand::Private::ensureDialogCreated() { if (dialog) { return; } dialog = new CreateCSRForCardKeyDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); connect(dialog, &QDialog::accepted, q, [this]() { slotDialogAccepted(); }); connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); }); } CreateCSRForCardKeyCommand::CreateCSRForCardKeyCommand(const std::string &keyRef, const std::string &serialNumber, const std::string &appName, QWidget *parent) : CardCommand(new Private(this, keyRef, serialNumber, appName, parent)) { } CreateCSRForCardKeyCommand::~CreateCSRForCardKeyCommand() { } void CreateCSRForCardKeyCommand::doStart() { d->start(); } void CreateCSRForCardKeyCommand::doCancel() { } #undef d #undef q #include "moc_createcsrforcardkeycommand.cpp" diff --git a/src/commands/deletecertificatescommand.cpp b/src/commands/deletecertificatescommand.cpp index 0b736c2ae..a26d30568 100644 --- a/src/commands/deletecertificatescommand.cpp +++ b/src/commands/deletecertificatescommand.cpp @@ -1,395 +1,395 @@ /* -*- mode: c++; c-basic-offset:4 -*- deleteCertificatescommand.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 "deletecertificatescommand.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Dialogs; using namespace QGpgME; class DeleteCertificatesCommand::Private : public Command::Private { friend class ::Kleo::DeleteCertificatesCommand; DeleteCertificatesCommand *q_func() const { return static_cast(q); } public: explicit Private(DeleteCertificatesCommand *qq, KeyListController *c); ~Private() override; void startDeleteJob(GpgME::Protocol protocol); void cancelJobs(); void pgpDeleteResult(const GpgME::Error &); void cmsDeleteResult(const GpgME::Error &); void showErrorsAndFinish(); bool canDelete(GpgME::Protocol proto) const { if (const auto cbp = (proto == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) if (DeleteJob *const job = cbp->deleteJob()) { job->slotCancel(); return true; } return false; } void ensureDialogCreated() { if (dialog) { return; } dialog = new DeleteCertificatesDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->setWindowTitle(i18nc("@title:window", "Delete Certificates")); connect(dialog, &QDialog::accepted, q_func(), [this]() { slotDialogAccepted(); }); connect(dialog, &QDialog::rejected, q_func(), [this]() { slotDialogRejected(); }); } void ensureDialogShown() { if (dialog) { dialog->show(); } } void slotDialogAccepted(); void slotDialogRejected() { canceled(); } private: QPointer dialog; QPointer cmsJob, pgpJob; GpgME::Error cmsError, pgpError; std::vector cmsKeys, pgpKeys; }; DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func() { return static_cast(d.get()); } const DeleteCertificatesCommand::Private *DeleteCertificatesCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() DeleteCertificatesCommand::Private::Private(DeleteCertificatesCommand *qq, KeyListController *c) : Command::Private(qq, c) { } DeleteCertificatesCommand::Private::~Private() { } DeleteCertificatesCommand::DeleteCertificatesCommand(KeyListController *p) : Command(new Private(this, p)) { } DeleteCertificatesCommand::DeleteCertificatesCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { } DeleteCertificatesCommand::~DeleteCertificatesCommand() { } namespace { enum Action { Nothing = 0, Failure = 1, ClearCMS = 2, ClearPGP = 4 }; // const unsigned int errorCase = // openpgp.empty() << 3U | d->canDelete( OpenPGP ) << 2U | // cms.empty() << 1U | d->canDelete( CMS ) << 0U ; static const struct { const KLazyLocalizedString text; Action actions; } deletionErrorCases[16] = { // if havePGP // if cantPGP // if haveCMS {kli18n("Neither the OpenPGP nor the CMS " "backends support certificate deletion.\n" "Check your installation."), Failure}, // cantCMS {kli18n("The OpenPGP backend does not support " "certificate deletion.\n" "Check your installation.\n" "Only the selected CMS certificates " "will be deleted."), ClearPGP}, // canCMS // if !haveCMS {kli18n("The OpenPGP backend does not support " "certificate deletion.\n" "Check your installation."), Failure}, {kli18n("The OpenPGP backend does not support " "certificate deletion.\n" "Check your installation."), Failure}, // if canPGP // if haveCMS {kli18n("The CMS backend does not support " "certificate deletion.\n" "Check your installation.\n" "Only the selected OpenPGP certificates " "will be deleted."), ClearCMS}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS // if !haveCMS {KLazyLocalizedString(), Nothing}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS // if !havePGP // if cantPGP // if haveCMS {kli18n("The CMS backend does not support " "certificate deletion.\n" "Check your installation."), Failure}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS // if !haveCMS {KLazyLocalizedString(), Nothing}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS // if canPGP // if haveCMS {kli18n("The CMS backend does not support " "certificate deletion.\n" "Check your installation."), Failure}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS // if !haveCMS {KLazyLocalizedString(), Nothing}, // cantCMS {KLazyLocalizedString(), Nothing}, // canCMS }; } // anon namespace void DeleteCertificatesCommand::doStart() { std::vector selected = d->keys(); if (selected.empty()) { d->finished(); return; } std::sort(selected.begin(), selected.end(), _detail::ByFingerprint()); // Calculate the closure of the selected keys (those that need to // be deleted with them, though not selected themselves): std::vector toBeDeleted = KeyCache::instance()->findSubjects(selected); std::sort(toBeDeleted.begin(), toBeDeleted.end(), _detail::ByFingerprint()); std::vector unselected; unselected.reserve(toBeDeleted.size()); std::set_difference(toBeDeleted.begin(), toBeDeleted.end(), selected.begin(), selected.end(), std::back_inserter(unselected), _detail::ByFingerprint()); d->ensureDialogCreated(); d->dialog->setSelectedKeys(selected); d->dialog->setUnselectedKeys(unselected); d->ensureDialogShown(); } void DeleteCertificatesCommand::Private::slotDialogAccepted() { std::vector keys = dialog->keys(); Q_ASSERT(!keys.empty()); auto pgpBegin = keys.begin(); auto pgpEnd = std::stable_partition(pgpBegin, keys.end(), [](const GpgME::Key &key) { return key.protocol() != GpgME::CMS; }); auto cmsBegin = pgpEnd; auto cmsEnd = keys.end(); std::vector openpgp(pgpBegin, pgpEnd); std::vector cms(cmsBegin, cmsEnd); const unsigned int errorCase = // openpgp.empty() << 3U // | canDelete(OpenPGP) << 2U // | cms.empty() << 1U // | canDelete(CMS) << 0U; if (const unsigned int actions = deletionErrorCases[errorCase].actions) { information(KLocalizedString(deletionErrorCases[errorCase].text).toString(), (actions & Failure) ? i18n("Certificate Deletion Failed") : i18n("Certificate Deletion Problem")); if (actions & ClearCMS) { cms.clear(); } if (actions & ClearPGP) { openpgp.clear(); } if (actions & Failure) { canceled(); return; } } Q_ASSERT(!openpgp.empty() || !cms.empty()); pgpKeys.swap(openpgp); cmsKeys.swap(cms); if (!pgpKeys.empty()) { startDeleteJob(GpgME::OpenPGP); } if (!cmsKeys.empty()) { startDeleteJob(GpgME::CMS); } if ((pgpKeys.empty() || pgpError.code()) && (cmsKeys.empty() || cmsError.code())) { showErrorsAndFinish(); } } void DeleteCertificatesCommand::Private::startDeleteJob(GpgME::Protocol protocol) { Q_ASSERT(protocol != GpgME::UnknownProtocol); const std::vector &keys = protocol == CMS ? cmsKeys : pgpKeys; const auto backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); std::unique_ptr job(new MultiDeleteJob(backend)); connect(job.get(), &QGpgME::MultiDeleteJob::result, q_func(), [this, protocol](const GpgME::Error &result) { if (protocol == CMS) { cmsDeleteResult(result); } else { pgpDeleteResult(result); } }); connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress); if (const Error err = job->start(keys, true /*allowSecretKeyDeletion*/)) { (protocol == CMS ? cmsError : pgpError) = err; } else { (protocol == CMS ? cmsJob : pgpJob) = job.release(); } } void DeleteCertificatesCommand::Private::showErrorsAndFinish() { Q_ASSERT(!pgpJob); Q_ASSERT(!cmsJob); if (pgpError || cmsError) { QString pgpErrorString; if (pgpError) { pgpErrorString = i18n("OpenPGP backend: %1", Formatting::errorAsString(pgpError)); } QString cmsErrorString; if (cmsError) { cmsErrorString = i18n("CMS backend: %1", Formatting::errorAsString(cmsError)); } const QString msg = i18n( "

An error occurred while trying to delete " "the certificate:

" "

%1

", - pgpError ? cmsError ? pgpErrorString + QLatin1String("
") + cmsErrorString : pgpErrorString : cmsErrorString); + pgpError ? cmsError ? pgpErrorString + QLatin1StringView("
") + cmsErrorString : pgpErrorString : cmsErrorString); error(msg, i18n("Certificate Deletion Failed")); } else if (!pgpError.isCanceled() && !cmsError.isCanceled()) { std::vector keys = pgpKeys; keys.insert(keys.end(), cmsKeys.begin(), cmsKeys.end()); KeyCache::mutableInstance()->remove(keys); } finished(); } void DeleteCertificatesCommand::doCancel() { d->cancelJobs(); } void DeleteCertificatesCommand::Private::pgpDeleteResult(const Error &err) { pgpError = err; pgpJob = nullptr; if (!cmsJob) { showErrorsAndFinish(); } } void DeleteCertificatesCommand::Private::cmsDeleteResult(const Error &err) { cmsError = err; cmsJob = nullptr; if (!pgpJob) { showErrorsAndFinish(); } } void DeleteCertificatesCommand::Private::cancelJobs() { if (cmsJob) { cmsJob->slotCancel(); } if (pgpJob) { pgpJob->slotCancel(); } } #undef d #undef q #include "moc_deletecertificatescommand.cpp" diff --git a/src/commands/dumpcertificatecommand.cpp b/src/commands/dumpcertificatecommand.cpp index 6a5530b76..cec5f8acf 100644 --- a/src/commands/dumpcertificatecommand.cpp +++ b/src/commands/dumpcertificatecommand.cpp @@ -1,352 +1,352 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/dumpcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "dumpcertificatecommand.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include static const int PROCESS_TERMINATE_TIMEOUT = 5000; // milliseconds namespace { class DumpCertificateDialog : public QDialog { Q_OBJECT public: explicit DumpCertificateDialog(QWidget *parent = nullptr) : QDialog(parent) , ui(this) { resize(600, 500); } Q_SIGNALS: void updateRequested(); public Q_SLOTS: void append(const QString &line) { ui.logTextWidget.append(line); ui.logTextWidget.ensureCursorVisible(); } void clear() { ui.logTextWidget.clear(); } private: struct Ui { QTextEdit logTextWidget; QPushButton updateButton, closeButton; QVBoxLayout vlay; QHBoxLayout hlay; explicit Ui(DumpCertificateDialog *q) : logTextWidget(q) , updateButton(i18nc("@action:button Update the log text widget", "&Update"), q) , closeButton(q) , vlay(q) , hlay() { KGuiItem::assign(&closeButton, KStandardGuiItem::close()); KDAB_SET_OBJECT_NAME(logTextWidget); KDAB_SET_OBJECT_NAME(updateButton); KDAB_SET_OBJECT_NAME(closeButton); KDAB_SET_OBJECT_NAME(vlay); KDAB_SET_OBJECT_NAME(hlay); logTextWidget.setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); logTextWidget.setReadOnly(true); logTextWidget.setWordWrapMode(QTextOption::NoWrap); vlay.addWidget(&logTextWidget, 1); vlay.addLayout(&hlay); hlay.addWidget(&updateButton); hlay.addStretch(1); hlay.addWidget(&closeButton); connect(&updateButton, &QAbstractButton::clicked, q, &DumpCertificateDialog::updateRequested); connect(&closeButton, &QAbstractButton::clicked, q, &QWidget::close); } } ui; }; } using namespace Kleo; using namespace Kleo::Commands; static QByteArray chomped(QByteArray ba) { while (ba.endsWith('\n') || ba.endsWith('\r')) { ba.chop(1); } return ba; } class DumpCertificateCommand::Private : Command::Private { friend class ::Kleo::Commands::DumpCertificateCommand; DumpCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(DumpCertificateCommand *qq, KeyListController *c); ~Private() override; QString errorString() const { return QString::fromLocal8Bit(errorBuffer); } private: void init(); void refreshView(); private: void slotProcessFinished(int, QProcess::ExitStatus); void slotProcessReadyReadStandardOutput() { while (process.canReadLine()) { const QString line = Kleo::stringFromGpgOutput(chomped(process.readLine())); if (dialog) { dialog->append(line); } outputBuffer.push_back(line); } } void slotProcessReadyReadStandardError() { errorBuffer += process.readAllStandardError(); } void slotUpdateRequested() { if (process.state() == QProcess::NotRunning) { refreshView(); } } void slotDialogDestroyed() { dialog = nullptr; if (process.state() != QProcess::NotRunning) { q->cancel(); } else { finished(); } } private: QPointer dialog; KProcess process; QByteArray errorBuffer; QStringList outputBuffer; bool useDialog; bool canceled; }; DumpCertificateCommand::Private *DumpCertificateCommand::d_func() { return static_cast(d.get()); } const DumpCertificateCommand::Private *DumpCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() DumpCertificateCommand::Private::Private(DumpCertificateCommand *qq, KeyListController *c) : Command::Private(qq, c) , process() , errorBuffer() , outputBuffer() , useDialog(true) , canceled(false) { process.setOutputChannelMode(KProcess::SeparateChannels); process.setReadChannel(KProcess::StandardOutput); } DumpCertificateCommand::Private::~Private() { if (dialog && !dialog->isVisible()) { delete dialog; } } DumpCertificateCommand::DumpCertificateCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } DumpCertificateCommand::DumpCertificateCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } DumpCertificateCommand::DumpCertificateCommand(const GpgME::Key &k) : Command(k, new Private(this, nullptr)) { d->init(); } void DumpCertificateCommand::Private::init() { connect(&process, &QProcess::finished, q, [this](int exitCode, QProcess::ExitStatus status) { slotProcessFinished(exitCode, status); }); connect(&process, &QProcess::readyReadStandardError, q, [this]() { slotProcessReadyReadStandardError(); }); connect(&process, &QProcess::readyReadStandardOutput, q, [this] { slotProcessReadyReadStandardOutput(); }); if (!key().isNull()) { - process << gpgSmPath() << QStringLiteral("--dump-cert") << QLatin1String(key().primaryFingerprint()); + process << gpgSmPath() << QStringLiteral("--dump-cert") << QLatin1StringView(key().primaryFingerprint()); } } DumpCertificateCommand::~DumpCertificateCommand() { } void DumpCertificateCommand::setUseDialog(bool use) { d->useDialog = use; } bool DumpCertificateCommand::useDialog() const { return d->useDialog; } QStringList DumpCertificateCommand::output() const { return d->outputBuffer; } void DumpCertificateCommand::doStart() { const std::vector keys = d->keys(); if (keys.size() != 1 || keys.front().protocol() != GpgME::CMS) { d->finished(); return; } if (d->useDialog) { d->dialog = new DumpCertificateDialog; d->applyWindowID(d->dialog); d->dialog->setAttribute(Qt::WA_DeleteOnClose); d->dialog->setWindowTitle(i18nc("@title:window", "Certificate Dump")); connect(d->dialog, &DumpCertificateDialog::updateRequested, this, [this]() { d->slotUpdateRequested(); }); connect(d->dialog, &QObject::destroyed, this, [this]() { d->slotDialogDestroyed(); }); } d->refreshView(); } void DumpCertificateCommand::Private::refreshView() { if (dialog) { dialog->clear(); } errorBuffer.clear(); outputBuffer.clear(); process.start(); if (process.waitForStarted()) { if (dialog) { dialog->show(); } } else { KMessageBox::error(dialog ? static_cast(dialog) : parentWidgetOrView(), i18n("Unable to start process gpgsm. " "Please check your installation."), i18n("Dump Certificate Error")); finished(); } } void DumpCertificateCommand::doCancel() { d->canceled = true; if (d->process.state() != QProcess::NotRunning) { d->process.terminate(); QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, &d->process, &QProcess::kill); } if (d->dialog) { d->dialog->close(); } d->dialog = nullptr; } void DumpCertificateCommand::Private::slotProcessFinished(int code, QProcess::ExitStatus status) { if (!canceled) { if (status == QProcess::CrashExit) KMessageBox::error(dialog, i18n("The GpgSM process that tried to dump the certificate " "ended prematurely because of an unexpected error. " "Please check the output of gpgsm --dump-cert %1 for details.", - QLatin1String(key().primaryFingerprint())), + QLatin1StringView(key().primaryFingerprint())), i18nc("@title:window", "Dump Certificate Error")); else if (code) KMessageBox::error(dialog, i18n("An error occurred while trying to dump the certificate. " "The output from GpgSM was:\n%1", errorString()), i18nc("@title:window", "Dump Certificate Error")); } if (!useDialog) { slotDialogDestroyed(); } } #undef d #undef q #include "dumpcertificatecommand.moc" #include "moc_dumpcertificatecommand.cpp" diff --git a/src/commands/exportcertificatecommand.cpp b/src/commands/exportcertificatecommand.cpp index b5d657fe8..3551af6d0 100644 --- a/src/commands/exportcertificatecommand.cpp +++ b/src/commands/exportcertificatecommand.cpp @@ -1,411 +1,411 @@ /* -*- mode: c++; c-basic-offset:4 -*- exportcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "exportcertificatecommand.h" #include "fileoperationspreferences.h" #include "command_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; using namespace QGpgME; class ExportCertificateCommand::Private : public Command::Private { friend class ::ExportCertificateCommand; ExportCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportCertificateCommand *qq, KeyListController *c); ~Private() override; void startExportJob(GpgME::Protocol protocol, const std::vector &keys); void cancelJobs(); void exportResult(const GpgME::Error &, const QByteArray &); void showError(const GpgME::Error &error); bool confirmExport(const std::vector &pgpKeys); bool requestFileNames(GpgME::Protocol prot); void finishedIfLastJob(); private: QMap fileNames; uint jobsPending = 0; QMap outFileForSender; QPointer cmsJob; QPointer pgpJob; }; ExportCertificateCommand::Private *ExportCertificateCommand::d_func() { return static_cast(d.get()); } const ExportCertificateCommand::Private *ExportCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportCertificateCommand::Private::Private(ExportCertificateCommand *qq, KeyListController *c) : Command::Private(qq, c) { } ExportCertificateCommand::Private::~Private() { } ExportCertificateCommand::ExportCertificateCommand(KeyListController *p) : Command(new Private(this, p)) { } ExportCertificateCommand::ExportCertificateCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { } ExportCertificateCommand::ExportCertificateCommand(const Key &key) : Command(key, new Private(this, nullptr)) { } ExportCertificateCommand::~ExportCertificateCommand() { } void ExportCertificateCommand::setOpenPGPFileName(const QString &fileName) { if (!d->jobsPending) { d->fileNames[OpenPGP] = fileName; } } QString ExportCertificateCommand::openPGPFileName() const { return d->fileNames[OpenPGP]; } void ExportCertificateCommand::setX509FileName(const QString &fileName) { if (!d->jobsPending) { d->fileNames[CMS] = fileName; } } QString ExportCertificateCommand::x509FileName() const { return d->fileNames[CMS]; } void ExportCertificateCommand::doStart() { if (d->keys().empty()) { d->finished(); return; } const auto keys = Kleo::partitionKeysByProtocol(d->keys()); if (!keys.openpgp.empty() && !d->confirmExport(keys.openpgp)) { d->canceled(); return; } const bool haveBoth = !keys.cms.empty() && !keys.openpgp.empty(); const GpgME::Protocol prot = haveBoth ? UnknownProtocol : (!keys.cms.empty() ? CMS : OpenPGP); if (!d->requestFileNames(prot)) { d->canceled(); return; } if (!keys.openpgp.empty()) { d->startExportJob(GpgME::OpenPGP, keys.openpgp); } if (!keys.cms.empty()) { d->startExportJob(GpgME::CMS, keys.cms); } } bool ExportCertificateCommand::Private::confirmExport(const std::vector &pgpKeys) { auto notCertifiedKeys = std::accumulate(pgpKeys.cbegin(), pgpKeys.cend(), QStringList{}, [](auto keyNames, const auto &key) { const bool allValidUserIDsAreCertifiedByUser = Kleo::all_of(key.userIDs(), [](const UserID &userId) { return userId.isBad() || Kleo::userIDIsCertifiedByUser(userId); }); if (!allValidUserIDsAreCertifiedByUser) { keyNames.push_back(Formatting::formatForComboBox(key)); } return keyNames; }); if (!notCertifiedKeys.empty()) { if (pgpKeys.size() == 1) { const auto answer = KMessageBox::warningContinueCancel( // parentWidgetOrView(), xi18nc("@info", "You haven't certified all valid user IDs of this certificate " "with an exportable certification. People relying on your certifications " "may not be able to verify the certificate." "Do you want to continue the export?"), i18nc("@title:window", "Confirm Certificate Export"), KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", 1)}, KStandardGuiItem::cancel(), QStringLiteral("confirm-export-of-uncertified-keys")); return answer == KMessageBox::Continue; } else { std::sort(notCertifiedKeys.begin(), notCertifiedKeys.end()); const auto answer = KMessageBox::warningContinueCancelList( // parentWidgetOrView(), xi18nc("@info", "You haven't certified all valid user IDs of the certificates listed below " "with exportable certifications. People relying on your certifications " "may not be able to verify the certificates." "Do you want to continue the export?"), notCertifiedKeys, i18nc("@title:window", "Confirm Certificate Export"), KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", pgpKeys.size())}, KStandardGuiItem::cancel(), QStringLiteral("confirm-export-of-uncertified-keys")); return answer == KMessageBox::Continue; } } return true; } bool ExportCertificateCommand::Private::requestFileNames(GpgME::Protocol protocol) { if (protocol == UnknownProtocol) { if (!fileNames[GpgME::OpenPGP].isEmpty() && !fileNames[GpgME::CMS].isEmpty()) { return true; } /* Unknown protocol ask for first PGP Export file name */ if (fileNames[GpgME::OpenPGP].isEmpty() && !requestFileNames(GpgME::OpenPGP)) { return false; } /* And then for CMS */ return requestFileNames(GpgME::CMS); } if (!fileNames[protocol].isEmpty()) { return true; } const auto lastDir = ApplicationState::lastUsedExportDirectory(); QString proposedFileName = lastDir + QLatin1Char('/'); if (keys().size() == 1) { const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); const auto key = keys().front(); auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto asciiArmoredCertificateClass = (protocol == OpenPGP ? Class::OpenPGP : Class::CMS) | Class::Ascii | Class::Certificate; /* Not translated so it's better to use in tutorials etc. */ proposedFileName += QStringLiteral("%1_%2_public.%3") .arg(name) .arg(Formatting::prettyKeyID(key.shortKeyID())) .arg(outputFileExtension(asciiArmoredCertificateClass, usePGPFileExt)); } if (protocol == GpgME::CMS) { if (!fileNames[GpgME::OpenPGP].isEmpty()) { /* If the user has already selected a PGP file name then use that as basis * for a proposal for the S/MIME file. */ proposedFileName = fileNames[GpgME::OpenPGP]; const int idx = proposedFileName.size() - 4; - if (proposedFileName.endsWith(QLatin1String(".asc"))) { - proposedFileName.replace(idx, 4, QLatin1String(".pem")); + if (proposedFileName.endsWith(QLatin1StringView(".asc"))) { + proposedFileName.replace(idx, 4, QLatin1StringView(".pem")); } - if (proposedFileName.endsWith(QLatin1String(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) { - proposedFileName.replace(idx, 4, QLatin1String(".der")); + if (proposedFileName.endsWith(QLatin1StringView(".gpg")) || proposedFileName.endsWith(QLatin1String(".pgp"))) { + proposedFileName.replace(idx, 4, QLatin1StringView(".der")); } } } if (proposedFileName.isEmpty()) { proposedFileName = lastDir; proposedFileName += i18nc("A generic filename for exported certificates", "certificates"); proposedFileName += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem"); } auto fname = FileDialog::getSaveFileNameEx(parentWidgetOrView(), i18nc("1 is protocol", "Export %1 Certificates", Formatting::displayName(protocol)), QStringLiteral("imp"), proposedFileName, - protocol == GpgME::OpenPGP ? i18n("OpenPGP Certificates") + QLatin1String(" (*.asc *.gpg *.pgp)") - : i18n("S/MIME Certificates") + QLatin1String(" (*.pem *.der)")); + protocol == GpgME::OpenPGP ? i18n("OpenPGP Certificates") + QLatin1StringView(" (*.asc *.gpg *.pgp)") + : i18n("S/MIME Certificates") + QLatin1StringView(" (*.pem *.der)")); if (!fname.isEmpty() && protocol == GpgME::CMS && fileNames[GpgME::OpenPGP] == fname) { KMessageBox::error(parentWidgetOrView(), i18n("You have to select different filenames for different protocols."), i18nc("@title:window", "Export Error")); return false; } const QFileInfo fi(fname); if (fi.suffix().isEmpty()) { fname += protocol == GpgME::OpenPGP ? QStringLiteral(".asc") : QStringLiteral(".pem"); } fileNames[protocol] = fname; ApplicationState::setLastUsedExportDirectory(fi.absolutePath()); return !fname.isEmpty(); } void ExportCertificateCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector &keys) { Q_ASSERT(protocol != GpgME::UnknownProtocol); const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); const QString fileName = fileNames[protocol]; const bool binary = protocol == GpgME::OpenPGP - ? fileName.endsWith(QLatin1String(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive) - : fileName.endsWith(QLatin1String(".der"), Qt::CaseInsensitive); + ? fileName.endsWith(QLatin1StringView(".gpg"), Qt::CaseInsensitive) || fileName.endsWith(QLatin1String(".pgp"), Qt::CaseInsensitive) + : fileName.endsWith(QLatin1StringView(".der"), Qt::CaseInsensitive); std::unique_ptr job(backend->publicKeyExportJob(!binary)); Q_ASSERT(job.get()); connect(job.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &result, const QByteArray &keyData) { exportResult(result, keyData); }); connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress); QStringList fingerprints; fingerprints.reserve(keys.size()); for (const Key &i : keys) { - fingerprints << QLatin1String(i.primaryFingerprint()); + fingerprints << QLatin1StringView(i.primaryFingerprint()); } const GpgME::Error err = job->start(fingerprints); if (err) { showError(err); finished(); return; } Q_EMIT q->info(i18n("Exporting certificates...")); ++jobsPending; const QPointer exportJob(job.release()); outFileForSender[exportJob.data()] = fileName; (protocol == CMS ? cmsJob : pgpJob) = exportJob; } void ExportCertificateCommand::Private::showError(const GpgME::Error &err) { Q_ASSERT(err); const QString msg = i18n( "

An error occurred while trying to export " "the certificate:

" "

%1

", Formatting::errorAsString(err)); error(msg, i18n("Certificate Export Failed")); } void ExportCertificateCommand::doCancel() { d->cancelJobs(); } void ExportCertificateCommand::Private::finishedIfLastJob() { if (jobsPending <= 0) { finished(); } } static bool write_complete(QIODevice &iod, const QByteArray &data) { qint64 total = 0; qint64 toWrite = data.size(); while (total < toWrite) { const qint64 written = iod.write(data.data() + total, toWrite); if (written < 0) { return false; } total += written; toWrite -= written; } return true; } void ExportCertificateCommand::Private::exportResult(const GpgME::Error &err, const QByteArray &data) { Q_ASSERT(jobsPending > 0); --jobsPending; Q_ASSERT(outFileForSender.contains(q->sender())); const QString outFile = outFileForSender[q->sender()]; if (err) { showError(err); finishedIfLastJob(); return; } QSaveFile savefile(outFile); // TODO: use KIO const QString writeErrorMsg = i18n("Could not write to file %1.", outFile); const QString errorCaption = i18n("Certificate Export Failed"); if (!savefile.open(QIODevice::WriteOnly)) { error(writeErrorMsg, errorCaption); finishedIfLastJob(); return; } if (!write_complete(savefile, data) || !savefile.commit()) { error(writeErrorMsg, errorCaption); } finishedIfLastJob(); } void ExportCertificateCommand::Private::cancelJobs() { if (cmsJob) { cmsJob->slotCancel(); } if (pgpJob) { pgpJob->slotCancel(); } } #undef d #undef q #include "moc_exportcertificatecommand.cpp" diff --git a/src/commands/exportgroupscommand.cpp b/src/commands/exportgroupscommand.cpp index 5f4a6afbc..295068787 100644 --- a/src/commands/exportgroupscommand.cpp +++ b/src/commands/exportgroupscommand.cpp @@ -1,335 +1,335 @@ /* -*- mode: c++; c-basic-offset:4 -*- exportgroupscommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "exportgroupscommand.h" #include "utils/filedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; using namespace QGpgME; namespace { -static const QString certificateGroupFileExtension{QLatin1String{".kgrp"}}; +static const QString certificateGroupFileExtension{QLatin1StringView{".kgrp"}}; QString proposeFilename(const std::vector &groups) { QString filename; filename = ApplicationState::lastUsedExportDirectory() + QLatin1Char{'/'}; if (groups.size() == 1) { filename += groups.front().name().replace(QLatin1Char{'/'}, QLatin1Char{'_'}); } else { filename += i18nc("A generic filename for exported certificate groups", "certificate groups"); } return filename + certificateGroupFileExtension; } QString requestFilename(QWidget *parent, const std::vector &groups) { const QString proposedFilename = proposeFilename(groups); auto filename = FileDialog::getSaveFileNameEx(parent, i18ncp("@title:window", "Export Certificate Group", "Export Certificate Groups", groups.size()), QStringLiteral("imp"), proposedFilename, i18nc("filename filter like Certificate Groups (*.foo)", "Certificate Groups (*%1)", certificateGroupFileExtension)); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += certificateGroupFileExtension; } ApplicationState::setLastUsedExportDirectory(filename); } return filename; } } class ExportGroupsCommand::Private : public Command::Private { friend class ::ExportGroupsCommand; ExportGroupsCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportGroupsCommand *qq); ~Private() override; void start(); bool confirmExport(); bool exportGroups(); bool startExportJob(GpgME::Protocol protocol, const std::vector &keys); void onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData); void cancelJobs(); void showError(const GpgME::Error &err); void finishedIfLastJob(const QGpgME::Job *job); private: std::vector groups; QString filename; std::vector> exportJobs; }; ExportGroupsCommand::Private *ExportGroupsCommand::d_func() { return static_cast(d.get()); } const ExportGroupsCommand::Private *ExportGroupsCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportGroupsCommand::Private::Private(ExportGroupsCommand *qq) : Command::Private(qq) { } ExportGroupsCommand::Private::~Private() = default; void ExportGroupsCommand::Private::start() { if (groups.empty()) { finished(); return; } if (!confirmExport()) { canceled(); return; } filename = requestFilename(parentWidgetOrView(), groups); if (filename.isEmpty()) { canceled(); return; } const auto groupKeys = std::accumulate(std::begin(groups), std::end(groups), KeyGroup::Keys{}, [](auto allKeys, const auto &group) { const auto keys = group.keys(); allKeys.insert(std::begin(keys), std::end(keys)); return allKeys; }); const auto keys = Kleo::partitionKeysByProtocol(groupKeys); // remove/overwrite existing file if (QFile::exists(filename) && !QFile::remove(filename)) { error(xi18n("Cannot overwrite existing %1.", filename), i18nc("@title:window", "Export Failed")); finished(); return; } if (!exportGroups()) { finished(); return; } if (!keys.openpgp.empty()) { if (!startExportJob(GpgME::OpenPGP, keys.openpgp)) { finished(); return; } } if (!keys.cms.empty()) { if (!startExportJob(GpgME::CMS, keys.cms)) { finishedIfLastJob(nullptr); } } } bool ExportGroupsCommand::Private::confirmExport() { auto notFullyCertifiedGroups = std::accumulate(groups.cbegin(), groups.cend(), QStringList{}, [](auto groupNames, const auto &group) { const bool allOpenPGPKeysAreCertifiedByUser = Kleo::all_of(group.keys(), [](const Key &key) { // we only check the primary user ID of OpenPGP keys because currently group certification only certifies the primary user ID return key.protocol() != GpgME::OpenPGP || Kleo::userIDIsCertifiedByUser(key.userID(0)); }); if (!allOpenPGPKeysAreCertifiedByUser) { groupNames.push_back(group.name()); } return groupNames; }); if (!notFullyCertifiedGroups.empty()) { if (groups.size() == 1) { const auto answer = KMessageBox::warningContinueCancel( // parentWidgetOrView(), xi18nc("@info", "You haven't certified all OpenPGP certificates in this group " "with an exportable certification. People relying on your certifications " "may not be able to verify the certificates." "Do you want to continue the export?"), i18nc("@title:window", "Confirm Group Export"), KGuiItem{i18nc("@action:button", "Export Group")}, KStandardGuiItem::cancel(), QStringLiteral("confirm-export-of-uncertified-groups")); return answer == KMessageBox::Continue; } else { std::sort(notFullyCertifiedGroups.begin(), notFullyCertifiedGroups.end()); const auto answer = KMessageBox::warningContinueCancelList( // parentWidgetOrView(), xi18nc("@info", "You haven't certified all OpenPGP certificates in the groups listed below " "with exportable certifications. People relying on your certifications " "may not be able to verify the certificates." "Do you want to continue the export?"), notFullyCertifiedGroups, i18nc("@title:window", "Confirm Group Export"), KGuiItem{i18nc("@action:button", "Export Groups")}, KStandardGuiItem::cancel(), QStringLiteral("confirm-export-of-uncertified-groups")); return answer == KMessageBox::Continue; } } return true; } bool ExportGroupsCommand::Private::exportGroups() { const auto result = writeKeyGroups(filename, groups); if (result != WriteKeyGroups::Success) { error(xi18n("Writing groups to file %1 failed.", filename), i18nc("@title:window", "Export Failed")); } return result == WriteKeyGroups::Success; } bool ExportGroupsCommand::Private::startExportJob(GpgME::Protocol protocol, const std::vector &keys) { const QGpgME::Protocol *const backend = (protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); std::unique_ptr jobOwner(backend->publicKeyExportJob(/*armor=*/true)); auto job = jobOwner.get(); Q_ASSERT(job); connect(job, &ExportJob::result, q, [this, job](const GpgME::Error &err, const QByteArray &keyData) { onExportJobResult(job, err, keyData); }); connect(job, &QGpgME::Job::jobProgress, q, &Command::progress); const GpgME::Error err = job->start(Kleo::getFingerprints(keys)); if (err) { showError(err); return false; } Q_EMIT q->info(i18n("Exporting certificate groups...")); exportJobs.push_back(jobOwner.release()); return true; } void ExportGroupsCommand::Private::onExportJobResult(const QGpgME::Job *job, const GpgME::Error &err, const QByteArray &keyData) { Q_ASSERT(Kleo::contains(exportJobs, job)); if (err) { showError(err); finishedIfLastJob(job); return; } QFile f{filename}; if (!f.open(QIODevice::WriteOnly | QIODevice::Append)) { error(xi18n("Cannot open file %1 for writing.", filename), i18nc("@title:window", "Export Failed")); finishedIfLastJob(job); return; } const auto bytesWritten = f.write(keyData); if (bytesWritten != keyData.size()) { error(xi18n("Writing certificates to file %1 failed.", filename), i18nc("@title:window", "Export Failed")); } finishedIfLastJob(job); } void ExportGroupsCommand::Private::showError(const GpgME::Error &err) { error(xi18n("An error occurred during the export:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Export Failed")); } void ExportGroupsCommand::Private::finishedIfLastJob(const QGpgME::Job *job) { if (job) { exportJobs.erase(std::remove(exportJobs.begin(), exportJobs.end(), job), exportJobs.end()); } if (exportJobs.size() == 0) { finished(); } } void ExportGroupsCommand::Private::cancelJobs() { std::for_each(std::cbegin(exportJobs), std::cend(exportJobs), [](const auto &job) { if (job) { job->slotCancel(); } }); exportJobs.clear(); } ExportGroupsCommand::ExportGroupsCommand(const std::vector &groups) : Command{new Private{this}} { d->groups = groups; } ExportGroupsCommand::~ExportGroupsCommand() = default; void ExportGroupsCommand::doStart() { d->start(); } void ExportGroupsCommand::doCancel() { d->cancelJobs(); } #undef d #undef q #include "moc_exportgroupscommand.cpp" diff --git a/src/commands/exportopenpgpcertstoservercommand.cpp b/src/commands/exportopenpgpcertstoservercommand.cpp index fd674cbe5..fadc02544 100644 --- a/src/commands/exportopenpgpcertstoservercommand.cpp +++ b/src/commands/exportopenpgpcertstoservercommand.cpp @@ -1,179 +1,179 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportopenpgpcertstoservercommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "exportopenpgpcertstoservercommand.h" #include "command_p.h" #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(KeyListController *c) : GnuPGProcessCommand(c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const Key &key) : GnuPGProcessCommand(key) { } ExportOpenPGPCertsToServerCommand::ExportOpenPGPCertsToServerCommand(const std::vector &keys) : GnuPGProcessCommand(keys) { } ExportOpenPGPCertsToServerCommand::~ExportOpenPGPCertsToServerCommand() = default; static bool confirmExport(const std::vector &pgpKeys, QWidget *parentWidget) { auto notCertifiedKeys = std::accumulate(pgpKeys.cbegin(), pgpKeys.cend(), QStringList{}, [](auto keyNames, const auto &key) { const bool allValidUserIDsAreCertifiedByUser = Kleo::all_of(key.userIDs(), [](const UserID &userId) { return userId.isBad() || Kleo::userIDIsCertifiedByUser(userId); }); if (!allValidUserIDsAreCertifiedByUser) { keyNames.push_back(Formatting::formatForComboBox(key)); } return keyNames; }); if (!notCertifiedKeys.empty()) { if (pgpKeys.size() == 1) { const auto answer = KMessageBox::warningContinueCancel( // parentWidget, xi18nc("@info", "You haven't certified all valid user IDs of this certificate " "with an exportable certification. People relying on your certifications " "may not be able to verify the certificate." "Do you want to continue the export?"), i18nc("@title:window", "Confirm Certificate Export"), KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", 1)}, KStandardGuiItem::cancel(), QStringLiteral("confirm-upload-of-uncertified-keys")); return answer == KMessageBox::Continue; } else { std::sort(notCertifiedKeys.begin(), notCertifiedKeys.end()); const auto answer = KMessageBox::warningContinueCancelList( // parentWidget, xi18nc("@info", "You haven't certified all valid user IDs of the certificates listed below " "with exportable certifications. People relying on your certifications " "may not be able to verify the certificates." "Do you want to continue the export?"), notCertifiedKeys, i18nc("@title:window", "Confirm Certificate Export"), KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", pgpKeys.size())}, KStandardGuiItem::cancel(), QStringLiteral("confirm-upload-of-uncertified-keys")); return answer == KMessageBox::Continue; } } return true; } bool ExportOpenPGPCertsToServerCommand::preStartHook(QWidget *parent) const { if (!haveKeyserverConfigured()) { d->error(i18ncp("@info", "Exporting the certificate to a key server is not possible " "because the usage of key servers has been disabled explicitly.", "Exporting the certificates to a key server is not possible " "because the usage of key servers has been disabled explicitly.", d->keys().size())); return false; } if (!confirmExport(d->keys(), parent)) { return false; } return KMessageBox::warningContinueCancel(parent, xi18nc("@info", "When OpenPGP certificates have been exported to a public directory server, " "it is nearly impossible to remove them again." "Before exporting your certificate to a public directory server, make sure that you " "have created a revocation certificate so you can revoke the certificate if needed later." "Are you sure you want to continue?"), i18nc("@title:window", "OpenPGP Certificate Export"), KGuiItem{i18ncp("@action:button", "Export Certificate", "Export Certificates", d->keys().size())}, KStandardGuiItem::cancel(), QStringLiteral("warn-export-openpgp-nonrevocable")) == KMessageBox::Continue; } QStringList ExportOpenPGPCertsToServerCommand::arguments() const { QStringList result; result << gpgPath(); result << QStringLiteral("--send-keys"); const auto keys = d->keys(); for (const Key &key : keys) { - result << QLatin1String(key.primaryFingerprint()); + result << QLatin1StringView(key.primaryFingerprint()); } return result; } QString ExportOpenPGPCertsToServerCommand::errorCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Error"); } QString ExportOpenPGPCertsToServerCommand::successCaption() const { return i18nc("@title:window", "OpenPGP Certificate Export Finished"); } QString ExportOpenPGPCertsToServerCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to export OpenPGP certificates " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ExportOpenPGPCertsToServerCommand::errorExitMessage(const QStringList &args) const { // ki18n(" ") as initializer because initializing with empty string leads to // (I18N_EMPTY_MESSAGE) const auto errorLines = errorString().split(QLatin1Char{'\n'}); const auto errorText = std::accumulate(errorLines.begin(), errorLines.end(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &line) { return kxi18nc("@info used for concatenating multiple lines of text with line breaks; most likely this shouldn't be translated", "%1%2") .subs(temp) .subs(line); }); return xi18nc("@info", "An error occurred while trying to export OpenPGP certificates. " "The output of %1 was:%2", args[0], errorText); } QString ExportOpenPGPCertsToServerCommand::successMessage(const QStringList &) const { return i18nc("@info", "OpenPGP certificates exported successfully."); } #include "moc_exportopenpgpcertstoservercommand.cpp" diff --git a/src/commands/exportpaperkeycommand.cpp b/src/commands/exportpaperkeycommand.cpp index 81b776371..b39fce0ed 100644 --- a/src/commands/exportpaperkeycommand.cpp +++ b/src/commands/exportpaperkeycommand.cpp @@ -1,128 +1,128 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportpaperkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "exportpaperkeycommand.h" #include #include #include #include #include #include #include #include #include "command_p.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ExportPaperKeyCommand::ExportPaperKeyCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) , mParent(v) { connect(&mPkProc, &QProcess::finished, this, &ExportPaperKeyCommand::pkProcFinished); mPkProc.setProgram(paperKeyInstallPath()); mPkProc.setArguments(QStringList() << QStringLiteral("--output-type=base16")); process()->setStandardOutputProcess(&mPkProc); qCDebug(KLEOPATRA_LOG) << "Starting PaperKey process."; mPkProc.start(); setAutoDelete(false); } QStringList ExportPaperKeyCommand::arguments() const { const Key key = d->key(); QStringList result; result << gpgPath() << QStringLiteral("--batch"); result << QStringLiteral("--export-secret-key"); - result << QLatin1String(key.primaryFingerprint()); + result << QLatin1StringView(key.primaryFingerprint()); return result; } bool ExportPaperKeyCommand::preStartHook(QWidget *parent) const { if (paperKeyInstallPath().isNull()) { KMessageBox::error(parent, xi18nc("@info", "Kleopatra uses " "PaperKey to create a minimized and" " printable version of your secret key." "Please make sure it is installed."), i18nc("@title", "Failed to find PaperKey executable.")); return false; } return true; } void ExportPaperKeyCommand::pkProcFinished(int code, QProcess::ExitStatus status) { qCDebug(KLEOPATRA_LOG) << "Paperkey export finished: " << code << "status: " << status; if (status == QProcess::CrashExit || code) { qCDebug(KLEOPATRA_LOG) << "Aborting because paperkey failed"; deleteLater(); return; } QPrinter printer; const Key key = d->key(); printer.setDocName(QStringLiteral("0x%1-sec").arg(QString::fromLatin1(key.shortKeyID()))); QPrintDialog printDialog(&printer, mParent); printDialog.setWindowTitle(i18nc("@title:window", "Print Secret Key")); if (printDialog.exec() != QDialog::Accepted) { qCDebug(KLEOPATRA_LOG) << "Printing aborted."; deleteLater(); return; } QTextDocument doc(QString::fromLatin1(mPkProc.readAllStandardOutput())); doc.setDefaultFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); doc.print(&printer); deleteLater(); } QString ExportPaperKeyCommand::errorCaption() const { return i18nc("@title:window", "Error printing secret key"); } QString ExportPaperKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to export the secret key " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ExportPaperKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to export the secret key. " "The output from %1 was: %2", args[0], errorString()); } #include "moc_exportpaperkeycommand.cpp" diff --git a/src/commands/exportsecretkeycommand.cpp b/src/commands/exportsecretkeycommand.cpp index 53d71b71f..e714170fb 100644 --- a/src/commands/exportsecretkeycommand.cpp +++ b/src/commands/exportsecretkeycommand.cpp @@ -1,299 +1,299 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportsecretkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "exportsecretkeycommand.h" #include "fileoperationspreferences.h" #include "utils/filedialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; namespace { QString openPGPCertificateFileExtension() { return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt()); } QString cmsCertificateFileExtension() { return outputFileExtension(Class::CMS | Class::Binary | Class::ExportedPSM, /*usePGPFileExt=*/false); } QString certificateFileExtension(GpgME::Protocol protocol) { switch (protocol) { case GpgME::OpenPGP: return openPGPCertificateFileExtension(); case GpgME::CMS: return cmsCertificateFileExtension(); default: qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; return QStringLiteral("txt"); } } QString proposeFilename(const Key &key) { QString filename; auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); /* Not translated so it's better to use in tutorials etc. */ filename = QStringView{u"%1_%2_SECRET"}.arg(name, shortKeyID); filename.replace(u'/', u'_'); return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + certificateFileExtension(key.protocol()); } QString secretKeyFileFilters(GpgME::Protocol protocol) { switch (protocol) { case GpgME::OpenPGP: - return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"}; + return i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.asc *.gpg *.pgp)"}; case GpgME::CMS: - return i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.p12)"}; + return i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.p12)"}; default: qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Unknown protocol" << protocol; - return i18nc("description of filename filter", "All Files") + QLatin1String{" (*)"}; + return i18nc("description of filename filter", "All Files") + QLatin1StringView{" (*)"}; } } QString requestFilename(const Key &key, const QString &proposedFilename, QWidget *parent) { auto filename = FileDialog::getSaveFileNameEx(parent, i18nc("@title:window", "Secret Key Backup"), QStringLiteral("imp"), proposedFilename, secretKeyFileFilters(key.protocol())); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += u'.' + certificateFileExtension(key.protocol()); } ApplicationState::setLastUsedExportDirectory(filename); } return filename; } QString errorCaption() { return i18nc("@title:window", "Secret Key Backup Error"); } } class ExportSecretKeyCommand::Private : public Command::Private { friend class ::ExportSecretKeyCommand; ExportSecretKeyCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportSecretKeyCommand *qq, KeyListController *c = nullptr); ~Private() override; void start(); void cancel(); private: std::unique_ptr startExportJob(const Key &key); void onExportJobResult(const Error &err, const QByteArray &keyData); void showError(const Error &err); private: QString filename; QPointer job; }; ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() { return static_cast(d.get()); } const ExportSecretKeyCommand::Private *ExportSecretKeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportSecretKeyCommand::Private::Private(ExportSecretKeyCommand *qq, KeyListController *c) : Command::Private{qq, c} { } ExportSecretKeyCommand::Private::~Private() = default; void ExportSecretKeyCommand::Private::start() { const Key key = this->key(); if (key.isNull()) { finished(); return; } filename = requestFilename(key, proposeFilename(key), parentWidgetOrView()); if (filename.isEmpty()) { canceled(); return; } auto exportJob = startExportJob(key); if (!exportJob) { finished(); return; } job = exportJob.release(); } void ExportSecretKeyCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } std::unique_ptr ExportSecretKeyCommand::Private::startExportJob(const Key &key) { const bool armor = key.protocol() == GpgME::OpenPGP && filename.endsWith(u".asc", Qt::CaseInsensitive); const QGpgME::Protocol *const backend = (key.protocol() == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); Q_ASSERT(backend); std::unique_ptr exportJob{backend->secretKeyExportJob(armor)}; Q_ASSERT(exportJob); if (key.protocol() == GpgME::CMS) { exportJob->setExportFlags(GpgME::Context::ExportPKCS12); } connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) { onExportJobResult(err, keyData); }); connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); - const GpgME::Error err = exportJob->start({QLatin1String{key.primaryFingerprint()}}); + const GpgME::Error err = exportJob->start({QLatin1StringView{key.primaryFingerprint()}}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Backing up secret key...")); return exportJob; } void ExportSecretKeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) { if (err.isCanceled()) { finished(); return; } if (err) { showError(err); finished(); return; } if (keyData.isEmpty()) { error(i18nc("@info", "The result of the backup is empty. Maybe you entered an empty or a wrong passphrase."), errorCaption()); finished(); return; } QFile f{filename}; if (!f.open(QIODevice::WriteOnly)) { error(xi18nc("@info", "Cannot open file %1 for writing.", filename), errorCaption()); finished(); return; } const auto bytesWritten = f.write(keyData); if (bytesWritten != keyData.size()) { error(xi18nc("@info", "Writing key to file %1 failed.", filename), errorCaption()); finished(); return; } information(i18nc("@info", "The backup of the secret key was created successfully."), i18nc("@title:window", "Secret Key Backup")); finished(); } void ExportSecretKeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the backup of the secret key:" "%1", Formatting::errorAsString(err)), errorCaption()); } ExportSecretKeyCommand::ExportSecretKeyCommand(QAbstractItemView *view, KeyListController *controller) : Command{view, new Private{this, controller}} { } ExportSecretKeyCommand::ExportSecretKeyCommand(const GpgME::Key &key) : Command{key, new Private{this}} { } ExportSecretKeyCommand::~ExportSecretKeyCommand() = default; void ExportSecretKeyCommand::doStart() { d->start(); } void ExportSecretKeyCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_exportsecretkeycommand.cpp" diff --git a/src/commands/exportsecretsubkeycommand.cpp b/src/commands/exportsecretsubkeycommand.cpp index d39884aa5..67aade33f 100644 --- a/src/commands/exportsecretsubkeycommand.cpp +++ b/src/commands/exportsecretsubkeycommand.cpp @@ -1,272 +1,272 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/exportsecretsubkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "exportsecretsubkeycommand.h" #include "fileoperationspreferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { QString openPGPCertificateFileExtension() { return outputFileExtension(Class::OpenPGP | Class::Ascii | Class::Certificate, FileOperationsPreferences().usePGPFileExt()); } QString proposeFilename(const std::vector &subkeys) { QString filename; if (subkeys.size() == 1) { const auto subkey = subkeys.front(); const auto key = subkey.parent(); auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData()); - const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"}); + const auto usage = Formatting::usageString(subkey).replace(QLatin1StringView{", "}, QLatin1String{"_"}); /* Not translated so it's better to use in tutorials etc. */ filename = QStringView{u"%1_%2_SECRET_SUBKEY_%3_%4"}.arg(name, shortKeyID, shortSubkeyID, usage); } else { filename = i18nc("Generic filename for exported subkeys", "subkeys"); } filename.replace(u'/', u'_'); return ApplicationState::lastUsedExportDirectory() + u'/' + filename + u'.' + openPGPCertificateFileExtension(); } QString requestFilename(const std::vector &subkeys, const QString &proposedFilename, QWidget *parent) { auto filename = FileDialog::getSaveFileNameEx(parent, i18ncp("@title:window", "Export Subkey", "Export Subkeys", subkeys.size()), QStringLiteral("imp"), proposedFilename, - i18nc("description of filename filter", "Secret Key Files") + QLatin1String{" (*.asc *.gpg *.pgp)"}); + i18nc("description of filename filter", "Secret Key Files") + QLatin1StringView{" (*.asc *.gpg *.pgp)"}); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += u'.' + openPGPCertificateFileExtension(); } ApplicationState::setLastUsedExportDirectory(filename); } return filename; } template QStringList getSubkeyFingerprints(const SubkeyContainer &subkeys) { QStringList fingerprints; fingerprints.reserve(subkeys.size()); std::transform(std::begin(subkeys), std::end(subkeys), std::back_inserter(fingerprints), [](const auto &subkey) { - return QLatin1String{subkey.fingerprint()} + u'!'; + return QLatin1StringView{subkey.fingerprint()} + u'!'; }); return fingerprints; } } class ExportSecretSubkeyCommand::Private : public Command::Private { friend class ::ExportSecretSubkeyCommand; ExportSecretSubkeyCommand *q_func() const { return static_cast(q); } public: explicit Private(ExportSecretSubkeyCommand *qq); ~Private() override; void start(); void cancel(); private: std::unique_ptr startExportJob(const std::vector &subkeys); void onExportJobResult(const Error &err, const QByteArray &keyData); void showError(const Error &err); private: std::vector subkeys; QString filename; QPointer job; }; ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() { return static_cast(d.get()); } const ExportSecretSubkeyCommand::Private *ExportSecretSubkeyCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() ExportSecretSubkeyCommand::Private::Private(ExportSecretSubkeyCommand *qq) : Command::Private{qq} { } ExportSecretSubkeyCommand::Private::~Private() = default; void ExportSecretSubkeyCommand::Private::start() { if (subkeys.empty()) { finished(); return; } filename = requestFilename(subkeys, proposeFilename(subkeys), parentWidgetOrView()); if (filename.isEmpty()) { canceled(); return; } auto exportJob = startExportJob(subkeys); if (!exportJob) { finished(); return; } job = exportJob.release(); } void ExportSecretSubkeyCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } std::unique_ptr ExportSecretSubkeyCommand::Private::startExportJob(const std::vector &subkeys) { const bool armor = filename.endsWith(u".asc", Qt::CaseInsensitive); std::unique_ptr exportJob{QGpgME::openpgp()->secretSubkeyExportJob(armor)}; Q_ASSERT(exportJob); connect(exportJob.get(), &QGpgME::ExportJob::result, q, [this](const GpgME::Error &err, const QByteArray &keyData) { onExportJobResult(err, keyData); }); connect(exportJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); const GpgME::Error err = exportJob->start(getSubkeyFingerprints(subkeys)); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Exporting subkeys...")); return exportJob; } void ExportSecretSubkeyCommand::Private::onExportJobResult(const Error &err, const QByteArray &keyData) { if (err) { showError(err); finished(); return; } if (err.isCanceled()) { finished(); return; } if (keyData.isEmpty()) { error(i18nc("@info", "The result of the export is empty."), i18nc("@title:window", "Export Failed")); finished(); return; } QFile f{filename}; if (!f.open(QIODevice::WriteOnly)) { error(xi18nc("@info", "Cannot open file %1 for writing.", filename), i18nc("@title:window", "Export Failed")); finished(); return; } const auto bytesWritten = f.write(keyData); if (bytesWritten != keyData.size()) { error(xi18ncp("@info", "Writing subkey to file %2 failed.", "Writing subkeys to file %2 failed.", subkeys.size(), filename), i18nc("@title:window", "Export Failed")); finished(); return; } information(i18ncp("@info", "The subkey was exported successfully.", "%1 subkeys were exported successfully.", subkeys.size()), i18nc("@title:window", "Secret Key Backup")); finished(); } void ExportSecretSubkeyCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred during the export:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Export Failed")); } ExportSecretSubkeyCommand::ExportSecretSubkeyCommand(const std::vector &subkeys) : Command{new Private{this}} { d->subkeys = subkeys; } ExportSecretSubkeyCommand::~ExportSecretSubkeyCommand() = default; void ExportSecretSubkeyCommand::doStart() { d->start(); } void ExportSecretSubkeyCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_exportsecretsubkeycommand.cpp" diff --git a/src/commands/genrevokecommand.cpp b/src/commands/genrevokecommand.cpp index 64e1fc99c..3e12d551a 100644 --- a/src/commands/genrevokecommand.cpp +++ b/src/commands/genrevokecommand.cpp @@ -1,203 +1,203 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/genrevokecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "genrevokecommand.h" #include #include #include #include #include #include #include #include #include #include #include "command_p.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; GenRevokeCommand::GenRevokeCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c) { } GenRevokeCommand::GenRevokeCommand(KeyListController *c) : GnuPGProcessCommand(c) { } GenRevokeCommand::GenRevokeCommand(const Key &key) : GnuPGProcessCommand(key) { } // Fixup the revocation certificate similar to GnuPG void GenRevokeCommand::postSuccessHook(QWidget *parentWidget) { QFile f(mOutputFileName); if (!f.open(QIODevice::ReadOnly)) { // Should never happen because in this case we would not have had a success. KMessageBox::error(parentWidget, QStringLiteral("Failed to access the created output file."), errorCaption()); return; } const QString revCert = QString::fromLocal8Bit(f.readAll()); f.close(); if (!f.open(QIODevice::WriteOnly)) { KMessageBox::error(parentWidget, QStringLiteral("Failed to write to the created output file."), errorCaption()); return; } QTextStream s(&f); s << i18n("This is a revocation certificate for the OpenPGP key:") << "\n\n" // << " " << Formatting::prettyNameAndEMail(d->key()) << "\n" // << "Fingerprint: " << d->key().primaryFingerprint() << "\n\n" << i18n( "A revocation certificate is a kind of \"kill switch\" to publicly\n" "declare that a key shall not anymore be used. It is not possible\n" "to retract such a revocation certificate once it has been published.") << "\n\n" << i18n( "Use it to revoke this key in case of a compromise or loss of\n" "the secret key.") << "\n\n" << i18n( "To avoid an accidental use of this file, a colon has been inserted\n" "before the 5 dashes below. Remove this colon with a text editor\n" "before importing and publishing this revocation certificate.") << "\n\n:" << revCert; s.flush(); qCDebug(KLEOPATRA_LOG) << "revocation certificate stored as:" << mOutputFileName; f.close(); KMessageBox::information(d->parentWidgetOrView(), i18nc("@info", "Certificate successfully created.

" "Note:
To prevent accidental import of the revocation
" "it is required to manually edit the certificate
" "before it can be imported."), i18nc("@title:window", "Revocation certificate created")); } /* Well not much to do with GnuPGProcessCommand anymore I guess.. */ void GenRevokeCommand::doStart() { - auto proposedFileName = ApplicationState::lastUsedExportDirectory() + u'/' + QString::fromLatin1(d->key().primaryFingerprint()) + QLatin1String{".rev"}; + auto proposedFileName = ApplicationState::lastUsedExportDirectory() + u'/' + QString::fromLatin1(d->key().primaryFingerprint()) + QLatin1StringView{".rev"}; while (mOutputFileName.isEmpty()) { mOutputFileName = QFileDialog::getSaveFileName(d->parentWidgetOrView(), i18n("Generate revocation certificate"), proposedFileName, QStringLiteral("%1 (*.rev)").arg(i18n("Revocation Certificates ")), {}, QFileDialog::DontConfirmOverwrite); if (mOutputFileName.isEmpty()) { d->finished(); return; } - if (!mOutputFileName.endsWith(QLatin1String(".rev"))) { - mOutputFileName += QLatin1String(".rev"); + if (!mOutputFileName.endsWith(QLatin1StringView(".rev"))) { + mOutputFileName += QLatin1StringView(".rev"); } const QFileInfo fi{mOutputFileName}; if (fi.exists()) { auto sel = KMessageBox::questionTwoActions(d->parentWidgetOrView(), xi18n("The file %1 already exists. Do you wish to overwrite it?", fi.fileName()), i18nc("@title:window", "Overwrite File?"), KStandardGuiItem::overwrite(), KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::Dangerous); if (sel == KMessageBox::ButtonCode::SecondaryAction) { proposedFileName = mOutputFileName; mOutputFileName.clear(); } } } ApplicationState::setLastUsedExportDirectory(mOutputFileName); auto proc = process(); // We do custom io disconnect(m_procReadyReadStdErrConnection); proc->setReadChannel(QProcess::StandardOutput); GnuPGProcessCommand::doStart(); connect(proc, &QProcess::readyReadStandardOutput, this, [proc]() { while (proc->canReadLine()) { const QString line = QString::fromUtf8(proc->readLine()).trimmed(); // Command-fd is a stable interface, while this is all kind of hacky we // are on a deadline :-/ - if (line == QLatin1String("[GNUPG:] GET_BOOL gen_revoke.okay")) { + if (line == QLatin1StringView("[GNUPG:] GET_BOOL gen_revoke.okay")) { proc->write("y\n"); - } else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.code")) { + } else if (line == QLatin1StringView("[GNUPG:] GET_LINE ask_revocation_reason.code")) { proc->write("0\n"); - } else if (line == QLatin1String("[GNUPG:] GET_LINE ask_revocation_reason.text")) { + } else if (line == QLatin1StringView("[GNUPG:] GET_LINE ask_revocation_reason.text")) { proc->write("\n"); - } else if (line == QLatin1String("[GNUPG:] GET_BOOL openfile.overwrite.okay")) { + } else if (line == QLatin1StringView("[GNUPG:] GET_BOOL openfile.overwrite.okay")) { // We asked before proc->write("y\n"); - } else if (line == QLatin1String("[GNUPG:] GET_BOOL ask_revocation_reason.okay")) { + } else if (line == QLatin1StringView("[GNUPG:] GET_BOOL ask_revocation_reason.okay")) { proc->write("y\n"); } } }); } QStringList GenRevokeCommand::arguments() const { const Key key = d->key(); return { gpgPath(), QStringLiteral("--command-fd"), QStringLiteral("0"), QStringLiteral("--status-fd"), QStringLiteral("1"), QStringLiteral("-o"), mOutputFileName, QStringLiteral("--gen-revoke"), - QLatin1String(key.primaryFingerprint()), + QLatin1StringView(key.primaryFingerprint()), }; } QString GenRevokeCommand::errorCaption() const { return i18nc("@title:window", "Error creating revocation certificate"); } QString GenRevokeCommand::crashExitMessage(const QStringList &) const { // We show a success message so a failure is either the user aborted // or a bug. qCDebug(KLEOPATRA_LOG) << "Crash exit of GenRevokeCommand"; return QString(); } QString GenRevokeCommand::errorExitMessage(const QStringList &) const { // We show a success message so a failure is either the user aborted // or a bug. qCDebug(KLEOPATRA_LOG) << "Error exit of GenRevokeCommand"; return QString(); } #include "moc_genrevokecommand.cpp" diff --git a/src/commands/importcertificatefromfilecommand.cpp b/src/commands/importcertificatefromfilecommand.cpp index 70e797f17..a2abceb09 100644 --- a/src/commands/importcertificatefromfilecommand.cpp +++ b/src/commands/importcertificatefromfilecommand.cpp @@ -1,185 +1,185 @@ /* -*- mode: c++; c-basic-offset:4 -*- importcertificatefromfilecommand.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 "importcertificatefromfilecommand.h" #include "importcertificatescommand_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace QGpgME; class ImportCertificateFromFileCommand::Private : public ImportCertificatesCommand::Private { friend class ::ImportCertificateFromFileCommand; ImportCertificateFromFileCommand *q_func() const { return static_cast(q); } public: explicit Private(ImportCertificateFromFileCommand *qq, KeyListController *c); ~Private() override; bool ensureHaveFile(); private: QStringList files; }; ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func() { return static_cast(d.get()); } const ImportCertificateFromFileCommand::Private *ImportCertificateFromFileCommand::d_func() const { return static_cast(d.get()); } ImportCertificateFromFileCommand::Private::Private(ImportCertificateFromFileCommand *qq, KeyListController *c) : ImportCertificatesCommand::Private(qq, c) , files() { } ImportCertificateFromFileCommand::Private::~Private() { } #define d d_func() #define q q_func() ImportCertificateFromFileCommand::ImportCertificateFromFileCommand() : ImportCertificatesCommand(new Private(this, nullptr)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(KeyListController *p) : ImportCertificatesCommand(new Private(this, p)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(QAbstractItemView *v, KeyListController *p) : ImportCertificatesCommand(v, new Private(this, p)) { } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, KeyListController *p) : ImportCertificatesCommand(new Private(this, p)) { d->files = files; } ImportCertificateFromFileCommand::ImportCertificateFromFileCommand(const QStringList &files, QAbstractItemView *v, KeyListController *p) : ImportCertificatesCommand(v, new Private(this, p)) { d->files = files; } ImportCertificateFromFileCommand::~ImportCertificateFromFileCommand() { } void ImportCertificateFromFileCommand::setFiles(const QStringList &files) { d->files = files; } void ImportCertificateFromFileCommand::doStart() { if (!d->ensureHaveFile()) { Q_EMIT canceled(); d->finished(); return; } d->setProgressWindowTitle(i18nc("@title:window", "Importing Certificates")); d->setProgressLabelText(i18np("Importing certificates from 1 file...", "Importing certificates from %1 files...", d->files.size())); // TODO: use KIO here d->setWaitForMoreJobs(true); for (const QString &fn : std::as_const(d->files)) { QFile in(fn); if (!in.open(QIODevice::ReadOnly)) { d->error(i18n("Could not open file %1 for reading: %2", in.fileName(), in.errorString()), i18n("Certificate Import Failed")); d->addImportResult({fn, GpgME::UnknownProtocol, ImportType::Local, ImportResult{}, AuditLogEntry{}}); continue; } auto data = in.readAll(); // check for UTF-16- (or UTF-32- or UTF-8-with-BOM-)encoded text file; // binary certificate files don't start with a BOM, so that it's safe // to assume that data starting with a BOM is UTF-encoded text if (const auto encoding = QStringDecoder::encodingForData(data)) { QStringDecoder codec(*encoding); qCDebug(KLEOPATRA_LOG) << this << __func__ << "Decoding" << codec.name() << "encoded data"; data = QString(codec.decode(data)).toUtf8(); } d->startImport(GpgME::OpenPGP, data, fn); d->startImport(GpgME::CMS, data, fn); d->importGroupsFromFile(fn); } d->setWaitForMoreJobs(false); } static QStringList get_file_name(QWidget *parent) { - const QString certificateFilter = i18n("Certificates") + QLatin1String(" (*.asc *.cer *.cert *.crt *.der *.pem *.gpg *.p7c *.p12 *.pfx *.pgp *.kgrp)"); - const QString anyFilesFilter = i18n("Any files") + QLatin1String(" (*)"); + const QString certificateFilter = i18n("Certificates") + QLatin1StringView(" (*.asc *.cer *.cert *.crt *.der *.pem *.gpg *.p7c *.p12 *.pfx *.pgp *.kgrp)"); + const QString anyFilesFilter = i18n("Any files") + QLatin1StringView(" (*)"); QString previousDir; if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) { const KConfigGroup group(config, QStringLiteral("Import Certificate")); previousDir = group.readPathEntry("last-open-file-directory", QDir::homePath()); } const QStringList files = - QFileDialog::getOpenFileNames(parent, i18n("Select Certificate File"), previousDir, certificateFilter + QLatin1String(";;") + anyFilesFilter); + QFileDialog::getOpenFileNames(parent, i18n("Select Certificate File"), previousDir, certificateFilter + QLatin1StringView(";;") + anyFilesFilter); if (!files.empty()) if (const KSharedConfig::Ptr config = KSharedConfig::openConfig()) { KConfigGroup group(config, QStringLiteral("Import Certificate")); group.writePathEntry("last-open-file-directory", QFileInfo(files.front()).path()); } return files; } bool ImportCertificateFromFileCommand::Private::ensureHaveFile() { if (files.empty()) { files = get_file_name(parentWidgetOrView()); } return !files.empty(); } #undef d #undef q #include "moc_importcertificatefromfilecommand.cpp" diff --git a/src/commands/importcertificatescommand.cpp b/src/commands/importcertificatescommand.cpp index a02d2c9c1..e8ca4a9e6 100644 --- a/src/commands/importcertificatescommand.cpp +++ b/src/commands/importcertificatescommand.cpp @@ -1,1081 +1,1081 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/importcertificatescommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "importcertificatescommand.h" #include "importcertificatescommand_p.h" #include "certifycertificatecommand.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace QGpgME; static void disconnectConnection(const QMetaObject::Connection &connection) { // trivial function for disconnecting a signal-slot connection QObject::disconnect(connection); } bool operator==(const ImportJobData &lhs, const ImportJobData &rhs) { return lhs.job == rhs.job; } namespace { make_comparator_str(ByImportFingerprint, .fingerprint()); class ImportResultProxyModel : public AbstractKeyListSortFilterProxyModel { Q_OBJECT public: ImportResultProxyModel(const std::vector &results, QObject *parent = nullptr) : AbstractKeyListSortFilterProxyModel(parent) { updateFindCache(results); } ~ImportResultProxyModel() override { } ImportResultProxyModel *clone() const override { // compiler-generated copy ctor is fine! return new ImportResultProxyModel(*this); } void setImportResults(const std::vector &results) { updateFindCache(results); invalidateFilter(); } protected: QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid() || role != Qt::ToolTipRole) { return AbstractKeyListSortFilterProxyModel::data(index, role); } const QString fpr = index.data(KeyList::FingerprintRole).toString(); // find information: const std::vector::const_iterator it = Kleo::binary_find(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint()); if (it == m_importsByFingerprint.end()) { return AbstractKeyListSortFilterProxyModel::data(index, role); } else { QStringList rv; const auto ids = m_idsByFingerprint[it->fingerprint()]; rv.reserve(ids.size()); std::copy(ids.cbegin(), ids.cend(), std::back_inserter(rv)); return Formatting::importMetaData(*it, rv); } } bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override { // // 0. Keep parents of matching children: // const QModelIndex index = sourceModel()->index(source_row, 0, source_parent); Q_ASSERT(index.isValid()); for (int i = 0, end = sourceModel()->rowCount(index); i != end; ++i) if (filterAcceptsRow(i, index)) { return true; } // // 1. Check that this is an imported key: // const QString fpr = index.data(KeyList::FingerprintRole).toString(); return std::binary_search(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), fpr.toLatin1().constData(), ByImportFingerprint()); } private: void updateFindCache(const std::vector &results) { m_importsByFingerprint.clear(); m_idsByFingerprint.clear(); m_results = results; for (const auto &r : results) { const std::vector imports = r.result.imports(); m_importsByFingerprint.insert(m_importsByFingerprint.end(), imports.begin(), imports.end()); for (std::vector::const_iterator it = imports.begin(), end = imports.end(); it != end; ++it) { m_idsByFingerprint[it->fingerprint()].insert(r.id); } } std::sort(m_importsByFingerprint.begin(), m_importsByFingerprint.end(), ByImportFingerprint()); } private: mutable std::vector m_importsByFingerprint; mutable std::map, ByImportFingerprint> m_idsByFingerprint; std::vector m_results; }; bool importFailed(const ImportResultData &r) { // ignore GPG_ERR_EOF error to handle the "failed" import of files // without X.509 certificates by gpgsm gracefully return r.result.error() && r.result.error().code() != GPG_ERR_EOF; } bool importWasCanceled(const ImportResultData &r) { return r.result.error().isCanceled(); } } ImportCertificatesCommand::Private::Private(ImportCertificatesCommand *qq, KeyListController *c) : Command::Private(qq, c) , progressWindowTitle{i18nc("@title:window", "Importing Certificates")} , progressLabelText{i18n("Importing certificates... (this can take a while)")} { } ImportCertificatesCommand::Private::~Private() { if (progressDialog) { delete progressDialog; } } #define d d_func() #define q q_func() ImportCertificatesCommand::ImportCertificatesCommand(KeyListController *p) : Command(new Private(this, p)) { } ImportCertificatesCommand::ImportCertificatesCommand(QAbstractItemView *v, KeyListController *p) : Command(v, new Private(this, p)) { } ImportCertificatesCommand::~ImportCertificatesCommand() = default; static QString format_ids(const std::vector &ids) { QStringList escapedIds; for (const QString &id : ids) { if (!id.isEmpty()) { escapedIds << id.toHtmlEscaped(); } } - return escapedIds.join(QLatin1String("
")); + return escapedIds.join(QLatin1StringView("
")); } static QString make_tooltip(const std::vector &results) { if (results.empty()) { return {}; } std::vector ids; ids.reserve(results.size()); std::transform(std::begin(results), std::end(results), std::back_inserter(ids), [](const auto &r) { return r.id; }); std::sort(std::begin(ids), std::end(ids)); ids.erase(std::unique(std::begin(ids), std::end(ids)), std::end(ids)); if (ids.size() == 1) if (ids.front().isEmpty()) { return {}; } else return i18nc("@info:tooltip", "Imported Certificates from %1", ids.front().toHtmlEscaped()); else return i18nc("@info:tooltip", "Imported certificates from these sources:
%1", format_ids(ids)); } void ImportCertificatesCommand::Private::setImportResultProxyModel(const std::vector &results) { if (std::none_of(std::begin(results), std::end(results), [](const auto &r) { return r.result.numConsidered() > 0; })) { return; } q->addTemporaryView(i18nc("@title:tab", "Imported Certificates"), new ImportResultProxyModel(results), make_tooltip(results)); if (QTreeView *const tv = qobject_cast(parentWidgetOrView())) { tv->expandAll(); } } int sum(const std::vector &res, int (ImportResult::*fun)() const) { return kdtools::accumulate_transform(res.begin(), res.end(), std::mem_fn(fun), 0); } static QString make_report(const std::vector &results, const std::vector &groups) { const KLocalizedString normalLine = ki18n("%1%2"); const KLocalizedString boldLine = ki18n("%1%2"); const KLocalizedString headerLine = ki18n("%1"); std::vector res; res.reserve(results.size()); std::transform(std::begin(results), std::end(results), std::back_inserter(res), [](const auto &r) { return r.result; }); const auto numProcessedCertificates = sum(res, &ImportResult::numConsidered); QStringList lines; if (numProcessedCertificates > 0 || groups.size() == 0) { lines.push_back(headerLine.subs(i18n("Certificates")).toString()); lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(numProcessedCertificates).toString()); lines.push_back(normalLine.subs(i18n("Imported:")).subs(sum(res, &ImportResult::numImported)).toString()); if (const int n = sum(res, &ImportResult::newSignatures)) lines.push_back(normalLine.subs(i18n("New signatures:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::newUserIDs)) lines.push_back(normalLine.subs(i18n("New user IDs:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numKeysWithoutUserID)) lines.push_back(normalLine.subs(i18n("Certificates without user IDs:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::newSubkeys)) lines.push_back(normalLine.subs(i18n("New subkeys:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::newRevocations)) lines.push_back(boldLine.subs(i18n("Newly revoked:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::notImported)) lines.push_back(boldLine.subs(i18n("Not imported:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numUnchanged)) lines.push_back(normalLine.subs(i18n("Unchanged:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numSecretKeysConsidered)) lines.push_back(normalLine.subs(i18n("Secret keys processed:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numSecretKeysImported)) lines.push_back(normalLine.subs(i18n("Secret keys imported:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numSecretKeysConsidered) - sum(res, &ImportResult::numSecretKeysImported) - sum(res, &ImportResult::numSecretKeysUnchanged)) if (n > 0) lines.push_back(boldLine.subs(i18n("Secret keys not imported:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numSecretKeysUnchanged)) lines.push_back(normalLine.subs(i18n("Secret keys unchanged:")).subs(n).toString()); if (const int n = sum(res, &ImportResult::numV3KeysSkipped)) lines.push_back(normalLine.subs(i18n("Deprecated PGP-2 keys skipped:")).subs(n).toString()); } if (!lines.empty()) { - lines.push_back(headerLine.subs(QLatin1String{" "}).toString()); + lines.push_back(headerLine.subs(QLatin1StringView{" "}).toString()); } if (groups.size() > 0) { const auto newGroups = std::count_if(std::begin(groups), std::end(groups), [](const auto &g) { return g.status == ImportedGroup::Status::New; }); const auto updatedGroups = groups.size() - newGroups; lines.push_back(headerLine.subs(i18n("Certificate Groups")).toString()); lines.push_back(normalLine.subs(i18n("Total number processed:")).subs(groups.size()).toString()); lines.push_back(normalLine.subs(i18n("New groups:")).subs(newGroups).toString()); lines.push_back(normalLine.subs(i18n("Updated groups:")).subs(updatedGroups).toString()); } - return lines.join(QLatin1String{}); + return lines.join(QLatin1StringView{}); } static bool isImportFromSingleSource(const std::vector &res) { return (res.size() == 1) || (res.size() == 2 && res[0].id == res[1].id); } static QString make_message_report(const std::vector &res, const std::vector &groups) { - QString report{QLatin1String{""}}; + QString report{QLatin1StringView{""}}; if (res.empty()) { report += i18n("No imports (should not happen, please report a bug)."); } else { const QString title = isImportFromSingleSource(res) && !res.front().id.isEmpty() ? i18n("Detailed results of importing %1:", res.front().id) : i18n("Detailed results of import:"); - report += QLatin1String{"

"} + title + QLatin1String{"

"}; - report += QLatin1String{"

"}; + report += QLatin1StringView{"

"} + title + QLatin1String{"

"}; + report += QLatin1StringView{"

"}; report += make_report(res, groups); - report += QLatin1String{"

"}; + report += QLatin1StringView{"

"}; } - report += QLatin1String{""}; + report += QLatin1StringView{""}; return report; } // Returns false on error, true if please certify was shown. bool ImportCertificatesCommand::Private::showPleaseCertify(const GpgME::Import &imp) { if (!Kleo::userHasCertificationKey()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "No certification key available"; return false; } const char *fpr = imp.fingerprint(); if (!fpr) { // WTF qCWarning(KLEOPATRA_LOG) << "Import without fingerprint"; return false; } // Exactly one public key imported. Let's see if it is openpgp. We are async here so // we can just fetch it. auto ctx = wrap_unique(GpgME::Context::createForProtocol(GpgME::OpenPGP)); if (!ctx) { // WTF qCWarning(KLEOPATRA_LOG) << "Failed to create OpenPGP proto"; return false; } ctx->addKeyListMode(KeyListMode::WithSecret); GpgME::Error err; const auto key = ctx->key(fpr, err, false); if (key.isNull() || err) { // No such key most likely not OpenPGP return false; } if (!Kleo::canBeCertified(key)) { // key is expired or revoked return false; } if (key.hasSecret()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "Secret key is available -> skipping certification"; return false; } for (const auto &uid : key.userIDs()) { if (uid.validity() >= GpgME::UserID::Marginal) { // Already marginal so don't bug the user return false; } } const QStringList suggestions = { i18n("A phone call to the person."), i18n("Using a business card."), i18n("Confirming it on a trusted website."), }; auto sel = KMessageBox::questionTwoActions(parentWidgetOrView(), i18n("In order to mark the certificate as valid it needs to be certified.") + QStringLiteral("
") + i18n("Certifying means that you check the Fingerprint.") + QStringLiteral("
") + i18n("Some suggestions to do this are:") + QStringLiteral("
    • %1").arg(suggestions.join(QStringLiteral("
      "))) + QStringLiteral("
  • ") + i18n("Do you wish to start this process now?"), i18nc("@title", "You have imported a new certificate (public key)"), KGuiItem(i18nc("@action:button", "Certify")), KStandardGuiItem::cancel(), QStringLiteral("CertifyQuestion")); if (sel == KMessageBox::ButtonCode::PrimaryAction) { QEventLoop loop; auto cmd = new Commands::CertifyCertificateCommand(key); cmd->setParentWidget(parentWidgetOrView()); connect(cmd, &Command::finished, &loop, &QEventLoop::quit); QMetaObject::invokeMethod(cmd, &Commands::CertifyCertificateCommand::start, Qt::QueuedConnection); loop.exec(); } return true; } namespace { /** * Returns the Import of an OpenPGP key, if a single certificate was imported and this was an OpenPGP key. * Otherwise, returns a null Import. */ auto getSingleOpenPGPImport(const std::vector &res) { static const Import nullImport; if (!isImportFromSingleSource(res)) { return nullImport; } const auto numImported = std::accumulate(res.cbegin(), res.cend(), 0, [](auto s, const auto &r) { return s + r.result.numImported(); }); if (numImported > 1) { return nullImport; } if ((res.size() >= 1) && (res[0].protocol == GpgME::OpenPGP) && (res[0].result.numImported() == 1) && (res[0].result.imports().size() == 1)) { return res[0].result.imports()[0]; } else if ((res.size() == 2) && (res[1].protocol == GpgME::OpenPGP) && (res[1].result.numImported() == 1) && (res[1].result.imports().size() == 1)) { return res[1].result.imports()[0]; } return nullImport; } auto consolidatedAuditLogEntries(const std::vector &res) { static const QString gpg = QStringLiteral("gpg"); static const QString gpgsm = QStringLiteral("gpgsm"); if (res.size() == 1) { return res.front().auditLog; } QStringList auditLogs; auto extractAndAnnotateAuditLog = [](const ImportResultData &r) { QString s; if (!r.id.isEmpty()) { const auto program = r.protocol == GpgME::OpenPGP ? gpg : gpgsm; const auto headerLine = i18nc("file name (imported with gpg/gpgsm)", "%1 (imported with %2)").arg(r.id, program); s += QStringLiteral("
    %1
    ").arg(headerLine); } if (r.auditLog.error().code() == GPG_ERR_NO_DATA) { s += QStringLiteral("") + i18nc("@info", "Audit log is empty.") + QStringLiteral(""); } else if (r.result.error().isCanceled()) { s += QStringLiteral("") + i18nc("@info", "Import was canceled.") + QStringLiteral(""); } else { s += r.auditLog.text(); } return s; }; std::transform(res.cbegin(), res.cend(), std::back_inserter(auditLogs), extractAndAnnotateAuditLog); - return AuditLogEntry{auditLogs.join(QLatin1String{"
    "}), Error{}}; + return AuditLogEntry{auditLogs.join(QLatin1StringView{"
    "}), Error{}}; } } void ImportCertificatesCommand::Private::showDetails(const std::vector &res, const std::vector &groups) { const auto singleOpenPGPImport = getSingleOpenPGPImport(res); if (!singleOpenPGPImport.isNull()) { if (showPleaseCertify(singleOpenPGPImport)) { return; } } setImportResultProxyModel(res); MessageBox::information(parentWidgetOrView(), make_message_report(res, groups), consolidatedAuditLogEntries(res), i18n("Certificate Import Result")); } static QString make_error_message(const Error &err, const QString &id) { Q_ASSERT(err); Q_ASSERT(!err.isCanceled()); if (id.isEmpty()) { return i18n( "

    An error occurred while trying to import the certificate:

    " "

    %1

    ", Formatting::errorAsString(err)); } else { return i18n( "

    An error occurred while trying to import the certificate %1:

    " "

    %2

    ", id, Formatting::errorAsString(err)); } } void ImportCertificatesCommand::Private::showError(const ImportResultData &result) { MessageBox::error(parentWidgetOrView(), make_error_message(result.result.error(), result.id), result.auditLog); } void ImportCertificatesCommand::Private::setWaitForMoreJobs(bool wait) { if (wait == waitForMoreJobs) { return; } waitForMoreJobs = wait; if (!waitForMoreJobs) { tryToFinish(); } } void ImportCertificatesCommand::Private::onImportResult(const ImportResult &result, QGpgME::Job *finishedJob) { if (!finishedJob) { finishedJob = qobject_cast(q->sender()); } Q_ASSERT(finishedJob); qCDebug(KLEOPATRA_LOG) << q << __func__ << finishedJob; auto it = std::find_if(std::begin(runningJobs), std::end(runningJobs), [finishedJob](const auto &job) { return job.job == finishedJob; }); Q_ASSERT(it != std::end(runningJobs)); if (it == std::end(runningJobs)) { qCWarning(KLEOPATRA_LOG) << __func__ << "Error: Finished job not found"; return; } Kleo::for_each(it->connections, &disconnectConnection); it->connections.clear(); increaseProgressValue(); const auto job = *it; addImportResult({job.id, job.protocol, job.type, result, AuditLogEntry::fromJob(finishedJob)}, job); } void ImportCertificatesCommand::Private::addImportResult(const ImportResultData &result, const ImportJobData &job) { qCDebug(KLEOPATRA_LOG) << q << __func__ << result.id << "Result:" << Formatting::errorAsString(result.result.error()); results.push_back(result); if (importFailed(result)) { showError(result); } if (job.job) { const auto count = std::erase(runningJobs, job); Q_ASSERT(count == 1); } tryToFinish(); } static void handleOwnerTrust(const std::vector &results, QWidget *dialog) { std::unordered_set askedAboutFingerprints; for (const auto &r : results) { if (r.protocol != GpgME::Protocol::OpenPGP) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping non-OpenPGP import"; continue; } const auto imports = r.result.imports(); for (const auto &import : imports) { if (!(import.status() & (Import::Status::NewKey | Import::Status::ContainedSecretKey))) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping already known imported public key"; continue; } const char *fpr = import.fingerprint(); if (!fpr) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import without fingerprint"; continue; } if (Kleo::contains(askedAboutFingerprints, fpr)) { // imports of secret keys can result in multiple Imports for the same key qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping import for already handled fingerprint"; continue; } GpgME::Error err; auto ctx = wrap_unique(Context::createForProtocol(GpgME::Protocol::OpenPGP)); if (!ctx) { qCWarning(KLEOPATRA_LOG) << "Failed to get context"; continue; } ctx->addKeyListMode(KeyListMode::WithSecret); const Key toTrustOwner = ctx->key(fpr, err, false); if (toTrustOwner.isNull() || !toTrustOwner.hasSecret()) { continue; } if (toTrustOwner.ownerTrust() == Key::OwnerTrust::Ultimate) { qCDebug(KLEOPATRA_LOG) << __func__ << "Skipping key with ultimate ownertrust"; continue; } const auto toTrustOwnerUserIDs{toTrustOwner.userIDs()}; // ki18n(" ") as initializer because initializing with empty string leads to // (I18N_EMPTY_MESSAGE) const KLocalizedString uids = std::accumulate(toTrustOwnerUserIDs.cbegin(), toTrustOwnerUserIDs.cend(), KLocalizedString{ki18n(" ")}, [](KLocalizedString temp, const auto &uid) { return kxi18nc("@info", "%1%2").subs(temp).subs(Formatting::prettyNameAndEMail(uid)); }); const QString str = xi18nc("@info", "You have imported a certificate with fingerprint" "%1" "" "and user IDs" "%2" "" "Is this your own certificate?", Formatting::prettyID(fpr), uids); int k = KMessageBox::questionTwoActionsCancel(dialog, str, i18nc("@title:window", "Mark Own Certificate"), KGuiItem{i18nc("@action:button", "Yes, It's Mine")}, KGuiItem{i18nc("@action:button", "No, It's Not Mine")}); askedAboutFingerprints.insert(fpr); if (k == KMessageBox::ButtonCode::PrimaryAction) { // To use the ChangeOwnerTrustJob over // the CryptoBackendFactory const QGpgME::Protocol *const backend = QGpgME::openpgp(); if (!backend) { qCWarning(KLEOPATRA_LOG) << "Failed to get CryptoBackend"; return; } ChangeOwnerTrustJob *const j = backend->changeOwnerTrustJob(); j->start(toTrustOwner, Key::Ultimate); } else if (k == KMessageBox::ButtonCode::Cancel) { // do not bother the user with further "Is this yours?" questions return; } } } } static void validateImportedCertificate(const GpgME::Import &import) { if (const auto fpr = import.fingerprint()) { auto key = KeyCache::instance()->findByFingerprint(fpr); if (!key.isNull()) { // this triggers a keylisting with validation for this certificate key.update(); } else { qCWarning(KLEOPATRA_LOG) << __func__ << "Certificate with fingerprint" << fpr << "not found"; } } } static void handleExternalCMSImports(const std::vector &results) { // For external CMS Imports we have to manually do a keylist // with validation to get the intermediate and root ca imported // automatically if trusted-certs and extra-certs are used. for (const auto &r : results) { if (r.protocol == GpgME::CMS && r.type == ImportType::External && !importFailed(r) && !importWasCanceled(r)) { const auto imports = r.result.imports(); std::for_each(std::begin(imports), std::end(imports), &validateImportedCertificate); } } } void ImportCertificatesCommand::Private::processResults() { importGroups(); if (Settings{}.retrieveSignerKeysAfterImport() && !importingSignerKeys) { importingSignerKeys = true; const auto missingSignerKeys = getMissingSignerKeyIds(results); if (!missingSignerKeys.empty()) { importSignerKeys(missingSignerKeys); return; } } handleExternalCMSImports(results); // ensure that the progress dialog is closed before we show any other dialogs setProgressToMaximum(); handleOwnerTrust(results, parentWidgetOrView()); showDetails(results, importedGroups); auto tv = dynamic_cast(view()); if (!tv) { qCDebug(KLEOPATRA_LOG) << "Failed to find treeview"; } else { tv->expandAll(); } finished(); } void ImportCertificatesCommand::Private::tryToFinish() { qCDebug(KLEOPATRA_LOG) << q << __func__; if (waitForMoreJobs) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "Waiting for more jobs -> keep going"; return; } if (!runningJobs.empty()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are unfinished jobs -> keep going"; return; } if (!pendingJobs.empty()) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "There are pending jobs -> start the next one"; auto job = pendingJobs.front(); pendingJobs.pop(); job.job->startNow(); runningJobs.push_back(job); return; } if (keyListConnection) { qCWarning(KLEOPATRA_LOG) << q << __func__ << "There is already a valid keyListConnection!"; } else { auto keyCache = KeyCache::mutableInstance(); keyListConnection = connect(keyCache.get(), &KeyCache::keyListingDone, q, [this]() { keyCacheUpdated(); }); keyCache->startKeyListing(); } } void ImportCertificatesCommand::Private::keyCacheUpdated() { qCDebug(KLEOPATRA_LOG) << q << __func__; if (!disconnect(keyListConnection)) { qCWarning(KLEOPATRA_LOG) << q << __func__ << "Failed to disconnect keyListConnection"; } keyCacheAutoRefreshSuspension.reset(); const auto allIds = std::accumulate(std::cbegin(results), std::cend(results), std::set{}, [](auto allIds, const auto &r) { allIds.insert(r.id); return allIds; }); const auto canceledIds = std::accumulate(std::cbegin(results), std::cend(results), std::set{}, [](auto canceledIds, const auto &r) { if (importWasCanceled(r)) { canceledIds.insert(r.id); } return canceledIds; }); const auto totalConsidered = std::accumulate(std::cbegin(results), std::cend(results), 0, [](auto totalConsidered, const auto &r) { return totalConsidered + r.result.numConsidered(); }); if (totalConsidered == 0 && canceledIds.size() == allIds.size()) { // nothing was considered for import and at least one import per id was // canceled => treat the command as canceled canceled(); return; } processResults(); } static ImportedGroup storeGroup(const KeyGroup &group, const QString &id) { const auto status = KeyCache::instance()->group(group.id()).isNull() ? ImportedGroup::Status::New : ImportedGroup::Status::Updated; if (status == ImportedGroup::Status::New) { KeyCache::mutableInstance()->insert(group); } else { KeyCache::mutableInstance()->update(group); } return {id, group, status}; } void ImportCertificatesCommand::Private::importGroups() { for (const auto &path : filesToImportGroupsFrom) { const bool certificateImportSucceeded = std::any_of(std::cbegin(results), std::cend(results), [path](const auto &r) { return r.id == path && !importFailed(r) && !importWasCanceled(r); }); if (certificateImportSucceeded) { qCDebug(KLEOPATRA_LOG) << __func__ << "Importing groups from file" << path; const auto groups = readKeyGroups(path); std::transform(std::begin(groups), std::end(groups), std::back_inserter(importedGroups), [path](const auto &group) { return storeGroup(group, path); }); } increaseProgressValue(); } filesToImportGroupsFrom.clear(); } static auto accumulateNewKeys(std::vector &fingerprints, const std::vector &imports) { return std::accumulate(std::begin(imports), std::end(imports), fingerprints, [](auto fingerprints, const auto &import) { if (import.status() == Import::NewKey) { fingerprints.push_back(import.fingerprint()); } return fingerprints; }); } static auto accumulateNewOpenPGPKeys(const std::vector &results) { return std::accumulate(std::begin(results), std::end(results), std::vector{}, [](auto fingerprints, const auto &r) { if (r.protocol == GpgME::OpenPGP) { fingerprints = accumulateNewKeys(fingerprints, r.result.imports()); } return fingerprints; }); } std::set ImportCertificatesCommand::Private::getMissingSignerKeyIds(const std::vector &results) { auto newOpenPGPKeys = KeyCache::instance()->findByFingerprint(accumulateNewOpenPGPKeys(results)); // update all new OpenPGP keys to get information about certifications std::for_each(std::begin(newOpenPGPKeys), std::end(newOpenPGPKeys), std::mem_fn(&Key::update)); auto missingSignerKeyIds = Kleo::getMissingSignerKeyIds(newOpenPGPKeys); return missingSignerKeyIds; } void ImportCertificatesCommand::Private::importSignerKeys(const std::set &keyIds) { Q_ASSERT(!keyIds.empty()); setProgressLabelText(i18np("Fetching 1 signer key... (this can take a while)", "Fetching %1 signer keys... (this can take a while)", keyIds.size())); setWaitForMoreJobs(true); // start one import per key id to allow canceling the key retrieval without // losing already retrieved keys for (const auto &keyId : keyIds) { startImport(GpgME::OpenPGP, {keyId}, QStringLiteral("Retrieve Signer Keys")); } setWaitForMoreJobs(false); } static std::unique_ptr get_import_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { return std::unique_ptr(backend->importJob()); } else { return std::unique_ptr(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const QByteArray &data, const QString &id, [[maybe_unused]] const ImportOptions &options) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr job = get_import_job(protocol); if (!job.get()) { nonWorkingProtocols.push_back(protocol); error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)), i18n("Certificate Import Failed")); addImportResult({id, protocol, ImportType::Local, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; job->setImportFilter(options.importFilter); job->setKeyOrigin(options.keyOrigin, options.keyOriginUrl); const GpgME::Error err = job->startLater(data); if (err.code()) { addImportResult({id, protocol, ImportType::Local, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); pendingJobs.push({id, protocol, ImportType::Local, job.release(), connections}); } } static std::unique_ptr get_import_from_keyserver_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { return std::unique_ptr(backend->importFromKeyserverJob()); } else { return std::unique_ptr(); } } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, const std::vector &keys, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); if (std::find(nonWorkingProtocols.cbegin(), nonWorkingProtocols.cend(), protocol) != nonWorkingProtocols.cend()) { return; } std::unique_ptr job = get_import_from_keyserver_job(protocol); if (!job.get()) { nonWorkingProtocols.push_back(protocol); error(i18n("The type of this certificate (%1) is not supported by this Kleopatra installation.", Formatting::displayName(protocol)), i18n("Certificate Import Failed")); addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; const GpgME::Error err = job->start(keys); if (err.code()) { addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections}); } } static auto get_receive_keys_job(GpgME::Protocol protocol) { Q_ASSERT(protocol != UnknownProtocol); std::unique_ptr job{}; if (const auto backend = (protocol == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime())) { job.reset(backend->receiveKeysJob()); } return job; } void ImportCertificatesCommand::Private::startImport(GpgME::Protocol protocol, [[maybe_unused]] const QStringList &keyIds, const QString &id) { Q_ASSERT(protocol != UnknownProtocol); auto job = get_receive_keys_job(protocol); if (!job.get()) { qCWarning(KLEOPATRA_LOG) << "Failed to get ReceiveKeysJob for protocol" << Formatting::displayName(protocol); addImportResult({id, protocol, ImportType::External, ImportResult{}, AuditLogEntry{}}); return; } keyCacheAutoRefreshSuspension = KeyCache::mutableInstance()->suspendAutoRefresh(); std::vector connections = { connect(job.get(), &AbstractImportJob::result, q, [this](const GpgME::ImportResult &result) { onImportResult(result); }), connect(job.get(), &QGpgME::Job::jobProgress, q, &Command::progress), }; const GpgME::Error err = job->start(keyIds); if (err.code()) { addImportResult({id, protocol, ImportType::External, ImportResult{err}, AuditLogEntry{}}); } else { increaseProgressMaximum(); runningJobs.push_back({id, protocol, ImportType::External, job.release(), connections}); } } void ImportCertificatesCommand::Private::importGroupsFromFile(const QString &filename) { increaseProgressMaximum(); filesToImportGroupsFrom.push_back(filename); } void ImportCertificatesCommand::Private::setUpProgressDialog() { if (progressDialog) { return; } progressDialog = new QProgressDialog{parentWidgetOrView()}; // use a non-modal progress dialog to avoid reentrancy problems (and crashes) if multiple jobs finish in the same event loop cycle // (cf. the warning for QProgressDialog::setValue() in the API documentation) progressDialog->setModal(false); progressDialog->setWindowTitle(progressWindowTitle); progressDialog->setLabelText(progressLabelText); progressDialog->setMinimumDuration(1000); progressDialog->setMaximum(1); progressDialog->setValue(0); connect(progressDialog, &QProgressDialog::canceled, q, &Command::cancel); connect(q, &Command::finished, progressDialog, [this]() { progressDialog->accept(); }); } void ImportCertificatesCommand::Private::setProgressWindowTitle(const QString &title) { if (progressDialog) { progressDialog->setWindowTitle(title); } else { progressWindowTitle = title; } } void ImportCertificatesCommand::Private::setProgressLabelText(const QString &text) { if (progressDialog) { progressDialog->setLabelText(text); } else { progressLabelText = text; } } void ImportCertificatesCommand::Private::increaseProgressMaximum() { setUpProgressDialog(); progressDialog->setMaximum(progressDialog->maximum() + 1); qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum(); } void ImportCertificatesCommand::Private::increaseProgressValue() { progressDialog->setValue(progressDialog->value() + 1); qCDebug(KLEOPATRA_LOG) << __func__ << "progress:" << progressDialog->value() << "/" << progressDialog->maximum(); } void ImportCertificatesCommand::Private::setProgressToMaximum() { qCDebug(KLEOPATRA_LOG) << __func__; progressDialog->setValue(progressDialog->maximum()); } void ImportCertificatesCommand::doCancel() { const auto jobsToCancel = d->runningJobs; std::for_each(std::begin(jobsToCancel), std::end(jobsToCancel), [this](const auto &job) { if (!job.connections.empty()) { // ignore jobs without connections; they are already completed qCDebug(KLEOPATRA_LOG) << "Canceling job" << job.job; job.job->slotCancel(); d->onImportResult(ImportResult{Error::fromCode(GPG_ERR_CANCELED)}, job.job); } }); } #undef d #undef q #include "importcertificatescommand.moc" #include "moc_importcertificatescommand.cpp" diff --git a/src/commands/importpaperkeycommand.cpp b/src/commands/importpaperkeycommand.cpp index f8618dc66..6752d0152 100644 --- a/src/commands/importpaperkeycommand.cpp +++ b/src/commands/importpaperkeycommand.cpp @@ -1,223 +1,223 @@ /* commands/importperkeycommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "importpaperkeycommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "command_p.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ImportPaperKeyCommand::ImportPaperKeyCommand(const GpgME::Key &k) : GnuPGProcessCommand(k) { } QStringList ImportPaperKeyCommand::arguments() const { const Key key = d->key(); return { paperKeyInstallPath(), QStringLiteral("--pubring"), mTmpDir.path() + QStringLiteral("/pubkey.gpg"), QStringLiteral("--secrets"), mTmpDir.path() + QStringLiteral("/secrets.txt"), QStringLiteral("--output"), mTmpDir.path() + QStringLiteral("/seckey.gpg"), }; } void ImportPaperKeyCommand::exportResult(const GpgME::Error &err, const QByteArray &data) { if (err) { d->error(Formatting::errorAsString(err), errorCaption()); d->finished(); return; } if (!mTmpDir.isValid()) { // Should not happen so no i18n d->error(QStringLiteral("Failed to get temporary directory"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to get temporary dir"; d->finished(); return; } const QString fileName = mTmpDir.path() + QStringLiteral("/pubkey.gpg"); QFile f(fileName); if (!f.open(QIODevice::WriteOnly)) { d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; d->finished(); return; } f.write(data); f.close(); // Copy and sanitize input a bit QFile input(mFileName); if (!input.open(QIODevice::ReadOnly)) { d->error(xi18n("Cannot open %1 for reading.", mFileName), errorCaption()); d->finished(); return; } const QString outName = mTmpDir.path() + QStringLiteral("/secrets.txt"); QFile out(outName); if (!out.open(QIODevice::WriteOnly)) { // Should not happen d->error(QStringLiteral("Failed to create temporary file"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file for writing"; d->finished(); return; } QTextStream in(&input); while (!in.atEnd()) { // Paperkey is picky, tabs may not be part. Neither may be empty lines. const QString line = in.readLine().trimmed().replace(QLatin1Char('\t'), QStringLiteral(" ")) + QLatin1Char('\n'); out.write(line.toUtf8()); } input.close(); out.close(); GnuPGProcessCommand::doStart(); } void ImportPaperKeyCommand::postSuccessHook(QWidget *) { qCDebug(KLEOPATRA_LOG) << "Paperkey secrets restore finished successfully."; QFile secKey(mTmpDir.path() + QStringLiteral("/seckey.gpg")); if (!secKey.open(QIODevice::ReadOnly)) { d->error(QStringLiteral("Failed to open temporary secret"), errorCaption()); qCWarning(KLEOPATRA_LOG) << "Failed to open tmp file"; Q_EMIT finished(); return; } auto data = secKey.readAll(); secKey.close(); auto importjob = QGpgME::openpgp()->importJob(); auto result = importjob->exec(data); delete importjob; if (result.error()) { d->error(Formatting::errorAsString(result.error()), errorCaption()); Q_EMIT finished(); return; } if (!result.numSecretKeysImported() || (result.numSecretKeysUnchanged() == result.numSecretKeysImported())) { d->error(i18n("Failed to restore any secret keys."), errorCaption()); Q_EMIT finished(); return; } // Refresh the key after success KeyCache::mutableInstance()->reload(OpenPGP); Q_EMIT finished(); d->information(xi18nc("@info", "Successfully restored the secret key parts from %1", mFileName)); return; } void ImportPaperKeyCommand::doStart() { if (paperKeyInstallPath().isNull()) { KMessageBox::error(d->parentWidgetOrView(), xi18nc("@info", "Kleopatra uses " "PaperKey to import your " "text backup." "Please make sure it is installed."), i18nc("@title", "Failed to find PaperKey executable.")); return; } mFileName = QFileDialog::getOpenFileName(d->parentWidgetOrView(), i18n("Select input file"), QString(), QStringLiteral("%1 (*.txt)").arg(i18n("Paper backup")) #ifdef Q_OS_WIN /* For whatever reason at least with Qt 5.6.1 the native file dialog crashes in * my (aheinecke) Windows 10 environment when invoked here. * In other places it works, with the same arguments as in other places (e.g. import) * it works. But not here. Maybe it's our (gpg4win) build? But why did it only * crash here? * * It does not crash immediately, the program flow continues for a while before it * crashes so this is hard to debug. * * There are some reports about this * QTBUG-33119 QTBUG-41416 where different people describe "bugs" but they * describe them differently also not really reproducible. * Anyway this works for now and for such an exotic feature its good enough for now. */ , nullptr, QFileDialog::DontUseNativeDialog #endif ); if (mFileName.isEmpty()) { d->finished(); return; } auto exportJob = QGpgME::openpgp()->publicKeyExportJob(); connect(exportJob, &QGpgME::ExportJob::result, this, &ImportPaperKeyCommand::exportResult); - exportJob->start(QStringList() << QLatin1String(d->key().primaryFingerprint())); + exportJob->start(QStringList() << QLatin1StringView(d->key().primaryFingerprint())); } QString ImportPaperKeyCommand::errorCaption() const { return i18nc("@title:window", "Error importing secret key"); } QString ImportPaperKeyCommand::crashExitMessage(const QStringList &args) const { return xi18nc("@info", "The GPG process that tried to restore the secret key " "ended prematurely because of an unexpected error." "Please check the output of %1 for details.", args.join(QLatin1Char(' '))); } QString ImportPaperKeyCommand::errorExitMessage(const QStringList &args) const { return xi18nc("@info", "An error occurred while trying to restore the secret key. " "The output from %1 was:" "%2", args[0], errorString()); } QString ImportPaperKeyCommand::successMessage(const QStringList &) const { return QString(); } #include "moc_importpaperkeycommand.cpp" diff --git a/src/commands/keytocardcommand.cpp b/src/commands/keytocardcommand.cpp index 4dc3c1023..47f36b263 100644 --- a/src/commands/keytocardcommand.cpp +++ b/src/commands/keytocardcommand.cpp @@ -1,782 +1,782 @@ /* commands/keytocardcommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020,2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keytocardcommand.h" #include "cardcommand_p.h" #include "authenticatepivcardapplicationcommand.h" #include "smartcard/algorithminfo.h" #include "smartcard/openpgpcard.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "smartcard/utils.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if GPG_ERROR_VERSION_NUMBER >= 0x12400 // 1.36 #define GPG_ERROR_HAS_NO_AUTH #endif #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; using namespace GpgME; namespace { QString cardDisplayName(const std::shared_ptr &card) { return i18nc("smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber()); } } class KeyToCardCommand::Private : public CardCommand::Private { friend class ::Kleo::Commands::KeyToCardCommand; KeyToCardCommand *q_func() const { return static_cast(q); } public: explicit Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey); explicit Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName); private: enum Confirmation { AskForConfirmation, SkipConfirmation, }; void start(); void startKeyToOpenPGPCard(); Subkey getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr &card); void startKeyToPIVCard(); void authenticate(); void authenticationFinished(); void authenticationCanceled(); void keyToCardDone(const GpgME::Error &err); void keyToPIVCardDone(const GpgME::Error &err); void updateDone(); void keyHasBeenCopiedToCard(); void backupHasBeenCreated(const QString &backupFilename); QString backupKey(); std::vector readSecretKeyFile(); bool writeSecretKeyBackup(const QString &filename, const std::vector &keydata); void startDeleteSecretKeyLocally(Confirmation confirmation); void deleteSecretKeyLocallyFinished(const GpgME::Error &err); private: std::string appName; GpgME::Subkey subkey; std::string cardSlot; bool overwriteExistingAlreadyApproved = false; bool hasBeenCanceled = false; QMetaObject::Connection updateConnection; }; KeyToCardCommand::Private *KeyToCardCommand::d_func() { return static_cast(d.get()); } const KeyToCardCommand::Private *KeyToCardCommand::d_func() const { return static_cast(d.get()); } #define q q_func() #define d d_func() KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const GpgME::Subkey &subkey_) : CardCommand::Private(qq, "", nullptr) , subkey(subkey_) { } KeyToCardCommand::Private::Private(KeyToCardCommand *qq, const std::string &slot, const std::string &serialNumber, const std::string &appName_) : CardCommand::Private(qq, serialNumber, nullptr) , appName(appName_) , cardSlot(slot) { } namespace { static std::shared_ptr getCardToTransferSubkeyTo(const Subkey &subkey, QWidget *parent) { const std::vector> suitableCards = KeyToCardCommand::getSuitableCards(subkey); if (suitableCards.empty()) { return std::shared_ptr(); } else if (suitableCards.size() == 1) { return suitableCards[0]; } QStringList options; for (const auto &card : suitableCards) { options.push_back(cardDisplayName(card)); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card"), i18n("Please select the card the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); if (!ok) { return std::shared_ptr(); } const int index = options.indexOf(choice); return suitableCards[index]; } } void KeyToCardCommand::Private::start() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::start()"; if (!subkey.isNull() && serialNumber().empty()) { const auto card = getCardToTransferSubkeyTo(subkey, parentWidgetOrView()); if (!card) { finished(); return; } setSerialNumber(card->serialNumber()); appName = card->appName(); } const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (card->appName() == SmartCard::OpenPGPCard::AppName) { startKeyToOpenPGPCard(); } else if (card->appName() == SmartCard::PIVCard::AppName) { startKeyToPIVCard(); } else { error(xi18nc("@info", "Sorry! Writing keys to the card %1 is not supported.", cardDisplayName(card))); finished(); return; } } namespace { static std::string getOpenPGPCardSlotForKey(const GpgME::Subkey &subKey, QWidget *parent) { // Check if we need to ask the user for the slot if ((subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt() && !subKey.canAuthenticate()) { // Signing only return OpenPGPCard::pgpSigKeyRef(); } if (subKey.canEncrypt() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canAuthenticate()) { // Encrypt only return OpenPGPCard::pgpEncKeyRef(); } if (subKey.canAuthenticate() && !(subKey.canSign() || subKey.canCertify()) && !subKey.canEncrypt()) { // Auth only return OpenPGPCard::pgpAuthKeyRef(); } // Multiple uses, ask user. QStringList options; std::vector cardSlots; if (subKey.canSign() || subKey.canCertify()) { options.push_back(i18nc("@item:inlistbox", "Signature")); cardSlots.push_back(OpenPGPCard::pgpSigKeyRef()); } if (subKey.canEncrypt()) { options.push_back(i18nc("@item:inlistbox", "Encryption")); cardSlots.push_back(OpenPGPCard::pgpEncKeyRef()); } if (subKey.canAuthenticate()) { options.push_back(i18nc("@item:inlistbox", "Authentication")); cardSlots.push_back(OpenPGPCard::pgpAuthKeyRef()); } bool ok; const QString choice = QInputDialog::getItem(parent, i18n("Select Card Slot"), i18n("Please select the card slot the key should be written to:"), options, /* current= */ 0, /* editable= */ false, &ok); const int choiceIndex = options.indexOf(choice); if (ok && choiceIndex >= 0) { return cardSlots[choiceIndex]; } else { return {}; } } } void KeyToCardCommand::Private::startKeyToOpenPGPCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToOpenPGPCard()"; const auto pgpCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!pgpCard) { error(i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (subkey.isNull()) { finished(); return; } if (subkey.parent().protocol() != GpgME::OpenPGP) { error(i18n("Sorry! This key cannot be transferred to an OpenPGP card.")); finished(); return; } cardSlot = getOpenPGPCardSlotForKey(subkey, parentWidgetOrView()); if (cardSlot.empty()) { finished(); return; } // Check if we need to do the overwrite warning. const std::string existingKey = pgpCard->keyFingerprint(cardSlot); if (!existingKey.empty()) { const auto encKeyWarning = (cardSlot == OpenPGPCard::pgpEncKeyRef()) ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : QString{}; const QString message = i18nc("@info", "

    The card %1 already contains a key in this slot. Continuing will overwrite that key.

    " "

    If there is no backup the existing key will be irrecoverably lost.

    ", cardDisplayName(pgpCard)) + i18n("The existing key has the fingerprint:") + QStringLiteral("
    %1
    ").arg(Formatting::prettyID(existingKey.c_str())) + encKeyWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KGuiItem{i18nc("@action:button", "Overwrite Existing Key")}, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } } // Now do the deed const auto time = QDateTime::fromSecsSinceEpoch(quint32(subkey.creationTime()), Qt::UTC); const auto timestamp = time.toString(QStringLiteral("yyyyMMdd'T'HHmmss")); const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3 %4") .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber()), QString::fromStdString(cardSlot), timestamp); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) { keyToCardDone(err); }); } namespace { static std::vector getSigningCertificates() { std::vector signingCertificates = KeyCache::instance()->secretKeys(); const auto it = std::remove_if(signingCertificates.begin(), signingCertificates.end(), [](const Key &key) { return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canSign() && !key.subkey(0).canEncrypt() && key.subkey(0).isSecret() && !key.subkey(0).isCardKey()); }); signingCertificates.erase(it, signingCertificates.end()); return signingCertificates; } static std::vector getEncryptionCertificates() { std::vector encryptionCertificates = KeyCache::instance()->secretKeys(); const auto it = std::remove_if(encryptionCertificates.begin(), encryptionCertificates.end(), [](const Key &key) { return !(key.protocol() == GpgME::CMS && !key.subkey(0).isNull() && key.subkey(0).canEncrypt() && key.subkey(0).isSecret() && !key.subkey(0).isCardKey()); }); encryptionCertificates.erase(it, encryptionCertificates.end()); return encryptionCertificates; } } Subkey KeyToCardCommand::Private::getSubkeyToTransferToPIVCard(const std::string &cardSlot, const std::shared_ptr & /*card*/) { if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) { return Subkey(); } const std::vector certificates = cardSlot == PIVCard::cardAuthenticationKeyRef() ? getSigningCertificates() : getEncryptionCertificates(); if (certificates.empty()) { error(i18n("Sorry! No suitable certificate to write to this card slot was found.")); return Subkey(); } auto dialog = new KeySelectionDialog(parentWidgetOrView()); dialog->setWindowTitle(i18nc("@title:window", "Select Certificate")); dialog->setText(i18n("Please select the certificate whose key pair you want to write to the card:")); dialog->setKeys(certificates); if (dialog->exec() == QDialog::Rejected) { return Subkey(); } return dialog->selectedKey().subkey(0); } void KeyToCardCommand::Private::startKeyToPIVCard() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::Private::startKeyToPIVCard()"; const auto pivCard = SmartCard::ReaderStatus::instance()->getCard(serialNumber()); if (!pivCard) { error(i18n("Failed to find the PIV card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (cardSlot != PIVCard::cardAuthenticationKeyRef() && cardSlot != PIVCard::keyManagementKeyRef()) { // key to card is only supported for the Card Authentication key and the Key Management key finished(); return; } if (subkey.isNull()) { subkey = getSubkeyToTransferToPIVCard(cardSlot, pivCard); } if (subkey.isNull()) { finished(); return; } if (subkey.parent().protocol() != GpgME::CMS) { error(i18n("Sorry! This key cannot be transferred to a PIV card.")); finished(); return; } if (!subkey.canEncrypt() && !subkey.canSign()) { error(i18n("Sorry! Only encryption keys and signing keys can be transferred to a PIV card.")); finished(); return; } // Check if we need to do the overwrite warning. if (!overwriteExistingAlreadyApproved) { const std::string existingKey = pivCard->keyInfo(cardSlot).grip; if (!existingKey.empty() && (existingKey != subkey.keyGrip())) { const QString decryptionWarning = (cardSlot == PIVCard::keyManagementKeyRef()) ? i18n("It will no longer be possible to decrypt past communication encrypted for the existing key.") : QString(); const QString message = i18nc("@info", "

    The card %1 already contains a key in this slot. Continuing will overwrite that key.

    " "

    If there is no backup the existing key will be irrecoverably lost.

    ", cardDisplayName(pivCard)) + i18n("The existing key has the key grip:") + QStringLiteral("
    %1
    ").arg(QString::fromStdString(existingKey)) + decryptionWarning; const auto choice = KMessageBox::warningContinueCancel(parentWidgetOrView(), message, i18nc("@title:window", "Overwrite existing key"), KGuiItem{i18nc("@action:button", "Overwrite Existing Key")}, KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (choice != KMessageBox::Continue) { finished(); return; } overwriteExistingAlreadyApproved = true; } } const QString cmd = QStringLiteral("KEYTOCARD --force %1 %2 %3") .arg(QString::fromLatin1(subkey.keyGrip()), QString::fromStdString(serialNumber())) .arg(QString::fromStdString(cardSlot)); ReaderStatus::mutableInstance()->startSimpleTransaction(pivCard, cmd.toUtf8(), q_func(), [this](const GpgME::Error &err) { keyToPIVCardDone(err); }); } void KeyToCardCommand::Private::authenticate() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticate()"; auto cmd = new AuthenticatePIVCardApplicationCommand(serialNumber(), parentWidgetOrView()); cmd->setAutoResetCardToOpenPGP(false); connect(cmd, &AuthenticatePIVCardApplicationCommand::finished, q, [this]() { authenticationFinished(); }); connect(cmd, &AuthenticatePIVCardApplicationCommand::canceled, q, [this]() { authenticationCanceled(); }); cmd->start(); } void KeyToCardCommand::Private::authenticationFinished() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationFinished()"; if (!hasBeenCanceled) { startKeyToPIVCard(); } } void KeyToCardCommand::Private::authenticationCanceled() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::authenticationCanceled()"; hasBeenCanceled = true; canceled(); } void KeyToCardCommand::Private::updateDone() { disconnect(updateConnection); const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } const std::string keyGripOnCard = card->keyInfo(cardSlot).grip; if (keyGripOnCard != subkey.keyGrip()) { qCWarning(KLEOPATRA_LOG) << q << __func__ << "KEYTOCARD succeeded, but key on card doesn't match copied key"; error(i18nc("@info", "Copying the key to the card failed.")); finished(); return; } keyHasBeenCopiedToCard(); } void KeyToCardCommand::Private::keyHasBeenCopiedToCard() { const auto answer = KMessageBox::questionTwoActionsCancel(parentWidgetOrView(), xi18nc("@info", "The key has been copied to the card." "You can now delete the copy of the key stored on this computer. " "Optionally, you can first create a backup of the key."), i18nc("@title:window", "Success"), KGuiItem{i18nc("@action:button", "Create backup")}, KGuiItem{i18nc("@action:button", "Delete copy on disk")}, KGuiItem{i18nc("@action:button", "Keep copy on disk")}); if (answer == KMessageBox::ButtonCode::PrimaryAction) { const QString backupFilename = backupKey(); if (backupFilename.isEmpty()) { // user canceled the backup or there was an error; repeat above question QMetaObject::invokeMethod(q, [this]() { keyHasBeenCopiedToCard(); }); } backupHasBeenCreated(backupFilename); } else if (answer == KMessageBox::ButtonCode::SecondaryAction) { startDeleteSecretKeyLocally(AskForConfirmation); } else { finished(); } } void KeyToCardCommand::Private::backupHasBeenCreated(const QString &backupFilename) { const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(), xi18nc("@info", "The key has been copied to the card and a backup has been written to %1." "Do you want to delete the copy of the key stored on this computer?", backupFilename), i18nc("@title:window", "Success"), KGuiItem{i18nc("@action:button", "Delete copy on disk")}, KGuiItem{i18nc("@action:button", "Keep copy on disk")}); if (answer == KMessageBox::ButtonCode::PrimaryAction) { // the user has created a backup; don't ask again for confirmation before deleting the copy on disk startDeleteSecretKeyLocally(SkipConfirmation); } else { finished(); } } namespace { QString gnupgPrivateKeyBackupExtension() { return QStringLiteral(".gpgsk"); } QString proposeFilename(const Subkey &subkey) { QString filename; const auto key = subkey.parent(); auto name = Formatting::prettyName(key); if (name.isEmpty()) { name = Formatting::prettyEMail(key); } const auto shortKeyID = Formatting::prettyKeyID(key.shortKeyID()); const auto shortSubkeyID = Formatting::prettyKeyID(QByteArray{subkey.keyID()}.right(8).constData()); - const auto usage = Formatting::usageString(subkey).replace(QLatin1String{", "}, QLatin1String{"_"}); + const auto usage = Formatting::usageString(subkey).replace(QLatin1StringView{", "}, QLatin1String{"_"}); /* Not translated so it's better to use in tutorials etc. */ filename = ((shortKeyID == shortSubkeyID) // ? QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3"}.arg(name, shortKeyID, usage) : QStringView{u"%1_%2_SECRET_KEY_BACKUP_%3_%4"}.arg(name, shortKeyID, shortSubkeyID, usage)); filename.replace(u'/', u'_'); return QDir{ApplicationState::lastUsedExportDirectory()}.filePath(filename + gnupgPrivateKeyBackupExtension()); } QString requestPrivateKeyBackupFilename(const QString &proposedFilename, QWidget *parent) { auto filename = FileDialog::getSaveFileNameEx(parent, i18nc("@title:window", "Backup Secret Key"), QStringLiteral("imp"), proposedFilename, - i18nc("description of filename filter", "Secret Key Backup Files") + QLatin1String{" (*.gpgsk)"}); + i18nc("description of filename filter", "Secret Key Backup Files") + QLatin1StringView{" (*.gpgsk)"}); if (!filename.isEmpty()) { const QFileInfo fi{filename}; if (fi.suffix().isEmpty()) { filename += gnupgPrivateKeyBackupExtension(); } ApplicationState::setLastUsedExportDirectory(filename); } return filename; } } QString KeyToCardCommand::Private::backupKey() { static const QByteArray backupInfoName = "Backup-info:"; auto keydata = readSecretKeyFile(); if (keydata.empty()) { return {}; } const auto filename = requestPrivateKeyBackupFilename(proposeFilename(subkey), parentWidgetOrView()); if (filename.isEmpty()) { return {}; } // remove old backup info Kleo::erase_if(keydata, [](const auto &line) { return line.startsWith(backupInfoName); }); // prepend new backup info const QByteArrayList backupInfo = { backupInfoName, subkey.keyGrip(), QDateTime::currentDateTimeUtc().toString(Qt::ISODate).toUtf8(), "Kleopatra", Formatting::prettyNameAndEMail(subkey.parent()).toUtf8(), }; keydata.insert(keydata.begin(), backupInfo.join(' ') + '\n'); if (writeSecretKeyBackup(filename, keydata)) { return filename; } else { return {}; } } std::vector KeyToCardCommand::Private::readSecretKeyFile() { - const auto filename = QString::fromLatin1(subkey.keyGrip()) + QLatin1String{".key"}; + const auto filename = QString::fromLatin1(subkey.keyGrip()) + QLatin1StringView{".key"}; const auto path = QDir{Kleo::gnupgPrivateKeysDirectory()}.filePath(filename); QFile file{path}; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) { error(xi18n("Cannot open the private key file %1 for reading.", path)); return {}; } std::vector lines; while (!file.atEnd()) { lines.push_back(file.readLine()); } if (lines.empty()) { error(xi18n("The private key file %1 is empty.", path)); } return lines; } bool KeyToCardCommand::Private::writeSecretKeyBackup(const QString &filename, const std::vector &keydata) { QSaveFile file{filename}; // open the file in binary format because we want to write Unix line endings if (!file.open(QIODevice::WriteOnly)) { error(xi18n("Cannot open the file %1 for writing.", filename)); return false; } for (const auto &line : keydata) { file.write(line); } if (!file.commit()) { error(xi18n("Writing the backup of the secret key to %1 failed.", filename)); return false; }; return true; } void KeyToCardCommand::Private::startDeleteSecretKeyLocally(Confirmation confirmation) { const auto card = SmartCard::ReaderStatus::instance()->getCard(serialNumber(), appName); if (!card) { error(i18n("Failed to find the card with the serial number: %1", QString::fromStdString(serialNumber()))); finished(); return; } if (confirmation == AskForConfirmation) { const auto answer = KMessageBox::questionTwoActions(parentWidgetOrView(), xi18nc("@info", "Do you really want to delete the copy of the key stored on this computer?"), i18nc("@title:window", "Confirm Deletion"), KStandardGuiItem::del(), KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::Dangerous); if (answer != KMessageBox::ButtonCode::PrimaryAction) { finished(); return; } } const auto cmd = QByteArray{"DELETE_KEY --force "} + subkey.keyGrip(); ReaderStatus::mutableInstance()->startSimpleTransaction(card, cmd, q, [this](const GpgME::Error &err) { deleteSecretKeyLocallyFinished(err); }); } void KeyToCardCommand::Private::deleteSecretKeyLocallyFinished(const GpgME::Error &err) { if (err) { error(xi18nc("@info", "Failed to delete the copy of the key stored on this computer:%1", Formatting::errorAsString(err))); } ReaderStatus::mutableInstance()->updateStatus(); success(i18nc("@info", "Successfully deleted the copy of the key stored on this computer.")); finished(); } KeyToCardCommand::KeyToCardCommand(const GpgME::Subkey &subkey) : CardCommand(new Private(this, subkey)) { } KeyToCardCommand::KeyToCardCommand(const std::string &cardSlot, const std::string &serialNumber, const std::string &appName) : CardCommand(new Private(this, cardSlot, serialNumber, appName)) { } KeyToCardCommand::~KeyToCardCommand() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::~KeyToCardCommand()"; } namespace { bool cardSupportsKeyAlgorithm(const std::shared_ptr &card, const std::string &keyAlgo) { if (card->appName() == OpenPGPCard::AppName) { const auto pgpCard = static_cast(card.get()); const auto cardAlgos = pgpCard->supportedAlgorithms(); return Kleo::any_of(cardAlgos, [keyAlgo](const auto &algo) { return (keyAlgo == algo.id) // || (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpEncKeyRef())) || (keyAlgo == OpenPGPCard::getAlgorithmName(algo.id, OpenPGPCard::pgpSigKeyRef())); }); } return false; } } // static std::vector> KeyToCardCommand::getSuitableCards(const GpgME::Subkey &subkey) { std::vector> suitableCards; if (subkey.isNull() || subkey.parent().protocol() != GpgME::OpenPGP) { return suitableCards; } const auto keyAlgo = subkey.algoName(); Kleo::copy_if(ReaderStatus::instance()->getCards(), std::back_inserter(suitableCards), [keyAlgo](const auto &card) { return cardSupportsKeyAlgorithm(card, keyAlgo); }); return suitableCards; } void KeyToCardCommand::Private::keyToCardDone(const GpgME::Error &err) { if (!err && !err.isCanceled()) { updateConnection = connect(ReaderStatus::instance(), &ReaderStatus::updateFinished, q, [this]() { updateDone(); }); ReaderStatus::mutableInstance()->updateCard(serialNumber(), appName); return; } if (err) { error(xi18nc("@info", "Copying the key to the card failed:%1", Formatting::errorAsString(err))); } finished(); } void KeyToCardCommand::Private::keyToPIVCardDone(const GpgME::Error &err) { qCDebug(KLEOPATRA_LOG) << q << __func__ << Formatting::errorAsString(err) << "(" << err.code() << ")"; #ifdef GPG_ERROR_HAS_NO_AUTH // gpgme 1.13 reports "BAD PIN" instead of "NO AUTH" if (err.code() == GPG_ERR_NO_AUTH || err.code() == GPG_ERR_BAD_PIN) { authenticate(); return; } #endif keyToCardDone(err); } void KeyToCardCommand::doStart() { qCDebug(KLEOPATRA_LOG) << "KeyToCardCommand::doStart()"; d->start(); } void KeyToCardCommand::doCancel() { } #undef q_func #undef d_func #include "moc_keytocardcommand.cpp" diff --git a/src/commands/lookupcertificatescommand.cpp b/src/commands/lookupcertificatescommand.cpp index afb53f446..9cb5d6889 100644 --- a/src/commands/lookupcertificatescommand.cpp +++ b/src/commands/lookupcertificatescommand.cpp @@ -1,598 +1,598 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/lookupcertificatescommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008, 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "lookupcertificatescommand.h" #include "importcertificatescommand_p.h" #include "detailscommand.h" #include #include "view/tabwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Dialogs; using namespace GpgME; using namespace QGpgME; class LookupCertificatesCommand::Private : public ImportCertificatesCommand::Private { friend class ::Kleo::Commands::LookupCertificatesCommand; LookupCertificatesCommand *q_func() const { return static_cast(q); } public: explicit Private(LookupCertificatesCommand *qq, KeyListController *c); ~Private() override; void init(); private: void slotSearchTextChanged(const QString &str); void slotNextKey(const Key &key); void slotKeyListResult(const KeyListResult &result); void slotWKDLookupResult(const WKDLookupResult &result); void tryToFinishKeyLookup(); void slotImportRequested(const std::vector &keys); void slotDetailsRequested(const Key &key); void slotSaveAsRequested(const std::vector &keys); void slotDialogRejected() { canceled(); } private: using ImportCertificatesCommand::Private::showError; void showError(QWidget *parent, const KeyListResult &result); void showResult(QWidget *parent, const KeyListResult &result); void createDialog(); KeyListJob *createKeyListJob(GpgME::Protocol proto) const { const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); return cbp ? cbp->keyListJob(true) : nullptr; } WKDLookupJob *createWKDLookupJob() const { const auto cbp = QGpgME::openpgp(); return cbp ? cbp->wkdLookupJob() : nullptr; } ImportFromKeyserverJob *createImportJob(GpgME::Protocol proto) const { const auto cbp = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); return cbp ? cbp->importFromKeyserverJob() : nullptr; } void startKeyListJob(GpgME::Protocol proto, const QString &str); void startWKDLookupJob(const QString &str); bool checkConfig() const; QWidget *dialogOrParentWidgetOrView() const { if (dialog) { return dialog; } else { return parentWidgetOrView(); } } private: GpgME::Protocol protocol = GpgME::UnknownProtocol; QString query; bool autoStartLookup = false; QPointer dialog; struct KeyListingVariables { QPointer cms, openpgp; QPointer wkdJob; QString pattern; KeyListResult result; std::vector keys; int numKeysWithoutUserId = 0; std::set wkdKeyFingerprints; QByteArray wkdKeyData; QString wkdSource; bool cmsKeysHaveNoFingerprints = false; bool openPgpKeysHaveNoFingerprints = false; void reset() { *this = KeyListingVariables(); } } keyListing; }; LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func() { return static_cast(d.get()); } const LookupCertificatesCommand::Private *LookupCertificatesCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() LookupCertificatesCommand::Private::Private(LookupCertificatesCommand *qq, KeyListController *c) : ImportCertificatesCommand::Private(qq, c) , dialog() { if (!Settings{}.cmsEnabled()) { protocol = GpgME::OpenPGP; } } LookupCertificatesCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG); delete dialog; } LookupCertificatesCommand::LookupCertificatesCommand(KeyListController *c) : ImportCertificatesCommand(new Private(this, c)) { d->init(); } LookupCertificatesCommand::LookupCertificatesCommand(const QString &query, KeyListController *c) : ImportCertificatesCommand(new Private(this, c)) { d->init(); d->query = query; d->autoStartLookup = true; } LookupCertificatesCommand::LookupCertificatesCommand(QAbstractItemView *v, KeyListController *c) : ImportCertificatesCommand(v, new Private(this, c)) { d->init(); if (c->tabWidget()) { d->query = c->tabWidget()->stringFilter(); // do not start the lookup automatically to prevent unwanted leaking // of information } } void LookupCertificatesCommand::Private::init() { } LookupCertificatesCommand::~LookupCertificatesCommand() { qCDebug(KLEOPATRA_LOG); } void LookupCertificatesCommand::setProtocol(GpgME::Protocol protocol) { d->protocol = protocol; } GpgME::Protocol LookupCertificatesCommand::protocol() const { return d->protocol; } void LookupCertificatesCommand::doStart() { if (!d->checkConfig()) { d->finished(); return; } d->createDialog(); Q_ASSERT(d->dialog); // if we have a prespecified query, load it into find field // and start the search, if auto-start is enabled if (!d->query.isEmpty()) { d->dialog->setSearchText(d->query); if (d->autoStartLookup) { d->slotSearchTextChanged(d->query); } } else { d->dialog->setPassive(false); } d->dialog->show(); } void LookupCertificatesCommand::Private::createDialog() { if (dialog) { return; } dialog = new LookupCertificatesDialog; applyWindowID(dialog); dialog->setAttribute(Qt::WA_DeleteOnClose); const bool wkdOnly = !haveKeyserverConfigured() && !haveX509DirectoryServerConfigured(); dialog->setQueryMode(wkdOnly ? LookupCertificatesDialog::EmailQuery : LookupCertificatesDialog::AnyQuery); connect(dialog, &LookupCertificatesDialog::searchTextChanged, q, [this](const QString &text) { slotSearchTextChanged(text); }); using CertsVec = std::vector; connect(dialog, &LookupCertificatesDialog::saveAsRequested, q, [this](const CertsVec &certs) { slotSaveAsRequested(certs); }); connect(dialog, &LookupCertificatesDialog::importRequested, q, [this](const CertsVec &certs) { slotImportRequested(certs); }); connect(dialog, &LookupCertificatesDialog::detailsRequested, q, [this](const GpgME::Key &gpgKey) { slotDetailsRequested(gpgKey); }); connect(dialog, &QDialog::rejected, q, [this]() { slotDialogRejected(); }); } static auto searchTextToEmailAddress(const QString &s) { return QString::fromStdString(UserID::addrSpecFromString(s.toStdString().c_str())); } void LookupCertificatesCommand::Private::slotSearchTextChanged(const QString &str) { // pressing return might trigger both search and dialog destruction (search focused and default key set) // On Windows, the dialog is then destroyed before this slot is called if (dialog) { // thus test dialog->setPassive(true); dialog->setCertificates(std::vector()); dialog->showInformation({}); } keyListing.reset(); keyListing.pattern = str; if (protocol != GpgME::OpenPGP) { startKeyListJob(CMS, str); } if (protocol != GpgME::CMS) { - static const QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1String("[0-9a-fA-F]{6,}"))); + static const QRegularExpression rx(QRegularExpression::anchoredPattern(QLatin1StringView("[0-9a-fA-F]{6,}"))); if (rx.match(str).hasMatch()) { qCDebug(KLEOPATRA_LOG) << "Adding 0x prefix to query" << str; - startKeyListJob(OpenPGP, QLatin1String{"0x"} + str); + startKeyListJob(OpenPGP, QLatin1StringView{"0x"} + str); } else { startKeyListJob(OpenPGP, str); } if (str.contains(QLatin1Char{'@'}) && !searchTextToEmailAddress(str).isEmpty()) { startWKDLookupJob(str); } } } void LookupCertificatesCommand::Private::startKeyListJob(GpgME::Protocol proto, const QString &str) { if ((proto == GpgME::OpenPGP) && !haveKeyserverConfigured()) { // avoid starting an OpenPGP key server lookup if key server usage has been disabled; // for S/MIME we start the job regardless of configured directory servers to account for // dirmngr knowing better than our check for directory servers return; } KeyListJob *const klj = createKeyListJob(proto); if (!klj) { return; } connect(klj, &QGpgME::KeyListJob::result, q, [this](const GpgME::KeyListResult &result) { slotKeyListResult(result); }); connect(klj, &QGpgME::KeyListJob::nextKey, q, [this](const GpgME::Key &key) { slotNextKey(key); }); if (const Error err = klj->start(QStringList(str))) { keyListing.result.mergeWith(KeyListResult(err)); } else if (proto == CMS) { keyListing.cms = klj; } else { keyListing.openpgp = klj; } } void LookupCertificatesCommand::Private::startWKDLookupJob(const QString &str) { const auto job = createWKDLookupJob(); if (!job) { qCDebug(KLEOPATRA_LOG) << "Failed to create WKDLookupJob"; return; } connect(job, &WKDLookupJob::result, q, [this](const WKDLookupResult &result) { slotWKDLookupResult(result); }); if (const Error err = job->start(str)) { keyListing.result.mergeWith(KeyListResult{err}); } else { keyListing.wkdJob = job; } } void LookupCertificatesCommand::Private::slotNextKey(const Key &key) { if (!key.primaryFingerprint()) { qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without fingerprint" << key; if (q->sender() == keyListing.cms) { keyListing.cmsKeysHaveNoFingerprints = true; } else if (q->sender() == keyListing.openpgp) { keyListing.openPgpKeysHaveNoFingerprints = true; } } else if (key.numUserIDs() == 0) { qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring key without user IDs" << key; keyListing.numKeysWithoutUserId++; } else { qCDebug(KLEOPATRA_LOG) << __func__ << "got key" << key; keyListing.keys.push_back(key); } } void LookupCertificatesCommand::Private::slotKeyListResult(const KeyListResult &r) { if (q->sender() == keyListing.cms) { keyListing.cms = nullptr; } else if (q->sender() == keyListing.openpgp) { keyListing.openpgp = nullptr; } else { qCDebug(KLEOPATRA_LOG) << "unknown sender()" << q->sender(); } keyListing.result.mergeWith(r); tryToFinishKeyLookup(); } static auto removeKeysNotMatchingEmail(const std::vector &keys, const std::string &email) { std::vector filteredKeys; const auto addrSpec = UserID::addrSpecFromString(email.c_str()); std::copy_if(std::begin(keys), std::end(keys), std::back_inserter(filteredKeys), [addrSpec](const auto &key) { const auto uids = key.userIDs(); return std::any_of(std::begin(uids), std::end(uids), [addrSpec](const auto &uid) { return uid.addrSpec() == addrSpec; }); }); return filteredKeys; } void LookupCertificatesCommand::Private::slotWKDLookupResult(const WKDLookupResult &result) { if (q->sender() == keyListing.wkdJob) { keyListing.wkdJob = nullptr; } else { qCDebug(KLEOPATRA_LOG) << __func__ << "unknown sender()" << q->sender(); } // we do not want to bother the user with errors during the WKD lookup; // therefore, we log the result, but we do not merge it into keyListing.result qCDebug(KLEOPATRA_LOG) << "Result of WKD lookup:" << result.error(); const auto keys = removeKeysNotMatchingEmail(result.keyData().toKeys(GpgME::OpenPGP), result.pattern()); if (!keys.empty()) { keyListing.wkdKeyData = QByteArray::fromStdString(result.keyData().toString()); keyListing.wkdSource = QString::fromStdString(result.source()); std::copy(std::begin(keys), std::end(keys), std::back_inserter(keyListing.keys)); // remember the keys retrieved via WKD for import std::transform(std::begin(keys), std::end(keys), std::inserter(keyListing.wkdKeyFingerprints, std::begin(keyListing.wkdKeyFingerprints)), [](const auto &k) { return k.primaryFingerprint(); }); } tryToFinishKeyLookup(); } namespace { void showKeysWithoutFingerprintsNotification(QWidget *parent, GpgME::Protocol protocol) { if (protocol != GpgME::CMS && protocol != GpgME::OpenPGP) { return; } QString message; if (protocol == GpgME::CMS) { message = xi18nc("@info", "One of the X.509 directory services returned certificates without " "fingerprints. Those certificates are ignored because fingerprints " "are required as unique identifiers for certificates." "You may want to configure a different X.509 directory service " "in the configuration dialog."); } else { message = xi18nc("@info", "The OpenPGP keyserver returned certificates without " "fingerprints. Those certificates are ignored because fingerprints " "are required as unique identifiers for certificates." "You may want to configure a different OpenPGP keyserver " "in the configuration dialog."); } KMessageBox::information(parent, message, i18nc("@title", "Invalid Server Reply"), QStringLiteral("certificates-lookup-missing-fingerprints")); } } void LookupCertificatesCommand::Private::tryToFinishKeyLookup() { if (keyListing.cms || keyListing.openpgp || keyListing.wkdJob) { // still waiting for jobs to complete return; } if (keyListing.result.error() && !keyListing.result.error().isCanceled()) { showError(dialog, keyListing.result); } if (keyListing.result.isTruncated()) { showResult(dialog, keyListing.result); } if (keyListing.cmsKeysHaveNoFingerprints) { showKeysWithoutFingerprintsNotification(dialog, GpgME::CMS); } if (keyListing.openPgpKeysHaveNoFingerprints) { showKeysWithoutFingerprintsNotification(dialog, GpgME::OpenPGP); } if (dialog) { dialog->setPassive(false); dialog->setCertificates(keyListing.keys); if (keyListing.numKeysWithoutUserId > 0) { dialog->showInformation(i18ncp("@info", "One certificate without name and email address was ignored.", "%1 certificates without name and email address were ignored.", keyListing.numKeysWithoutUserId)); } } else { finished(); } } void LookupCertificatesCommand::Private::slotImportRequested(const std::vector &keys) { dialog = nullptr; Q_ASSERT(!keys.empty()); Q_ASSERT(std::none_of(keys.cbegin(), keys.cend(), [](const Key &key) { return key.isNull(); })); std::vector wkdKeys, otherKeys; otherKeys.reserve(keys.size()); kdtools::separate_if(std::begin(keys), std::end(keys), std::back_inserter(wkdKeys), std::back_inserter(otherKeys), [this](const auto &key) { return key.primaryFingerprint() && keyListing.wkdKeyFingerprints.find(key.primaryFingerprint()) != std::end(keyListing.wkdKeyFingerprints); }); std::vector pgp, cms; pgp.reserve(otherKeys.size()); cms.reserve(otherKeys.size()); kdtools::separate_if(otherKeys.begin(), otherKeys.end(), std::back_inserter(pgp), std::back_inserter(cms), [](const Key &key) { return key.protocol() == GpgME::OpenPGP; }); setWaitForMoreJobs(true); if (!wkdKeys.empty()) { // set an import filter, so that only user IDs matching the email address used for the WKD lookup are imported - const QString importFilter = QLatin1String{"keep-uid=mbox = "} + searchTextToEmailAddress(keyListing.pattern); + const QString importFilter = QLatin1StringView{"keep-uid=mbox = "} + searchTextToEmailAddress(keyListing.pattern); startImport(OpenPGP, keyListing.wkdKeyData, keyListing.wkdSource, {importFilter, Key::OriginWKD, keyListing.wkdSource}); } if (!pgp.empty()) { startImport(OpenPGP, pgp, i18nc(R"(@title %1:"OpenPGP" or "S/MIME")", "%1 Certificate Server", Formatting::displayName(OpenPGP))); } if (!cms.empty()) { startImport(CMS, cms, i18nc(R"(@title %1:"OpenPGP" or "S/MIME")", "%1 Certificate Server", Formatting::displayName(CMS))); } setWaitForMoreJobs(false); } void LookupCertificatesCommand::Private::slotSaveAsRequested(const std::vector &keys) { Q_UNUSED(keys) qCDebug(KLEOPATRA_LOG) << "not implemented"; } void LookupCertificatesCommand::Private::slotDetailsRequested(const Key &key) { Command *const cmd = new DetailsCommand(key); cmd->setParentWidget(dialogOrParentWidgetOrView()); cmd->start(); } void LookupCertificatesCommand::doCancel() { ImportCertificatesCommand::doCancel(); if (QDialog *const dlg = d->dialog) { d->dialog = nullptr; dlg->close(); } } void LookupCertificatesCommand::Private::showError(QWidget *parent, const KeyListResult &result) { if (!result.error()) { return; } KMessageBox::information(parent, i18nc("@info", "Failed to search on certificate server. The error returned was:\n%1", Formatting::errorAsString(result.error()))); } void LookupCertificatesCommand::Private::showResult(QWidget *parent, const KeyListResult &result) { if (result.isTruncated()) KMessageBox::information(parent, xi18nc("@info", "The query result has been truncated." "Either the local or a remote limit on " "the maximum number of returned hits has " "been exceeded." "You can try to increase the local limit " "in the configuration dialog, but if one " "of the configured servers is the limiting " "factor, you have to refine your search."), i18nc("@title", "Result Truncated"), QStringLiteral("lookup-certificates-truncated-result")); } bool LookupCertificatesCommand::Private::checkConfig() const { // unless CMS-only lookup is requested we always try a lookup via WKD const bool ok = (protocol != GpgME::CMS) || haveX509DirectoryServerConfigured(); if (!ok) { information(xi18nc("@info", "You do not have any directory servers configured." "You need to configure at least one directory server to " "search on one." "You can configure directory servers here: " "Settings->Configure Kleopatra."), i18nc("@title", "No Directory Servers Configured")); } return ok; } #undef d #undef q #include "moc_lookupcertificatescommand.cpp" diff --git a/src/commands/newopenpgpcertificatecommand.cpp b/src/commands/newopenpgpcertificatecommand.cpp index a39fd471e..d92ef1c1a 100644 --- a/src/commands/newopenpgpcertificatecommand.cpp +++ b/src/commands/newopenpgpcertificatecommand.cpp @@ -1,277 +1,277 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/newopenpgpcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newopenpgpcertificatecommand.h" #include "command_p.h" #include "dialogs/newopenpgpcertificatedetailsdialog.h" #include "kleopatraapplication.h" #include "utils/emptypassphraseprovider.h" #include "utils/keyparameters.h" #include "utils/userinfo.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; class NewOpenPGPCertificateCommand::Private : public Command::Private { friend class ::Kleo::NewOpenPGPCertificateCommand; NewOpenPGPCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(NewOpenPGPCertificateCommand *qq, KeyListController *c) : Command::Private{qq, c} { } void getCertificateDetails(); void createCertificate(); void showResult(const KeyGenerationResult &result); void showErrorDialog(const KeyGenerationResult &result); private: KeyParameters keyParameters; bool protectKeyWithPassword = false; EmptyPassphraseProvider emptyPassphraseProvider; QPointer detailsDialog; QPointer job; QPointer progressDialog; }; NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() { return static_cast(d.get()); } const NewOpenPGPCertificateCommand::Private *NewOpenPGPCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() void NewOpenPGPCertificateCommand::Private::getCertificateDetails() { detailsDialog = new NewOpenPGPCertificateDetailsDialog; detailsDialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(detailsDialog); if (keyParameters.protocol() == KeyParameters::NoProtocol) { const auto settings = Kleo::Settings{}; - const KConfigGroup config{KSharedConfig::openConfig(), QLatin1String("CertificateCreationWizard")}; + const KConfigGroup config{KSharedConfig::openConfig(), QLatin1StringView("CertificateCreationWizard")}; // prefer the last used name and email address over the values retrieved from the system detailsDialog->setName(config.readEntry("NAME", QString{})); if (detailsDialog->name().isEmpty() && settings.prefillName()) { detailsDialog->setName(userFullName()); } detailsDialog->setEmail(config.readEntry("EMAIL", QString{})); if (detailsDialog->email().isEmpty() && settings.prefillEmail()) { detailsDialog->setEmail(userEmailAddress()); } } else { detailsDialog->setKeyParameters(keyParameters); detailsDialog->setProtectKeyWithPassword(protectKeyWithPassword); } connect(detailsDialog, &QDialog::accepted, q, [this]() { keyParameters = detailsDialog->keyParameters(); protectKeyWithPassword = detailsDialog->protectKeyWithPassword(); QMetaObject::invokeMethod( q, [this] { createCertificate(); }, Qt::QueuedConnection); }); connect(detailsDialog, &QDialog::rejected, q, [this]() { canceled(); }); detailsDialog->show(); } void NewOpenPGPCertificateCommand::Private::createCertificate() { Q_ASSERT(keyParameters.protocol() == KeyParameters::OpenPGP); auto keyGenJob = QGpgME::openpgp()->keyGenerationJob(); if (!keyGenJob) { finished(); return; } if (!protectKeyWithPassword) { auto ctx = QGpgME::Job::context(keyGenJob); ctx->setPassphraseProvider(&emptyPassphraseProvider); ctx->setPinentryMode(Context::PinentryLoopback); } auto settings = KleopatraApplication::instance()->distributionSettings(); if (settings) { keyParameters.setComment(settings->value(QStringLiteral("uidcomment"), {}).toString()); } if (auto settings = Settings{}; !settings.designatedRevoker().isEmpty()) { keyParameters.addDesignatedRevoker(settings.designatedRevoker()); } connect(keyGenJob, &QGpgME::KeyGenerationJob::result, q, [this](const KeyGenerationResult &result) { QMetaObject::invokeMethod( q, [this, result] { showResult(result); }, Qt::QueuedConnection); }); if (const Error err = keyGenJob->start(keyParameters.toString())) { error(i18n("Could not start key pair creation: %1", Formatting::errorAsString(err))); finished(); return; } else { job = keyGenJob; } progressDialog = new QProgressDialog; progressDialog->setAttribute(Qt::WA_DeleteOnClose); applyWindowID(progressDialog); progressDialog->setModal(true); progressDialog->setWindowTitle(i18nc("@title", "Creating Key Pair...")); progressDialog->setLabelText(i18n("The process of creating a key requires large amounts of random numbers. This may require several minutes...")); progressDialog->setRange(0, 0); connect(progressDialog, &QProgressDialog::canceled, job, &QGpgME::Job::slotCancel); connect(job, &QGpgME::Job::done, q, [this]() { if (progressDialog) { progressDialog->accept(); } }); progressDialog->show(); } void NewOpenPGPCertificateCommand::Private::showResult(const KeyGenerationResult &result) { if (result.error().isCanceled()) { finished(); return; } // Ensure that we have the key in the cache Key key; if (!result.error().code() && result.fingerprint()) { std::unique_ptr ctx{Context::createForProtocol(OpenPGP)}; if (ctx) { Error err; key = ctx->key(result.fingerprint(), err, /*secret=*/true); if (!key.isNull()) { KeyCache::mutableInstance()->insert(key); } } } if (!key.isNull()) { success( xi18n("A new OpenPGP certificate was created successfully." "Fingerprint of the new certificate: %1", Formatting::prettyID(key.primaryFingerprint()))); finished(); } else { showErrorDialog(result); } } void NewOpenPGPCertificateCommand::Private::showErrorDialog(const KeyGenerationResult &result) { QString text; if (result.error() || !result.fingerprint()) { text = xi18n( "The creation of a new OpenPGP certificate failed." "Error: %1", Formatting::errorAsString(result.error())); } else { // no error and we have a fingerprint, but there was no corresponding key in the key ring text = xi18n( "A new OpenPGP certificate was created successfully, but it has not been found in the key ring." "Fingerprint of the new certificate:%1", Formatting::prettyID(result.fingerprint())); } auto dialog = new QDialog; applyWindowID(dialog); dialog->setWindowTitle(i18nc("@title:window", "Error")); auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Retry | QDialogButtonBox::Ok, dialog}; const auto buttonCode = KMessageBox::createKMessageBox(dialog, buttonBox, QMessageBox::Critical, text, {}, {}, nullptr, {}); if (buttonCode == QDialogButtonBox::Retry) { QMetaObject::invokeMethod( q, [this]() { getCertificateDetails(); }, Qt::QueuedConnection); } else { finished(); } } NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand() : NewOpenPGPCertificateCommand(nullptr, nullptr) { } NewOpenPGPCertificateCommand::NewOpenPGPCertificateCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { } NewOpenPGPCertificateCommand::~NewOpenPGPCertificateCommand() = default; void NewOpenPGPCertificateCommand::doStart() { d->getCertificateDetails(); } void NewOpenPGPCertificateCommand::doCancel() { if (d->detailsDialog) { d->detailsDialog->close(); } if (d->job) { d->job->slotCancel(); } } #undef d #undef q #include "moc_newopenpgpcertificatecommand.cpp" diff --git a/src/commands/refreshcertificatecommand.cpp b/src/commands/refreshcertificatecommand.cpp index 46af5f0eb..d0eef4bf7 100644 --- a/src/commands/refreshcertificatecommand.cpp +++ b/src/commands/refreshcertificatecommand.cpp @@ -1,389 +1,390 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/refreshcertificatecommand.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "command_p.h" #include "refreshcertificatecommand.h" #include #include #include #include #include #include #include #if QGPGME_SUPPORTS_WKD_REFRESH_JOB #include #endif #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; class RefreshCertificateCommand::Private : public Command::Private { friend class ::RefreshCertificateCommand; RefreshCertificateCommand *q_func() const { return static_cast(q); } public: explicit Private(RefreshCertificateCommand *qq); ~Private() override; void start(); void cancel(); std::unique_ptr startReceiveKeysJob(); std::unique_ptr startSMIMEJob(); #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr startWKDRefreshJob(); #endif void onReceiveKeysJobResult(const ImportResult &result); void onWKDRefreshJobResult(const ImportResult &result); void onSMIMEJobResult(const Error &err); void showOpenPGPResult(); void showError(const Error &err); private: Key key; QPointer job; ImportResult receiveKeysResult; ImportResult wkdRefreshResult; }; RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func() { return static_cast(d.get()); } const RefreshCertificateCommand::Private *RefreshCertificateCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() RefreshCertificateCommand::Private::Private(RefreshCertificateCommand *qq) : Command::Private{qq} { } RefreshCertificateCommand::Private::~Private() = default; namespace { Key getKey(const std::vector &keys) { if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Expected exactly one key, but got" << keys.size(); return {}; } const Key key = keys.front(); if (key.protocol() == GpgME::UnknownProtocol) { qCWarning(KLEOPATRA_LOG) << "Key has unknown protocol"; return {}; } return key; } } void RefreshCertificateCommand::Private::start() { key = getKey(keys()); if (key.isNull()) { finished(); return; } std::unique_ptr refreshJob; switch (key.protocol()) { case GpgME::OpenPGP: refreshJob = startReceiveKeysJob(); break; case GpgME::CMS: refreshJob = startSMIMEJob(); break; default:; // cannot happen ;-) } if (!refreshJob) { finished(); return; } job = refreshJob.release(); } void RefreshCertificateCommand::Private::cancel() { if (job) { job->slotCancel(); } job.clear(); } std::unique_ptr RefreshCertificateCommand::Private::startReceiveKeysJob() { std::unique_ptr refreshJob{QGpgME::openpgp()->receiveKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::ReceiveKeysJob::result, q, [this](const GpgME::ImportResult &result) { onReceiveKeysJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); const GpgME::Error err = refreshJob->start({QString::fromLatin1(key.primaryFingerprint())}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } std::unique_ptr RefreshCertificateCommand::Private::startSMIMEJob() { std::unique_ptr refreshJob{QGpgME::smime()->refreshKeysJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::RefreshKeysJob::result, q, [this](const GpgME::Error &err) { onSMIMEJobResult(err); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); const GpgME::Error err = refreshJob->start({key}); if (err) { showError(err); return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating certificate...")); return refreshJob; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr RefreshCertificateCommand::Private::startWKDRefreshJob() { if (!Settings{}.queryWKDsForAllUserIDs()) { // check if key is eligible for WKD refresh, i.e. if any user ID has WKD as origin const auto userIds = key.userIDs(); const auto eligibleForWKDRefresh = std::any_of(userIds.begin(), userIds.end(), [](const auto &userId) { return !userId.isRevoked() && !userId.addrSpec().empty() && userId.origin() == Key::OriginWKD; }); if (!eligibleForWKDRefresh) { wkdRefreshResult = ImportResult{Error::fromCode(GPG_ERR_USER_1)}; return {}; } } std::unique_ptr refreshJob{QGpgME::openpgp()->wkdRefreshJob()}; Q_ASSERT(refreshJob); connect(refreshJob.get(), &QGpgME::WKDRefreshJob::result, q, [this](const GpgME::ImportResult &result) { onWKDRefreshJobResult(result); }); connect(refreshJob.get(), &QGpgME::Job::jobProgress, q, &Command::progress); Error err; if (Settings{}.queryWKDsForAllUserIDs()) { err = refreshJob->start(key.userIDs()); } else { err = refreshJob->start({key}); } if (err) { wkdRefreshResult = ImportResult{err}; return {}; } Q_EMIT q->info(i18nc("@info:status", "Updating key...")); return refreshJob; } #endif namespace { static auto informationOnChanges(const ImportResult &result) { QString text; // if additional keys have been retrieved via WKD, then most of the below // details are just a guess and may concern the additional keys instead of // the refresh keys; this could only be clarified by a thorough comparison of // unrefreshed and refreshed key if (result.numUnchanged() == result.numConsidered()) { // if numUnchanged < numConsidered, then it is not clear whether the refreshed key // hasn't changed or whether another key retrieved via WKD hasn't changed text = i18n("The key hasn't changed."); } else if (result.newRevocations() > 0) { // it is possible that a revoked key has been newly imported via WKD, // but it is much more likely that the refreshed key was revoked text = i18n("The key has been revoked."); } else { // it doesn't make much sense to list below details if the key has been revoked text = i18n("The key has been updated."); QStringList details; if (result.newUserIDs() > 0) { details.push_back(i18n("New user IDs: %1", result.newUserIDs())); } if (result.newSubkeys() > 0) { details.push_back(i18n("New subkeys: %1", result.newSubkeys())); } if (result.newSignatures() > 0) { details.push_back(i18n("New signatures: %1", result.newSignatures())); } if (!details.empty()) { - text += QLatin1String{"

    "} + details.join(QLatin1String{"
    "}); + text += QLatin1StringView{"

    "} + details.join(QLatin1String{"
    "}); } } - text = QLatin1String{"

    "} + text + QLatin1String{"

    "}; + text = QLatin1StringView{"

    "} + text + QLatin1String{"

    "}; if (result.numImported() > 0) { - text += QLatin1String{"

    "} + text += QLatin1StringView{"

    "} + i18np("Additionally, one new key has been retrieved.", "Additionally, %1 new keys have been retrieved.", result.numImported()) - + QLatin1String{"

    "}; + + QLatin1StringView{"

    "}; } return text; } } void RefreshCertificateCommand::Private::onReceiveKeysJobResult(const ImportResult &result) { receiveKeysResult = result; if (result.error().isCanceled()) { finished(); return; } #if QGPGME_SUPPORTS_WKD_REFRESH_JOB std::unique_ptr refreshJob = startWKDRefreshJob(); if (!refreshJob) { showOpenPGPResult(); return; } job = refreshJob.release(); #else if (result.error()) { showError(result.error()); } else { information(informationOnChanges(result), i18nc("@title:window", "Key Updated")); } finished(); #endif } void RefreshCertificateCommand::Private::onWKDRefreshJobResult(const ImportResult &result) { wkdRefreshResult = result; showOpenPGPResult(); } void RefreshCertificateCommand::Private::onSMIMEJobResult(const Error &err) { if (err) { showError(err); finished(); return; } if (!err.isCanceled()) { information(i18nc("@info", "The certificate has been updated."), i18nc("@title:window", "Certificate Updated")); } finished(); } void RefreshCertificateCommand::Private::showOpenPGPResult() { if (wkdRefreshResult.error().code() == GPG_ERR_USER_1 || wkdRefreshResult.error().isCanceled()) { if (receiveKeysResult.error()) { showError(receiveKeysResult.error()); } else { information(informationOnChanges(receiveKeysResult), i18nc("@title:window", "Key Updated")); } finished(); return; } if (receiveKeysResult.error() && wkdRefreshResult.error()) { error(xi18nc("@info", "Updating the certificate from a keyserver, an LDAP server, or Active Directory failed:" "%1" "Updating the certificate via Web Key Directory failed:" "%2", Formatting::errorAsString(receiveKeysResult.error()), Formatting::errorAsString(wkdRefreshResult.error())), i18nc("@title:window", "Update Failed")); finished(); return; } QString text; - text += QLatin1String{"

    "} + i18nc("@info", "Result of update from keyserver, LDAP server, or Active Directory") + QLatin1String{"

    "}; + text += + QLatin1StringView{"

    "} + i18nc("@info", "Result of update from keyserver, LDAP server, or Active Directory") + QLatin1String{"

    "}; if (receiveKeysResult.error()) { text += xi18nc("@info", "The update failed: %1", Formatting::errorAsString(receiveKeysResult.error())); } else { text += informationOnChanges(receiveKeysResult); } - text += QLatin1String{"

    "} + i18nc("@info", "Result of update via Web Key Directory") + QLatin1String{"

    "}; + text += QLatin1StringView{"

    "} + i18nc("@info", "Result of update via Web Key Directory") + QLatin1String{"

    "}; if (wkdRefreshResult.error()) { text += xi18nc("@info", "The update failed: %1", Formatting::errorAsString(wkdRefreshResult.error())); } else { text += informationOnChanges(wkdRefreshResult); } information(text, i18nc("@title:window", "Key Updated")); finished(); } void RefreshCertificateCommand::Private::showError(const Error &err) { error(xi18nc("@info", "An error occurred while updating the certificate:" "%1", Formatting::errorAsString(err)), i18nc("@title:window", "Update Failed")); } RefreshCertificateCommand::RefreshCertificateCommand(const GpgME::Key &key) : Command{key, new Private{this}} { } RefreshCertificateCommand::~RefreshCertificateCommand() = default; void RefreshCertificateCommand::doStart() { d->start(); } void RefreshCertificateCommand::doCancel() { d->cancel(); } #undef d #undef q #include "moc_refreshcertificatecommand.cpp" diff --git a/src/conf/dirservconfigpage.cpp b/src/conf/dirservconfigpage.cpp index 561599687..388ea46b7 100644 --- a/src/conf/dirservconfigpage.cpp +++ b/src/conf/dirservconfigpage.cpp @@ -1,476 +1,476 @@ /* -*- mode: c++; c-basic-offset:4 -*- conf/dirservconfigpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2004, 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "dirservconfigpage.h" #include "labelledwidget.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace QGpgME; // Option for configuring X.509 servers (available via gpgconf since GnuPG 2.3.5 and 2.2.34) static const char s_x509services_componentName[] = "dirmngr"; static const char s_x509services_entryName[] = "ldapserver"; // Legacy option for configuring X.509 servers (deprecated with GnuPG 2.2.28 and 2.3.2) static const char s_x509services_legacy_componentName[] = "gpgsm"; static const char s_x509services_legacy_entryName[] = "keyserver"; static const char s_pgpservice_componentName[] = "dirmngr"; static const char s_pgpservice_entryName[] = "keyserver"; // legacy config entry used until GnuPG 2.2 static const char s_pgpservice_legacy_componentName[] = "gpg"; static const char s_pgpservice_legacy_entryName[] = "keyserver"; static const char s_timeout_componentName[] = "dirmngr"; static const char s_timeout_entryName[] = "ldaptimeout"; static const char s_maxitems_componentName[] = "dirmngr"; static const char s_maxitems_entryName[] = "max-replies"; class DirectoryServicesConfigurationPage::Private { DirectoryServicesConfigurationPage *q = nullptr; public: Private(DirectoryServicesConfigurationPage *q); void load(); void save(); void defaults(); private: enum EntryMultiplicity { SingleValue, ListValue, }; enum ShowError { DoNotShowError, DoShowError, }; void setX509ServerEntry(const std::vector &servers); void load(const Kleo::Settings &settings); QGpgME::CryptoConfigEntry *configEntry(const char *componentName, const char *entryName, QGpgME::CryptoConfigEntry::ArgType argType, EntryMultiplicity multiplicity, ShowError showError); Kleo::LabelledWidget mOpenPGPKeyserverEdit; Kleo::DirectoryServicesWidget *mDirectoryServices = nullptr; Kleo::LabelledWidget mTimeout; Kleo::LabelledWidget mMaxItems; QCheckBox *mFetchMissingSignerKeysCB = nullptr; QCheckBox *mQueryWKDsForAllUserIDsCB = nullptr; QGpgME::CryptoConfigEntry *mOpenPGPServiceEntry = nullptr; QGpgME::CryptoConfigEntry *mTimeoutConfigEntry = nullptr; QGpgME::CryptoConfigEntry *mMaxItemsConfigEntry = nullptr; QGpgME::CryptoConfig *mConfig = nullptr; }; DirectoryServicesConfigurationPage::Private::Private(DirectoryServicesConfigurationPage *q) { mConfig = QGpgME::cryptoConfig(); auto glay = new QGridLayout(q->widget()); // OpenPGP keyserver int row = 0; { auto l = new QHBoxLayout{}; l->setContentsMargins(0, 0, 0, 0); mOpenPGPKeyserverEdit.createWidgets(q->widget()); mOpenPGPKeyserverEdit.label()->setText(i18n("OpenPGP keyserver:")); if (engineIsVersion(2, 4, 4) // || (engineIsVersion(2, 2, 42) && !engineIsVersion(2, 3, 0))) { mOpenPGPKeyserverEdit.widget()->setToolTip( // xi18nc("@info:tooltip", "Enter the address of the keyserver to use when searching for OpenPGP certificates and " "when uploading OpenPGP certificates. If you do not enter an address then an internal " "default will be used. To disable the use of an OpenPGP keyserver enter the special value none.")); } l->addWidget(mOpenPGPKeyserverEdit.label()); l->addWidget(mOpenPGPKeyserverEdit.widget()); glay->addLayout(l, row, 0, 1, 3); connect(mOpenPGPKeyserverEdit.widget(), &QLineEdit::textEdited, q, &DirectoryServicesConfigurationPage::markAsChanged); } // X.509 servers if (Settings{}.cmsEnabled()) { ++row; auto groupBox = new QGroupBox{i18n("X.509 Directory Services"), q->widget()}; groupBox->setFlat(true); auto groupBoxLayout = new QVBoxLayout{groupBox}; groupBoxLayout->setContentsMargins({}); if (gpgme_check_version("1.16.0")) { mDirectoryServices = new Kleo::DirectoryServicesWidget(q->widget()); if (QLayout *l = mDirectoryServices->layout()) { l->setContentsMargins(0, 0, 0, 0); } groupBoxLayout->addWidget(mDirectoryServices); connect(mDirectoryServices, &DirectoryServicesWidget::changed, q, &DirectoryServicesConfigurationPage::markAsChanged); } else { // QGpgME does not properly support keyserver flags for X.509 keyservers (added in GnuPG 2.2.28); // disable the configuration to prevent the configuration from being corrupted groupBoxLayout->addWidget(new QLabel{i18n("Configuration of directory services is not possible " "because the used gpgme libraries are too old."), q->widget()}); } glay->addWidget(groupBox, row, 0, 1, 3); } // LDAP timeout ++row; mTimeout.createWidgets(q->widget()); mTimeout.label()->setText(i18n("LDAP &timeout (minutes:seconds):")); mTimeout.widget()->setDisplayFormat(QStringLiteral("mm:ss")); connect(mTimeout.widget(), &QTimeEdit::timeChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mTimeout.label(), row, 0); glay->addWidget(mTimeout.widget(), row, 1); // Max number of items returned by queries ++row; mMaxItems.createWidgets(q->widget()); mMaxItems.label()->setText(i18n("&Maximum number of items returned by query:")); mMaxItems.widget()->setMinimum(0); connect(mMaxItems.widget(), &QSpinBox::valueChanged, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mMaxItems.label(), row, 0); glay->addWidget(mMaxItems.widget(), row, 1); ++row; mFetchMissingSignerKeysCB = new QCheckBox{q->widget()}; mFetchMissingSignerKeysCB->setText(i18nc("@option:check", "Retrieve missing certification keys when importing new keys")); mFetchMissingSignerKeysCB->setToolTip(xi18nc("@info:tooltip", "If enabled, then Kleopatra will automatically try to retrieve the keys " "that were used to certify the user IDs of newly imported OpenPGP keys.")); connect(mFetchMissingSignerKeysCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mFetchMissingSignerKeysCB, row, 0, 1, 3); ++row; mQueryWKDsForAllUserIDsCB = new QCheckBox{q->widget()}; mQueryWKDsForAllUserIDsCB->setText(i18nc("@option:check", "Query certificate directories of providers for all user IDs")); mQueryWKDsForAllUserIDsCB->setToolTip(xi18nc("@info:tooltip", "By default, Kleopatra only queries the certificate directories of providers (WKD) " "for user IDs that were originally retrieved from a WKD when you update an OpenPGP " "certificate. If this option is enabled, then Kleopatra will query WKDs for all user IDs.")); connect(mQueryWKDsForAllUserIDsCB, &QCheckBox::toggled, q, &DirectoryServicesConfigurationPage::markAsChanged); glay->addWidget(mQueryWKDsForAllUserIDsCB, row, 0, 1, 3); glay->setRowStretch(++row, 1); glay->setColumnStretch(2, 1); } static auto readKeyserverConfigs(const CryptoConfigEntry *configEntry) { std::vector servers; if (configEntry) { const auto urls = configEntry->urlValueList(); servers.reserve(urls.size()); std::transform(std::begin(urls), std::end(urls), std::back_inserter(servers), &KeyserverConfig::fromUrl); } return servers; } void DirectoryServicesConfigurationPage::Private::load(const Kleo::Settings &settings) { if (mDirectoryServices) { mDirectoryServices->clear(); // gpgsm uses the deprecated keyserver option in gpgsm.conf additionally to the ldapserver option in dirmngr.conf; // we (try to) read servers from both entries, but always write to the newest existing entry const auto *const newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); const auto *const legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); auto entry = newEntry ? newEntry : legacyEntry; if (entry) { const auto additionalServers = readKeyserverConfigs(legacyEntry); auto servers = readKeyserverConfigs(newEntry); std::copy(std::begin(additionalServers), std::end(additionalServers), std::back_inserter(servers)); mDirectoryServices->setKeyservers(servers); mDirectoryServices->setReadOnly(entry->isReadOnly()); } else { qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; mDirectoryServices->setDisabled(true); } } { // gpg prefers the deprecated keyserver option in gpg.conf over the keyserver option in dirmngr.conf; // therefore, we use the deprecated keyserver option if it is set or if the new option doesn't exist (gpg < 2.1.9) auto const newEntry = configEntry(s_pgpservice_componentName, s_pgpservice_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); auto const legacyEntry = configEntry(s_pgpservice_legacy_componentName, s_pgpservice_legacy_entryName, CryptoConfigEntry::ArgType_String, SingleValue, DoNotShowError); mOpenPGPServiceEntry = ((legacyEntry && legacyEntry->isSet()) || !newEntry) ? legacyEntry : newEntry; if (!mOpenPGPServiceEntry) { qCWarning(KLEOPATRA_LOG) << "Unknown or wrong typed config entries" << s_pgpservice_componentName << "/" << s_pgpservice_entryName << "and" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; } else if (mOpenPGPServiceEntry == legacyEntry) { qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_legacy_componentName << "/" << s_pgpservice_legacy_entryName; } else { qCDebug(KLEOPATRA_LOG) << "Using config entry" << s_pgpservice_componentName << "/" << s_pgpservice_entryName; } mOpenPGPKeyserverEdit.widget()->setText(mOpenPGPServiceEntry && mOpenPGPServiceEntry->isSet() ? mOpenPGPServiceEntry->stringValue() : QString()); mOpenPGPKeyserverEdit.setEnabled(mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()); if (newEntry && !newEntry->defaultValue().isNull()) { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(newEntry->defaultValue().toString()); } else { if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.16") { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkp://keys.gnupg.net")); } else { mOpenPGPKeyserverEdit.widget()->setPlaceholderText(QStringLiteral("hkps://hkps.pool.sks-keyservers.net")); } } } // read LDAP timeout // first try to read the config entry as int (GnuPG 2.3) mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); if (!mTimeoutConfigEntry) { // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) mTimeoutConfigEntry = configEntry(s_timeout_componentName, s_timeout_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); } if (mTimeoutConfigEntry) { const int ldapTimeout = mTimeoutConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mTimeoutConfigEntry->intValue() : static_cast(mTimeoutConfigEntry->uintValue()); const QTime time = QTime(0, 0, 0, 0).addSecs(ldapTimeout); // qCDebug(KLEOPATRA_LOG) <<"timeout:" << mTimeoutConfigEntry->uintValue() <<" ->" << time; mTimeout.widget()->setTime(time); } mTimeout.setEnabled(mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()); // read max-replies config entry // first try to read the config entry as int (GnuPG 2.3) mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_Int, SingleValue, DoNotShowError); if (!mMaxItemsConfigEntry) { // if this fails, then try to read the config entry as unsigned int (GnuPG <= 2.2) mMaxItemsConfigEntry = configEntry(s_maxitems_componentName, s_maxitems_entryName, CryptoConfigEntry::ArgType_UInt, SingleValue, DoShowError); } if (mMaxItemsConfigEntry) { const int value = mMaxItemsConfigEntry->argType() == CryptoConfigEntry::ArgType_Int ? mMaxItemsConfigEntry->intValue() : static_cast(mMaxItemsConfigEntry->uintValue()); mMaxItems.widget()->blockSignals(true); // KNumInput emits valueChanged from setValue! mMaxItems.widget()->setValue(value); mMaxItems.widget()->blockSignals(false); } mMaxItems.setEnabled(mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()); mFetchMissingSignerKeysCB->setChecked(settings.retrieveSignerKeysAfterImport()); mFetchMissingSignerKeysCB->setEnabled(!settings.isImmutable(QStringLiteral("RetrieveSignerKeysAfterImport"))); mQueryWKDsForAllUserIDsCB->setChecked(settings.queryWKDsForAllUserIDs()); mQueryWKDsForAllUserIDsCB->setEnabled(!settings.isImmutable(QStringLiteral("QueryWKDsForAllUserIDs"))); } void DirectoryServicesConfigurationPage::Private::load() { load(Settings{}); } namespace { void updateIntegerConfigEntry(QGpgME::CryptoConfigEntry *configEntry, int value) { if (!configEntry) { return; } if (configEntry->argType() == CryptoConfigEntry::ArgType_Int) { if (configEntry->intValue() != value) { configEntry->setIntValue(value); } } else { const auto newValue = static_cast(value); if (configEntry->uintValue() != newValue) { configEntry->setUIntValue(newValue); } } } } void DirectoryServicesConfigurationPage::Private::setX509ServerEntry(const std::vector &servers) { const auto newEntry = configEntry(s_x509services_componentName, s_x509services_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); const auto legacyEntry = configEntry(s_x509services_legacy_componentName, s_x509services_legacy_entryName, CryptoConfigEntry::ArgType_LDAPURL, ListValue, DoNotShowError); if ((newEntry && newEntry->isReadOnly()) || (legacyEntry && legacyEntry->isReadOnly())) { // do not change the config entries if either config entry is read-only return; } QList urls; urls.reserve(servers.size()); std::transform(std::begin(servers), std::end(servers), std::back_inserter(urls), std::mem_fn(&KeyserverConfig::toUrl)); if (newEntry) { // write all servers to the new config entry newEntry->setURLValueList(urls); // and clear the legacy config entry if (legacyEntry) { legacyEntry->setURLValueList({}); } } else if (legacyEntry) { // write all servers to the legacy config entry if the new entry is not available legacyEntry->setURLValueList(urls); } else { qCWarning(KLEOPATRA_LOG) << "Could not store the X.509 servers. Unknown or wrong typed config entries" << s_x509services_componentName << "/" << s_x509services_entryName << "and" << s_x509services_legacy_componentName << "/" << s_x509services_legacy_entryName; } } void DirectoryServicesConfigurationPage::Private::save() { if (mDirectoryServices && mDirectoryServices->isEnabled()) { setX509ServerEntry(mDirectoryServices->keyservers()); } if (mOpenPGPServiceEntry) { const auto keyserver = mOpenPGPKeyserverEdit.widget()->text().trimmed(); if (keyserver.isEmpty()) { mOpenPGPServiceEntry->resetToDefault(); - } else if (keyserver == QLatin1String{"none"}) { + } else if (keyserver == QLatin1StringView{"none"}) { mOpenPGPServiceEntry->setStringValue(keyserver); } else { - const auto keyserverUrl = keyserver.contains(QLatin1String{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver); + const auto keyserverUrl = keyserver.contains(QLatin1StringView{"://"}) ? keyserver : (QLatin1String{"hkps://"} + keyserver); mOpenPGPServiceEntry->setStringValue(keyserverUrl); } } const QTime time{mTimeout.widget()->time()}; updateIntegerConfigEntry(mTimeoutConfigEntry, time.minute() * 60 + time.second()); updateIntegerConfigEntry(mMaxItemsConfigEntry, mMaxItems.widget()->value()); mConfig->sync(true); Settings settings; settings.setRetrieveSignerKeysAfterImport(mFetchMissingSignerKeysCB->isChecked()); settings.setQueryWKDsForAllUserIDs(mQueryWKDsForAllUserIDsCB->isChecked()); settings.save(); } void DirectoryServicesConfigurationPage::Private::defaults() { // these guys don't have a default, to clear them: if (mDirectoryServices && mDirectoryServices->isEnabled()) { setX509ServerEntry({}); } if (mOpenPGPServiceEntry && !mOpenPGPServiceEntry->isReadOnly()) { mOpenPGPServiceEntry->setStringValue(QString()); } // these presumably have a default, use that one: if (mTimeoutConfigEntry && !mTimeoutConfigEntry->isReadOnly()) { mTimeoutConfigEntry->resetToDefault(); } if (mMaxItemsConfigEntry && !mMaxItemsConfigEntry->isReadOnly()) { mMaxItemsConfigEntry->resetToDefault(); } Settings settings; settings.setRetrieveSignerKeysAfterImport(settings.findItem(QStringLiteral("RetrieveSignerKeysAfterImport"))->getDefault().toBool()); settings.setQueryWKDsForAllUserIDs(settings.findItem(QStringLiteral("QueryWKDsForAllUserIDs"))->getDefault().toBool()); load(settings); } // Find config entry for ldap servers. Implements runtime checks on the configuration option. CryptoConfigEntry *DirectoryServicesConfigurationPage::Private::configEntry(const char *componentName, const char *entryName, CryptoConfigEntry::ArgType argType, EntryMultiplicity multiplicity, ShowError showError) { CryptoConfigEntry *const entry = Kleo::getCryptoConfigEntry(mConfig, componentName, entryName); if (!entry) { if (showError == DoShowError) { KMessageBox::error( q->widget(), - i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1String(componentName), QLatin1String(entryName))); + i18n("Backend error: gpgconf does not seem to know the entry for %1/%2", QLatin1StringView(componentName), QLatin1String(entryName))); } return nullptr; } if (entry->argType() != argType || entry->isList() != bool(multiplicity)) { if (showError == DoShowError) { KMessageBox::error(q->widget(), i18n("Backend error: gpgconf has wrong type for %1/%2: %3 %4", - QLatin1String(componentName), - QLatin1String(entryName), + QLatin1StringView(componentName), + QLatin1StringView(entryName), entry->argType(), entry->isList())); } return nullptr; } return entry; } DirectoryServicesConfigurationPage::DirectoryServicesConfigurationPage(QObject *parent, const KPluginMetaData &data) : KCModule(parent, data) , d{new Private{this}} { } DirectoryServicesConfigurationPage::~DirectoryServicesConfigurationPage() = default; void DirectoryServicesConfigurationPage::load() { d->load(); } void DirectoryServicesConfigurationPage::save() { d->save(); } void DirectoryServicesConfigurationPage::defaults() { d->defaults(); } #include "moc_dirservconfigpage.cpp" diff --git a/src/conf/groupsconfigdialog.cpp b/src/conf/groupsconfigdialog.cpp index b5628fc11..64cf2c4dc 100644 --- a/src/conf/groupsconfigdialog.cpp +++ b/src/conf/groupsconfigdialog.cpp @@ -1,167 +1,167 @@ /* conf/groupsconfigdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "groupsconfigdialog.h" #include "groupsconfigwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; class GroupsConfigDialog::Private { friend class ::GroupsConfigDialog; GroupsConfigDialog *const q; public: Private(GroupsConfigDialog *qq) : q(qq) { } private: void saveLayout(); void restoreLayout(const QSize &defaultSize = {}); void loadGroups(); void saveGroups(); void onKeysMayHaveChanged(); private: GroupsConfigWidget *configWidget = nullptr; bool savingChanges = false; }; void GroupsConfigDialog::Private::saveLayout() { - KConfigGroup configGroup(KSharedConfig::openStateConfig(), QLatin1String("GroupsConfigDialog")); + KConfigGroup configGroup(KSharedConfig::openStateConfig(), QLatin1StringView("GroupsConfigDialog")); configGroup.writeEntry("Size", q->size()); configGroup.sync(); } void GroupsConfigDialog::Private::restoreLayout(const QSize &defaultSize) { - const KConfigGroup configGroup(KSharedConfig::openStateConfig(), QLatin1String("GroupsConfigDialog")); + const KConfigGroup configGroup(KSharedConfig::openStateConfig(), QLatin1StringView("GroupsConfigDialog")); const QSize size = configGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } } void GroupsConfigDialog::Private::loadGroups() { qCDebug(KLEOPATRA_LOG) << q << __func__; configWidget->setGroups(KeyCache::instance()->configurableGroups()); } void GroupsConfigDialog::Private::saveGroups() { qCDebug(KLEOPATRA_LOG) << q << __func__; savingChanges = true; KeyCache::mutableInstance()->saveConfigurableGroups(configWidget->groups()); savingChanges = false; // reload after saving to ensure that the groups reflect the saved groups (e.g. in case of immutable entries) loadGroups(); } void GroupsConfigDialog::Private::onKeysMayHaveChanged() { if (savingChanges) { qCDebug(KLEOPATRA_LOG) << __func__ << "ignoring changes caused by ourselves"; return; } qCDebug(KLEOPATRA_LOG) << "Reloading groups"; loadGroups(); } GroupsConfigDialog::GroupsConfigDialog(QWidget *parent) : QDialog(parent) , d(new Private(this)) { setWindowTitle(i18nc("@title:window", "Configure Groups")); auto mainLayout = new QVBoxLayout{this}; auto scrollArea = new ScrollArea{this}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins({}); d->configWidget = new GroupsConfigWidget{this}; d->configWidget->setContentsMargins({}); scrollAreaLayout->addWidget(d->configWidget); mainLayout->addWidget(scrollArea); auto buttonBox = new QDialogButtonBox{QDialogButtonBox::Close, this}; mainLayout->addWidget(buttonBox); auto helpAction = std::make_unique( QIcon::fromTheme(QStringLiteral("help")), i18n("Help"), i18nc("Only available in German and English. Leave to English for other languages.", "handout_group-feature_gnupg_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), this); if (helpAction->isEnabled()) { auto helpButton = buttonBox->addButton(QDialogButtonBox::Help); disconnect(helpButton, &QAbstractButton::clicked, nullptr, nullptr); connect(helpButton, &QAbstractButton::clicked, helpAction.get(), &QAction::trigger); helpAction.release(); } // prevent accidental closing of dialog when pressing Enter while the search field has focus Kleo::unsetAutoDefaultButtons(this); // Close button (defined with RejectRole) should close the dialog connect(buttonBox, &QDialogButtonBox::rejected, this, &QDialog::accept); connect(d->configWidget, &GroupsConfigWidget::changed, this, [this]() { d->saveGroups(); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this]() { d->onKeysMayHaveChanged(); }); d->restoreLayout(); d->loadGroups(); } GroupsConfigDialog::~GroupsConfigDialog() { d->saveLayout(); } #include "moc_groupsconfigdialog.cpp" diff --git a/src/conf/kleopageconfigdialog.cpp b/src/conf/kleopageconfigdialog.cpp index bc9886ee9..13c3a272c 100644 --- a/src/conf/kleopageconfigdialog.cpp +++ b/src/conf/kleopageconfigdialog.cpp @@ -1,239 +1,239 @@ /* kleopageconfigdialog.cpp This file is part of Kleopatra SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-only It is derived from KCMultidialog which is: SPDX-FileCopyrightText: 2000 Matthias Elter SPDX-FileCopyrightText: 2003 Daniel Molkentin SPDX-FileCopyrightText: 2003, 2006 Matthias Kretz SPDX-FileCopyrightText: 2004 Frans Englich SPDX-FileCopyrightText: 2006 Tobias Koenig SPDX-License-Identifier: LGPL-2.0-or-later */ #include #include "kleopageconfigdialog.h" #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" KleoPageConfigDialog::KleoPageConfigDialog(QWidget *parent) : KPageDialog(parent) { setModal(false); QDialogButtonBox *buttonBox = new QDialogButtonBox(this); buttonBox->setStandardButtons(QDialogButtonBox::Help // | QDialogButtonBox::RestoreDefaults // | QDialogButtonBox::Cancel // | QDialogButtonBox::Apply // | QDialogButtonBox::Ok // | QDialogButtonBox::Reset); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Ok), KStandardGuiItem::ok()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Cancel), KStandardGuiItem::cancel()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::RestoreDefaults), KStandardGuiItem::defaults()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Apply), KStandardGuiItem::apply()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Reset), KStandardGuiItem::reset()); KGuiItem::assign(buttonBox->button(QDialogButtonBox::Help), KStandardGuiItem::help()); buttonBox->button(QDialogButtonBox::Reset)->setEnabled(false); buttonBox->button(QDialogButtonBox::Apply)->setEnabled(false); connect(buttonBox->button(QDialogButtonBox::Apply), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotApplyClicked); connect(buttonBox->button(QDialogButtonBox::Ok), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotOkClicked); connect(buttonBox->button(QDialogButtonBox::RestoreDefaults), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotDefaultClicked); connect(buttonBox->button(QDialogButtonBox::Help), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotHelpClicked); connect(buttonBox->button(QDialogButtonBox::Reset), &QAbstractButton::clicked, this, &KleoPageConfigDialog::slotUser1Clicked); setButtonBox(buttonBox); connect(this, &KPageDialog::currentPageChanged, this, &KleoPageConfigDialog::slotCurrentPageChanged); } void KleoPageConfigDialog::slotCurrentPageChanged(KPageWidgetItem *current, KPageWidgetItem *previous) { if (!previous) { return; } blockSignals(true); setCurrentPage(previous); KCModule *previousModule = qobject_cast(previous->widget()); bool canceled = false; if (previousModule && mChangedModules.contains(previousModule)) { const int queryUser = KMessageBox::warningTwoActionsCancel(this, i18n("The settings of the current module have changed.\n" "Do you want to apply the changes or discard them?"), i18nc("@title:window", "Apply Settings"), KStandardGuiItem::apply(), KStandardGuiItem::discard(), KStandardGuiItem::cancel()); if (queryUser == KMessageBox::ButtonCode::PrimaryAction) { previousModule->save(); } else if (queryUser == KMessageBox::ButtonCode::SecondaryAction) { previousModule->load(); } canceled = queryUser == KMessageBox::Cancel; } if (!canceled) { mChangedModules.removeAll(previousModule); setCurrentPage(current); } blockSignals(false); clientChanged(); } void KleoPageConfigDialog::apply() { QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply); applyButton->setFocus(); for (KCModule *module : mChangedModules) { module->save(); } mChangedModules.clear(); Q_EMIT configCommitted(); clientChanged(); } void KleoPageConfigDialog::slotDefaultClicked() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } KCModule *module = qobject_cast(item->widget()); if (!module) { return; } module->defaults(); clientChanged(); } void KleoPageConfigDialog::slotUser1Clicked() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } KCModule *module = qobject_cast(item->widget()); if (!module) { return; } module->load(); mChangedModules.removeAll(module); clientChanged(); } void KleoPageConfigDialog::slotApplyClicked() { apply(); } void KleoPageConfigDialog::slotOkClicked() { apply(); accept(); } void KleoPageConfigDialog::slotHelpClicked() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } const QString docPath = mHelpUrls.value(item->name()); QUrl docUrl; #ifdef Q_OS_WIN - docUrl = QUrl(QLatin1String("https://docs.kde.org/index.php?branch=stable5&language=") + QLocale().name() + QLatin1String("&application=kleopatra")); + docUrl = QUrl(QLatin1StringView("https://docs.kde.org/index.php?branch=stable5&language=") + QLocale().name() + QLatin1String("&application=kleopatra")); #else docUrl = QUrl(QStringLiteral("help:/")).resolved(QUrl(docPath)); // same code as in KHelpClient::invokeHelp #endif - if (docUrl.scheme() == QLatin1String("help") || docUrl.scheme() == QLatin1String("man") || docUrl.scheme() == QLatin1String("info")) { + if (docUrl.scheme() == QLatin1StringView("help") || docUrl.scheme() == QLatin1String("man") || docUrl.scheme() == QLatin1String("info")) { // Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process. QProcess::startDetached(QStringLiteral("khelpcenter"), QStringList() << docUrl.toString()); } else { QDesktopServices::openUrl(docUrl); } } void KleoPageConfigDialog::addModule(const QString &name, const QString &docPath, const QString &icon, KCModule *module) { mModules << module; KPageWidgetItem *item = addPage(module->widget(), name); item->setIcon(QIcon::fromTheme(icon)); connect(module, &KCModule::needsSaveChanged, this, [this, module]() { moduleChanged(module->needsSave()); }); mHelpUrls.insert(name, docPath); } void KleoPageConfigDialog::moduleChanged(bool state) { KCModule *module = qobject_cast(sender()); qCDebug(KLEOPATRA_LOG) << "Module changed: " << state << " mod " << module; if (mChangedModules.contains(module)) { if (!state) { mChangedModules.removeAll(module); } else { return; } } if (state) { mChangedModules << module; } clientChanged(); } void KleoPageConfigDialog::clientChanged() { const KPageWidgetItem *item = currentPage(); if (!item) { return; } KCModule *module = qobject_cast(item->widget()); if (!module) { return; } qCDebug(KLEOPATRA_LOG) << "Client changed: " << " mod " << module; bool change = mChangedModules.contains(module); QPushButton *resetButton = buttonBox()->button(QDialogButtonBox::Reset); if (resetButton) { resetButton->setEnabled(change); } QPushButton *applyButton = buttonBox()->button(QDialogButtonBox::Apply); if (applyButton) { applyButton->setEnabled(change); } } #include "moc_kleopageconfigdialog.cpp" diff --git a/src/conf/smimevalidationconfigurationwidget.cpp b/src/conf/smimevalidationconfigurationwidget.cpp index 64400e113..9baea7c12 100644 --- a/src/conf/smimevalidationconfigurationwidget.cpp +++ b/src/conf/smimevalidationconfigurationwidget.cpp @@ -1,397 +1,397 @@ /* -*- mode: c++; c-basic-offset:4 -*- conf/smimevalidationconfigurationwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "smimevalidationconfigurationwidget.h" #include "ui_smimevalidationconfigurationwidget.h" #include "labelledwidget.h" #include "smimevalidationpreferences.h" #include #include #include "kleopatra_debug.h" #include #if HAVE_QDBUS #include #endif using namespace Kleo; using namespace Kleo::Config; using namespace QGpgME; class SMimeValidationConfigurationWidget::Private { friend class ::Kleo::Config::SMimeValidationConfigurationWidget; SMimeValidationConfigurationWidget *const q; public: explicit Private(SMimeValidationConfigurationWidget *qq) : q(qq) , ui(qq) { #if HAVE_QDBUS QDBusConnection::sessionBus().connect(QString(), QString(), QStringLiteral("org.kde.kleo.CryptoConfig"), QStringLiteral("changed"), q, SLOT(load())); #endif auto changedSignal = &SMimeValidationConfigurationWidget::changed; connect(ui.intervalRefreshCB, &QCheckBox::toggled, q, changedSignal); connect(ui.intervalRefreshSB, &QSpinBox::valueChanged, q, changedSignal); connect(ui.OCSPCB, &QCheckBox::toggled, q, changedSignal); connect(ui.OCSPResponderURL, &QLineEdit::textChanged, q, changedSignal); auto certRequesterSignal = &KleopatraClientCopy::Gui::CertificateRequester::selectedCertificatesChanged; connect(ui.OCSPResponderSignature, certRequesterSignal, q, changedSignal); connect(ui.doNotCheckCertPolicyCB, &QCheckBox::toggled, q, changedSignal); connect(ui.neverConsultCB, &QCheckBox::toggled, q, changedSignal); connect(ui.allowMarkTrustedCB, &QCheckBox::toggled, q, changedSignal); connect(ui.fetchMissingCB, &QCheckBox::toggled, q, changedSignal); connect(ui.ignoreServiceURLCB, &QCheckBox::toggled, q, changedSignal); connect(ui.ignoreHTTPDPCB, &QCheckBox::toggled, q, changedSignal); connect(ui.disableHTTPCB, &QCheckBox::toggled, q, changedSignal); connect(ui.honorHTTPProxyRB, &QRadioButton::toggled, q, changedSignal); connect(ui.useCustomHTTPProxyRB, &QRadioButton::toggled, q, changedSignal); connect(ui.customHTTPProxy, &QLineEdit::textChanged, q, changedSignal); connect(ui.ignoreLDAPDPCB, &QCheckBox::toggled, q, changedSignal); connect(ui.disableLDAPCB, &QCheckBox::toggled, q, changedSignal); connect(ui.customLDAPProxy, &QLineEdit::textChanged, q, changedSignal); auto enableDisableSlot = [this]() { enableDisableActions(); }; connect(ui.useCustomHTTPProxyRB, &QRadioButton::toggled, q, enableDisableSlot); connect(ui.disableHTTPCB, &QCheckBox::toggled, q, enableDisableSlot); } bool customHTTPProxyWritable = false; private: void enableDisableActions() { ui.customHTTPProxy->setEnabled(ui.useCustomHTTPProxyRB->isChecked() && !ui.disableHTTPCB->isChecked() && customHTTPProxyWritable); } private: struct UI : Ui_SMimeValidationConfigurationWidget { LabelledWidget labelledOCSPResponderSignature; LabelledWidget labelledOCSPResponderURL; explicit UI(SMimeValidationConfigurationWidget *q) : Ui_SMimeValidationConfigurationWidget() { setupUi(q); labelledOCSPResponderURL.setWidgets(OCSPResponderURL, OCSPResponderURLLabel); labelledOCSPResponderSignature.setWidgets(OCSPResponderSignature, OCSPResponderSignatureLabel); OCSPResponderSignature->setOnlyX509CertificatesAllowed(true); OCSPResponderSignature->setOnlySigningCertificatesAllowed(true); OCSPResponderSignature->setMultipleCertificatesAllowed(false); // OCSPResponderSignature->setAllowedKeys( KeySelectionDialog::TrustedKeys|KeySelectionDialog::ValidKeys ); } } ui; }; SMimeValidationConfigurationWidget::SMimeValidationConfigurationWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f) , d(new Private(this)) { } SMimeValidationConfigurationWidget::~SMimeValidationConfigurationWidget() { } static void disableDirmngrWidget(QWidget *w) { w->setEnabled(false); w->setWhatsThis(i18n("This option requires dirmngr >= 0.9.0")); } static void initializeDirmngrCheckbox(QCheckBox *cb, CryptoConfigEntry *entry) { if (entry) { cb->setChecked(entry->boolValue()); } if (!entry || entry->isReadOnly()) { disableDirmngrWidget(cb); } } struct SMIMECryptoConfigEntries { enum ShowError { DoNotShowError, DoShowError, }; SMIMECryptoConfigEntries(CryptoConfig *config) : mConfig(config) // Checkboxes , mCheckUsingOCSPConfigEntry(configEntry("gpgsm", "enable-ocsp", CryptoConfigEntry::ArgType_None)) , mEnableOCSPsendingConfigEntry(configEntry("dirmngr", "allow-ocsp", CryptoConfigEntry::ArgType_None)) , mDoNotCheckCertPolicyConfigEntry(configEntry("gpgsm", "disable-policy-checks", CryptoConfigEntry::ArgType_None)) , mNeverConsultConfigEntry(configEntry("gpgsm", "disable-crl-checks", CryptoConfigEntry::ArgType_None)) , mAllowMarkTrustedConfigEntry( configEntry("gpg-agent", "allow-mark-trusted", CryptoConfigEntry::ArgType_None, DoNotShowError)) // legacy entry -> ignore error , mFetchMissingConfigEntry(configEntry("gpgsm", "auto-issuer-key-retrieve", CryptoConfigEntry::ArgType_None)) , mNoAllowMarkTrustedConfigEntry(configEntry("gpg-agent", "no-allow-mark-trusted", CryptoConfigEntry::ArgType_None)) // dirmngr-0.9.0 options , mIgnoreServiceURLEntry(configEntry("dirmngr", "ignore-ocsp-service-url", CryptoConfigEntry::ArgType_None)) , mIgnoreHTTPDPEntry(configEntry("dirmngr", "ignore-http-dp", CryptoConfigEntry::ArgType_None)) , mDisableHTTPEntry(configEntry("dirmngr", "disable-http", CryptoConfigEntry::ArgType_None)) , mHonorHTTPProxy(configEntry("dirmngr", "honor-http-proxy", CryptoConfigEntry::ArgType_None)) , mIgnoreLDAPDPEntry(configEntry("dirmngr", "ignore-ldap-dp", CryptoConfigEntry::ArgType_None)) , mDisableLDAPEntry(configEntry("dirmngr", "disable-ldap", CryptoConfigEntry::ArgType_None)) // Other widgets , mOCSPResponderURLConfigEntry(configEntry("dirmngr", "ocsp-responder", CryptoConfigEntry::ArgType_String)) , mOCSPResponderSignature(configEntry("dirmngr", "ocsp-signer", CryptoConfigEntry::ArgType_String)) , mCustomHTTPProxy(configEntry("dirmngr", "http-proxy", CryptoConfigEntry::ArgType_String)) , mCustomLDAPProxy(configEntry("dirmngr", "ldap-proxy", CryptoConfigEntry::ArgType_String)) { } CryptoConfigEntry *configEntry(const char *componentName, const char *entryName, int argType, ShowError showError = DoShowError); CryptoConfig *const mConfig; // Checkboxes CryptoConfigEntry *const mCheckUsingOCSPConfigEntry; CryptoConfigEntry *const mEnableOCSPsendingConfigEntry; CryptoConfigEntry *const mDoNotCheckCertPolicyConfigEntry; CryptoConfigEntry *const mNeverConsultConfigEntry; CryptoConfigEntry *const mAllowMarkTrustedConfigEntry; CryptoConfigEntry *const mFetchMissingConfigEntry; // gnupg 2.0.17+ option that should inhibit allow-mark-trusted display CryptoConfigEntry *const mNoAllowMarkTrustedConfigEntry; // dirmngr-0.9.0 options CryptoConfigEntry *const mIgnoreServiceURLEntry; CryptoConfigEntry *const mIgnoreHTTPDPEntry; CryptoConfigEntry *const mDisableHTTPEntry; CryptoConfigEntry *const mHonorHTTPProxy; CryptoConfigEntry *const mIgnoreLDAPDPEntry; CryptoConfigEntry *const mDisableLDAPEntry; // Other widgets CryptoConfigEntry *const mOCSPResponderURLConfigEntry; CryptoConfigEntry *const mOCSPResponderSignature; CryptoConfigEntry *const mCustomHTTPProxy; CryptoConfigEntry *const mCustomLDAPProxy; }; void SMimeValidationConfigurationWidget::defaults() { qCDebug(KLEOPATRA_LOG) << "not implemented"; } void SMimeValidationConfigurationWidget::load() { const SMimeValidationPreferences preferences; const unsigned int refreshInterval = preferences.refreshInterval(); d->ui.intervalRefreshCB->setChecked(refreshInterval > 0); d->ui.intervalRefreshSB->setValue(refreshInterval); const bool isRefreshIntervalImmutable = preferences.isImmutable(QStringLiteral("RefreshInterval")); d->ui.intervalRefreshCB->setEnabled(!isRefreshIntervalImmutable); d->ui.intervalRefreshSB->setEnabled(!isRefreshIntervalImmutable); CryptoConfig *const config = QGpgME::cryptoConfig(); if (!config) { setEnabled(false); return; } #if 0 // crashes other pages' save() by nuking the CryptoConfigEntries under their feet. // This was probably not a problem in KMail, where this code comes // from. But here, it's fatal. // Force re-parsing gpgconf data, in case e.g. kleopatra or "configure backend" was used // (which ends up calling us via D-Bus) config->clear(); #endif // Create config entries // Don't keep them around, they'll get deleted by clear(), which could be // done by the "configure backend" button even before we save(). const SMIMECryptoConfigEntries e(config); // Initialize GUI items from the config entries if (e.mCheckUsingOCSPConfigEntry) { d->ui.OCSPCB->setChecked(e.mCheckUsingOCSPConfigEntry->boolValue()); } d->ui.OCSPCB->setEnabled(e.mCheckUsingOCSPConfigEntry && !e.mCheckUsingOCSPConfigEntry->isReadOnly()); d->ui.OCSPGroupBox->setEnabled(d->ui.OCSPCB->isChecked()); if (e.mDoNotCheckCertPolicyConfigEntry) { d->ui.doNotCheckCertPolicyCB->setChecked(e.mDoNotCheckCertPolicyConfigEntry->boolValue()); } d->ui.doNotCheckCertPolicyCB->setEnabled(e.mDoNotCheckCertPolicyConfigEntry && !e.mDoNotCheckCertPolicyConfigEntry->isReadOnly()); if (e.mNeverConsultConfigEntry) { d->ui.neverConsultCB->setChecked(e.mNeverConsultConfigEntry->boolValue()); } d->ui.neverConsultCB->setEnabled(e.mNeverConsultConfigEntry && !e.mNeverConsultConfigEntry->isReadOnly()); if (e.mNoAllowMarkTrustedConfigEntry) { d->ui.allowMarkTrustedCB->hide(); // this option was only here to _enable_ allow-mark-trusted, and makes no sense if it's already default on } if (e.mAllowMarkTrustedConfigEntry) { d->ui.allowMarkTrustedCB->setChecked(e.mAllowMarkTrustedConfigEntry->boolValue()); } d->ui.allowMarkTrustedCB->setEnabled(e.mAllowMarkTrustedConfigEntry && !e.mAllowMarkTrustedConfigEntry->isReadOnly()); if (e.mFetchMissingConfigEntry) { d->ui.fetchMissingCB->setChecked(e.mFetchMissingConfigEntry->boolValue()); } d->ui.fetchMissingCB->setEnabled(e.mFetchMissingConfigEntry && !e.mFetchMissingConfigEntry->isReadOnly()); if (e.mOCSPResponderURLConfigEntry) { d->ui.OCSPResponderURL->setText(e.mOCSPResponderURLConfigEntry->stringValue()); } d->ui.labelledOCSPResponderURL.setEnabled(e.mOCSPResponderURLConfigEntry && !e.mOCSPResponderURLConfigEntry->isReadOnly()); if (e.mOCSPResponderSignature) { d->ui.OCSPResponderSignature->setSelectedCertificate(e.mOCSPResponderSignature->stringValue()); } d->ui.labelledOCSPResponderSignature.setEnabled(e.mOCSPResponderSignature && !e.mOCSPResponderSignature->isReadOnly()); // dirmngr-0.9.0 options initializeDirmngrCheckbox(d->ui.ignoreServiceURLCB, e.mIgnoreServiceURLEntry); initializeDirmngrCheckbox(d->ui.ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry); initializeDirmngrCheckbox(d->ui.disableHTTPCB, e.mDisableHTTPEntry); initializeDirmngrCheckbox(d->ui.ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry); initializeDirmngrCheckbox(d->ui.disableLDAPCB, e.mDisableLDAPEntry); if (e.mCustomHTTPProxy) { QString systemProxy = QString::fromLocal8Bit(qgetenv("http_proxy")); if (systemProxy.isEmpty()) { systemProxy = i18n("no proxy"); } d->ui.systemHTTPProxy->setText(i18n("(Current system setting: %1)", systemProxy)); const bool honor = e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue(); d->ui.honorHTTPProxyRB->setChecked(honor); d->ui.useCustomHTTPProxyRB->setChecked(!honor); d->ui.customHTTPProxy->setText(e.mCustomHTTPProxy->stringValue()); } d->customHTTPProxyWritable = e.mCustomHTTPProxy && !e.mCustomHTTPProxy->isReadOnly(); if (!d->customHTTPProxyWritable) { disableDirmngrWidget(d->ui.honorHTTPProxyRB); disableDirmngrWidget(d->ui.useCustomHTTPProxyRB); disableDirmngrWidget(d->ui.systemHTTPProxy); disableDirmngrWidget(d->ui.customHTTPProxy); } if (e.mCustomLDAPProxy) { d->ui.customLDAPProxy->setText(e.mCustomLDAPProxy->stringValue()); } if (!e.mCustomLDAPProxy || e.mCustomLDAPProxy->isReadOnly()) { disableDirmngrWidget(d->ui.customLDAPProxy); disableDirmngrWidget(d->ui.customLDAPLabel); } d->enableDisableActions(); } static void saveCheckBoxToKleoEntry(QCheckBox *cb, CryptoConfigEntry *entry) { const bool b = cb->isChecked(); if (entry && entry->boolValue() != b) { entry->setBoolValue(b); } } void SMimeValidationConfigurationWidget::save() const { CryptoConfig *const config = QGpgME::cryptoConfig(); if (!config) { return; } { SMimeValidationPreferences preferences; preferences.setRefreshInterval(d->ui.intervalRefreshCB->isChecked() ? d->ui.intervalRefreshSB->value() : 0); preferences.save(); } // Create config entries // Don't keep them around, they'll get deleted by clear(), which could be done by the // "configure backend" button. const SMIMECryptoConfigEntries e(config); const bool b = d->ui.OCSPCB->isChecked(); if (e.mCheckUsingOCSPConfigEntry && e.mCheckUsingOCSPConfigEntry->boolValue() != b) { e.mCheckUsingOCSPConfigEntry->setBoolValue(b); } // Set allow-ocsp together with enable-ocsp if (e.mEnableOCSPsendingConfigEntry && e.mEnableOCSPsendingConfigEntry->boolValue() != b) { e.mEnableOCSPsendingConfigEntry->setBoolValue(b); } saveCheckBoxToKleoEntry(d->ui.doNotCheckCertPolicyCB, e.mDoNotCheckCertPolicyConfigEntry); saveCheckBoxToKleoEntry(d->ui.neverConsultCB, e.mNeverConsultConfigEntry); saveCheckBoxToKleoEntry(d->ui.allowMarkTrustedCB, e.mAllowMarkTrustedConfigEntry); saveCheckBoxToKleoEntry(d->ui.fetchMissingCB, e.mFetchMissingConfigEntry); QString txt = d->ui.OCSPResponderURL->text(); if (e.mOCSPResponderURLConfigEntry && e.mOCSPResponderURLConfigEntry->stringValue() != txt) { e.mOCSPResponderURLConfigEntry->setStringValue(txt); } txt = d->ui.OCSPResponderSignature->selectedCertificate(); if (e.mOCSPResponderSignature && e.mOCSPResponderSignature->stringValue() != txt) { e.mOCSPResponderSignature->setStringValue(txt); } // dirmngr-0.9.0 options saveCheckBoxToKleoEntry(d->ui.ignoreServiceURLCB, e.mIgnoreServiceURLEntry); saveCheckBoxToKleoEntry(d->ui.ignoreHTTPDPCB, e.mIgnoreHTTPDPEntry); saveCheckBoxToKleoEntry(d->ui.disableHTTPCB, e.mDisableHTTPEntry); saveCheckBoxToKleoEntry(d->ui.ignoreLDAPDPCB, e.mIgnoreLDAPDPEntry); saveCheckBoxToKleoEntry(d->ui.disableLDAPCB, e.mDisableLDAPEntry); if (e.mCustomHTTPProxy) { const bool honor = d->ui.honorHTTPProxyRB->isChecked(); if (e.mHonorHTTPProxy && e.mHonorHTTPProxy->boolValue() != honor) { e.mHonorHTTPProxy->setBoolValue(honor); } const QString chosenProxy = d->ui.customHTTPProxy->text(); if (chosenProxy != e.mCustomHTTPProxy->stringValue()) { e.mCustomHTTPProxy->setStringValue(chosenProxy); } } txt = d->ui.customLDAPProxy->text(); if (e.mCustomLDAPProxy && e.mCustomLDAPProxy->stringValue() != txt) { e.mCustomLDAPProxy->setStringValue(d->ui.customLDAPProxy->text()); } config->sync(true); } CryptoConfigEntry * SMIMECryptoConfigEntries::configEntry(const char *componentName, const char *entryName, int /*CryptoConfigEntry::ArgType*/ argType, ShowError showError) { CryptoConfigEntry *const entry = getCryptoConfigEntry(mConfig, componentName, entryName); if (!entry) { if (showError == DoShowError) { qCWarning(KLEOPATRA_LOG) << QStringLiteral("Backend error: gpgconf doesn't seem to know the entry for %1/%2") - .arg(QLatin1String(componentName), QLatin1String(entryName)); + .arg(QLatin1StringView(componentName), QLatin1String(entryName)); } return nullptr; } if (entry->argType() != argType || entry->isList()) { if (showError == DoShowError) { qCWarning(KLEOPATRA_LOG) << QStringLiteral("Backend error: gpgconf has wrong type for %1/%2: %3 %4") - .arg(QLatin1String(componentName), QLatin1String(entryName)) + .arg(QLatin1StringView(componentName), QLatin1String(entryName)) .arg(entry->argType()) .arg(entry->isList()); } return nullptr; } return entry; } #include "moc_smimevalidationconfigurationwidget.cpp" diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index eb5676855..79eba04cc 100644 --- a/src/crypto/autodecryptverifyfilescontroller.cpp +++ b/src/crypto/autodecryptverifyfilescontroller.cpp @@ -1,666 +1,666 @@ /* -*- mode: c++; c-basic-offset:4 -*- autodecryptverifyfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 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 "autodecryptverifyfilescontroller.h" #include "fileoperationspreferences.h" #include #include #include #include #include "commands/decryptverifyfilescommand.h" #include #include #include #include #include #include #include #include #include #ifndef Q_OS_WIN #include #include #endif #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class AutoDecryptVerifyFilesController::Private { AutoDecryptVerifyFilesController *const q; public: explicit Private(AutoDecryptVerifyFilesController *qq); void schedule(); QString getEmbeddedFileName(const QString &fileName) const; void exec(); std::vector> buildTasks(const QStringList &, QStringList &); struct CryptoFile { QString baseName; QString fileName; GpgME::Protocol protocol = GpgME::UnknownProtocol; int classification = 0; std::shared_ptr output; }; QList classifyAndSortFiles(const QStringList &files); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; std::vector> m_results; std::vector> m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_errorDetected = false; DecryptVerifyOperation m_operation = DecryptVerify; QPointer m_dialog; std::unique_ptr m_workDir; }; AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq) { qRegisterMetaType(); } void AutoDecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const std::shared_ptr &i : std::as_const(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } QString AutoDecryptVerifyFilesController::Private::getEmbeddedFileName(const QString &fileName) const { auto it = std::find_if(m_results.cbegin(), m_results.cend(), [fileName](const auto &r) { return r->fileName() == fileName; }); if (it != m_results.cend()) { const auto embeddedFilePath = QString::fromUtf8((*it)->decryptionResult().fileName()); if (embeddedFilePath.contains(QLatin1Char{'\\'})) { // ignore embedded file names containing '\' return {}; } // strip the path from the embedded file name return QFileInfo{embeddedFilePath}.fileName(); } else { return {}; } } void AutoDecryptVerifyFilesController::Private::exec() { Q_ASSERT(!m_dialog); QStringList undetected; std::vector> tasks = buildTasks(m_passedFiles, undetected); if (!undetected.isEmpty()) { // Since GpgME 1.7.0 Classification is supposed to be reliable // so we really can't do anything with this data. reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to find encrypted or signed data in one or more files." "You can manually select what to do with the files now." "If they contain signed or encrypted data please report a bug (see Help->Report Bug).")); auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true); cmd->start(); } if (tasks.empty()) { q->emitDoneOrError(); return; } Q_ASSERT(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr coll(new TaskCollection); for (const std::shared_ptr &i : std::as_const(m_runnableTasks)) { q->connectTask(i); } coll->setTasks(m_runnableTasks); DecryptVerifyFilesDialog dialog{coll}; m_dialog = &dialog; m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles)); QTimer::singleShot(0, q, SLOT(schedule())); const auto result = m_dialog->exec(); if (result == QDialog::Rejected) { q->cancel(); } else if (result == QDialog::Accepted && m_workDir) { // Without workdir there is nothing to move. const QDir workdir(m_workDir->path()); const QDir outDir(m_dialog->outputLocation()); qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); const auto filesAndFoldersToMove = workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); const auto fileCount = Kleo::count_if(filesAndFoldersToMove, [](const auto &fi) { return !fi.isDir(); }); OverwritePolicy overwritePolicy{m_dialog, fileCount > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}}; for (const QFileInfo &fi : filesAndFoldersToMove) { const auto inpath = fi.absoluteFilePath(); if (fi.isDir()) { // A directory. Assume that the input was an archive // and avoid directory merges by trying to find a non // existing directory. auto candidate = fi.fileName(); if (candidate.startsWith(QLatin1Char('-'))) { // Bug in GpgTar Extracts stdout passed archives to a dir named - candidate = QFileInfo(m_passedFiles.first()).baseName(); } QString suffix; QFileInfo ofi; int i = 0; do { ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix)); if (!ofi.exists()) { break; } suffix = QStringLiteral("_%1").arg(++i); } while (i < 1000); const auto destPath = ofi.absoluteFilePath(); #ifndef Q_OS_WIN auto job = KIO::moveAs(QUrl::fromLocalFile(inpath), QUrl::fromLocalFile(destPath)); qCDebug(KLEOPATRA_LOG) << "Moving" << job->srcUrls().front().toLocalFile() << "to" << job->destUrl().toLocalFile(); if (!job->exec()) { if (job->error() == KIO::ERR_USER_CANCELED) { break; } reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18nc("@info", "Failed to move %1 to %2." "%3", inpath, destPath, job->errorString())); } #else // On Windows, KIO::move does not work for folders when crossing partition boundaries if (!moveDir(inpath, destPath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18nc("@info", "Failed to move %1 to %2.", inpath, destPath)); } #endif continue; } const auto embeddedFileName = getEmbeddedFileName(inpath); QString outFileName = fi.fileName(); if (!embeddedFileName.isEmpty() && embeddedFileName != fi.fileName()) { // we switch "Yes" and "No" because Yes is default, but saving with embedded file name could be dangerous const auto answer = KMessageBox::questionTwoActionsCancel( m_dialog, xi18n("Shall the file be saved with the original file name %1?", embeddedFileName), i18nc("@title:window", "Use Original File Name?"), KGuiItem(xi18n("No, Save As %1", fi.fileName())), KGuiItem(xi18n("Yes, Save As %1", embeddedFileName))); if (answer == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath; continue; } else if (answer == KMessageBox::ButtonCode::SecondaryAction) { outFileName = embeddedFileName; } } auto outpath = outDir.absoluteFilePath(outFileName); qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath; const QFileInfo ofi(outpath); if (ofi.exists()) { const auto newPath = overwritePolicy.obtainOverwritePermission(outpath); if (newPath.isEmpty()) { if (overwritePolicy.policy() == OverwritePolicy::Cancel) { qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath; break; } // else Skip continue; } else if (newPath == outpath) { // overwrite existing file if (!QFile::remove(outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete %1.", outpath)); continue; } } else { // use new name for file outpath = newPath; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, outpath)); } } } q->emitDoneOrError(); } QList AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) // || mayBeOpaqueSignature(classification) // || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QList out; for (const auto &file : files) { CryptoFile cFile; cFile.fileName = file; cFile.baseName = stripSuffix(file); cFile.classification = classify(file); cFile.protocol = findProtocol(cFile.classification); auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) { return other.protocol == cFile.protocol && other.baseName == cFile.baseName; }); if (it != out.end()) { // If we found a file with the same basename, make sure that encrypted // file is before the signature file, so that we first decrypt and then // verify if (isSignature(cFile.classification) && isCipherText(it->classification)) { out.insert(it + 1, cFile); } else if (isCipherText(cFile.classification) && isSignature(it->classification)) { out.insert(it, cFile); } else { // both are signatures or both are encrypted files, in which // case order does not matter out.insert(it, cFile); } } else { out.push_back(cFile); } } return out; } static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported(); } std::vector> AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QList cryptoFiles = classifyAndSortFiles(fileNames); std::vector> tasks; for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) { auto &cFile = (*it); QFileInfo fi(cFile.fileName); qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification); if (!fi.isReadable()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open %1 for reading.", cFile.fileName)); continue; } if (mayBeAnyCertStoreType(cFile.classification)) { // Trying to verify a certificate. Possible because extensions are often similar // for PGP Keys. reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("The file %1 contains certificates and can't be decrypted or verified.", cFile.fileName)); qCDebug(KLEOPATRA_LOG) << "reported error"; continue; } // We can't reliably detect CMS detached signatures, so we will try to do // our best to use the current file as a detached signature and fallback to // opaque signature otherwise. if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) { // First, see if previous task was a decryption task for the same file // and "pipe" it's output into our input std::shared_ptr input; bool prepend = false; if (it != cryptoFiles.begin()) { const auto prev = it - 1; if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) { input = Input::createFromOutput(prev->output); prepend = true; } } if (!input) { if (QFile::exists(cFile.baseName)) { input = Input::createFromFile(cFile.baseName); } } if (input) { qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(input); t->setProtocol(cFile.protocol); if (prepend) { // Put the verify task BEFORE the decrypt task in the tasks queue, // because the tasks are executed in reverse order! tasks.insert(tasks.end() - 1, t); } else { tasks.push_back(t); } continue; } else { // No signed data, maybe not a detached signature } } if (isDetachedSignature(cFile.classification)) { // Detached signature, try to find data or ask the user. QString signedDataFileName = cFile.baseName; if (!QFile::exists(signedDataFileName)) { signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with the signature %1", fi.fileName()), fi.path()); } if (signedDataFileName.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted."; } else { qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName; std::shared_ptr t(new VerifyDetachedTask); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (cFile.protocol == GpgME::OpenPGP) { t->setSignatureFile(cFile.fileName); t->setSignedFile(signedDataFileName); } else { t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); } #else t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); #endif t->setProtocol(cFile.protocol); tasks.push_back(t); } continue; } if (!mayBeAnyMessageType(cFile.classification)) { // Not a Message? Maybe there is a signature for this file? const auto signatures = findSignatures(cFile.fileName); bool foundSig = false; if (!signatures.empty()) { for (const QString &sig : signatures) { const auto classification = classify(sig); qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification; const auto proto = findProtocol(classification); if (proto == GpgME::UnknownProtocol) { qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess."; continue; } foundSig = true; std::shared_ptr t(new VerifyDetachedTask); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (cFile.protocol == GpgME::OpenPGP) { t->setSignatureFile(sig); t->setSignedFile(cFile.fileName); } else { t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); } #else t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); #endif t->setProtocol(proto); tasks.push_back(t); } } if (!foundSig) { undetected << cFile.fileName; qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; } } else { const FileOperationsPreferences fileOpSettings; // Any Message type so we have input and output. std::shared_ptr input; #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (cFile.protocol != GpgME::OpenPGP) { input = Input::createFromFile(cFile.fileName); } #else input = Input::createFromFile(cFile.fileName); #endif std::shared_ptr ad; if (fileOpSettings.autoExtractArchives()) { const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); } if (fileOpSettings.dontUseTmpDir()) { if (!m_workDir) { m_workDir = std::make_unique(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX")); } if (!m_workDir->isValid()) { qCDebug(KLEOPATRA_LOG) << heuristicBaseDirectory(fileNames) << "not a valid temporary directory."; m_workDir.reset(); } } if (!m_workDir) { m_workDir = std::make_unique(); } qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory."; const auto wd = QDir(m_workDir->path()); std::shared_ptr output; QString outputFilePath; if (ad) { - if ((ad->id() == QLatin1String{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) { + if ((ad->id() == QLatin1StringView{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) { // we don't need an output } else { output = ad->createOutputFromUnpackCommand(cFile.protocol, ad->stripExtension(cFile.protocol, cFile.baseName), wd); } } else { outputFilePath = wd.absoluteFilePath(outputFileName(fi.fileName())); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (cFile.protocol != GpgME::OpenPGP) { output = Output::createFromFile(outputFilePath, false); } #else output = Output::createFromFile(outputFilePath, false); #endif } // If this might be opaque CMS signature, then try that. We already handled // detached CMS signature above const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification); if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr t(new VerifyOpaqueTask); if (input) { t->setInput(input); } if (output) { t->setOutput(output); } t->setProtocol(cFile.protocol); if (ad) { t->setExtractArchive(true); t->setInputFile(cFile.fileName); if (output) { t->setOutputDirectory(m_workDir->path()); } else { // make gpgtar extract to a subfolder of the work directory based on the input file name const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName(); t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName)); } #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO } else if (cFile.protocol == GpgME::OpenPGP) { t->setInputFile(cFile.fileName); t->setOutputFile(outputFilePath); #endif } tasks.push_back(t); } else { // Any message. That is not an opaque signature needs to be // decrypted. Verify we always do because we can't know if // an encrypted message is also signed. qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr t(new DecryptVerifyTask); if (input) { t->setInput(input); } if (output) { t->setOutput(output); } t->setProtocol(cFile.protocol); if (ad) { t->setExtractArchive(true); t->setInputFile(cFile.fileName); if (output) { t->setOutputDirectory(m_workDir->path()); } else { // make gpgtar extract to a subfolder of the work directory based on the input file name const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName(); t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName)); } #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO } else if (cFile.protocol == GpgME::OpenPGP) { t->setInputFile(cFile.fileName); t->setOutputFile(outputFilePath); #endif } cFile.output = output; tasks.push_back(t); } } } return tasks; } void AutoDecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent) : DecryptVerifyFilesController(parent) , d(new Private(this)) { } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr &ctx, QObject *parent) : DecryptVerifyFilesController(ctx, parent) , d(new Private(this)) { } AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::start() { d->exec(); } void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const { return d->m_operation; } void AutoDecryptVerifyFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. m_runnableTasks.clear(); // a cancel() will result in a call to if (m_runningTask) { m_runningTask->cancel(); } } void AutoDecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; try { d->m_errorDetected = true; if (d->m_dialog) { d->m_dialog->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); Q_UNUSED(task) // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container d->m_completedTasks.push_back(d->m_runningTask); d->m_runningTask.reset(); if (const std::shared_ptr &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } #include "moc_autodecryptverifyfilescontroller.cpp" diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index b20165572..d1c1c8940 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,454 +1,454 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifyfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "decryptverifyfilescontroller.h" #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class DecryptVerifyFilesController::Private { DecryptVerifyFilesController *const q; public: static std::shared_ptr taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr &overwritePolicy); explicit Private(DecryptVerifyFilesController *qq); void slotWizardOperationPrepared(); void slotWizardCanceled(); void schedule(); void prepareWizardFromPassedFiles(); std::vector> buildTasks(const QStringList &, const std::shared_ptr &); void ensureWizardCreated(); void ensureWizardVisible(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; QPointer m_wizard; std::vector> m_results; std::vector> m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_errorDetected; DecryptVerifyOperation m_operation; }; // static std::shared_ptr DecryptVerifyFilesController::Private::taskFromOperationWidget(const DecryptVerifyOperationWidget *w, const QString &fileName, const QDir &outDir, const std::shared_ptr &overwritePolicy) { kleo_assert(w); std::shared_ptr task; switch (w->mode()) { case DecryptVerifyOperationWidget::VerifyDetachedWithSignature: { std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(fileName)); t->setSignedData(Input::createFromFile(w->signedDataFileName())); task = t; kleo_assert(fileName == w->inputFileName()); } break; case DecryptVerifyOperationWidget::VerifyDetachedWithSignedData: { std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(w->inputFileName())); t->setSignedData(Input::createFromFile(fileName)); task = t; kleo_assert(fileName == w->signedDataFileName()); } break; case DecryptVerifyOperationWidget::DecryptVerifyOpaque: { const unsigned int classification = classify(fileName); qCDebug(KLEOPATRA_LOG) << "classified" << fileName << "as" << printableClassification(classification); const std::shared_ptr ad = w->selectedArchiveDefinition(); const Protocol proto = isOpenPGP(classification) ? OpenPGP : isCMS(classification) ? CMS : ad ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS")) : UnknownProtocol; const std::shared_ptr input = Input::createFromFile(fileName); const std::shared_ptr output = ad ? ad->createOutputFromUnpackCommand(proto, fileName, outDir) : Output::createFromFile(outDir.absoluteFilePath(outputFileName(QFileInfo(fileName).fileName())), overwritePolicy); if (mayBeCipherText(classification)) { qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr t(new DecryptVerifyTask); t->setInput(input); t->setOutput(output); task = t; } else { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr t(new VerifyOpaqueTask); t->setInput(input); t->setOutput(output); task = t; } kleo_assert(fileName == w->inputFileName()); } break; } task->autodetectProtocolFromInput(); return task; } DecryptVerifyFilesController::Private::Private(DecryptVerifyFilesController *qq) : q(qq) , m_errorDetected(false) , m_operation(DecryptVerify) { qRegisterMetaType(); } void DecryptVerifyFilesController::Private::slotWizardOperationPrepared() { ensureWizardCreated(); std::vector> tasks = buildTasks(m_filesAfterPreparation, std::make_shared(m_wizard, OverwritePolicy::MultipleFiles)); if (tasks.empty()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), i18n("No usable inputs found")); } kleo_assert(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr coll(new TaskCollection); for (const auto &i : m_runnableTasks) { q->connectTask(i); } coll->setTasks(m_runnableTasks); m_wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } void DecryptVerifyFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG) << this << __func__; q->cancel(); q->emitDoneOrError(); } void DecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); Q_UNUSED(task) // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container d->m_completedTasks.push_back(d->m_runningTask); d->m_runningTask.reset(); if (const std::shared_ptr &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const auto &i : m_results) { Q_EMIT q->verificationResult(i->verificationResult()); } q->emitDoneOrError(); } } void DecryptVerifyFilesController::Private::ensureWizardCreated() { if (m_wizard) { return; } std::unique_ptr w(new DecryptVerifyFilesWizard); w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files")); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); m_wizard = w.release(); } namespace { struct FindExtension { const QString ext; const Protocol proto; FindExtension(const QString &ext, Protocol proto) : ext(ext) , proto(proto) { } bool operator()(const std::shared_ptr &ad) const { qCDebug(KLEOPATRA_LOG) << " considering" << (ad ? ad->label() : QStringLiteral("")) << "for" << ext; bool result; if (proto == UnknownProtocol) { result = ad && (ad->extensions(OpenPGP).contains(ext, Qt::CaseInsensitive) || ad->extensions(CMS).contains(ext, Qt::CaseInsensitive)); } else { result = ad && ad->extensions(proto).contains(ext, Qt::CaseInsensitive); } qCDebug(KLEOPATRA_LOG) << (result ? " -> matches" : " -> doesn't match"); return result; } }; } std::shared_ptr DecryptVerifyFilesController::pick_archive_definition(GpgME::Protocol proto, const std::vector> &ads, const QString &filename) { const QFileInfo fi(outputFileName(filename)); QString extension = fi.completeSuffix(); - if (extension == QLatin1String("out")) { // added by outputFileName() -> useless + if (extension == QLatin1StringView("out")) { // added by outputFileName() -> useless return std::shared_ptr(); } - if (extension.endsWith(QLatin1String(".out"))) { // added by outputFileName() -> remove + if (extension.endsWith(QLatin1StringView(".out"))) { // added by outputFileName() -> remove extension.chop(4); } for (;;) { const auto it = std::find_if(ads.begin(), ads.end(), FindExtension(extension, proto)); if (it != ads.end()) { return *it; } const int idx = extension.indexOf(QLatin1Char('.')); if (idx < 0) { return std::shared_ptr(); } extension = extension.mid(idx + 1); } } void DecryptVerifyFilesController::Private::prepareWizardFromPassedFiles() { ensureWizardCreated(); const std::vector> archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); unsigned int counter = 0; for (const auto &fname : std::as_const(m_passedFiles)) { kleo_assert(!fname.isEmpty()); const unsigned int classification = classify(fname); const Protocol proto = findProtocol(classification); if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification) || mayBeDetachedSignature(classification)) { DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); const QString signedDataFileName = findSignedData(fname); // this breaks opaque signatures whose source files still // happen to exist in the same directory. Until we have // content-based classification, this is the most unlikely // case, so that's the case we break. ### FIXME remove when content-classify is done if (mayBeDetachedSignature(classification) && !signedDataFileName.isEmpty()) { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } // ### end FIXME else if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification)) { op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); } else { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } op->setInputFileName(fname); op->setSignedDataFileName(signedDataFileName); m_filesAfterPreparation << fname; } else { // probably the signed data file was selected: const QStringList signatures = findSignatures(fname); if (signatures.empty()) { // We are assuming this is a detached signature file, but // there were no signature files for it. Let's guess it's encrypted after all. // ### FIXME once we have a proper heuristic for this, this should move into // classify() and/or classifyContent() DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); op->setInputFileName(fname); m_filesAfterPreparation << fname; } else { for (const auto &s : signatures) { DecryptVerifyOperationWidget *op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignedData); op->setInputFileName(s); op->setSignedDataFileName(fname); m_filesAfterPreparation << fname; } } } } m_wizard->setOutputDirectory(heuristicBaseDirectory(m_passedFiles)); return; } std::vector> DecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, const std::shared_ptr &overwritePolicy) { const bool useOutDir = m_wizard->useOutputDirectory(); const QFileInfo outDirInfo(m_wizard->outputDirectory()); kleo_assert(!useOutDir || outDirInfo.isDir()); const QDir outDir(outDirInfo.absoluteFilePath()); kleo_assert(!useOutDir || outDir.exists()); std::vector> tasks; for (int i = 0, end = fileNames.size(); i != end; ++i) try { const QDir fileDir = QFileInfo(fileNames[i]).absoluteDir(); kleo_assert(fileDir.exists()); tasks.push_back( taskFromOperationWidget(m_wizard->operationWidget(static_cast(i)), fileNames[i], useOutDir ? outDir : fileDir, overwritePolicy)); } catch (const GpgME::Exception &e) { tasks.push_back(Task::makeErrorTask(e.error(), QString::fromLocal8Bit(e.what()), fileNames[i])); } return tasks; } void DecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } void DecryptVerifyFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(m_wizard); } DecryptVerifyFilesController::DecryptVerifyFilesController(QObject *parent) : Controller(parent) , d(new Private(this)) { } DecryptVerifyFilesController::DecryptVerifyFilesController(const std::shared_ptr &ctx, QObject *parent) : Controller(ctx, parent) , d(new Private(this)) { } DecryptVerifyFilesController::~DecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void DecryptVerifyFilesController::start() { d->prepareWizardFromPassedFiles(); d->ensureWizardVisible(); } void DecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation DecryptVerifyFilesController::operation() const { return d->m_operation; } void DecryptVerifyFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. m_runnableTasks.clear(); // a cancel() will result in a call to if (m_runningTask) { m_runningTask->cancel(); } } void DecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; try { d->m_errorDetected = true; if (d->m_wizard) { d->m_wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } #include "moc_decryptverifyfilescontroller.cpp" diff --git a/src/crypto/decryptverifytask.cpp b/src/crypto/decryptverifytask.cpp index 2a1796c6d..4517d83d3 100644 --- a/src/crypto/decryptverifytask.cpp +++ b/src/crypto/decryptverifytask.cpp @@ -1,1936 +1,1936 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifytask.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "decryptverifytask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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 // Qt::escape #include #include using namespace Kleo::Crypto; using namespace Kleo; using namespace GpgME; using namespace KMime::Types; namespace { static AuditLogEntry auditLogFromSender(QObject *sender) { return AuditLogEntry::fromJob(qobject_cast(sender)); } static bool addrspec_equal(const AddrSpec &lhs, const AddrSpec &rhs, Qt::CaseSensitivity cs) { return lhs.localPart.compare(rhs.localPart, cs) == 0 && lhs.domain.compare(rhs.domain, Qt::CaseInsensitive) == 0; } static bool mailbox_equal(const Mailbox &lhs, const Mailbox &rhs, Qt::CaseSensitivity cs) { return addrspec_equal(lhs.addrSpec(), rhs.addrSpec(), cs); } static std::string stripAngleBrackets(const std::string &str) { if (str.empty()) { return str; } if (str[0] == '<' && str[str.size() - 1] == '>') { return str.substr(1, str.size() - 2); } return str; } static std::string email(const UserID &uid) { if (uid.parent().protocol() == OpenPGP) { if (const char *const email = uid.email()) { return stripAngleBrackets(email); } else { return std::string(); } } Q_ASSERT(uid.parent().protocol() == CMS); if (const char *const id = uid.id()) if (*id == '<') { return stripAngleBrackets(id); } else { return DN(id)[QStringLiteral("EMAIL")].trimmed().toUtf8().constData(); } else { return std::string(); } } static Mailbox mailbox(const UserID &uid) { const std::string e = email(uid); Mailbox mbox; if (!e.empty()) { mbox.setAddress(e.c_str()); } return mbox; } static std::vector extractMailboxes(const Key &key) { std::vector res; const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) { const Mailbox mbox = mailbox(id); if (!mbox.addrSpec().isEmpty()) { res.push_back(mbox); } } return res; } static std::vector extractMailboxes(const std::vector &signers) { std::vector res; for (const Key &i : signers) { const std::vector bxs = extractMailboxes(i); res.insert(res.end(), bxs.begin(), bxs.end()); } return res; } static bool keyContainsMailbox(const Key &key, const Mailbox &mbox) { const std::vector mbxs = extractMailboxes(key); return std::find_if(mbxs.cbegin(), mbxs.cend(), [mbox](const Mailbox &m) { return mailbox_equal(mbox, m, Qt::CaseInsensitive); }) != mbxs.cend(); } static bool keysContainMailbox(const std::vector &keys, const Mailbox &mbox) { return std::find_if(keys.cbegin(), keys.cend(), [mbox](const Key &key) { return keyContainsMailbox(key, mbox); }) != keys.cend(); } static bool relevantInDecryptVerifyContext(const VerificationResult &r) { // for D/V operations, we ignore verification results which are not errors and contain // no signatures (which means that the data was just not signed) return (r.error() && r.error().code() != GPG_ERR_DECRYPT_FAILED) || r.numSignatures() > 0; } static QString signatureSummaryToString(int summary) { if (summary & Signature::None) { return i18n("Error: Signature not verified"); } else if (summary & Signature::Valid || summary & Signature::Green) { return i18n("Good signature"); } else if (summary & Signature::KeyRevoked) { return i18n("Signing certificate was revoked"); } else if (summary & Signature::KeyExpired) { return i18n("Signing certificate is expired"); } else if (summary & Signature::KeyMissing) { return i18n("Certificate is not available"); } else if (summary & Signature::SigExpired) { return i18n("Signature expired"); } else if (summary & Signature::CrlMissing) { return i18n("CRL missing"); } else if (summary & Signature::CrlTooOld) { return i18n("CRL too old"); } else if (summary & Signature::BadPolicy) { return i18n("Bad policy"); } else if (summary & Signature::SysError) { return i18n("System error"); // ### retrieve system error details? } else if (summary & Signature::Red) { return i18n("Bad signature"); } return QString(); } static QString formatValidSignatureWithTrustLevel(const UserID &id) { if (id.isNull()) { return QString(); } switch (id.validity()) { case UserID::Marginal: return i18n("The signature is valid but the trust in the certificate's validity is only marginal."); case UserID::Full: return i18n("The signature is valid and the certificate's validity is fully trusted."); case UserID::Ultimate: return i18n("The signature is valid and the certificate's validity is ultimately trusted."); case UserID::Never: return i18n("The signature is valid but the certificate's validity is not trusted."); case UserID::Unknown: return i18n("The signature is valid but the certificate's validity is unknown."); case UserID::Undefined: default: return i18n("The signature is valid but the certificate's validity is undefined."); } } static QString renderKeyLink(const QString &fpr, const QString &text) { return QStringLiteral("%2").arg(fpr, text); } static QString renderKey(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } if (key.primaryFingerprint() && strlen(key.primaryFingerprint()) > 16 && key.numUserIDs()) { const QString text = QStringLiteral("%1 (%2)") .arg(Formatting::prettyNameAndEMail(key).toHtmlEscaped()) .arg(Formatting::prettyID(QString::fromLocal8Bit(key.primaryFingerprint()).right(16).toLatin1().constData())); - return renderKeyLink(QLatin1String(key.primaryFingerprint()), text); + return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), text); } - return renderKeyLink(QLatin1String(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint())); + return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), Formatting::prettyID(key.primaryFingerprint())); } static QString renderKeyEMailOnlyNameAsFallback(const Key &key) { if (key.isNull()) { return i18n("Unknown certificate"); } const QString email = Formatting::prettyEMail(key); const QString user = !email.isEmpty() ? email : Formatting::prettyName(key); - return renderKeyLink(QLatin1String(key.primaryFingerprint()), user); + return renderKeyLink(QLatin1StringView(key.primaryFingerprint()), user); } static QString formatDate(const QDateTime &dt) { return QLocale().toString(dt); } static QString formatSigningInformation(const Signature &sig) { if (sig.isNull()) { return QString(); } const QDateTime dt = sig.creationTime() != 0 ? QDateTime::fromSecsSinceEpoch(quint32(sig.creationTime())) : QDateTime(); QString text; Key key = sig.key(); if (dt.isValid()) { text = i18nc("1 is a date", "Signature created on %1", formatDate(dt)) + QStringLiteral("
    "); } if (key.isNull()) { return text += i18n("With unavailable certificate:") + QStringLiteral("
    ID: 0x%1").arg(QString::fromLatin1(sig.fingerprint()).toUpper()); } text += i18n("With certificate:") + QStringLiteral("
    ") + renderKey(key); if (DeVSCompliance::isCompliant()) { text += (QStringLiteral("
    ") + (sig.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "The signature is %1", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "The signature is not %1.", DeVSCompliance::name(true)))); } return text; } static QString strikeOut(const QString &str, bool strike) { return QString(strike ? QStringLiteral("%1") : QStringLiteral("%1")).arg(str.toHtmlEscaped()); } static QString formatInputOutputLabel(const QString &input, const QString &output, bool inputDeleted, bool outputDeleted) { if (output.isEmpty()) { return strikeOut(input, inputDeleted); } return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", strikeOut(input, inputDeleted), strikeOut(output, outputDeleted)); } static bool IsErrorOrCanceled(const GpgME::Error &err) { return err || err.isCanceled(); } static bool IsErrorOrCanceled(const Result &res) { return IsErrorOrCanceled(res.error()); } static bool IsBad(const Signature &sig) { return sig.summary() & Signature::Red; } static bool IsGoodOrValid(const Signature &sig) { return (sig.summary() & Signature::Valid) || (sig.summary() & Signature::Green); } static UserID findUserIDByMailbox(const Key &key, const Mailbox &mbox) { const auto userIDs{key.userIDs()}; for (const UserID &id : userIDs) if (mailbox_equal(mailbox(id), mbox, Qt::CaseInsensitive)) { return id; } return UserID(); } static void updateKeys(const VerificationResult &result) { // This little hack works around the problem that GnuPG / GpgME does not // provide Key information in a verification result. The Key object is // a dummy just holding the KeyID. This hack ensures that all available // keys are fetched from the backend and are populated for (const auto &sig : result.signatures()) { // Update key information sig.key(true, true); } } static QString ensureUniqueDirectory(const QString &path) { // make sure that we don't use an existing directory QString uniquePath = path; const QFileInfo outputInfo{path}; if (outputInfo.exists()) { const auto uniqueName = KFileUtils::suggestName(QUrl::fromLocalFile(outputInfo.absolutePath()), outputInfo.fileName()); uniquePath = outputInfo.dir().filePath(uniqueName); } if (!QDir{}.mkpath(uniquePath)) { return {}; } return uniquePath; } static bool mimeTypeInherits(const QMimeType &mimeType, const QString &mimeTypeName) { // inherits is expensive on an invalid mimeType return mimeType.isValid() && mimeType.inherits(mimeTypeName); } } class DecryptVerifyResult::SenderInfo { public: explicit SenderInfo(const Mailbox &infSender, const std::vector &signers_) : informativeSender(infSender) , signers(signers_) { } const Mailbox informativeSender; const std::vector signers; bool hasInformativeSender() const { return !informativeSender.addrSpec().isEmpty(); } bool conflicts() const { return hasInformativeSender() && hasKeys() && !keysContainMailbox(signers, informativeSender); } bool hasKeys() const { return std::any_of(signers.cbegin(), signers.cend(), [](const Key &key) { return !key.isNull(); }); } std::vector signerMailboxes() const { return extractMailboxes(signers); } }; namespace { static Task::Result::VisualCode codeForVerificationResult(const VerificationResult &res) { if (res.isNull()) { return Task::Result::NeutralSuccess; } const std::vector sigs = res.signatures(); if (sigs.empty()) { return Task::Result::Warning; } if (std::find_if(sigs.begin(), sigs.end(), IsBad) != sigs.end()) { return Task::Result::Danger; } if ((size_t)std::count_if(sigs.begin(), sigs.end(), IsGoodOrValid) == sigs.size()) { return Task::Result::AllGood; } return Task::Result::Warning; } static QString formatVerificationResultOverview(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info) { if (res.isNull()) { return QString(); } const Error err = res.error(); if (err.isCanceled()) { return i18n("Verification canceled."); } else if (err) { return i18n("Verification failed: %1.", Formatting::errorAsString(err).toHtmlEscaped()); } const std::vector sigs = res.signatures(); if (sigs.empty()) { return i18n("No signatures found."); } const uint bad = std::count_if(sigs.cbegin(), sigs.cend(), IsBad); if (bad > 0) { return i18np("Invalid signature.", "%1 invalid signatures.", bad); } const uint warn = std::count_if(sigs.cbegin(), sigs.cend(), [](const Signature &sig) { return !IsGoodOrValid(sig); }); if (warn == sigs.size()) { return i18np("The data could not be verified.", "%1 signatures could not be verified.", warn); } // Good signature: QString text; if (sigs.size() == 1) { text = i18n("Valid signature by %1", renderKeyEMailOnlyNameAsFallback(sigs[0].key())); if (info.conflicts()) text += i18n("
    Warning: The sender's mail address is not stored in the %1 used for signing.", - renderKeyLink(QLatin1String(sigs[0].key().primaryFingerprint()), i18n("certificate"))); + renderKeyLink(QLatin1StringView(sigs[0].key().primaryFingerprint()), i18n("certificate"))); } else { text = i18np("Valid signature.", "%1 valid signatures.", sigs.size()); if (info.conflicts()) { text += i18n("
    Warning: The sender's mail address is not stored in the certificates used for signing."); } } return text; } static QString formatDecryptionResultOverview(const DecryptionResult &result, const QString &errorString = QString()) { const Error err = result.error(); if (err.isCanceled()) { return i18n("Decryption canceled."); } else if (result.isLegacyCipherNoMDC()) { return i18n("Decryption failed: %1.", i18n("No integrity protection (MDC).")); } else if (!errorString.isEmpty()) { return i18n("Decryption failed: %1.", errorString.toHtmlEscaped()); } else if (err) { return i18n("Decryption failed: %1.", Formatting::errorAsString(err).toHtmlEscaped()); } return i18n("Decryption succeeded."); } static QString formatSignature(const Signature &sig, const DecryptVerifyResult::SenderInfo &info) { if (sig.isNull()) { return QString(); } - const QString text = formatSigningInformation(sig) + QLatin1String("
    "); + const QString text = formatSigningInformation(sig) + QLatin1StringView("
    "); const Key key = sig.key(); // Green if (sig.summary() & Signature::Valid) { const UserID id = findUserIDByMailbox(key, info.informativeSender); return text + formatValidSignatureWithTrustLevel(!id.isNull() ? id : key.userID(0)); } // Red if ((sig.summary() & Signature::Red)) { const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status())); } return ret; } // Key missing if ((sig.summary() & Signature::KeyMissing)) { return text + i18n("You can search the certificate on a keyserver or import it from a file."); } // Yellow if ((sig.validity() & Signature::Validity::Undefined) // || (sig.validity() & Signature::Validity::Unknown) // || (sig.summary() == Signature::Summary::None)) { return text + (key.protocol() == OpenPGP ? i18n("The used key is not certified by you or any trusted person.") : i18n("The used certificate is not certified by a trustworthy Certificate Authority or the Certificate Authority is unknown.")); } // Catch all fall through const QString ret = text + i18n("The signature is invalid: %1", signatureSummaryToString(sig.summary())); if (sig.summary() & Signature::SysError) { return ret + QStringLiteral(" (%1)").arg(Formatting::errorAsString(sig.status())); } return ret; } static QStringList format(const std::vector &mbxs) { QStringList res; std::transform(mbxs.cbegin(), mbxs.cend(), std::back_inserter(res), [](const Mailbox &mbox) { return mbox.prettyAddress(); }); return res; } static QString formatVerificationResultDetails(const VerificationResult &res, const DecryptVerifyResult::SenderInfo &info, const QString &errorString) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } const std::vector sigs = res.signatures(); QString details; for (const Signature &sig : sigs) { details += formatSignature(sig, info) + QLatin1Char('\n'); } details = details.trimmed(); details.replace(QLatin1Char('\n'), QStringLiteral("

    ")); if (info.conflicts()) { details += i18n("

    The sender's address %1 is not stored in the certificate. Stored: %2

    ", info.informativeSender.prettyAddress(), format(info.signerMailboxes()).join(i18nc("separator for a list of e-mail addresses", ", "))); } return details; } static QString formatRecipientsDetails(const std::vector &knownRecipients, unsigned int numRecipients) { if (numRecipients == 0) { return {}; } if (knownRecipients.empty()) { - return QLatin1String("") + i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) + QLatin1String(""); + return QLatin1StringView("") + i18np("One unknown recipient.", "%1 unknown recipients.", numRecipients) + QLatin1String(""); } QString details = i18np("Recipient:", "Recipients:", numRecipients); if (numRecipients == 1) { details += QLatin1Char(' ') + renderKey(knownRecipients.front()); } else { - details += QLatin1String("
      "); + details += QLatin1StringView("
        "); for (const Key &key : knownRecipients) { - details += QLatin1String("
      • ") + renderKey(key) + QLatin1String("
      • "); + details += QLatin1StringView("
      • ") + renderKey(key) + QLatin1String("
      • "); } if (knownRecipients.size() < numRecipients) { - details += QLatin1String("
      • ") + i18np("One unknown recipient", "%1 unknown recipients", numRecipients - knownRecipients.size()) - + QLatin1String("
      • "); + details += QLatin1StringView("
      • ") + i18np("One unknown recipient", "%1 unknown recipients", numRecipients - knownRecipients.size()) + + QLatin1StringView("
      • "); } - details += QLatin1String("
      "); + details += QLatin1StringView("
    "); } return details; } static QString formatDecryptionResultDetails(const DecryptionResult &res, const std::vector &recipients, const QString &errorString, bool isSigned, const QPointer &task) { if ((res.error().code() == GPG_ERR_EIO || res.error().code() == GPG_ERR_NO_DATA) && !errorString.isEmpty()) { return i18n("Input error: %1", errorString); } if (res.isNull() || res.error() || res.error().isCanceled()) { return formatRecipientsDetails(recipients, res.numRecipients()); } QString details; if (DeVSCompliance::isCompliant()) { details += ((res.isDeVs() ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "The decryption is %1.", DeVSCompliance::name(true)) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "The decryption is not %1.", DeVSCompliance::name(true))) + QStringLiteral("
    ")); } if (res.fileName()) { const auto decVerifyTask = qobject_cast(task.data()); if (decVerifyTask) { const auto embedFileName = QString::fromUtf8(res.fileName()).toHtmlEscaped(); if (embedFileName != decVerifyTask->outputLabel()) { details += i18n("Embedded file name: '%1'", embedFileName); details += QStringLiteral("
    "); } } } if (!isSigned) { - details += i18n("Note: You cannot be sure who encrypted this message as it is not signed.") + QLatin1String("
    "); + details += i18n("Note: You cannot be sure who encrypted this message as it is not signed.") + QLatin1StringView("
    "); } if (res.isLegacyCipherNoMDC()) { details += i18nc("Integrity protection was missing because an old cipher was used.", "Hint: If this file was encrypted before the year 2003 it is " "likely that the file is legitimate. This is because back " "then integrity protection was not widely used.") + QStringLiteral("

    ") + i18nc("The user is offered to force decrypt a non integrity protected message. With the strong advice to re-encrypt it.", "If you are confident that the file was not manipulated you should re-encrypt it after you have forced the decryption.") + QStringLiteral("

    "); } details += formatRecipientsDetails(recipients, res.numRecipients()); return details; } static QString formatDecryptVerifyResultOverview(const DecryptionResult &dr, const VerificationResult &vr, const DecryptVerifyResult::SenderInfo &info) { if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return formatDecryptionResultOverview(dr); } return formatVerificationResultOverview(vr, info); } static QString formatDecryptVerifyResultDetails(const DecryptionResult &dr, const VerificationResult &vr, const std::vector &recipients, const DecryptVerifyResult::SenderInfo &info, const QString &errorString, const QPointer &task) { const QString drDetails = formatDecryptionResultDetails(dr, recipients, errorString, relevantInDecryptVerifyContext(vr), task); if (IsErrorOrCanceled(dr) || !relevantInDecryptVerifyContext(vr)) { return drDetails; } return drDetails + (drDetails.isEmpty() ? QString() : QStringLiteral("
    ")) + formatVerificationResultDetails(vr, info, errorString); } } // anon namespace class DecryptVerifyResult::Private { DecryptVerifyResult *const q; public: Private(DecryptVerifyOperation type, const VerificationResult &vr, const DecryptionResult &dr, const QByteArray &stuff, const QString &fileName, const GpgME::Error &error, const QString &errString, const QString &input, const QString &output, const AuditLogEntry &auditLog, Task *parentTask, const Mailbox &informativeSender, DecryptVerifyResult *qq) : q(qq) , m_type(type) , m_verificationResult(vr) , m_decryptionResult(dr) , m_stuff(stuff) , m_fileName(fileName) , m_error(error) , m_errorString(errString) , m_inputLabel(input) , m_outputLabel(output) , m_auditLog(auditLog) , m_parentTask(QPointer(parentTask)) , m_informativeSender(informativeSender) { } QString label() const { return formatInputOutputLabel(m_inputLabel, m_outputLabel, false, q->hasError()); } DecryptVerifyResult::SenderInfo makeSenderInfo() const; bool isDecryptOnly() const { return m_type == Decrypt; } bool isVerifyOnly() const { return m_type == Verify; } bool isDecryptVerify() const { return m_type == DecryptVerify; } DecryptVerifyOperation m_type; VerificationResult m_verificationResult; DecryptionResult m_decryptionResult; QByteArray m_stuff; QString m_fileName; GpgME::Error m_error; QString m_errorString; QString m_inputLabel; QString m_outputLabel; const AuditLogEntry m_auditLog; QPointer m_parentTask; const Mailbox m_informativeSender; }; DecryptVerifyResult::SenderInfo DecryptVerifyResult::Private::makeSenderInfo() const { return SenderInfo(m_informativeSender, KeyCache::instance()->findSigners(m_verificationResult)); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const DecryptionResult &dr, const QByteArray &plaintext, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Decrypt, // VerificationResult(), dr, plaintext, {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptResult(const GpgME::Error &err, const QString &what, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Decrypt, // VerificationResult(), DecryptionResult(err), QByteArray(), {}, err, what, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptVerifyResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plaintext, const QString &fileName, const AuditLogEntry &auditLog) { const auto err = dr.error().code() ? dr.error() : vr.error(); return std::shared_ptr(new DecryptVerifyResult(DecryptVerify, // vr, dr, plaintext, fileName, err, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromDecryptVerifyResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(DecryptVerify, // VerificationResult(), DecryptionResult(err), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const VerificationResult &vr, const QByteArray &plaintext, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // vr, DecryptionResult(), plaintext, {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyOpaqueResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // VerificationResult(err), DecryptionResult(), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const VerificationResult &vr, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // vr, DecryptionResult(), QByteArray(), {}, {}, QString(), inputLabel(), outputLabel(), auditLog, this, informativeSender())); } std::shared_ptr AbstractDecryptVerifyTask::fromVerifyDetachedResult(const GpgME::Error &err, const QString &details, const AuditLogEntry &auditLog) { return std::shared_ptr(new DecryptVerifyResult(Verify, // VerificationResult(err), DecryptionResult(), QByteArray(), {}, err, details, inputLabel(), outputLabel(), auditLog, this, informativeSender())); } DecryptVerifyResult::DecryptVerifyResult(DecryptVerifyOperation type, const VerificationResult &vr, const DecryptionResult &dr, const QByteArray &stuff, const QString &fileName, const GpgME::Error &error, const QString &errString, const QString &inputLabel, const QString &outputLabel, const AuditLogEntry &auditLog, Task *parentTask, const Mailbox &informativeSender) : Task::Result() , d(new Private(type, vr, dr, stuff, fileName, error, errString, inputLabel, outputLabel, auditLog, parentTask, informativeSender, this)) { } Task::Result::ContentType DecryptVerifyResult::viewableContentType() const { #if QGPGME_SUPPORTS_IS_MIME if (decryptionResult().isMime()) { return Task::Result::ContentType::Mime; } #endif if (fileName().endsWith(QStringLiteral("openpgp-encrypted-message"))) { return Task::Result::ContentType::Mime; } QMimeDatabase mimeDatabase; const auto mimeType = mimeDatabase.mimeTypeForFile(fileName()); if (mimeTypeInherits(mimeType, QStringLiteral("message/rfc822"))) { return Task::Result::ContentType::Mime; } if (mimeTypeInherits(mimeType, QStringLiteral("application/mbox"))) { return Task::Result::ContentType::Mbox; } return Task::Result::ContentType::None; } QString DecryptVerifyResult::overview() const { QString ov; if (d->isDecryptOnly()) { ov += formatDecryptionResultOverview(d->m_decryptionResult); } else if (d->isVerifyOnly()) { ov += formatVerificationResultOverview(d->m_verificationResult, d->makeSenderInfo()); } else { ov += formatDecryptVerifyResultOverview(d->m_decryptionResult, d->m_verificationResult, d->makeSenderInfo()); } if (ov.size() + d->label().size() > 120) { // Avoid ugly breaks ov = QStringLiteral("
    ") + ov; } return i18nc("label: result example: foo.sig: Verification failed. ", "%1: %2", d->label(), ov); } QString DecryptVerifyResult::details() const { if (d->isDecryptOnly()) { return formatDecryptionResultDetails(d->m_decryptionResult, KeyCache::instance()->findRecipients(d->m_decryptionResult), errorString(), false, d->m_parentTask); } if (d->isVerifyOnly()) { return formatVerificationResultDetails(d->m_verificationResult, d->makeSenderInfo(), errorString()); } return formatDecryptVerifyResultDetails(d->m_decryptionResult, d->m_verificationResult, KeyCache::instance()->findRecipients(d->m_decryptionResult), d->makeSenderInfo(), errorString(), d->m_parentTask); } GpgME::Error DecryptVerifyResult::error() const { return d->m_error; } QString DecryptVerifyResult::errorString() const { return d->m_errorString; } AuditLogEntry DecryptVerifyResult::auditLog() const { return d->m_auditLog; } QPointer DecryptVerifyResult::parentTask() const { return d->m_parentTask; } Task::Result::VisualCode DecryptVerifyResult::code() const { if ((d->m_type == DecryptVerify || d->m_type == Verify) && relevantInDecryptVerifyContext(verificationResult())) { return codeForVerificationResult(verificationResult()); } return hasError() ? NeutralError : NeutralSuccess; } GpgME::VerificationResult DecryptVerifyResult::verificationResult() const { return d->m_verificationResult; } GpgME::DecryptionResult DecryptVerifyResult::decryptionResult() const { return d->m_decryptionResult; } QString DecryptVerifyResult::fileName() const { return d->m_fileName; } class AbstractDecryptVerifyTask::Private { public: Mailbox informativeSender; QPointer job; }; AbstractDecryptVerifyTask::AbstractDecryptVerifyTask(QObject *parent) : Task(parent) , d(new Private) { } AbstractDecryptVerifyTask::~AbstractDecryptVerifyTask() { } void AbstractDecryptVerifyTask::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; if (d->job) { d->job->slotCancel(); } } Mailbox AbstractDecryptVerifyTask::informativeSender() const { return d->informativeSender; } void AbstractDecryptVerifyTask::setInformativeSender(const Mailbox &sender) { d->informativeSender = sender; } QGpgME::Job *AbstractDecryptVerifyTask::job() const { return d->job; } void AbstractDecryptVerifyTask::setJob(QGpgME::Job *job) { d->job = job; } class DecryptVerifyTask::Private { DecryptVerifyTask *const q; public: explicit Private(DecryptVerifyTask *qq) : q{qq} { } void startDecryptVerifyJob(); void startDecryptVerifyArchiveJob(); void slotResult(const DecryptionResult &, const VerificationResult &, const QByteArray & = {}); std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend = nullptr; Protocol m_protocol = UnknownProtocol; bool m_ignoreMDCError = false; bool m_extractArchive = false; QString m_inputFilePath; QString m_outputFilePath; QString m_outputDirectory; }; void DecryptVerifyTask::Private::slotResult(const DecryptionResult &dr, const VerificationResult &vr, const QByteArray &plainText) { updateKeys(vr); { std::stringstream ss; ss << dr << '\n' << vr; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLogEntry auditLog = auditLogFromSender(q->sender()); if (m_output) { if (dr.error().code() || vr.error().code()) { m_output->cancel(); } else { try { kleo_assert(!dr.isNull() || !vr.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { q->emitResult( q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } } const int drErr = dr.error().code(); const QString errorString = m_output ? m_output->errorString() : QString{}; if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || (m_output && m_output->failed())) { q->emitResult(q->fromDecryptResult(drErr ? dr.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } q->emitResult(q->fromDecryptVerifyResult(dr, vr, plainText, m_output ? m_output->fileName() : QString{}, auditLog)); } DecryptVerifyTask::DecryptVerifyTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } DecryptVerifyTask::~DecryptVerifyTask() { } void DecryptVerifyTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptVerifyTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void DecryptVerifyTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = prot == GpgME::OpenPGP ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void DecryptVerifyTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception( gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature/ciphertext - maybe it is neither ciphertext nor a signature?"), Exception::MessageOnly); } setProtocol(p); } QString DecryptVerifyTask::label() const { return i18n("Decrypting %1...", inputLabel()); } unsigned long long DecryptVerifyTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString DecryptVerifyTask::inputLabel() const { return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName(); } QString DecryptVerifyTask::outputLabel() const { if (d->m_output) { return d->m_output->label(); } else if (!d->m_outputFilePath.isEmpty()) { return QFileInfo{d->m_outputFilePath}.fileName(); } else { return d->m_outputDirectory; } } Protocol DecryptVerifyTask::protocol() const { return d->m_protocol; } static void ensureIOOpen(QIODevice *input, QIODevice *output) { if (input && !input->isOpen()) { input->open(QIODevice::ReadOnly); } if (output && !output->isOpen()) { output->open(QIODevice::WriteOnly); } } void DecryptVerifyTask::setIgnoreMDCError(bool value) { d->m_ignoreMDCError = value; } void DecryptVerifyTask::setExtractArchive(bool extract) { d->m_extractArchive = extract; } void DecryptVerifyTask::setInputFile(const QString &path) { d->m_inputFilePath = path; } void DecryptVerifyTask::setOutputFile(const QString &path) { d->m_outputFilePath = path; } void DecryptVerifyTask::setOutputDirectory(const QString &directory) { d->m_outputDirectory = directory; } static bool archiveJobsCanBeUsed(GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported(); } void DecryptVerifyTask::doStart() { kleo_assert(d->m_backend); if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) { d->startDecryptVerifyArchiveJob(); } else { d->startDecryptVerifyJob(); } } static void setIgnoreMDCErrorFlag(QGpgME::Job *job, bool ignoreMDCError) { if (ignoreMDCError) { qCDebug(KLEOPATRA_LOG) << "Modifying job to ignore MDC errors."; auto ctx = QGpgME::Job::context(job); if (!ctx) { qCWarning(KLEOPATRA_LOG) << "Failed to get context for job"; } else { const auto err = ctx->setFlag("ignore-mdc-error", "1"); if (err) { qCWarning(KLEOPATRA_LOG) << "Failed to set ignore mdc errors" << Formatting::errorAsString(err); } } } } void DecryptVerifyTask::Private::startDecryptVerifyJob() { #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (!m_outputFilePath.isEmpty() && QFile::exists(m_outputFilePath)) { // The output files are always written to a temporary location. Therefore, this can only occur // if two signed/encrypted files with the same name in different folders are verified/decrypted // because they would be written to the same temporary location. QMetaObject::invokeMethod( q, [this]() { slotResult(DecryptionResult{Error::fromCode(GPG_ERR_EEXIST)}, VerificationResult{}); }, Qt::QueuedConnection); return; } #endif try { std::unique_ptr job{m_backend->decryptVerifyJob()}; kleo_assert(job); setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError); QObject::connect(job.get(), &QGpgME::DecryptVerifyJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult, const QByteArray &plainText) { slotResult(decryptResult, verifyResult, plainText); }); connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (!m_inputFilePath.isEmpty() && !m_outputFilePath.isEmpty()) { job->setInputFile(m_inputFilePath); job->setOutputFile(m_outputFilePath); const auto err = job->startIt(); } else { ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get()); job->start(m_input->ioDevice(), m_output->ioDevice()); } #else ensureIOOpen(m_input->ioDevice().get(), m_output->ioDevice().get()); job->start(m_input->ioDevice(), m_output->ioDevice()); #endif q->setJob(job.release()); } catch (const GpgME::Exception &e) { q->emitResult(q->fromDecryptVerifyResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry())); } catch (const std::exception &e) { q->emitResult( q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry())); } catch (...) { q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry())); } } void DecryptVerifyTask::Private::startDecryptVerifyArchiveJob() { std::unique_ptr job{m_backend->decryptVerifyArchiveJob()}; kleo_assert(job); setIgnoreMDCErrorFlag(job.get(), m_ignoreMDCError); connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const GpgME::DecryptionResult &decryptResult, const GpgME::VerificationResult &verifyResult) { slotResult(decryptResult, verifyResult); }); connect(job.get(), &QGpgME::Job::jobProgress, q, &DecryptVerifyTask::setProgress); #if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME // make sure that we don't use an existing output directory const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory); if (outputDirectory.isEmpty()) { q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {})); return; } m_outputDirectory = outputDirectory; job->setInputFile(m_inputFilePath); job->setOutputDirectory(m_outputDirectory); const auto err = job->startIt(); #else ensureIOOpen(m_input->ioDevice().get(), nullptr); job->setOutputDirectory(m_outputDirectory); const auto err = job->start(m_input->ioDevice()); #endif q->setJob(job.release()); if (err) { q->emitResult(q->fromDecryptVerifyResult(err, {}, {})); } } class DecryptTask::Private { DecryptTask *const q; public: explicit Private(DecryptTask *qq) : q{qq} { } void slotResult(const DecryptionResult &, const QByteArray &); void registerJob(QGpgME::DecryptJob *job) { q->connect(job, SIGNAL(result(GpgME::DecryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::DecryptionResult, QByteArray))); q->connect(job, &QGpgME::Job::jobProgress, q, &DecryptTask::setProgress); } std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend = nullptr; Protocol m_protocol = UnknownProtocol; }; void DecryptTask::Private::slotResult(const DecryptionResult &result, const QByteArray &plainText) { { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLogEntry auditLog = auditLogFromSender(q->sender()); if (result.error().code()) { m_output->cancel(); } else { try { kleo_assert(!result.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(q->fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { q->emitResult(q->fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } const int drErr = result.error().code(); const QString errorString = m_output->errorString(); if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || m_output->failed()) { q->emitResult(q->fromDecryptResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } q->emitResult(q->fromDecryptResult(result, plainText, auditLog)); } DecryptTask::DecryptTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } DecryptTask::~DecryptTask() { } void DecryptTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void DecryptTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void DecryptTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void DecryptTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this was S/MIME- or OpenPGP-encrypted - maybe it is not ciphertext at all?"), Exception::MessageOnly); } setProtocol(p); } QString DecryptTask::label() const { return i18n("Decrypting: %1...", d->m_input->label()); } unsigned long long DecryptTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString DecryptTask::inputLabel() const { return d->m_input ? d->m_input->label() : QString(); } QString DecryptTask::outputLabel() const { return d->m_output ? d->m_output->label() : QString(); } Protocol DecryptTask::protocol() const { return d->m_protocol; } void DecryptTask::doStart() { kleo_assert(d->m_backend); try { std::unique_ptr job{d->m_backend->decryptJob()}; kleo_assert(job); d->registerJob(job.get()); ensureIOOpen(d->m_input->ioDevice().get(), d->m_output->ioDevice().get()); job->start(d->m_input->ioDevice(), d->m_output->ioDevice()); setJob(job.release()); } catch (const GpgME::Exception &e) { emitResult(fromDecryptResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry())); } catch (const std::exception &e) { emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry())); } catch (...) { emitResult(fromDecryptResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry())); } } class VerifyOpaqueTask::Private { VerifyOpaqueTask *const q; public: explicit Private(VerifyOpaqueTask *qq) : q{qq} { } void startVerifyOpaqueJob(); void startDecryptVerifyArchiveJob(); void slotResult(const VerificationResult &, const QByteArray & = {}); std::shared_ptr m_input; std::shared_ptr m_output; const QGpgME::Protocol *m_backend = nullptr; Protocol m_protocol = UnknownProtocol; bool m_extractArchive = false; QString m_inputFilePath; QString m_outputFilePath; QString m_outputDirectory; }; void VerifyOpaqueTask::Private::slotResult(const VerificationResult &result, const QByteArray &plainText) { updateKeys(result); { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLogEntry auditLog = auditLogFromSender(q->sender()); if (m_output) { if (result.error().code()) { m_output->cancel(); } else { try { kleo_assert(!result.isNull()); m_output->finalize(); } catch (const GpgME::Exception &e) { q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } catch (const std::exception &e) { q->emitResult( q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); return; } catch (...) { q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); return; } } } const int drErr = result.error().code(); const QString errorString = m_output ? m_output->errorString() : QString{}; if (((drErr == GPG_ERR_EIO || drErr == GPG_ERR_NO_DATA) && !errorString.isEmpty()) || (m_output && m_output->failed())) { q->emitResult(q->fromVerifyOpaqueResult(result.error() ? result.error() : Error::fromCode(GPG_ERR_EIO), errorString, auditLog)); return; } q->emitResult(q->fromVerifyOpaqueResult(result, plainText, auditLog)); } VerifyOpaqueTask::VerifyOpaqueTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } VerifyOpaqueTask::~VerifyOpaqueTask() { } void VerifyOpaqueTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyOpaqueTask::setOutput(const std::shared_ptr &output) { d->m_output = output; kleo_assert(d->m_output && d->m_output->ioDevice()); } void VerifyOpaqueTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void VerifyOpaqueTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly); } setProtocol(p); } QString VerifyOpaqueTask::label() const { return i18n("Verifying %1...", inputLabel()); } unsigned long long VerifyOpaqueTask::inputSize() const { return d->m_input ? d->m_input->size() : 0; } QString VerifyOpaqueTask::inputLabel() const { return d->m_input ? d->m_input->label() : QFileInfo{d->m_inputFilePath}.fileName(); } QString VerifyOpaqueTask::outputLabel() const { if (d->m_output) { return d->m_output->label(); } else if (!d->m_outputFilePath.isEmpty()) { return QFileInfo{d->m_outputFilePath}.fileName(); } else { return d->m_outputDirectory; } } Protocol VerifyOpaqueTask::protocol() const { return d->m_protocol; } void VerifyOpaqueTask::setExtractArchive(bool extract) { d->m_extractArchive = extract; } void VerifyOpaqueTask::setInputFile(const QString &path) { d->m_inputFilePath = path; } void VerifyOpaqueTask::setOutputFile(const QString &path) { d->m_outputFilePath = path; } void VerifyOpaqueTask::setOutputDirectory(const QString &directory) { d->m_outputDirectory = directory; } void VerifyOpaqueTask::doStart() { kleo_assert(d->m_backend); if (d->m_extractArchive && archiveJobsCanBeUsed(d->m_protocol)) { d->startDecryptVerifyArchiveJob(); } else { d->startVerifyOpaqueJob(); } } void VerifyOpaqueTask::Private::startVerifyOpaqueJob() { #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (!m_outputFilePath.isEmpty() && QFile::exists(m_outputFilePath)) { // The output files are always written to a temporary location. Therefore, this can only occur // if two signed/encrypted files with the same name in different folders are verified/decrypted // because they would be written to the same temporary location. QMetaObject::invokeMethod( q, [this]() { slotResult(VerificationResult{Error::fromCode(GPG_ERR_EEXIST)}); }, Qt::QueuedConnection); return; } #endif try { std::unique_ptr job{m_backend->verifyOpaqueJob()}; kleo_assert(job); connect(job.get(), &QGpgME::VerifyOpaqueJob::result, q, [this](const GpgME::VerificationResult &result, const QByteArray &plainText) { slotResult(result, plainText); }); connect(job.get(), &QGpgME::Job::jobProgress, q, &VerifyOpaqueTask::setProgress); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (!m_inputFilePath.isEmpty() && !m_outputFilePath.isEmpty()) { job->setInputFile(m_inputFilePath); job->setOutputFile(m_outputFilePath); const auto err = job->startIt(); } else { ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr); job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr()); } #else ensureIOOpen(m_input->ioDevice().get(), m_output ? m_output->ioDevice().get() : nullptr); job->start(m_input->ioDevice(), m_output ? m_output->ioDevice() : std::shared_ptr()); #endif q->setJob(job.release()); } catch (const GpgME::Exception &e) { q->emitResult(q->fromVerifyOpaqueResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry())); } catch (const std::exception &e) { q->emitResult( q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry())); } catch (...) { q->emitResult(q->fromVerifyOpaqueResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry())); } } void VerifyOpaqueTask::Private::startDecryptVerifyArchiveJob() { std::unique_ptr job{m_backend->decryptVerifyArchiveJob()}; kleo_assert(job); connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::result, q, [this](const DecryptionResult &, const VerificationResult &verifyResult) { slotResult(verifyResult); }); connect(job.get(), &QGpgME::DecryptVerifyArchiveJob::dataProgress, q, &VerifyOpaqueTask::setProgress); #if QGPGME_ARCHIVE_JOBS_SUPPORT_INPUT_FILENAME // make sure that we don't use an existing output directory const auto outputDirectory = ensureUniqueDirectory(m_outputDirectory); if (outputDirectory.isEmpty()) { q->emitResult(q->fromDecryptVerifyResult(Error::fromCode(GPG_ERR_GENERAL), {}, {})); return; } m_outputDirectory = outputDirectory; job->setInputFile(m_inputFilePath); job->setOutputDirectory(m_outputDirectory); const auto err = job->startIt(); #else ensureIOOpen(m_input->ioDevice().get(), nullptr); job->setOutputDirectory(m_outputDirectory); const auto err = job->start(m_input->ioDevice()); #endif q->setJob(job.release()); if (err) { q->emitResult(q->fromVerifyOpaqueResult(err, {}, {})); } } class VerifyDetachedTask::Private { VerifyDetachedTask *const q; public: explicit Private(VerifyDetachedTask *qq) : q{qq} { } void slotResult(const VerificationResult &); void registerJob(QGpgME::VerifyDetachedJob *job) { q->connect(job, SIGNAL(result(GpgME::VerificationResult)), q, SLOT(slotResult(GpgME::VerificationResult))); q->connect(job, &QGpgME::Job::jobProgress, q, &VerifyDetachedTask::setProgress); } QString signatureLabel() const; QString signedDataLabel() const; std::shared_ptr m_input, m_signedData; const QGpgME::Protocol *m_backend = nullptr; Protocol m_protocol = UnknownProtocol; QString m_signatureFilePath; QString m_signedFilePath; }; void VerifyDetachedTask::Private::slotResult(const VerificationResult &result) { updateKeys(result); { std::stringstream ss; ss << result; qCDebug(KLEOPATRA_LOG) << ss.str().c_str(); } const AuditLogEntry auditLog = auditLogFromSender(q->sender()); try { kleo_assert(!result.isNull()); q->emitResult(q->fromVerifyDetachedResult(result, auditLog)); } catch (const GpgME::Exception &e) { q->emitResult(q->fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); } catch (const std::exception &e) { q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), auditLog)); } catch (...) { q->emitResult(q->fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), auditLog)); } } QString VerifyDetachedTask::Private::signatureLabel() const { return m_input ? m_input->label() : m_signatureFilePath; } QString VerifyDetachedTask::Private::signedDataLabel() const { return m_signedData ? m_signedData->label() : m_signedFilePath; } VerifyDetachedTask::VerifyDetachedTask(QObject *parent) : AbstractDecryptVerifyTask(parent) , d(new Private(this)) { } VerifyDetachedTask::~VerifyDetachedTask() { } void VerifyDetachedTask::setInput(const std::shared_ptr &input) { d->m_input = input; kleo_assert(d->m_input && d->m_input->ioDevice()); } void VerifyDetachedTask::setSignedData(const std::shared_ptr &signedData) { d->m_signedData = signedData; kleo_assert(d->m_signedData && d->m_signedData->ioDevice()); } void VerifyDetachedTask::setSignatureFile(const QString &path) { d->m_signatureFilePath = path; } void VerifyDetachedTask::setSignedFile(const QString &path) { d->m_signedFilePath = path; } void VerifyDetachedTask::setProtocol(Protocol prot) { kleo_assert(prot != UnknownProtocol); d->m_protocol = prot; d->m_backend = (prot == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(d->m_backend); } void VerifyDetachedTask::autodetectProtocolFromInput() { if (!d->m_input) { return; } const Protocol p = findProtocol(d->m_input->classification()); if (p == UnknownProtocol) { throw Exception(gpg_error(GPG_ERR_NOTHING_FOUND), i18n("Could not determine whether this is an S/MIME or an OpenPGP signature - maybe it is not a signature at all?"), Exception::MessageOnly); } setProtocol(p); } unsigned long long VerifyDetachedTask::inputSize() const { return d->m_signedData ? d->m_signedData->size() : 0; } QString VerifyDetachedTask::label() const { const QString signedDataLabel = d->signedDataLabel(); if (!signedDataLabel.isEmpty()) { return xi18nc( "Verification of a detached signature in progress. The first file contains the data." "The second file is the signature file.", "Verifying %1 with %2...", signedDataLabel, d->signatureLabel()); } return i18n("Verifying signature %1...", d->signatureLabel()); } QString VerifyDetachedTask::inputLabel() const { const QString signatureLabel = d->signatureLabel(); const QString signedDataLabel = d->signedDataLabel(); if (!signedDataLabel.isEmpty() && !signatureLabel.isEmpty()) { return xi18nc( "Verification of a detached signature summary. The first file contains the data." "The second file is signature.", "Verified %1 with %2", signedDataLabel, signatureLabel); } return signatureLabel; } QString VerifyDetachedTask::outputLabel() const { return QString(); } Protocol VerifyDetachedTask::protocol() const { return d->m_protocol; } void VerifyDetachedTask::doStart() { kleo_assert(d->m_backend); try { std::unique_ptr job{d->m_backend->verifyDetachedJob()}; kleo_assert(job); d->registerJob(job.get()); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (d->m_protocol == GpgME::OpenPGP && !d->m_signatureFilePath.isEmpty() && !d->m_signedFilePath.isEmpty()) { job->setSignatureFile(d->m_signatureFilePath); job->setSignedFile(d->m_signedFilePath); job->startIt(); } else { ensureIOOpen(d->m_input->ioDevice().get(), nullptr); ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr); job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice()); } #else ensureIOOpen(d->m_input->ioDevice().get(), nullptr); ensureIOOpen(d->m_signedData->ioDevice().get(), nullptr); job->start(d->m_input->ioDevice(), d->m_signedData->ioDevice()); #endif setJob(job.release()); } catch (const GpgME::Exception &e) { emitResult(fromVerifyDetachedResult(e.error(), QString::fromLocal8Bit(e.what()), AuditLogEntry())); } catch (const std::exception &e) { emitResult( fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught exception: %1", QString::fromLocal8Bit(e.what())), AuditLogEntry())); } catch (...) { emitResult(fromVerifyDetachedResult(Error::fromCode(GPG_ERR_INTERNAL), i18n("Caught unknown exception"), AuditLogEntry())); } } #include "moc_decryptverifytask.cpp" diff --git a/src/crypto/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp index f0effd478..8554add5f 100644 --- a/src/crypto/gui/certificatelineedit.cpp +++ b/src/crypto/gui/certificatelineedit.cpp @@ -1,815 +1,815 @@ /* crypto/gui/certificatelineedit.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "certificatelineedit.h" #include "commands/detailscommand.h" #include "dialogs/groupdetailsdialog.h" #include "utils/accessibility.h" #include "view/errorlabel.h" #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) Q_DECLARE_METATYPE(KeyGroup) static QStringList s_lookedUpKeys; namespace { class CompletionProxyModel : public KeyListSortFilterProxyModel { Q_OBJECT public: CompletionProxyModel(QObject *parent = nullptr) : KeyListSortFilterProxyModel(parent) { } int columnCount(const QModelIndex &parent = QModelIndex()) const override { Q_UNUSED(parent) // pretend that there is only one column to workaround a bug in // QAccessibleTable which provides the accessibility interface for the // completion pop-up return 1; } QVariant data(const QModelIndex &idx, int role) const override { if (!idx.isValid()) { return QVariant(); } switch (role) { case Qt::DecorationRole: { const auto key = KeyListSortFilterProxyModel::data(idx, KeyList::KeyRole).value(); if (!key.isNull()) { return Kleo::Formatting::iconForUid(key.userID(0)); } const auto group = KeyListSortFilterProxyModel::data(idx, KeyList::GroupRole).value(); if (!group.isNull()) { return QIcon::fromTheme(QStringLiteral("group")); } Q_ASSERT(!key.isNull() || !group.isNull()); return QVariant(); } default: return KeyListSortFilterProxyModel::data(index(idx.row(), KeyList::Summary), role); } } private: bool lessThan(const QModelIndex &left, const QModelIndex &right) const override { const auto leftKey = sourceModel()->data(left, KeyList::KeyRole).value(); const auto leftGroup = leftKey.isNull() ? sourceModel()->data(left, KeyList::GroupRole).value() : KeyGroup{}; const auto rightKey = sourceModel()->data(right, KeyList::KeyRole).value(); const auto rightGroup = rightKey.isNull() ? sourceModel()->data(right, KeyList::GroupRole).value() : KeyGroup{}; // shouldn't happen, but still put null entries at the end if (leftKey.isNull() && leftGroup.isNull()) { return false; } if (rightKey.isNull() && rightGroup.isNull()) { return true; } // first sort by the displayed name and/or email address const auto leftNameAndOrEmail = leftGroup.isNull() ? Formatting::nameAndEmailForSummaryLine(leftKey) : leftGroup.name(); const auto rightNameAndOrEmail = rightGroup.isNull() ? Formatting::nameAndEmailForSummaryLine(rightKey) : rightGroup.name(); const int cmp = QString::localeAwareCompare(leftNameAndOrEmail, rightNameAndOrEmail); if (cmp) { return cmp < 0; } // then sort groups before certificates if (!leftGroup.isNull() && !rightKey.isNull()) { return true; // left is group, right is certificate } if (!leftKey.isNull() && !rightGroup.isNull()) { return false; // left is certificate, right is group } // if both are groups (with identical names) sort them by their ID if (!leftGroup.isNull() && !rightGroup.isNull()) { return leftGroup.id() < rightGroup.id(); } // sort certificates with same name/email by validity and creation time const auto lUid = leftKey.userID(0); const auto rUid = rightKey.userID(0); if (lUid.validity() != rUid.validity()) { return lUid.validity() > rUid.validity(); } /* Both have the same validity, check which one is newer. */ time_t leftTime = 0; for (const GpgME::Subkey &s : leftKey.subkeys()) { if (s.isBad()) { continue; } if (s.creationTime() > leftTime) { leftTime = s.creationTime(); } } time_t rightTime = 0; for (const GpgME::Subkey &s : rightKey.subkeys()) { if (s.isBad()) { continue; } if (s.creationTime() > rightTime) { rightTime = s.creationTime(); } } if (rightTime != leftTime) { return leftTime > rightTime; } // as final resort we compare the fingerprints return strcmp(leftKey.primaryFingerprint(), rightKey.primaryFingerprint()) < 0; } }; auto createSeparatorAction(QObject *parent) { auto action = new QAction{parent}; action->setSeparator(true); return action; } } // namespace class CertificateLineEdit::Private { CertificateLineEdit *q; public: enum class Status { Empty, //< text is empty Success, //< a certificate or group is set None, //< entered text does not match any certificates or groups Ambiguous, //< entered text matches multiple certificates or groups }; enum class CursorPositioning { MoveToEnd, KeepPosition, MoveToStart, Default = MoveToEnd, }; explicit Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter); QString text() const; void setKey(const GpgME::Key &key); void setGroup(const KeyGroup &group); void setKeyFilter(const std::shared_ptr &filter); void setAccessibleName(const QString &s); private: void updateKey(CursorPositioning positioning); void editChanged(); void editFinished(); void checkLocate(); void onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector &keys); void openDetailsDialog(); void setTextWithBlockedSignals(const QString &s, CursorPositioning positioning); void showContextMenu(const QPoint &pos); QString errorMessage() const; QIcon statusIcon() const; QString statusToolTip() const; void updateStatusAction(); void updateErrorLabel(); void updateAccessibleNameAndDescription(); public: Status mStatus = Status::Empty; bool mEditingInProgress = false; GpgME::Key mKey; KeyGroup mGroup; struct Ui { explicit Ui(QWidget *parent) : lineEdit{parent} , button{parent} , errorLabel{parent} { } QLineEdit lineEdit; QToolButton button; ErrorLabel errorLabel; } ui; private: QString mAccessibleName; KeyListSortFilterProxyModel *const mFilterModel; CompletionProxyModel *const mCompleterFilterModel; QCompleter *mCompleter = nullptr; std::shared_ptr mFilter; QAction *const mStatusAction; QAction *const mShowDetailsAction; QPointer mLocateJob; Formatting::IconProvider mIconProvider; }; CertificateLineEdit::Private::Private(CertificateLineEdit *qq, AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter) : q{qq} , ui{qq} , mFilterModel{new KeyListSortFilterProxyModel{qq}} , mCompleterFilterModel{new CompletionProxyModel{qq}} , mCompleter{new QCompleter{qq}} , mFilter{std::shared_ptr{filter}} , mStatusAction{new QAction{qq}} , mShowDetailsAction{new QAction{qq}} , mIconProvider{usage} { ui.lineEdit.setPlaceholderText(i18n("Please enter a name or email address...")); ui.lineEdit.setClearButtonEnabled(true); ui.lineEdit.setContextMenuPolicy(Qt::CustomContextMenu); ui.lineEdit.addAction(mStatusAction, QLineEdit::LeadingPosition); mCompleterFilterModel->setKeyFilter(mFilter); mCompleterFilterModel->setSourceModel(model); // initialize dynamic sorting mCompleterFilterModel->sort(0); mCompleter->setModel(mCompleterFilterModel); mCompleter->setFilterMode(Qt::MatchContains); mCompleter->setCaseSensitivity(Qt::CaseInsensitive); ui.lineEdit.setCompleter(mCompleter); ui.button.setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new"))); ui.button.setToolTip(i18n("Show certificate list")); ui.button.setAccessibleName(i18n("Show certificate list")); ui.errorLabel.setVisible(false); auto vbox = new QVBoxLayout{q}; vbox->setContentsMargins(0, 0, 0, 0); auto l = new QHBoxLayout; l->setContentsMargins(0, 0, 0, 0); l->addWidget(&ui.lineEdit); l->addWidget(&ui.button); vbox->addLayout(l); vbox->addWidget(&ui.errorLabel); q->setFocusPolicy(ui.lineEdit.focusPolicy()); q->setFocusProxy(&ui.lineEdit); mShowDetailsAction->setIcon(QIcon::fromTheme(QStringLiteral("help-about"))); mShowDetailsAction->setText(i18nc("@action:inmenu", "Show Details")); mShowDetailsAction->setEnabled(false); mFilterModel->setSourceModel(model); mFilterModel->setFilterKeyColumn(KeyList::Summary); if (filter) { mFilterModel->setKeyFilter(mFilter); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { updateKey(CursorPositioning::KeepPosition); }); connect(KeyCache::instance().get(), &Kleo::KeyCache::groupUpdated, q, [this](const KeyGroup &group) { if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) { setTextWithBlockedSignals(Formatting::summaryLine(group), CursorPositioning::KeepPosition); // queue the update to ensure that the model has been updated QMetaObject::invokeMethod( q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection); } }); connect(KeyCache::instance().get(), &Kleo::KeyCache::groupRemoved, q, [this](const KeyGroup &group) { if (!mGroup.isNull() && mGroup.source() == group.source() && mGroup.id() == group.id()) { mGroup = KeyGroup(); QSignalBlocker blocky{&ui.lineEdit}; ui.lineEdit.clear(); // queue the update to ensure that the model has been updated QMetaObject::invokeMethod( q, [this]() { updateKey(CursorPositioning::KeepPosition); }, Qt::QueuedConnection); } }); connect(&ui.lineEdit, &QLineEdit::editingFinished, q, [this]() { // queue the call of editFinished() to ensure that QCompleter::activated is handled first QMetaObject::invokeMethod( q, [this]() { editFinished(); }, Qt::QueuedConnection); }); connect(&ui.lineEdit, &QLineEdit::textChanged, q, [this]() { editChanged(); }); connect(&ui.lineEdit, &QLineEdit::customContextMenuRequested, q, [this](const QPoint &pos) { showContextMenu(pos); }); connect(mStatusAction, &QAction::triggered, q, [this]() { openDetailsDialog(); }); connect(mShowDetailsAction, &QAction::triggered, q, [this]() { openDetailsDialog(); }); connect(&ui.button, &QToolButton::clicked, q, &CertificateLineEdit::certificateSelectionRequested); connect(mCompleter, qOverload(&QCompleter::activated), q, [this](const QModelIndex &index) { Key key = mCompleter->completionModel()->data(index, KeyList::KeyRole).value(); auto group = mCompleter->completionModel()->data(index, KeyList::GroupRole).value(); if (!key.isNull()) { q->setKey(key); } else if (!group.isNull()) { q->setGroup(group); } else { qCDebug(KLEOPATRA_LOG) << "Activated item is neither key nor group"; } // queue the call of editFinished() to ensure that QLineEdit finished its own work QMetaObject::invokeMethod( q, [this]() { editFinished(); }, Qt::QueuedConnection); }); updateKey(CursorPositioning::Default); } void CertificateLineEdit::Private::openDetailsDialog() { if (!q->key().isNull()) { auto cmd = new Commands::DetailsCommand{q->key()}; cmd->setParentWidget(q); cmd->start(); } else if (!q->group().isNull()) { auto dlg = new Dialogs::GroupDetailsDialog{q}; dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setGroup(q->group()); dlg->show(); } } void CertificateLineEdit::Private::setTextWithBlockedSignals(const QString &s, CursorPositioning positioning) { QSignalBlocker blocky{&ui.lineEdit}; const auto cursorPos = ui.lineEdit.cursorPosition(); ui.lineEdit.setText(s); switch (positioning) { case CursorPositioning::KeepPosition: ui.lineEdit.setCursorPosition(cursorPos); break; case CursorPositioning::MoveToStart: ui.lineEdit.setCursorPosition(0); break; case CursorPositioning::MoveToEnd: default:; // setText() already moved the cursor to the end of the line }; } void CertificateLineEdit::Private::showContextMenu(const QPoint &pos) { if (QMenu *menu = ui.lineEdit.createStandardContextMenu()) { auto *const firstStandardAction = menu->actions().value(0); menu->insertActions(firstStandardAction, {mShowDetailsAction, createSeparatorAction(menu)}); menu->setAttribute(Qt::WA_DeleteOnClose); menu->popup(ui.lineEdit.mapToGlobal(pos)); } } CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model, KeyUsage::Flags usage, KeyFilter *filter, QWidget *parent) : QWidget{parent} , d{new Private{this, model, usage, filter}} { /* Take ownership of the model to prevent double deletion when the * filter models are deleted */ model->setParent(parent ? parent : this); } CertificateLineEdit::~CertificateLineEdit() = default; void CertificateLineEdit::Private::editChanged() { const bool editingStarted = !mEditingInProgress; mEditingInProgress = true; updateKey(CursorPositioning::Default); if (editingStarted) { Q_EMIT q->editingStarted(); } if (q->isEmpty()) { Q_EMIT q->cleared(); } } void CertificateLineEdit::Private::editFinished() { // perform a first update with the "editing in progress" flag still set updateKey(CursorPositioning::MoveToStart); mEditingInProgress = false; checkLocate(); // perform another update with the "editing in progress" flag cleared // after a key locate may have been started; this makes sure that displaying // an error is delayed until the key locate job has finished updateKey(CursorPositioning::MoveToStart); } void CertificateLineEdit::Private::checkLocate() { if (mStatus != Status::None) { // try to locate key only if text matches no local certificates or groups return; } // Only check once per mailbox const auto mailText = ui.lineEdit.text().trimmed(); if (mailText.isEmpty() || s_lookedUpKeys.contains(mailText)) { return; } s_lookedUpKeys << mailText; if (mLocateJob) { mLocateJob->slotCancel(); mLocateJob.clear(); } auto job = QGpgME::openpgp()->locateKeysJob(); connect(job, &QGpgME::KeyListJob::result, q, [this, job, mailText](const KeyListResult &result, const std::vector &keys) { onLocateJobResult(job, mailText, result, keys); }); if (auto err = job->start({mailText}, /*secretOnly=*/false)) { qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Starting" << job << "for" << mailText << "failed with" << Formatting::errorAsString(err); } else { mLocateJob = job; qCDebug(KLEOPATRA_LOG) << __func__ << "Started" << job << "for" << mailText; } } void CertificateLineEdit::Private::onLocateJobResult(QGpgME::Job *job, const QString &email, const KeyListResult &result, const std::vector &keys) { if (mLocateJob != job) { qCDebug(KLEOPATRA_LOG) << __func__ << "Ignoring outdated finished" << job << "for" << email; return; } qCDebug(KLEOPATRA_LOG) << __func__ << job << "for" << email << "finished with" << Formatting::errorAsString(result.error()) << "and keys" << keys; mLocateJob.clear(); if (!keys.empty() && !keys.front().isNull()) { KeyCache::mutableInstance()->insert(keys.front()); // inserting the key implicitly triggers an update } else { // explicitly trigger an update to display "no key" error updateKey(CursorPositioning::MoveToStart); } } void CertificateLineEdit::Private::updateKey(CursorPositioning positioning) { static const _detail::ByFingerprint keysHaveSameFingerprint; const auto mailText = ui.lineEdit.text().trimmed(); auto newKey = Key(); auto newGroup = KeyGroup(); if (mailText.isEmpty()) { mStatus = Status::Empty; } else { mFilterModel->setFilterRegularExpression(QRegularExpression::escape(mailText)); if (mFilterModel->rowCount() > 1) { // keep current key or group if they still match if (!mKey.isNull()) { for (int row = 0; row < mFilterModel->rowCount(); ++row) { const QModelIndex index = mFilterModel->index(row, 0); Key key = mFilterModel->key(index); if (!key.isNull() && keysHaveSameFingerprint(key, mKey)) { newKey = mKey; break; } } } else if (!mGroup.isNull()) { newGroup = mGroup; for (int row = 0; row < mFilterModel->rowCount(); ++row) { const QModelIndex index = mFilterModel->index(row, 0); KeyGroup group = mFilterModel->group(index); if (!group.isNull() && group.source() == mGroup.source() && group.id() == mGroup.id()) { newGroup = mGroup; break; } } } if (newKey.isNull() && newGroup.isNull()) { mStatus = Status::Ambiguous; } } else if (mFilterModel->rowCount() == 1) { const auto index = mFilterModel->index(0, 0); newKey = mFilterModel->data(index, KeyList::KeyRole).value(); newGroup = mFilterModel->data(index, KeyList::GroupRole).value(); Q_ASSERT(!newKey.isNull() || !newGroup.isNull()); if (newKey.isNull() && newGroup.isNull()) { mStatus = Status::None; } } else { mStatus = Status::None; } } mKey = newKey; mGroup = newGroup; if (!mKey.isNull()) { /* FIXME: This needs to be solved by a multiple UID supporting model */ mStatus = Status::Success; ui.lineEdit.setToolTip(Formatting::toolTip(mKey, Formatting::ToolTipOption::AllOptions)); if (!mEditingInProgress) { setTextWithBlockedSignals(Formatting::summaryLine(mKey), positioning); } } else if (!mGroup.isNull()) { mStatus = Status::Success; ui.lineEdit.setToolTip(Formatting::toolTip(mGroup, Formatting::ToolTipOption::AllOptions)); if (!mEditingInProgress) { setTextWithBlockedSignals(Formatting::summaryLine(mGroup), positioning); } } else { ui.lineEdit.setToolTip({}); } mShowDetailsAction->setEnabled(mStatus == Status::Success); updateStatusAction(); updateErrorLabel(); Q_EMIT q->keyChanged(); } QString CertificateLineEdit::Private::errorMessage() const { switch (mStatus) { case Status::Empty: case Status::Success: return {}; case Status::None: return i18n("No matching certificates or groups found"); case Status::Ambiguous: return i18n("Multiple matching certificates or groups found"); default: qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast(mStatus); Q_ASSERT(!"Invalid status"); }; return {}; } QIcon CertificateLineEdit::Private::statusIcon() const { switch (mStatus) { case Status::Empty: return QIcon::fromTheme(QStringLiteral("emblem-unavailable")); case Status::Success: if (!mKey.isNull()) { return mIconProvider.icon(mKey); } else if (!mGroup.isNull()) { return mIconProvider.icon(mGroup); } else { qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group."; return {}; } case Status::None: case Status::Ambiguous: if (mEditingInProgress || mLocateJob) { return QIcon::fromTheme(QStringLiteral("emblem-question")); } else { return QIcon::fromTheme(QStringLiteral("emblem-error")); } default: qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast(mStatus); Q_ASSERT(!"Invalid status"); }; return {}; } QString CertificateLineEdit::Private::statusToolTip() const { switch (mStatus) { case Status::Empty: return {}; case Status::Success: if (!mKey.isNull()) { return Formatting::validity(mKey.userID(0)); } else if (!mGroup.isNull()) { return Formatting::validity(mGroup); } else { qDebug(KLEOPATRA_LOG) << __func__ << "Success, but neither key nor group."; return {}; } case Status::None: case Status::Ambiguous: return errorMessage(); default: qDebug(KLEOPATRA_LOG) << __func__ << "Invalid status:" << static_cast(mStatus); Q_ASSERT(!"Invalid status"); }; return {}; } void CertificateLineEdit::Private::updateStatusAction() { mStatusAction->setIcon(statusIcon()); mStatusAction->setToolTip(statusToolTip()); } namespace { QString decoratedError(const QString &text) { return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text); } } void CertificateLineEdit::Private::updateErrorLabel() { const auto currentErrorMessage = ui.errorLabel.text(); const auto newErrorMessage = decoratedError(errorMessage()); if (newErrorMessage == currentErrorMessage) { return; } if (currentErrorMessage.isEmpty() && (mEditingInProgress || mLocateJob)) { // delay showing the error message until editing is finished, so that we // do not annoy the user with an error message while they are still // entering the recipient; // on the other hand, we clear the error message immediately if it does // not apply anymore and we update the error message immediately if it // changed return; } ui.errorLabel.setVisible(!newErrorMessage.isEmpty()); ui.errorLabel.setText(newErrorMessage); updateAccessibleNameAndDescription(); } void CertificateLineEdit::Private::setAccessibleName(const QString &s) { mAccessibleName = s; updateAccessibleNameAndDescription(); } void CertificateLineEdit::Private::updateAccessibleNameAndDescription() { // fall back to default accessible name if accessible name wasn't set explicitly if (mAccessibleName.isEmpty()) { mAccessibleName = getAccessibleName(&ui.lineEdit); } const bool errorShown = ui.errorLabel.isVisible(); // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); // emulate this by setting the error message as accessible description of the input field const auto description = errorShown ? ui.errorLabel.text() : QString{}; if (ui.lineEdit.accessibleDescription() != description) { ui.lineEdit.setAccessibleDescription(description); } // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); // screen readers say something like "invalid data" if this state is set; // emulate this by adding "invalid data" to the accessible name of the input field - const auto name = errorShown ? mAccessibleName + QLatin1String{", "} + invalidEntryText() // + const auto name = errorShown ? mAccessibleName + QLatin1StringView{", "} + invalidEntryText() // : mAccessibleName; if (ui.lineEdit.accessibleName() != name) { ui.lineEdit.setAccessibleName(name); } } Key CertificateLineEdit::key() const { if (isEnabled()) { return d->mKey; } else { return Key(); } } KeyGroup CertificateLineEdit::group() const { if (isEnabled()) { return d->mGroup; } else { return KeyGroup(); } } QString CertificateLineEdit::Private::text() const { return ui.lineEdit.text().trimmed(); } QString CertificateLineEdit::text() const { return d->text(); } void CertificateLineEdit::Private::setKey(const Key &key) { mKey = key; mGroup = KeyGroup(); qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(key); // position cursor, so that that the start of the summary is visible setTextWithBlockedSignals(Formatting::summaryLine(key), CursorPositioning::MoveToStart); updateKey(CursorPositioning::MoveToStart); } void CertificateLineEdit::setKey(const Key &key) { d->setKey(key); } void CertificateLineEdit::Private::setGroup(const KeyGroup &group) { mGroup = group; mKey = Key(); const QString summary = Formatting::summaryLine(group); qCDebug(KLEOPATRA_LOG) << "Setting KeyGroup. " << summary; // position cursor, so that that the start of the summary is visible setTextWithBlockedSignals(summary, CursorPositioning::MoveToStart); updateKey(CursorPositioning::MoveToStart); } void CertificateLineEdit::setGroup(const KeyGroup &group) { d->setGroup(group); } bool CertificateLineEdit::isEmpty() const { return d->mStatus == Private::Status::Empty; } bool CertificateLineEdit::isEditingInProgress() const { return d->mEditingInProgress; } bool CertificateLineEdit::hasAcceptableInput() const { return d->mStatus == Private::Status::Empty // || d->mStatus == Private::Status::Success; } void CertificateLineEdit::Private::setKeyFilter(const std::shared_ptr &filter) { mFilter = filter; mFilterModel->setKeyFilter(filter); mCompleterFilterModel->setKeyFilter(mFilter); updateKey(CursorPositioning::Default); } void CertificateLineEdit::setKeyFilter(const std::shared_ptr &filter) { d->setKeyFilter(filter); } void CertificateLineEdit::setAccessibleNameOfLineEdit(const QString &name) { d->setAccessibleName(name); } #include "certificatelineedit.moc" #include "moc_certificatelineedit.cpp" diff --git a/src/crypto/gui/resolverecipientspage.cpp b/src/crypto/gui/resolverecipientspage.cpp index d3808d889..72de3407d 100644 --- a/src/crypto/gui/resolverecipientspage.cpp +++ b/src/crypto/gui/resolverecipientspage.cpp @@ -1,667 +1,667 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resolverecipientspage.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 "resolverecipientspage.h" #include "resolverecipientspage_p.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Dialogs; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace KMime::Types; ResolveRecipientsPage::ListWidget::ListWidget(QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) , m_protocol(UnknownProtocol) { m_listWidget = new QListWidget; m_listWidget->setSelectionMode(QAbstractItemView::MultiSelection); auto const layout = new QVBoxLayout(this); layout->addWidget(m_listWidget); connect(m_listWidget, &QListWidget::itemSelectionChanged, this, &ListWidget::onSelectionChange); } ResolveRecipientsPage::ListWidget::~ListWidget() { } void ResolveRecipientsPage::ListWidget::onSelectionChange() { const auto widgetskeys = widgets.keys(); for (const QString &i : widgetskeys) { Q_ASSERT(items.contains(i)); widgets[i]->setSelected(items[i]->isSelected()); } Q_EMIT selectionChanged(); } void ResolveRecipientsPage::ListWidget::addEntry(const Mailbox &mbox) { addEntry(mbox.prettyAddress(), mbox.prettyAddress(), mbox); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name) { addEntry(id, name, Mailbox()); } void ResolveRecipientsPage::ListWidget::addEntry(const QString &id, const QString &name, const Mailbox &mbox) { Q_ASSERT(!widgets.contains(id) && !items.contains(id)); auto item = new QListWidgetItem; item->setData(IdRole, id); auto wid = new ItemWidget(id, name, mbox, this); connect(wid, &ItemWidget::changed, this, &ListWidget::completeChanged); wid->setProtocol(m_protocol); item->setSizeHint(wid->sizeHint()); m_listWidget->addItem(item); m_listWidget->setItemWidget(item, wid); widgets[id] = wid; items[id] = item; } Mailbox ResolveRecipientsPage::ListWidget::mailbox(const QString &id) const { return widgets.contains(id) ? widgets[id]->mailbox() : Mailbox(); } void ResolveRecipientsPage::ListWidget::setCertificates(const QString &id, const std::vector &pgp, const std::vector &cms) { Q_ASSERT(widgets.contains(id)); widgets[id]->setCertificates(pgp, cms); } Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id) const { return widgets.contains(id) ? widgets[id]->selectedCertificate() : Key(); } GpgME::Key ResolveRecipientsPage::ListWidget::selectedCertificate(const QString &id, GpgME::Protocol prot) const { return widgets.contains(id) ? widgets[id]->selectedCertificate(prot) : Key(); } QStringList ResolveRecipientsPage::ListWidget::identifiers() const { return widgets.keys(); } void ResolveRecipientsPage::ListWidget::setProtocol(GpgME::Protocol prot) { if (m_protocol == prot) { return; } m_protocol = prot; for (ItemWidget *i : std::as_const(widgets)) { i->setProtocol(prot); } } void ResolveRecipientsPage::ListWidget::removeEntry(const QString &id) { if (!widgets.contains(id)) { return; } delete items[id]; items.remove(id); delete widgets[id]; widgets.remove(id); } void ResolveRecipientsPage::ListWidget::showSelectionDialog(const QString &id) { if (!widgets.contains(id)) { return; } widgets[id]->showSelectionDialog(); } QStringList ResolveRecipientsPage::ListWidget::selectedEntries() const { QStringList entries; const QList items = m_listWidget->selectedItems(); entries.reserve(items.count()); for (const QListWidgetItem *i : items) { entries.append(i->data(IdRole).toString()); } return entries; } ResolveRecipientsPage::ItemWidget::ItemWidget(const QString &id, const QString &name, const Mailbox &mbox, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) , m_id(id) , m_mailbox(mbox) , m_protocol(UnknownProtocol) , m_selected(false) { Q_ASSERT(!m_id.isEmpty()); setAutoFillBackground(true); auto layout = new QHBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); layout->addSpacing(15); m_nameLabel = new QLabel; m_nameLabel->setText(name); layout->addWidget(m_nameLabel); layout->addStretch(); m_certLabel = new QLabel; m_certLabel->setText(i18n("No certificate selected")); layout->addWidget(m_certLabel); m_certCombo = new QComboBox; connect(m_certCombo, SIGNAL(currentIndexChanged(int)), this, SIGNAL(changed())); layout->addWidget(m_certCombo); m_selectButton = new QToolButton; m_selectButton->setText(i18n("...")); connect(m_selectButton, &QAbstractButton::clicked, this, &ItemWidget::showSelectionDialog); layout->addWidget(m_selectButton); layout->addSpacing(15); setCertificates(std::vector(), std::vector()); } void ResolveRecipientsPage::ItemWidget::updateVisibility() { m_certLabel->setVisible(m_certCombo->count() == 0); m_certCombo->setVisible(m_certCombo->count() > 0); } ResolveRecipientsPage::ItemWidget::~ItemWidget() { } QString ResolveRecipientsPage::ItemWidget::id() const { return m_id; } void ResolveRecipientsPage::ItemWidget::setSelected(bool selected) { if (m_selected == selected) { return; } m_selected = selected; setBackgroundRole(selected ? QPalette::Highlight : QPalette::Base); const QPalette::ColorRole foreground = selected ? QPalette::HighlightedText : QPalette::Text; setForegroundRole(foreground); m_nameLabel->setForegroundRole(foreground); m_certLabel->setForegroundRole(foreground); } bool ResolveRecipientsPage::ItemWidget::isSelected() const { return m_selected; } static CertificateSelectionDialog *createCertificateSelectionDialog(QWidget *parent, GpgME::Protocol prot) { auto const dlg = new CertificateSelectionDialog(parent); const CertificateSelectionDialog::Options options = // CertificateSelectionDialog::SingleSelection | // CertificateSelectionDialog::EncryptOnly | // CertificateSelectionDialog::MultiSelection | // CertificateSelectionDialog::optionsFromProtocol(prot); dlg->setOptions(options); return dlg; } void ResolveRecipientsPage::ItemWidget::showSelectionDialog() { QPointer dlg = createCertificateSelectionDialog(this, m_protocol); if (dlg->exec() == QDialog::Accepted && dlg /* still with us? */) { const GpgME::Key cert = dlg->selectedCertificate(); if (!cert.isNull()) { addCertificateToComboBox(cert); selectCertificateInComboBox(cert); } } delete dlg; } Mailbox ResolveRecipientsPage::ItemWidget::mailbox() const { return m_mailbox; } void ResolveRecipientsPage::ItemWidget::selectCertificateInComboBox(const Key &key) { - m_certCombo->setCurrentIndex(m_certCombo->findData(QLatin1String(key.keyID()))); + m_certCombo->setCurrentIndex(m_certCombo->findData(QLatin1StringView(key.keyID()))); } void ResolveRecipientsPage::ItemWidget::addCertificateToComboBox(const GpgME::Key &key) { m_certCombo->addItem(Formatting::formatForComboBox(key), QByteArray(key.keyID())); if (m_certCombo->count() == 1) { m_certCombo->setCurrentIndex(0); } updateVisibility(); } void ResolveRecipientsPage::ItemWidget::resetCertificates() { std::vector certs; Key selected; switch (m_protocol) { case OpenPGP: certs = m_pgp; break; case CMS: certs = m_cms; break; case UnknownProtocol: certs = m_cms; certs.insert(certs.end(), m_pgp.begin(), m_pgp.end()); } m_certCombo->clear(); for (const Key &i : std::as_const(certs)) { addCertificateToComboBox(i); } if (!m_selectedCertificates[m_protocol].isNull()) { selectCertificateInComboBox(m_selectedCertificates[m_protocol]); } else if (m_certCombo->count() > 0) { m_certCombo->setCurrentIndex(0); } updateVisibility(); Q_EMIT changed(); } void ResolveRecipientsPage::ItemWidget::setProtocol(Protocol prot) { if (m_protocol == prot) { return; } m_selectedCertificates[m_protocol] = selectedCertificate(); if (m_protocol != UnknownProtocol) { (m_protocol == OpenPGP ? m_pgp : m_cms) = certificates(); } m_protocol = prot; resetCertificates(); } void ResolveRecipientsPage::ItemWidget::setCertificates(const std::vector &pgp, const std::vector &cms) { m_pgp = pgp; m_cms = cms; resetCertificates(); } Key ResolveRecipientsPage::ItemWidget::selectedCertificate() const { return KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(m_certCombo->currentIndex(), ListWidget::IdRole).toString().toStdString()); } GpgME::Key ResolveRecipientsPage::ItemWidget::selectedCertificate(GpgME::Protocol prot) const { return prot == m_protocol ? selectedCertificate() : m_selectedCertificates.value(prot); } std::vector ResolveRecipientsPage::ItemWidget::certificates() const { std::vector certs; for (int i = 0; i < m_certCombo->count(); ++i) { certs.push_back(KeyCache::instance()->findByKeyIDOrFingerprint(m_certCombo->itemData(i, ListWidget::IdRole).toString().toStdString())); } return certs; } class ResolveRecipientsPage::Private { friend class ::Kleo::Crypto::Gui::ResolveRecipientsPage; ResolveRecipientsPage *const q; public: explicit Private(ResolveRecipientsPage *qq); ~Private(); void setSelectedProtocol(Protocol protocol); void selectionChanged(); void removeSelectedEntries(); void addRecipient(); void addRecipient(const Mailbox &mbox); void addRecipient(const QString &id, const QString &name); void updateProtocolRBVisibility(); void protocolSelected(int prot); void writeSelectedCertificatesToPreferences(); void completeChangedInternal(); private: ListWidget *m_listWidget; QPushButton *m_addButton; QPushButton *m_removeButton; QRadioButton *m_pgpRB; QRadioButton *m_cmsRB; QLabel *m_additionalRecipientsLabel; Protocol m_presetProtocol; Protocol m_selectedProtocol; bool m_multipleProtocolsAllowed; std::shared_ptr m_recipientPreferences; }; ResolveRecipientsPage::Private::Private(ResolveRecipientsPage *qq) : q(qq) , m_presetProtocol(UnknownProtocol) , m_selectedProtocol(m_presetProtocol) , m_multipleProtocolsAllowed(false) , m_recipientPreferences() { connect(q, SIGNAL(completeChanged()), q, SLOT(completeChangedInternal())); q->setTitle(i18n("Recipients")); auto const layout = new QVBoxLayout(q); m_listWidget = new ListWidget; connect(m_listWidget, SIGNAL(selectionChanged()), q, SLOT(selectionChanged())); connect(m_listWidget, &ListWidget::completeChanged, q, &WizardPage::completeChanged); layout->addWidget(m_listWidget); m_additionalRecipientsLabel = new QLabel; m_additionalRecipientsLabel->setWordWrap(true); layout->addWidget(m_additionalRecipientsLabel); m_additionalRecipientsLabel->setVisible(false); auto buttonWidget = new QWidget; auto buttonLayout = new QHBoxLayout(buttonWidget); buttonLayout->setContentsMargins(0, 0, 0, 0); m_addButton = new QPushButton; connect(m_addButton, SIGNAL(clicked()), q, SLOT(addRecipient())); m_addButton->setText(i18n("Add Recipient...")); buttonLayout->addWidget(m_addButton); m_removeButton = new QPushButton; m_removeButton->setEnabled(false); m_removeButton->setText(i18n("Remove Selected")); connect(m_removeButton, SIGNAL(clicked()), q, SLOT(removeSelectedEntries())); buttonLayout->addWidget(m_removeButton); buttonLayout->addStretch(); layout->addWidget(buttonWidget); auto protocolWidget = new QWidget; auto protocolLayout = new QHBoxLayout(protocolWidget); auto protocolGroup = new QButtonGroup(q); connect(protocolGroup, &QButtonGroup::idClicked, q, [this](int buttonClicked) { protocolSelected(buttonClicked); }); m_pgpRB = new QRadioButton; m_pgpRB->setText(i18n("OpenPGP")); protocolGroup->addButton(m_pgpRB, OpenPGP); protocolLayout->addWidget(m_pgpRB); m_cmsRB = new QRadioButton; m_cmsRB->setText(i18n("S/MIME")); protocolGroup->addButton(m_cmsRB, CMS); protocolLayout->addWidget(m_cmsRB); protocolLayout->addStretch(); layout->addWidget(protocolWidget); } ResolveRecipientsPage::Private::~Private() { } void ResolveRecipientsPage::Private::completeChangedInternal() { const bool isComplete = q->isComplete(); const std::vector keys = q->resolvedCertificates(); const bool haveSecret = std::find_if(keys.begin(), keys.end(), [](const Key &key) { return key.hasSecret(); }) != keys.end(); if (isComplete && !haveSecret) { q->setExplanation( i18n("Warning: None of the selected certificates seem to be your own. You will not be able to decrypt the encrypted data again.")); } else { q->setExplanation(QString()); } } void ResolveRecipientsPage::Private::updateProtocolRBVisibility() { const bool visible = !m_multipleProtocolsAllowed && m_presetProtocol == UnknownProtocol; m_cmsRB->setVisible(visible); m_pgpRB->setVisible(visible); if (visible) { if (m_selectedProtocol == CMS) { m_cmsRB->click(); } else { m_pgpRB->click(); } } } bool ResolveRecipientsPage::isComplete() const { const QStringList ids = d->m_listWidget->identifiers(); if (ids.isEmpty()) { return false; } for (const QString &i : ids) { if (d->m_listWidget->selectedCertificate(i).isNull()) { return false; } } return true; } ResolveRecipientsPage::ResolveRecipientsPage(QWidget *parent) : WizardPage(parent) , d(new Private(this)) { } ResolveRecipientsPage::~ResolveRecipientsPage() { } Protocol ResolveRecipientsPage::selectedProtocol() const { return d->m_selectedProtocol; } void ResolveRecipientsPage::Private::setSelectedProtocol(Protocol protocol) { if (m_selectedProtocol == protocol) { return; } m_selectedProtocol = protocol; m_listWidget->setProtocol(m_selectedProtocol); Q_EMIT q->selectedProtocolChanged(); } void ResolveRecipientsPage::Private::protocolSelected(int p) { const auto protocol = static_cast(p); Q_ASSERT(protocol != UnknownProtocol); setSelectedProtocol(protocol); } void ResolveRecipientsPage::setPresetProtocol(Protocol prot) { if (d->m_presetProtocol == prot) { return; } d->m_presetProtocol = prot; d->setSelectedProtocol(prot); if (prot != UnknownProtocol) { d->m_multipleProtocolsAllowed = false; } d->updateProtocolRBVisibility(); } Protocol ResolveRecipientsPage::presetProtocol() const { return d->m_presetProtocol; } bool ResolveRecipientsPage::multipleProtocolsAllowed() const { return d->m_multipleProtocolsAllowed; } void ResolveRecipientsPage::setMultipleProtocolsAllowed(bool allowed) { if (d->m_multipleProtocolsAllowed == allowed) { return; } d->m_multipleProtocolsAllowed = allowed; if (d->m_multipleProtocolsAllowed) { setPresetProtocol(UnknownProtocol); d->setSelectedProtocol(UnknownProtocol); } d->updateProtocolRBVisibility(); } void ResolveRecipientsPage::Private::addRecipient(const QString &id, const QString &name) { m_listWidget->addEntry(id, name); } void ResolveRecipientsPage::Private::addRecipient(const Mailbox &mbox) { m_listWidget->addEntry(mbox); } void ResolveRecipientsPage::Private::addRecipient() { QPointer dlg = createCertificateSelectionDialog(q, q->selectedProtocol()); if (dlg->exec() != QDialog::Accepted || !dlg /*q already deleted*/) { return; } const std::vector keys = dlg->selectedCertificates(); int i = 0; for (const Key &key : keys) { const QStringList existing = m_listWidget->identifiers(); QString rec = i18n("Recipient"); while (existing.contains(rec)) { rec = i18nc("%1 == number", "Recipient (%1)", ++i); } addRecipient(rec, rec); const std::vector pgp = key.protocol() == OpenPGP ? std::vector(1, key) : std::vector(); const std::vector cms = key.protocol() == CMS ? std::vector(1, key) : std::vector(); m_listWidget->setCertificates(rec, pgp, cms); } Q_EMIT q->completeChanged(); } static QString listKeysForInfo(const std::vector &keys) { QStringList list; std::transform(keys.begin(), keys.end(), list.begin(), &Formatting::formatKeyLink); - return list.join(QLatin1String("
    ")); + return list.join(QLatin1StringView("
    ")); } void ResolveRecipientsPage::setAdditionalRecipientsInfo(const std::vector &recipients) { d->m_additionalRecipientsLabel->setVisible(!recipients.empty()); if (recipients.empty()) { return; } d->m_additionalRecipientsLabel->setText(i18n("

    Recipients predefined via GnuPG settings:

    %1
    ", listKeysForInfo(recipients))); } std::vector ResolveRecipientsPage::resolvedCertificates() const { std::vector certs; const QStringList identifiers = d->m_listWidget->identifiers(); for (const QString &i : identifiers) { const GpgME::Key cert = d->m_listWidget->selectedCertificate(i); if (!cert.isNull()) { certs.push_back(cert); } } return certs; } void ResolveRecipientsPage::Private::selectionChanged() { m_removeButton->setEnabled(!m_listWidget->selectedEntries().isEmpty()); } void ResolveRecipientsPage::Private::removeSelectedEntries() { const auto selectedEntries{m_listWidget->selectedEntries()}; for (const QString &i : selectedEntries) { m_listWidget->removeEntry(i); } Q_EMIT q->completeChanged(); } void ResolveRecipientsPage::setRecipientsUserMutable(bool isMutable) { d->m_addButton->setVisible(isMutable); d->m_removeButton->setVisible(isMutable); } bool ResolveRecipientsPage::recipientsUserMutable() const { return d->m_addButton->isVisible(); } std::shared_ptr ResolveRecipientsPage::recipientPreferences() const { return d->m_recipientPreferences; } void ResolveRecipientsPage::setRecipientPreferences(const std::shared_ptr &prefs) { d->m_recipientPreferences = prefs; } void ResolveRecipientsPage::Private::writeSelectedCertificatesToPreferences() { if (!m_recipientPreferences) { return; } const auto identifiers{m_listWidget->identifiers()}; for (const QString &i : identifiers) { const Mailbox mbox = m_listWidget->mailbox(i); if (!mbox.hasAddress()) { continue; } const Key pgp = m_listWidget->selectedCertificate(i, OpenPGP); if (!pgp.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, OpenPGP, pgp); } const Key cms = m_listWidget->selectedCertificate(i, CMS); if (!cms.isNull()) { m_recipientPreferences->setPreferredCertificate(mbox, CMS, cms); } } } void ResolveRecipientsPage::onNext() { d->writeSelectedCertificatesToPreferences(); } #include "moc_resolverecipientspage.cpp" #include "moc_resolverecipientspage_p.cpp" diff --git a/src/crypto/gui/resultitemwidget.cpp b/src/crypto/gui/resultitemwidget.cpp index 1dcf51358..0dca94dfb 100644 --- a/src/crypto/gui/resultitemwidget.cpp +++ b/src/crypto/gui/resultitemwidget.cpp @@ -1,375 +1,375 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resultitemwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 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 "resultitemwidget.h" #include "commands/command.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "crypto/decryptverifytask.h" #include "view/htmllabel.h" #include "view/urllabel.h" #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; namespace { // TODO move out of here static QColor colorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color(); case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NormalBackground).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color(); case Task::Result::NeutralSuccess: default: return QColor(0x00, 0x80, 0xFF); // light blue } } static QColor txtColorForVisualCode(Task::Result::VisualCode code) { switch (code) { case Task::Result::AllGood: case Task::Result::NeutralError: case Task::Result::Warning: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NormalText).color(); case Task::Result::Danger: return KColorScheme(QPalette::Active, KColorScheme::View).foreground(KColorScheme::NegativeText).color(); case Task::Result::NeutralSuccess: default: return QColor(0xFF, 0xFF, 0xFF); // white } } } class ResultItemWidget::Private { ResultItemWidget *const q; public: explicit Private(const std::shared_ptr &result, ResultItemWidget *qq) : q(qq) , m_result(result) { Q_ASSERT(m_result); } void slotLinkActivated(const QString &); void updateShowDetailsLabel(); void addKeyImportButton(QBoxLayout *lay, bool search); void addIgnoreMDCButton(QBoxLayout *lay); void oneImportFinished(); const std::shared_ptr m_result; UrlLabel *m_auditLogLabel = nullptr; QPushButton *m_closeButton = nullptr; QPushButton *m_showButton = nullptr; bool m_importCanceled = false; }; void ResultItemWidget::Private::oneImportFinished() { if (m_importCanceled) { return; } if (m_result->parentTask()) { m_result->parentTask()->start(); } q->setVisible(false); } void ResultItemWidget::Private::addIgnoreMDCButton(QBoxLayout *lay) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto decResult = dvResult->decryptionResult(); if (decResult.isNull() || !decResult.error() || !decResult.isLegacyCipherNoMDC()) { return; } auto btn = new QPushButton(i18n("Force decryption")); btn->setFixedSize(btn->sizeHint()); connect(btn, &QPushButton::clicked, q, [this]() { if (m_result->parentTask()) { const auto dvTask = dynamic_cast(m_result->parentTask().data()); dvTask->setIgnoreMDCError(true); dvTask->start(); q->setVisible(false); } else { qCWarning(KLEOPATRA_LOG) << "Failed to get parent task"; } }); lay->addWidget(btn); } void ResultItemWidget::Private::addKeyImportButton(QBoxLayout *lay, bool search) { if (!m_result || !lay) { return; } const auto dvResult = dynamic_cast(m_result.get()); if (!dvResult) { return; } const auto verifyResult = dvResult->verificationResult(); if (verifyResult.isNull()) { return; } for (const auto &sig : verifyResult.signatures()) { if (!(sig.summary() & GpgME::Signature::KeyMissing)) { continue; } auto btn = new QPushButton; QString suffix; - const auto keyid = QLatin1String(sig.fingerprint()); + const auto keyid = QLatin1StringView(sig.fingerprint()); if (verifyResult.numSignatures() > 1) { suffix = QLatin1Char(' ') + keyid; } btn = new QPushButton(search ? i18nc("1 is optional keyid. No space is intended as it can be empty.", "Search%1", suffix) : i18nc("1 is optional keyid. No space is intended as it can be empty.", "Import%1", suffix)); if (search) { btn->setIcon(QIcon::fromTheme(QStringLiteral("edit-find"))); connect(btn, &QPushButton::clicked, q, [this, btn, keyid]() { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::Commands::LookupCertificatesCommand(keyid, nullptr); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::Commands::LookupCertificatesCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } else { btn->setIcon(QIcon::fromTheme(QStringLiteral("view-certificate-import"))); connect(btn, &QPushButton::clicked, q, [this, btn]() { btn->setEnabled(false); m_importCanceled = false; auto cmd = new Kleo::ImportCertificateFromFileCommand(); connect(cmd, &Kleo::ImportCertificateFromFileCommand::canceled, q, [this]() { m_importCanceled = true; }); connect(cmd, &Kleo::ImportCertificateFromFileCommand::finished, q, [this, btn]() { btn->setEnabled(true); oneImportFinished(); }); cmd->setParentWidget(q); cmd->start(); }); } btn->setFixedSize(btn->sizeHint()); lay->addWidget(btn); } } static QUrl auditlog_url_template() { QUrl url(QStringLiteral("kleoresultitem://showauditlog")); return url; } void ResultItemWidget::Private::updateShowDetailsLabel() { const auto auditLogUrl = m_result->auditLog().asUrl(auditlog_url_template()); const auto auditLogLinkText = m_result->hasError() ? i18n("Diagnostics") // : i18nc("The Audit Log is a detailed error log from the gnupg backend", "Show Audit Log"); m_auditLogLabel->setUrl(auditLogUrl, auditLogLinkText); m_auditLogLabel->setVisible(!auditLogUrl.isEmpty()); } ResultItemWidget::ResultItemWidget(const std::shared_ptr &result, QWidget *parent, Qt::WindowFlags flags) : QWidget(parent, flags) , d(new Private(result, this)) { const QColor color = colorForVisualCode(d->m_result->code()); const QColor txtColor = txtColorForVisualCode(d->m_result->code()); const QColor linkColor = txtColor; const QString styleSheet = SystemInfo::isHighContrastModeActive() ? QStringLiteral( "QFrame,QLabel { margin: 0px; }" "QFrame#resultFrame{ border-style: solid; border-radius: 3px; border-width: 1px }" "QLabel { padding: 5px; border-radius: 3px }") : QStringLiteral( "QFrame,QLabel { background-color: %1; margin: 0px; }" "QFrame#resultFrame{ border-color: %2; border-style: solid; border-radius: 3px; border-width: 1px }" "QLabel { color: %3; padding: 5px; border-radius: 3px }") .arg(color.name()) .arg(color.darker(150).name()) .arg(txtColor.name()); auto topLayout = new QVBoxLayout(this); auto frame = new QFrame; frame->setObjectName(QLatin1StringView("resultFrame")); frame->setStyleSheet(styleSheet); topLayout->addWidget(frame); auto layout = new QHBoxLayout(frame); auto vlay = new QVBoxLayout(); auto overview = new HtmlLabel; overview->setWordWrap(true); overview->setHtml(d->m_result->overview()); overview->setStyleSheet(styleSheet); overview->setLinkColor(linkColor); setFocusPolicy(overview->focusPolicy()); setFocusProxy(overview); connect(overview, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); vlay->addWidget(overview); layout->addLayout(vlay); auto actionLayout = new QVBoxLayout; layout->addLayout(actionLayout); d->addKeyImportButton(actionLayout, false); // TODO: Only show if auto-key-retrieve is not set. d->addKeyImportButton(actionLayout, true); d->addIgnoreMDCButton(actionLayout); d->m_auditLogLabel = new UrlLabel; connect(d->m_auditLogLabel, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); actionLayout->addWidget(d->m_auditLogLabel); d->m_auditLogLabel->setStyleSheet(styleSheet); d->m_auditLogLabel->setLinkColor(linkColor); auto detailsLabel = new HtmlLabel; detailsLabel->setWordWrap(true); detailsLabel->setHtml(d->m_result->details()); detailsLabel->setStyleSheet(styleSheet); detailsLabel->setLinkColor(linkColor); connect(detailsLabel, &QLabel::linkActivated, this, [this](const auto &link) { d->slotLinkActivated(link); }); vlay->addWidget(detailsLabel); d->m_showButton = new QPushButton; d->m_showButton->setVisible(false); connect(d->m_showButton, &QAbstractButton::clicked, this, &ResultItemWidget::showButtonClicked); actionLayout->addWidget(d->m_showButton); d->m_closeButton = new QPushButton; KGuiItem::assign(d->m_closeButton, KStandardGuiItem::close()); d->m_closeButton->setFixedSize(d->m_closeButton->sizeHint()); connect(d->m_closeButton, &QAbstractButton::clicked, this, &ResultItemWidget::closeButtonClicked); actionLayout->addWidget(d->m_closeButton); d->m_closeButton->setVisible(false); layout->setStretch(0, 1); actionLayout->addStretch(-1); vlay->addStretch(-1); d->updateShowDetailsLabel(); setSizePolicy(QSizePolicy::Minimum, QSizePolicy::Maximum); } ResultItemWidget::~ResultItemWidget() { } void ResultItemWidget::showCloseButton(bool show) { d->m_closeButton->setVisible(show); } void ResultItemWidget::setShowButton(const QString &text, bool show) { d->m_showButton->setText(text); d->m_showButton->setVisible(show); } bool ResultItemWidget::hasErrorResult() const { return d->m_result->hasError(); } void ResultItemWidget::Private::slotLinkActivated(const QString &link) { Q_ASSERT(m_result); qCDebug(KLEOPATRA_LOG) << "Link activated: " << link; - if (link.startsWith(QLatin1String("key:"))) { + if (link.startsWith(QLatin1StringView("key:"))) { auto split = link.split(QLatin1Char(':')); auto fpr = split.value(1); if (split.size() == 2 && isFingerprint(fpr)) { /* There might be a security consideration here if somehow * a short keyid is used in a link and it collides with another. * So we additionally check that it really is a fingerprint. */ auto cmd = Command::commandForQuery(fpr); cmd->setParentWId(q->effectiveWinId()); cmd->start(); } else { qCWarning(KLEOPATRA_LOG) << "key link invalid " << link; } return; } const QUrl url(link); - if (url.host() == QLatin1String("showauditlog")) { + if (url.host() == QLatin1StringView("showauditlog")) { q->showAuditLog(); return; } qCWarning(KLEOPATRA_LOG) << "Unexpected link scheme: " << link; } void ResultItemWidget::showAuditLog() { AuditLogViewer::showAuditLog(parentWidget(), d->m_result->auditLog()); } #include "moc_resultitemwidget.cpp" diff --git a/src/crypto/signemailtask.cpp b/src/crypto/signemailtask.cpp index 55e38169f..9d3aa0e1a 100644 --- a/src/crypto/signemailtask.cpp +++ b/src/crypto/signemailtask.cpp @@ -1,290 +1,290 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signemailtask.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 "signemailtask.h" #include #include #include #include #include #include #include #include #include #include #include #include #include // for Qt::escape #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { class SignEMailResult : public Task::Result { const SigningResult m_result; const AuditLogEntry m_auditLog; public: explicit SignEMailResult(const SigningResult &r, const AuditLogEntry &auditLog) : Task::Result() , m_result(r) , m_auditLog(auditLog) { } 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; }; QString makeResultString(const SigningResult &res) { const Error err = res.error(); if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed: %1", Formatting::errorAsString(err).toHtmlEscaped()); } return i18n("Signing succeeded."); } } class SignEMailTask::Private { friend class ::Kleo::Crypto::SignEMailTask; SignEMailTask *const q; public: explicit Private(SignEMailTask *qq); private: std::unique_ptr createJob(GpgME::Protocol proto); private: void slotResult(const SigningResult &); private: std::shared_ptr input; std::shared_ptr output; std::vector signers; bool detached; bool clearsign; QString micAlg; QPointer job; }; SignEMailTask::Private::Private(SignEMailTask *qq) : q(qq) , input() , output() , signers() , detached(false) , clearsign(false) , micAlg() , job(nullptr) { } SignEMailTask::SignEMailTask(QObject *p) : Task(p) , d(new Private(this)) { } SignEMailTask::~SignEMailTask() { } void SignEMailTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEMailTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEMailTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); kleo_assert(!signers.empty()); kleo_assert(std::none_of(signers.cbegin(), signers.cend(), std::mem_fn(&Key::isNull))); d->signers = signers; } void SignEMailTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; d->clearsign = false; } void SignEMailTask::setClearsign(bool clear) { kleo_assert(!d->job); d->clearsign = clear; d->detached = false; } Protocol SignEMailTask::protocol() const { kleo_assert(!d->signers.empty()); return d->signers.front().protocol(); } QString SignEMailTask::label() const { return d->input ? d->input->label() : QString(); } unsigned long long SignEMailTask::inputSize() const { return d->input ? d->input->size() : 0; } void SignEMailTask::doStart() { kleo_assert(!d->job); kleo_assert(d->input); kleo_assert(d->output); kleo_assert(!d->signers.empty()); d->micAlg.clear(); std::unique_ptr job = d->createJob(protocol()); kleo_assert(job.get()); job->start(d->signers, d->input->ioDevice(), d->output->ioDevice(), d->clearsign ? GpgME::Clearsigned : d->detached ? GpgME::Detached : GpgME::NormalSignatureMode); d->job = job.release(); } void SignEMailTask::cancel() { if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEMailTask::Private::createJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); bool shouldArmor = (proto == OpenPGP || q->asciiArmor()) && !output->binaryOpt(); std::unique_ptr signJob(backend->signJob(/*armor=*/shouldArmor, /*textmode=*/false)); kleo_assert(signJob.get()); if (proto == CMS && !q->asciiArmor() && !output->binaryOpt()) { signJob->setOutputIsBase64Encoded(true); } connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEMailTask::setProgress); connect(signJob.get(), SIGNAL(result(GpgME::SigningResult, QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } static QString collect_micalgs(const GpgME::SigningResult &result, GpgME::Protocol proto) { const std::vector css = result.createdSignatures(); QStringList micalgs; std::transform(css.begin(), css.end(), std::back_inserter(micalgs), [](const GpgME::CreatedSignature &sig) { return QString::fromLatin1(sig.hashAlgorithmAsString()).toLower(); }); if (proto == GpgME::OpenPGP) for (QStringList::iterator it = micalgs.begin(), end = micalgs.end(); it != end; ++it) { - it->prepend(QLatin1String("pgp-")); + it->prepend(QLatin1StringView("pgp-")); } micalgs.sort(); micalgs.erase(std::unique(micalgs.begin(), micalgs.end()), micalgs.end()); return micalgs.join(QLatin1Char(',')); } void SignEMailTask::Private::slotResult(const SigningResult &result) { const auto *const job = qobject_cast(q->sender()); if (result.error().code()) { output->cancel(); } else { output->finalize(); micAlg = collect_micalgs(result, q->protocol()); } q->emitResult(std::shared_ptr(new SignEMailResult(result, AuditLogEntry::fromJob(job)))); } QString SignEMailTask::micAlg() const { return d->micAlg; } QString SignEMailResult::overview() const { return makeOverview(makeResultString(m_result)); } QString SignEMailResult::details() const { return QString(); } GpgME::Error SignEMailResult::error() const { return m_result.error(); } QString SignEMailResult::errorString() const { return hasError() ? makeResultString(m_result) : QString(); } Task::Result::VisualCode SignEMailResult::code() const { if (m_result.error().isCanceled()) { return Warning; } return m_result.error().code() ? NeutralError : NeutralSuccess; } AuditLogEntry SignEMailResult::auditLog() const { return m_auditLog; } #include "moc_signemailtask.cpp" diff --git a/src/crypto/signencrypttask.cpp b/src/crypto/signencrypttask.cpp index f332e654e..8e9da4278 100644 --- a/src/crypto/signencrypttask.cpp +++ b/src/crypto/signencrypttask.cpp @@ -1,973 +1,973 @@ /* -*- 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 #include #include #include #include #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 Formatting::errorAsString(err).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 Formatting::errorAsString(err).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: QString inputLabel() const; QString outputLabel() const; bool removeExistingOutputFile(); 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); 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); 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; QString labelText; 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{OverwritePolicy::Ask}} { 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, inputLabel(), outputLabel(), 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; } QString SignEncryptTask::outputFileName() const { return d->outputFileName; } 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->labelText.isEmpty()) { return d->labelText; } return d->inputLabel(); } QString SignEncryptTask::tag() const { return Formatting::displayName(protocol()); } unsigned long long SignEncryptTask::inputSize() const { return d->input ? d->input->size() : 0U; } static bool archiveJobsCanBeUsed(GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); } void SignEncryptTask::doStart() { kleo_assert(!d->job); if (d->sign) { kleo_assert(!d->signers.empty()); if (d->archive) { kleo_assert(!d->detached && !d->clearsign); } } const auto proto = protocol(); if (d->archive && archiveJobsCanBeUsed(proto)) { d->startSignEncryptArchiveJob(proto); } else { d->startSignEncryptJob(proto); } } QString SignEncryptTask::Private::inputLabel() const { if (input) { return input->label(); } if (!inputFileNames.empty()) { const auto firstFile = QFileInfo{inputFileNames.front()}.fileName(); return inputFileNames.size() == 1 ? firstFile : i18nc(", ...", "%1, ...", firstFile); } return {}; } QString SignEncryptTask::Private::outputLabel() const { return output ? output->label() : QFileInfo{outputFileName}.fileName(); } bool SignEncryptTask::Private::removeExistingOutputFile() { if (QFile::exists(outputFileName)) { bool fileRemoved = false; // we should already have asked the user for overwrite permission if (m_overwritePolicy && (m_overwritePolicy->policy() == OverwritePolicy::Overwrite)) { qCDebug(KLEOPATRA_LOG) << __func__ << "going to remove file for overwriting" << outputFileName; fileRemoved = QFile::remove(outputFileName); if (!fileRemoved) { qCDebug(KLEOPATRA_LOG) << __func__ << "removing file to overwrite failed"; } } else { qCDebug(KLEOPATRA_LOG) << __func__ << "we have no permission to overwrite" << outputFileName; } if (!fileRemoved) { QMetaObject::invokeMethod( q, [this]() { slotResult(nullptr, SigningResult{}, EncryptionResult{Error::fromCode(GPG_ERR_EEXIST)}); }, Qt::QueuedConnection); return false; } } return true; } void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto) { #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP) { // either input and output are both set (e.g. when encrypting the notepad), // or they are both unset (when encrypting files) kleo_assert((!input && !output) || (input && output)); } else { kleo_assert(input); if (!output) { output = Output::createFromFile(outputFileName, m_overwritePolicy); } } #else kleo_assert(input); if (!output) { output = Output::createFromFile(outputFileName, m_overwritePolicy); } #endif if (encrypt || symmetric) { Context::EncryptionFlags flags{Context::None}; if (proto == GpgME::OpenPGP) { flags = static_cast(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_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setSigners(signers); job->setRecipients(recipients); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); } #else if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); #endif this->job = job.release(); } else { std::unique_ptr job = createEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setRecipients(recipients); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(recipients, input->ioDevice(), output->ioDevice(), flags); } #else if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } job->start(recipients, input->ioDevice(), output->ioDevice(), flags); #endif this->job = job.release(); } } else if (sign) { std::unique_ptr job = createSignJob(proto); kleo_assert(job.get()); kleo_assert(!(detached && clearsign)); const GpgME::SignatureMode sigMode = detached ? GpgME::Detached : clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode; #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO if (proto == GpgME::OpenPGP && !input && !output) { kleo_assert(inputFileNames.size() == 1); job->setSigners(signers); job->setInputFile(inputFileNames.front()); job->setOutputFile(outputFileName); job->setSigningFlags(sigMode); if (!removeExistingOutputFile()) { return; } job->startIt(); } else { job->start(signers, input->ioDevice(), output->ioDevice(), sigMode); } #else job->start(signers, input->ioDevice(), output->ioDevice(), sigMode); #endif this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } void SignEncryptTask::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; 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()); connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); 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()); connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); 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()); connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult, QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto) { kleo_assert(!input); kleo_assert(!output); #if !QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME output = Output::createFromFile(outputFileName, m_overwritePolicy); #endif 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::None}; if (proto == GpgME::OpenPGP) { flags = static_cast(flags | Context::AlwaysTrust); } if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { labelText = i18nc("@info", "Creating signed and encrypted archive ..."); std::unique_ptr job = createSignEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setSigners(signers); job->setRecipients(recipients); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(signers, recipients, relativePaths, output->ioDevice(), flags); #endif this->job = job.release(); } else { labelText = i18nc("@info", "Creating encrypted archive ..."); std::unique_ptr job = createEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setRecipients(recipients); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); job->setEncryptionFlags(flags); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(recipients, relativePaths, output->ioDevice(), flags); #endif this->job = job.release(); } } else if (sign) { labelText = i18nc("@info", "Creating signed archive ..."); std::unique_ptr job = createSignArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); #if QGPGME_ARCHIVE_JOBS_SUPPORT_OUTPUT_FILENAME job->setSigners(signers); job->setInputPaths(relativePaths); job->setOutputFile(outputFileName); if (!removeExistingOutputFile()) { return; } job->startIt(); #else job->start(signers, relativePaths, output->ioDevice()); #endif 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; } 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) { qCDebug(KLEOPATRA_LOG) << q << __func__ << "job:" << job << "signing result:" << QGpgME::toLogString(sresult) << "encryption result:" << QGpgME::toLogString(eresult); const AuditLogEntry auditLog = AuditLogEntry::fromJob(job); bool outputCreated = false; if (input && input->failed()) { if (output) { 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()) { if (output) { output->cancel(); } if (!outputFileName.isEmpty() && eresult.error().code() != GPG_ERR_EEXIST) { // ensure that the output file is removed if the task was canceled or an error occurred; // unless a "file exists" error occurred because this means that the file with the name // of outputFileName wasn't created as result of this task if (QFile::exists(outputFileName)) { qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "after error or cancel"; if (!QFile::remove(outputFileName)) { qCDebug(KLEOPATRA_LOG) << __func__ << "Removing output file" << outputFileName << "failed"; } } } } else { try { kleo_assert(!sresult.isNull() || !eresult.isNull()); if (output) { 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{inputLabel(), input ? input->errorString() : QString{}}; const LabelAndError outputInfo{outputLabel(), output ? output->errorString() : QString{}}; 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)); + return files + QLatin1StringView(": ") + 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) // : 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/crypto/task.cpp b/src/crypto/task.cpp index 8fc42e06c..4768318e5 100644 --- a/src/crypto/task.cpp +++ b/src/crypto/task.cpp @@ -1,259 +1,259 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/task.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 "task.h" #include "task_p.h" #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { class ErrorResult : public Task::Result { public: ErrorResult(const GpgME::Error &error, const QString &details) : Task::Result() , m_error(error) , m_details(details) { } QString overview() const override { return makeOverview(m_details); } QString details() const override { return QString(); } GpgME::Error error() const override { return m_error; } QString errorString() const override { return m_details; } VisualCode code() const override { return NeutralError; } AuditLogEntry auditLog() const override { return AuditLogEntry(); } private: const GpgME::Error m_error; const QString m_details; }; } class Task::Private { friend class ::Kleo::Crypto::Task; Task *const q; public: explicit Private(Task *qq); private: int m_progress; int m_totalProgress; bool m_asciiArmor; int m_id; }; namespace { static int nextTaskId = 0; } Task::Private::Private(Task *qq) : q(qq) , m_progress(0) , m_totalProgress(0) , m_asciiArmor(false) , m_id(nextTaskId++) { } Task::Task(QObject *p) : QObject(p) , d(new Private(this)) { } Task::~Task() { } void Task::setAsciiArmor(bool armor) { d->m_asciiArmor = armor; } bool Task::asciiArmor() const { return d->m_asciiArmor; } std::shared_ptr Task::makeErrorTask(const GpgME::Error &error, const QString &details, const QString &label) { const std::shared_ptr t(new SimpleTask(label)); t->setResult(t->makeErrorResult(error, details)); return t; } int Task::id() const { return d->m_id; } int Task::currentProgress() const { return d->m_progress; } int Task::totalProgress() const { return d->m_totalProgress; } QString Task::tag() const { return QString(); } void Task::setProgress(int processed, int total) { d->m_progress = processed; d->m_totalProgress = total; Q_EMIT progress(processed, total, QPrivateSignal()); } void Task::start() { try { doStart(); } catch (const Kleo::Exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(GpgME::Error, e.error()), Q_ARG(QString, e.message())); } catch (const GpgME::Exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(GpgME::Error, e.error()), Q_ARG(QString, QString::fromLocal8Bit(e.what()))); } catch (const std::exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(GpgME::Error, Error::fromCode(GPG_ERR_UNEXPECTED)), Q_ARG(QString, QString::fromLocal8Bit(e.what()))); } catch (...) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(GpgME::Error, Error::fromCode(GPG_ERR_UNEXPECTED)), Q_ARG(QString, i18n("Unknown exception in Task::start()"))); } Q_EMIT started(QPrivateSignal()); } void Task::emitError(const GpgME::Error &error, const QString &details) { emitResult(makeErrorResult(error, details)); } void Task::emitResult(const std::shared_ptr &r) { d->m_progress = d->m_totalProgress; Q_EMIT progress(currentProgress(), totalProgress(), QPrivateSignal()); Q_EMIT result(r, QPrivateSignal()); } std::shared_ptr Task::makeErrorResult(const GpgME::Error &error, const QString &details) { return std::shared_ptr(new ErrorResult(error, details)); } class Task::Result::Private { public: Private() { } }; Task::Result::Result() : d(new Private()) { } Task::Result::~Result() { } bool Task::Result::hasError() const { return error().code() != 0; } Task::Result::ContentType Task::Result::viewableContentType() const { return Task::Result::ContentType::None; } static QString image(const char *img) { // ### escape? - return KIconLoader::global()->iconPath(QLatin1String(img), KIconLoader::Small); + return KIconLoader::global()->iconPath(QLatin1StringView(img), KIconLoader::Small); } QString Task::Result::makeOverview(const QString &msg) { - return QLatin1String("") + msg + QLatin1String(""); + return QLatin1StringView("") + msg + QLatin1String(""); } QString Task::Result::iconPath(VisualCode code) { switch (code) { case Danger: return image("dialog-error"); case AllGood: return image("dialog-ok"); case Warning: return image("dialog-warning"); case NeutralError: case NeutralSuccess: default: return QString(); } } QString Task::Result::icon() const { return iconPath(code()); } #include "moc_task_p.cpp" #include "moc_task.cpp" diff --git a/src/crypto/verifychecksumscontroller.cpp b/src/crypto/verifychecksumscontroller.cpp index cca236fc6..70cf2a185 100644 --- a/src/crypto/verifychecksumscontroller.cpp +++ b/src/crypto/verifychecksumscontroller.cpp @@ -1,570 +1,570 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/verifychecksumscontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "verifychecksumscontroller.h" #include "checksumsutils_p.h" #ifndef QT_NO_DIRMODEL #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; #if 0 static QStringList fs_sort(QStringList l) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; std::sort(l.begin(), l.end(), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0; }); return l; } static QStringList fs_intersect(QStringList l1, QStringList l2) { int (*QString_compare)(const QString &, const QString &, Qt::CaseSensitivity) = &QString::compare; fs_sort(l1); fs_sort(l2); QStringList result; std::set_intersection(l1.begin(), l1.end(), l2.begin(), l2.end(), std::back_inserter(result), [](const QString &lhs, const QString &rhs) { return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0; }); return result; } #endif namespace { struct matches_none_of { const std::vector m_regexps; explicit matches_none_of(const std::vector ®exps) : m_regexps(regexps) { } bool operator()(const QString &s) const { return std::none_of(m_regexps.cbegin(), m_regexps.cend(), [&s](const QRegularExpression &rx) { return rx.match(s).hasMatch(); }); } }; } class VerifyChecksumsController::Private : public QThread { Q_OBJECT friend class ::Kleo::Crypto::VerifyChecksumsController; VerifyChecksumsController *const q; public: explicit Private(VerifyChecksumsController *qq); ~Private() override; Q_SIGNALS: void baseDirectories(const QStringList &); void progress(int, int, const QString &); void status(const QString &file, Kleo::Crypto::Gui::VerifyChecksumsDialog::Status); private: void slotOperationFinished() { if (dialog) { dialog->setProgress(100, 100); dialog->setErrors(errors); } if (!errors.empty()) q->setLastError(gpg_error(GPG_ERR_GENERAL), errors.join(QLatin1Char('\n'))); q->emitDoneOrError(); } private: void run() override; private: QPointer dialog; mutable QMutex mutex; const std::vector> checksumDefinitions; QStringList files; QStringList errors; volatile bool canceled; }; VerifyChecksumsController::Private::Private(VerifyChecksumsController *qq) : q(qq) , dialog() , mutex() , checksumDefinitions(ChecksumDefinition::getChecksumDefinitions()) , files() , errors() , canceled(false) { connect(this, &Private::progress, q, &Controller::progress); connect(this, SIGNAL(finished()), q, SLOT(slotOperationFinished())); } VerifyChecksumsController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } VerifyChecksumsController::VerifyChecksumsController(QObject *p) : Controller(p) , d(new Private(this)) { } VerifyChecksumsController::VerifyChecksumsController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p) , d(new Private(this)) { } VerifyChecksumsController::~VerifyChecksumsController() { qCDebug(KLEOPATRA_LOG); } void VerifyChecksumsController::setFiles(const QStringList &files) { kleo_assert(!d->isRunning()); kleo_assert(!files.empty()); const QMutexLocker locker(&d->mutex); d->files = files; } void VerifyChecksumsController::start() { { const QMutexLocker locker(&d->mutex); d->dialog = new VerifyChecksumsDialog; d->dialog->setAttribute(Qt::WA_DeleteOnClose); d->dialog->setWindowTitle(i18nc("@title:window", "Verify Checksum Results")); connect(d->dialog.data(), &VerifyChecksumsDialog::canceled, this, &VerifyChecksumsController::cancel); connect(d.get(), &Private::baseDirectories, d->dialog.data(), &VerifyChecksumsDialog::setBaseDirectories); connect(d.get(), &Private::progress, d->dialog.data(), &VerifyChecksumsDialog::setProgress); connect(d.get(), &Private::status, d->dialog.data(), &VerifyChecksumsDialog::setStatus); d->canceled = false; d->errors.clear(); } d->start(); d->dialog->show(); } void VerifyChecksumsController::cancel() { qCDebug(KLEOPATRA_LOG); const QMutexLocker locker(&d->mutex); d->canceled = true; } namespace { struct SumFile { QDir dir; QString sumFile; quint64 totalSize; std::shared_ptr checksumDefinition; }; } static QStringList filter_checksum_files(QStringList l, const std::vector &rxs) { l.erase(std::remove_if(l.begin(), l.end(), matches_none_of(rxs)), l.end()); return l; } static quint64 aggregate_size(const QDir &dir, const QStringList &files) { quint64 n = 0; for (const QString &file : files) { n += QFileInfo(dir.absoluteFilePath(file)).size(); } return n; } namespace { struct less_dir { bool operator()(const QDir &lhs, const QDir &rhs) const { return QString::compare(lhs.absolutePath(), rhs.absolutePath(), ChecksumsUtils::fs_cs) < 0; } }; struct less_file { bool operator()(const QString &lhs, const QString &rhs) const { return QString::compare(lhs, rhs, ChecksumsUtils::fs_cs) < 0; } }; struct sumfile_contains_file { const QDir dir; const QString fileName; sumfile_contains_file(const QDir &dir_, const QString &fileName_) : dir(dir_) , fileName(fileName_) { } bool operator()(const QString &sumFile) const { const std::vector files = ChecksumsUtils::parse_sum_file(dir.absoluteFilePath(sumFile)); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << files.size() << " files listed in " << qPrintable(dir.absoluteFilePath(sumFile)); for (const ChecksumsUtils::File &file : files) { const bool isSameFileName = (QString::compare(file.name, fileName, ChecksumsUtils::fs_cs) == 0); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: " << qPrintable(file.name) << " == " << qPrintable(fileName) << " ? " << isSameFileName; if (isSameFileName) { return true; } } return false; } }; } // IF is_dir(file) // add all sumfiles \in dir(file) // inputs.prepend( all dirs \in dir(file) ) // ELSE IF is_sum_file(file) // add // ELSE IF \exists sumfile in dir(file) \where sumfile \contains file // add sumfile // ELSE // error: no checksum found for "file" static QStringList find_base_directories(const QStringList &files) { // Step 1: find base dirs: std::set dirs; for (const QString &file : files) { const QFileInfo fi(file); const QDir dir = fi.isDir() ? QDir(file) : fi.dir(); dirs.insert(dir); } // Step 1a: collapse direct child directories bool changed; do { changed = false; auto it = dirs.begin(); while (it != dirs.end()) { QDir dir = *it; if (dir.cdUp() && dirs.count(dir)) { dirs.erase(it++); changed = true; } else { ++it; } } } while (changed); QStringList rv; rv.reserve(dirs.size()); std::transform(dirs.cbegin(), dirs.cend(), std::back_inserter(rv), std::mem_fn(&QDir::absolutePath)); return rv; } static std::vector find_sums_by_input_files(const QStringList &files, QStringList &errors, const std::function &progress, const std::vector> &checksumDefinitions) { const std::vector patterns = ChecksumsUtils::get_patterns(checksumDefinitions); const ChecksumsUtils::matches_any is_sum_file(patterns); std::map, less_dir> dirs2sums; // Step 1: find the sumfiles we need to check: std::deque inputs(files.begin(), files.end()); int i = 0; while (!inputs.empty()) { const QString file = inputs.front(); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: considering " << qPrintable(file); inputs.pop_front(); const QFileInfo fi(file); const QString fileName = fi.fileName(); if (fi.isDir()) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a directory"; QDir dir(file); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() - << " sum files: " << qPrintable(sumfiles.join(QLatin1String(", "))); + << " sum files: " << qPrintable(sumfiles.join(QLatin1StringView(", "))); dirs2sums[dir].insert(sumfiles.begin(), sumfiles.end()); const QStringList dirs = dir.entryList(QDir::Dirs | QDir::NoDotAndDotDot); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << dirs.size() << " subdirs, prepending"; std::transform(dirs.cbegin(), dirs.cend(), std::inserter(inputs, inputs.begin()), [&dir](const QString &path) { return dir.absoluteFilePath(path); }); } else if (is_sum_file(fileName)) { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's a sum file"; dirs2sums[fi.dir()].insert(fileName); } else { qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: it's something else; checking whether we'll find a sumfile for it..."; const QDir dir = fi.dir(); const QStringList sumfiles = filter_checksum_files(dir.entryList(QDir::Files), patterns); qCDebug(KLEOPATRA_LOG) << "find_sums_by_input_files: found " << sumfiles.size() - << " potential sumfiles: " << qPrintable(sumfiles.join(QLatin1String(", "))); + << " potential sumfiles: " << qPrintable(sumfiles.join(QLatin1StringView(", "))); const auto it = std::find_if(sumfiles.cbegin(), sumfiles.cend(), sumfile_contains_file(dir, fileName)); if (it == sumfiles.end()) { errors.push_back(i18n("Cannot find checksums file for file %1", file)); } else { dirs2sums[dir].insert(*it); } } if (progress) { progress(++i); } } // Step 2: convert into vector: std::vector sumfiles; sumfiles.reserve(dirs2sums.size()); for (auto it = dirs2sums.begin(), end = dirs2sums.end(); it != end; ++it) { if (it->second.empty()) { continue; } const QDir &dir = it->first; for (const QString &sumFileName : std::as_const(it->second)) { const std::vector summedfiles = ChecksumsUtils::parse_sum_file(dir.absoluteFilePath(sumFileName)); QStringList files; files.reserve(summedfiles.size()); std::transform(summedfiles.cbegin(), summedfiles.cend(), std::back_inserter(files), std::mem_fn(&ChecksumsUtils::File::name)); const SumFile sumFile = { it->first, sumFileName, aggregate_size(it->first, files), ChecksumsUtils::filename2definition(sumFileName, checksumDefinitions), }; sumfiles.push_back(sumFile); } if (progress) { progress(++i); } } return sumfiles; } static QStringList c_lang_environment() { static const QRegularExpression re(QRegularExpression::anchoredPattern(u"LANG=.*"), ChecksumsUtils::s_regex_cs); QStringList env = QProcess::systemEnvironment(); env.erase(std::remove_if(env.begin(), env.end(), [](const QString &str) { return re.match(str).hasMatch(); }), env.end()); env.push_back(QStringLiteral("LANG=C")); return env; } static const struct { const char *string; VerifyChecksumsDialog::Status status; } statusStrings[] = { {"OK", VerifyChecksumsDialog::OK}, {"FAILED", VerifyChecksumsDialog::Failed}, }; static const size_t numStatusStrings = sizeof statusStrings / sizeof *statusStrings; static VerifyChecksumsDialog::Status string2status(const QByteArray &str) { for (unsigned int i = 0; i < numStatusStrings; ++i) if (str == statusStrings[i].string) { return statusStrings[i].status; } return VerifyChecksumsDialog::Unknown; } static QString process(const SumFile &sumFile, bool *fatal, const QStringList &env, const std::function &status) { QProcess p; p.setEnvironment(env); p.setWorkingDirectory(sumFile.dir.absolutePath()); p.setReadChannel(QProcess::StandardOutput); const QString absFilePath = sumFile.dir.absoluteFilePath(sumFile.sumFile); const QString program = sumFile.checksumDefinition->verifyCommand(); sumFile.checksumDefinition->startVerifyCommand(&p, QStringList(absFilePath)); QByteArray remainder; // used for filenames with newlines in them while (p.state() != QProcess::NotRunning) { p.waitForReadyRead(); while (p.canReadLine()) { const QByteArray line = p.readLine(); const int colonIdx = line.lastIndexOf(':'); if (colonIdx < 0) { remainder += line; // no colon -> probably filename with a newline continue; } #ifdef Q_OS_WIN const QString file = QString::fromUtf8(remainder + line.left(colonIdx)); #else const QString file = QFile::decodeName(remainder + line.left(colonIdx)); #endif remainder.clear(); const VerifyChecksumsDialog::Status result = string2status(line.mid(colonIdx + 1).trimmed()); status(sumFile.dir.absoluteFilePath(file), result); } } qCDebug(KLEOPATRA_LOG) << "[" << &p << "] Exit code " << p.exitCode(); if (p.exitStatus() != QProcess::NormalExit || p.exitCode() != 0) { if (fatal && p.error() == QProcess::FailedToStart) { *fatal = true; } if (p.error() == QProcess::UnknownError) return i18n("Error while running %1: %2", program, QString::fromLocal8Bit(p.readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", program, p.errorString()); } } return QString(); } namespace { static QDebug operator<<(QDebug s, const SumFile &sum) { return s << "SumFile(" << sum.dir << "->" << sum.sumFile << "<-(" << sum.totalSize << ')' << ")\n"; } } void VerifyChecksumsController::Private::run() { QMutexLocker locker(&mutex); const QStringList files = this->files; const std::vector> checksumDefinitions = this->checksumDefinitions; locker.unlock(); QStringList errors; // // Step 0: find base directories: // Q_EMIT baseDirectories(find_base_directories(files)); // // Step 1: build a list of work to do (no progress): // const QString scanning = i18n("Scanning directories..."); Q_EMIT progress(0, 0, scanning); const auto progressCb = [this, scanning](int arg) { Q_EMIT progress(arg, 0, scanning); }; const auto statusCb = [this](const QString &str, VerifyChecksumsDialog::Status st) { Q_EMIT status(str, st); }; const std::vector sumfiles = find_sums_by_input_files(files, errors, progressCb, checksumDefinitions); for (const SumFile &sumfile : sumfiles) { qCDebug(KLEOPATRA_LOG) << sumfile; } if (!canceled) { Q_EMIT progress(0, 0, i18n("Calculating total size...")); const quint64 total = kdtools::accumulate_transform(sumfiles.cbegin(), sumfiles.cend(), std::mem_fn(&SumFile::totalSize), Q_UINT64_C(0)); if (!canceled) { // // Step 2: perform work (with progress reporting): // const QStringList env = c_lang_environment(); // re-scale 'total' to fit into ints (wish QProgressDialog would use quint64...) const quint64 factor = total / std::numeric_limits::max() + 1; quint64 done = 0; for (const SumFile &sumFile : sumfiles) { Q_EMIT progress(done / factor, total / factor, i18n("Verifying checksums (%2) in %1", sumFile.checksumDefinition->label(), sumFile.dir.path())); bool fatal = false; const QString error = process(sumFile, &fatal, env, statusCb); if (!error.isEmpty()) { errors.push_back(error); } done += sumFile.totalSize; if (fatal || canceled) { break; } } Q_EMIT progress(done / factor, total / factor, i18n("Done.")); } } locker.relock(); this->errors = errors; // mutex unlocked by QMutexLocker } #include "moc_verifychecksumscontroller.cpp" #include "verifychecksumscontroller.moc" #endif // QT_NO_DIRMODEL diff --git a/src/dialogs/adduseriddialog.cpp b/src/dialogs/adduseriddialog.cpp index 260cc2cbf..c0ca5138b 100644 --- a/src/dialogs/adduseriddialog.cpp +++ b/src/dialogs/adduseriddialog.cpp @@ -1,192 +1,192 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/adduseriddialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "adduseriddialog.h" #include "nameandemailwidget.h" #include "utils/accessibility.h" #include "utils/scrollarea.h" #include "view/htmllabel.h" #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; class AddUserIDDialog::Private { friend class ::Kleo::AddUserIDDialog; AddUserIDDialog *const q; struct { ScrollArea *scrollArea; NameAndEmailWidget *nameAndEmail; HtmlLabel *resultLabel; QDialogButtonBox *buttonBox; } ui; LabelHelper labelHelper; public: explicit Private(AddUserIDDialog *qq) : q{qq} { q->setWindowTitle(i18nc("title:window", "Add User ID")); - const KConfigGroup config{KSharedConfig::openConfig(), QLatin1String("CertificateCreationWizard")}; + const KConfigGroup config{KSharedConfig::openConfig(), QLatin1StringView("CertificateCreationWizard")}; const auto attrOrder = config.readEntry("OpenPGPAttributeOrder", QStringList{}); - const auto nameIsRequired = attrOrder.contains(QLatin1String{"NAME!"}, Qt::CaseInsensitive); - const auto emailIsRequired = attrOrder.contains(QLatin1String{"EMAIL!"}, Qt::CaseInsensitive); + const auto nameIsRequired = attrOrder.contains(QLatin1StringView{"NAME!"}, Qt::CaseInsensitive); + const auto emailIsRequired = attrOrder.contains(QLatin1StringView{"EMAIL!"}, Qt::CaseInsensitive); auto mainLayout = new QVBoxLayout{q}; { const auto infoText = nameIsRequired || emailIsRequired // ? i18n("Enter a name and an email address to use for the user ID.") : i18n("Enter a name and/or an email address to use for the user ID."); auto label = new QLabel{infoText, q}; label->setWordWrap(true); mainLayout->addWidget(label); } mainLayout->addWidget(new KSeparator{Qt::Horizontal, q}); ui.scrollArea = new ScrollArea{q}; ui.scrollArea->setFocusPolicy(Qt::NoFocus); ui.scrollArea->setFrameStyle(QFrame::NoFrame); ui.scrollArea->setBackgroundRole(q->backgroundRole()); ui.scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); ui.scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(ui.scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins(0, 0, 0, 0); ui.nameAndEmail = new NameAndEmailWidget{q}; ui.nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0); ui.nameAndEmail->setNameIsRequired(nameIsRequired); ui.nameAndEmail->setNameLabel(config.readEntry("NAME_label")); ui.nameAndEmail->setNameHint(config.readEntry("NAME_hint", config.readEntry("NAME_placeholder"))); ui.nameAndEmail->setNamePattern(config.readEntry("NAME_regex")); ui.nameAndEmail->setEmailIsRequired(emailIsRequired); ui.nameAndEmail->setEmailLabel(config.readEntry("EMAIL_label")); ui.nameAndEmail->setEmailHint(config.readEntry("EMAIL_hint", config.readEntry("EMAIL_placeholder"))); ui.nameAndEmail->setEmailPattern(config.readEntry("EMAIL_regex")); scrollAreaLayout->addWidget(ui.nameAndEmail); scrollAreaLayout->addWidget(new KSeparator{Qt::Horizontal, q}); { ui.resultLabel = new HtmlLabel{q}; ui.resultLabel->setWordWrap(true); ui.resultLabel->setFocusPolicy(Qt::ClickFocus); labelHelper.addLabel(ui.resultLabel); scrollAreaLayout->addWidget(ui.resultLabel); } scrollAreaLayout->addStretch(1); mainLayout->addWidget(ui.scrollArea); mainLayout->addWidget(new KSeparator{Qt::Horizontal, q}); ui.buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, q}; mainLayout->addWidget(ui.buttonBox); connect(ui.nameAndEmail, &NameAndEmailWidget::userIDChanged, q, [this]() { updateResultLabel(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); }); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); updateResultLabel(); } private: void checkAccept() { QStringList errors; if (ui.nameAndEmail->userID().isEmpty() && !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) { errors.push_back(i18n("Enter a name or an email address.")); } const auto nameError = ui.nameAndEmail->nameError(); if (!nameError.isEmpty()) { errors.push_back(nameError); } const auto emailError = ui.nameAndEmail->emailError(); if (!emailError.isEmpty()) { errors.push_back(emailError); } if (errors.size() > 1) { KMessageBox::errorList(q, i18n("There is a problem."), errors); } else if (!errors.empty()) { KMessageBox::error(q, errors.first()); } else { q->accept(); } } void updateResultLabel() { ui.resultLabel->setHtml(i18nc("@info", "
    This is how the new user ID will be stored in the certificate:
    " "
    %1
    ", ui.nameAndEmail->userID().toHtmlEscaped())); } }; AddUserIDDialog::AddUserIDDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d(new Private{this}) { } AddUserIDDialog::~AddUserIDDialog() = default; void AddUserIDDialog::setName(const QString &name) { d->ui.nameAndEmail->setName(name); } QString AddUserIDDialog::name() const { return d->ui.nameAndEmail->name(); } void AddUserIDDialog::setEmail(const QString &email) { d->ui.nameAndEmail->setEmail(email); } QString AddUserIDDialog::email() const { return d->ui.nameAndEmail->email(); } QString AddUserIDDialog::userID() const { return d->ui.nameAndEmail->userID(); } #include "moc_adduseriddialog.cpp" diff --git a/src/dialogs/certificatedetailsinputwidget.cpp b/src/dialogs/certificatedetailsinputwidget.cpp index 9fe7b4a24..786b03ce0 100644 --- a/src/dialogs/certificatedetailsinputwidget.cpp +++ b/src/dialogs/certificatedetailsinputwidget.cpp @@ -1,363 +1,363 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/certificatedetailsinputwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "certificatedetailsinputwidget.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Dialogs; namespace { struct Line { QString attr; QString label; QString regex; QLineEdit *edit; std::shared_ptr validator; bool required; }; QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } QString attributeLabel(const QString &attr) { if (attr.isEmpty()) { return QString(); } const QString label = DN::attributeNameToLabel(attr); if (!label.isEmpty()) { return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); } else { return attr; } } QLineEdit *addRow(QGridLayout *l, const QString &label, const QString &preset, const std::shared_ptr &validator, bool readonly, bool required) { Q_ASSERT(l); auto lb = new QLabel(l->parentWidget()); lb->setText(i18nc("interpunctation for labels", "%1:", label)); auto le = new QLineEdit(l->parentWidget()); le->setText(preset); if (validator) { le->setValidator(validator.get()); } le->setReadOnly(readonly && le->hasAcceptableInput()); auto reqLB = new QLabel(l->parentWidget()); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); const int row = l->rowCount(); l->addWidget(lb, row, 0); l->addWidget(le, row, 1); l->addWidget(reqLB, row, 2); return le; } bool hasIntermediateInput(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } QString requirementsAreMet(const QList &lines) { for (const Line &line : lines) { const QLineEdit *le = line.edit; if (!le) { continue; } qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking \"" << line.attr << "\" against \"" << le->text() << "\":"; if (le->text().trimmed().isEmpty()) { if (line.required) { if (line.regex.isEmpty()) { return xi18nc("@info", "%1 is required, but empty.", line.label); } else { return xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); } } } else if (hasIntermediateInput(le)) { if (line.regex.isEmpty()) { return xi18nc("@info", "%1 is incomplete.", line.label); } else { return xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); } } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { return xi18nc("@info", "%1 is invalid.", line.label); } else { return xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", line.label, line.regex); } } } return QString(); } } class CertificateDetailsInputWidget::Private { friend class ::Kleo::Dialogs::CertificateDetailsInputWidget; CertificateDetailsInputWidget *const q; struct { QGridLayout *gridLayout; QList lines; QLineEdit *dn; QLabel *error; } ui; public: Private(CertificateDetailsInputWidget *qq) : q(qq) { auto mainLayout = new QVBoxLayout(q); ui.gridLayout = new QGridLayout(); mainLayout->addLayout(ui.gridLayout); createForm(); mainLayout->addStretch(1); ui.dn = new QLineEdit(); ui.dn->setFrame(false); ui.dn->setAlignment(Qt::AlignCenter); ui.dn->setReadOnly(true); mainLayout->addWidget(ui.dn); ui.error = new QLabel(); { QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(ui.error->sizePolicy().hasHeightForWidth()); ui.error->setSizePolicy(sizePolicy); } { QPalette palette; QBrush brush(QColor(255, 0, 0, 255)); brush.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Active, QPalette::WindowText, brush); palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush); QBrush brush1(QColor(114, 114, 114, 255)); brush1.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1); ui.error->setPalette(palette); } ui.error->setTextFormat(Qt::RichText); // set error label to have a fixed height of two lines: ui.error->setText(QStringLiteral("2
    1")); ui.error->setFixedHeight(ui.error->minimumSizeHint().height()); ui.error->clear(); mainLayout->addWidget(ui.error); // select the preset text in the first line edit if (!ui.lines.empty()) { ui.lines.first().edit->selectAll(); } // explicitly update DN and check requirements after setup is complete updateDN(); checkRequirements(); } ~Private() { // remember current attribute values as presets for next certificate KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); for (const Line &line : ui.lines) { const QString attr = attributeFromKey(line.attr); const QString value = line.edit->text().trimmed(); config.writeEntry(attr, value); } config.sync(); } void createForm() { static const QStringList defaultAttributeOrder = { QStringLiteral("CN!"), QStringLiteral("EMAIL!"), QStringLiteral("L"), QStringLiteral("OU"), QStringLiteral("O"), QStringLiteral("C"), }; const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); const QStringList attrOrder = config.readEntry("DNAttributeOrder", defaultAttributeOrder); for (const QString &rawKey : attrOrder) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString defaultPreset = // - (attr == QLatin1String("CN")) ? userFullName() - : (attr == QLatin1String("EMAIL")) ? userEmailAddress() - : QString(); + (attr == QLatin1StringView("CN")) ? userFullName() + : (attr == QLatin1StringView("EMAIL")) ? userEmailAddress() + : QString(); const QString preset = config.readEntry(attr, defaultPreset); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); - const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr)); - const QString regex = config.readEntry(attr + QLatin1String("_regex")); + const QString label = config.readEntry(attr + QLatin1StringView("_label"), attributeLabel(attr)); + const QString regex = config.readEntry(attr + QLatin1StringView("_regex")); std::shared_ptr validator; - if (attr == QLatin1String("EMAIL")) { + if (attr == QLatin1StringView("EMAIL")) { validator = regex.isEmpty() ? Validation::email() : Validation::email(regex); } else if (!regex.isEmpty()) { validator = std::make_shared(QRegularExpression{regex}); } QLineEdit *le = addRow(ui.gridLayout, label, preset, validator, readonly, required); const Line line = {attr, label, regex, le, validator, required}; ui.lines.push_back(line); - if (attr != QLatin1String("EMAIL")) { + if (attr != QLatin1StringView("EMAIL")) { connect(le, &QLineEdit::textChanged, le, [this]() { updateDN(); }); } connect(le, &QLineEdit::textChanged, le, [this]() { checkRequirements(); }); } } void updateDN() { ui.dn->setText(cmsDN()); } QString cmsDN() const { DN dn; for (const Line &line : ui.lines) { const QString text = line.edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(line.attr); - if (attr == QLatin1String("EMAIL")) { + if (attr == QLatin1StringView("EMAIL")) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } void checkRequirements() { const QString error = requirementsAreMet(ui.lines); ui.error->setText(error); Q_EMIT q->validityChanged(error.isEmpty()); } QLineEdit *attributeWidget(const QString &attribute) { for (const Line &line : ui.lines) { if (attributeFromKey(line.attr) == attribute) { return line.edit; } } qCWarning(KLEOPATRA_LOG) << "CertificateDetailsInputWidget: No widget for attribute" << attribute; return nullptr; } void setAttributeValue(const QString &attribute, const QString &value) { QLineEdit *w = attributeWidget(attribute); if (w) { w->setText(value); } } QString attributeValue(const QString &attribute) { const QLineEdit *w = attributeWidget(attribute); return w ? w->text().trimmed() : QString(); } }; CertificateDetailsInputWidget::CertificateDetailsInputWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { } CertificateDetailsInputWidget::~CertificateDetailsInputWidget() { } void CertificateDetailsInputWidget::setName(const QString &name) { d->setAttributeValue(QStringLiteral("CN"), name); } void CertificateDetailsInputWidget::setEmail(const QString &email) { d->setAttributeValue(QStringLiteral("EMAIL"), email); } QString CertificateDetailsInputWidget::email() const { return d->attributeValue(QStringLiteral("EMAIL")); } QString CertificateDetailsInputWidget::dn() const { return d->ui.dn->text(); } #include "moc_certificatedetailsinputwidget.cpp" diff --git a/src/dialogs/certificatedetailswidget.cpp b/src/dialogs/certificatedetailswidget.cpp index 416b8332e..08e19eaac 100644 --- a/src/dialogs/certificatedetailswidget.cpp +++ b/src/dialogs/certificatedetailswidget.cpp @@ -1,1136 +1,1136 @@ /* dialogs/certificatedetailswidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2016 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-FileCopyrightText: 2022 Felix Tiede SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "certificatedetailswidget.h" #include "exportdialog.h" #include "kleopatra_debug.h" #include "subkeyswidget.h" #include "trustchainwidget.h" #include "weboftrustdialog.h" #include "commands/certifycertificatecommand.h" #include "commands/changeexpirycommand.h" #include "commands/changepassphrasecommand.h" #ifdef MAILAKONADI_ENABLED #include "commands/exportopenpgpcerttoprovidercommand.h" #endif // MAILAKONADI_ENABLED #include "commands/adduseridcommand.h" #include "commands/detailscommand.h" #include "commands/dumpcertificatecommand.h" #include "commands/genrevokecommand.h" #include "commands/refreshcertificatecommand.h" #include "commands/revokecertificationcommand.h" #include "commands/revokeuseridcommand.h" #include "commands/setprimaryuseridcommand.h" #include "utils/accessibility.h" #include "utils/tags.h" #include "view/infofield.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if __has_include() #include #if defined(__cpp_lib_ranges) && __cpp_lib_ranges >= 201911L #define USE_RANGES #endif #endif #include Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; namespace { std::vector selectedUserIDs(const QTreeWidget *treeWidget) { if (!treeWidget) { return {}; } std::vector userIDs; const auto selected = treeWidget->selectedItems(); std::transform(selected.begin(), selected.end(), std::back_inserter(userIDs), [](const QTreeWidgetItem *item) { return item->data(0, Qt::UserRole).value(); }); return userIDs; } } class CertificateDetailsWidget::Private { public: Private(CertificateDetailsWidget *qq); void setupCommonProperties(); void updateUserIDActions(); void setUpUserIDTable(); void setUpSMIMEAdressList(); void setupPGPProperties(); void setupSMIMEProperties(); void revokeUserID(const GpgME::UserID &uid); void revokeSelectedUserID(); void genRevokeCert(); void refreshCertificate(); void certifyUserIDs(); void revokeCertifications(); void webOfTrustClicked(); void exportClicked(); void addUserID(); void setPrimaryUserID(const GpgME::UserID &uid = {}); void changePassphrase(); void changeExpiration(); void keysMayHaveChanged(); void showTrustChainDialog(); void showMoreDetails(); void userIDTableContextMenuRequested(const QPoint &p); QString tofuTooltipString(const GpgME::UserID &uid) const; QIcon trustLevelIcon(const GpgME::UserID &uid) const; QString trustLevelText(const GpgME::UserID &uid) const; void showIssuerCertificate(); void updateKey(); void setUpdatedKey(const GpgME::Key &key); void keyListDone(const GpgME::KeyListResult &, const std::vector &, const QString &, const GpgME::Error &); void copyFingerprintToClipboard(); private: CertificateDetailsWidget *const q; public: GpgME::Key key; bool updateInProgress = false; private: InfoField *attributeField(const QString &attributeName) { const auto keyValuePairIt = ui.smimeAttributeFields.find(attributeName); if (keyValuePairIt != ui.smimeAttributeFields.end()) { return (*keyValuePairIt).second.get(); } return nullptr; } private: struct UI { QWidget *userIDs = nullptr; QLabel *userIDTableLabel = nullptr; TreeWidget *userIDTable = nullptr; QPushButton *addUserIDBtn = nullptr; QPushButton *setPrimaryUserIDBtn = nullptr; QPushButton *certifyBtn = nullptr; QPushButton *revokeCertificationsBtn = nullptr; QPushButton *revokeUserIDBtn = nullptr; QPushButton *webOfTrustBtn = nullptr; std::map> smimeAttributeFields; std::unique_ptr smimeTrustLevelField; std::unique_ptr validFromField; std::unique_ptr expiresField; QAction *changeExpirationAction = nullptr; std::unique_ptr fingerprintField; QAction *copyFingerprintAction = nullptr; std::unique_ptr smimeIssuerField; QAction *showIssuerCertificateAction = nullptr; std::unique_ptr complianceField; std::unique_ptr trustedIntroducerField; QLabel *smimeRelatedAddresses = nullptr; QListWidget *smimeAddressList = nullptr; QPushButton *moreDetailsBtn = nullptr; QPushButton *trustChainDetailsBtn = nullptr; QPushButton *refreshBtn = nullptr; QPushButton *changePassphraseBtn = nullptr; QPushButton *exportBtn = nullptr; QPushButton *genRevokeBtn = nullptr; void setupUi(QWidget *parent) { auto mainLayout = new QVBoxLayout{parent}; userIDs = new QWidget{parent}; { auto userIDsLayout = new QVBoxLayout{userIDs}; userIDsLayout->setContentsMargins({}); userIDTableLabel = new QLabel(i18n("User IDs:"), parent); userIDsLayout->addWidget(userIDTableLabel); userIDTable = new TreeWidget{parent}; userIDTableLabel->setBuddy(userIDTable); userIDTable->setAccessibleName(i18n("User IDs")); QTreeWidgetItem *__qtreewidgetitem = new QTreeWidgetItem(); __qtreewidgetitem->setText(0, QString::fromUtf8("1")); userIDTable->setHeaderItem(__qtreewidgetitem); userIDTable->setEditTriggers(QAbstractItemView::NoEditTriggers); userIDTable->setSelectionMode(QAbstractItemView::ExtendedSelection); userIDTable->setRootIsDecorated(false); userIDTable->setUniformRowHeights(true); userIDTable->setAllColumnsShowFocus(false); userIDsLayout->addWidget(userIDTable); { auto buttonRow = new QHBoxLayout; addUserIDBtn = new QPushButton(i18nc("@action:button", "Add User ID"), parent); buttonRow->addWidget(addUserIDBtn); setPrimaryUserIDBtn = new QPushButton{i18nc("@action:button", "Flag as Primary"), parent}; setPrimaryUserIDBtn->setToolTip(i18nc("@info:tooltip", "Flag the selected user ID as the primary user ID of this key.")); buttonRow->addWidget(setPrimaryUserIDBtn); certifyBtn = new QPushButton(i18nc("@action:button", "Certify User IDs"), parent); buttonRow->addWidget(certifyBtn); webOfTrustBtn = new QPushButton(i18nc("@action:button", "Show Certifications"), parent); buttonRow->addWidget(webOfTrustBtn); revokeCertificationsBtn = new QPushButton(i18nc("@action:button", "Revoke Certifications"), parent); buttonRow->addWidget(revokeCertificationsBtn); revokeUserIDBtn = new QPushButton(i18nc("@action:button", "Revoke User ID"), parent); buttonRow->addWidget(revokeUserIDBtn); buttonRow->addStretch(1); userIDsLayout->addLayout(buttonRow); } userIDsLayout->addWidget(new KSeparator{Qt::Horizontal, parent}); } mainLayout->addWidget(userIDs); { auto gridLayout = new QGridLayout; gridLayout->setColumnStretch(1, 1); int row = -1; for (const auto &attribute : DN::attributeOrder()) { const auto attributeLabel = DN::attributeNameToLabel(attribute); if (attributeLabel.isEmpty()) { continue; } const auto labelWithColon = i18nc("interpunctation for labels", "%1:", attributeLabel); const auto &[it, inserted] = smimeAttributeFields.try_emplace(attribute, std::make_unique(labelWithColon, parent)); if (inserted) { row++; const auto &field = it->second; gridLayout->addWidget(field->label(), row, 0); gridLayout->addLayout(field->layout(), row, 1); } } row++; smimeTrustLevelField = std::make_unique(i18n("Trust level:"), parent); gridLayout->addWidget(smimeTrustLevelField->label(), row, 0); gridLayout->addLayout(smimeTrustLevelField->layout(), row, 1); row++; validFromField = std::make_unique(i18n("Valid from:"), parent); gridLayout->addWidget(validFromField->label(), row, 0); gridLayout->addLayout(validFromField->layout(), row, 1); row++; expiresField = std::make_unique(i18n("Valid until:"), parent); changeExpirationAction = new QAction{parent}; changeExpirationAction->setIcon(QIcon::fromTheme(QStringLiteral("editor"))); changeExpirationAction->setToolTip(i18nc("@info:tooltip", "Change the end of the validity period")); Kleo::setAccessibleName(changeExpirationAction, i18nc("@action:button", "Change Validity")); expiresField->setAction(changeExpirationAction); gridLayout->addWidget(expiresField->label(), row, 0); gridLayout->addLayout(expiresField->layout(), row, 1); row++; fingerprintField = std::make_unique(i18n("Fingerprint:"), parent); if (QGuiApplication::clipboard()) { copyFingerprintAction = new QAction{parent}; copyFingerprintAction->setIcon(QIcon::fromTheme(QStringLiteral("edit-copy"))); copyFingerprintAction->setToolTip(i18nc("@info:tooltip", "Copy the fingerprint to the clipboard")); Kleo::setAccessibleName(copyFingerprintAction, i18nc("@action:button", "Copy fingerprint")); fingerprintField->setAction(copyFingerprintAction); } gridLayout->addWidget(fingerprintField->label(), row, 0); gridLayout->addLayout(fingerprintField->layout(), row, 1); row++; smimeIssuerField = std::make_unique(i18n("Issuer:"), parent); showIssuerCertificateAction = new QAction{parent}; showIssuerCertificateAction->setIcon(QIcon::fromTheme(QStringLiteral("dialog-information"))); showIssuerCertificateAction->setToolTip(i18nc("@info:tooltip", "Show the issuer certificate")); Kleo::setAccessibleName(showIssuerCertificateAction, i18nc("@action:button", "Show certificate")); smimeIssuerField->setAction(showIssuerCertificateAction); gridLayout->addWidget(smimeIssuerField->label(), row, 0); gridLayout->addLayout(smimeIssuerField->layout(), row, 1); row++; complianceField = std::make_unique(i18n("Compliance:"), parent); gridLayout->addWidget(complianceField->label(), row, 0); gridLayout->addLayout(complianceField->layout(), row, 1); row++; trustedIntroducerField = std::make_unique(i18n("Trusted introducer for:"), parent); gridLayout->addWidget(trustedIntroducerField->label(), row, 0); trustedIntroducerField->setToolTip(i18n("See certifications for details.")); gridLayout->addLayout(trustedIntroducerField->layout(), row, 1); mainLayout->addLayout(gridLayout); } smimeRelatedAddresses = new QLabel(i18n("Related addresses:"), parent); mainLayout->addWidget(smimeRelatedAddresses); smimeAddressList = new QListWidget{parent}; smimeRelatedAddresses->setBuddy(smimeAddressList); smimeAddressList->setAccessibleName(i18n("Related addresses")); smimeAddressList->setEditTriggers(QAbstractItemView::NoEditTriggers); smimeAddressList->setSelectionMode(QAbstractItemView::SingleSelection); mainLayout->addWidget(smimeAddressList); mainLayout->addStretch(); { auto buttonRow = new QHBoxLayout; moreDetailsBtn = new QPushButton(i18nc("@action:button", "More Details..."), parent); buttonRow->addWidget(moreDetailsBtn); trustChainDetailsBtn = new QPushButton(i18nc("@action:button", "Trust Chain Details"), parent); buttonRow->addWidget(trustChainDetailsBtn); refreshBtn = new QPushButton{i18nc("@action:button", "Update"), parent}; buttonRow->addWidget(refreshBtn); exportBtn = new QPushButton(i18nc("@action:button", "Export"), parent); buttonRow->addWidget(exportBtn); changePassphraseBtn = new QPushButton(i18nc("@action:button", "Change Passphrase"), parent); buttonRow->addWidget(changePassphraseBtn); genRevokeBtn = new QPushButton(i18nc("@action:button", "Generate Revocation Certificate"), parent); genRevokeBtn->setToolTip(u"" % i18n("A revocation certificate is a file that serves as a \"kill switch\" to publicly " "declare that a key shall not anymore be used. It is not possible " "to retract such a revocation certificate once it has been published.") % u""); buttonRow->addWidget(genRevokeBtn); buttonRow->addStretch(1); mainLayout->addLayout(buttonRow); } } } ui; }; CertificateDetailsWidget::Private::Private(CertificateDetailsWidget *qq) : q{qq} { ui.setupUi(q); ui.userIDTable->setContextMenuPolicy(Qt::CustomContextMenu); connect(ui.userIDTable, &QAbstractItemView::customContextMenuRequested, q, [this](const QPoint &p) { userIDTableContextMenuRequested(p); }); connect(ui.userIDTable, &QTreeWidget::itemSelectionChanged, q, [this]() { updateUserIDActions(); }); connect(ui.addUserIDBtn, &QPushButton::clicked, q, [this]() { addUserID(); }); connect(ui.setPrimaryUserIDBtn, &QPushButton::clicked, q, [this]() { setPrimaryUserID(); }); connect(ui.revokeUserIDBtn, &QPushButton::clicked, q, [this]() { revokeSelectedUserID(); }); connect(ui.changePassphraseBtn, &QPushButton::clicked, q, [this]() { changePassphrase(); }); connect(ui.genRevokeBtn, &QPushButton::clicked, q, [this]() { genRevokeCert(); }); connect(ui.changeExpirationAction, &QAction::triggered, q, [this]() { changeExpiration(); }); connect(ui.showIssuerCertificateAction, &QAction::triggered, q, [this]() { showIssuerCertificate(); }); connect(ui.trustChainDetailsBtn, &QPushButton::pressed, q, [this]() { showTrustChainDialog(); }); connect(ui.moreDetailsBtn, &QPushButton::pressed, q, [this]() { showMoreDetails(); }); connect(ui.refreshBtn, &QPushButton::clicked, q, [this]() { refreshCertificate(); }); connect(ui.certifyBtn, &QPushButton::clicked, q, [this]() { certifyUserIDs(); }); connect(ui.revokeCertificationsBtn, &QPushButton::clicked, q, [this]() { revokeCertifications(); }); connect(ui.webOfTrustBtn, &QPushButton::clicked, q, [this]() { webOfTrustClicked(); }); connect(ui.exportBtn, &QPushButton::clicked, q, [this]() { exportClicked(); }); if (ui.copyFingerprintAction) { connect(ui.copyFingerprintAction, &QAction::triggered, q, [this]() { copyFingerprintToClipboard(); }); } connect(Kleo::KeyCache::instance().get(), &Kleo::KeyCache::keysMayHaveChanged, q, [this]() { keysMayHaveChanged(); }); } void CertificateDetailsWidget::Private::setupCommonProperties() { const bool isOpenPGP = key.protocol() == GpgME::OpenPGP; const bool isSMIME = key.protocol() == GpgME::CMS; const bool isOwnKey = key.hasSecret(); const auto isLocalKey = !isRemoteKey(key); const auto keyCanBeCertified = Kleo::canBeCertified(key); // update visibility of UI elements ui.userIDs->setVisible(isOpenPGP); ui.addUserIDBtn->setVisible(isOwnKey); ui.setPrimaryUserIDBtn->setVisible(isOwnKey); // ui.certifyBtn->setVisible(true); // always visible (for OpenPGP keys) // ui.webOfTrustBtn->setVisible(true); // always visible (for OpenPGP keys) ui.revokeCertificationsBtn->setVisible(Kleo::Commands::RevokeCertificationCommand::isSupported()); ui.revokeUserIDBtn->setVisible(isOwnKey); for (const auto &[_, field] : ui.smimeAttributeFields) { field->setVisible(isSMIME); } ui.smimeTrustLevelField->setVisible(isSMIME); // ui.validFromField->setVisible(true); // always visible // ui.expiresField->setVisible(true); // always visible if (isOpenPGP && isOwnKey) { ui.expiresField->setAction(ui.changeExpirationAction); } else { ui.expiresField->setAction(nullptr); } // ui.fingerprintField->setVisible(true); // always visible ui.smimeIssuerField->setVisible(isSMIME); ui.complianceField->setVisible(DeVSCompliance::isCompliant()); ui.trustedIntroducerField->setVisible(isOpenPGP); // may be hidden again by setupPGPProperties() ui.smimeRelatedAddresses->setVisible(isSMIME); ui.smimeAddressList->setVisible(isSMIME); ui.moreDetailsBtn->setVisible(isLocalKey); ui.moreDetailsBtn->setText(isSMIME ? i18nc("@action:button", "More Details...") : isOwnKey ? i18nc("@action:button", "Manage Subkeys") : i18nc("@action:button", "Show Subkeys")); ui.trustChainDetailsBtn->setVisible(isSMIME); ui.refreshBtn->setVisible(isLocalKey); ui.changePassphraseBtn->setVisible(isSecretKeyStoredInKeyRing(key)); ui.exportBtn->setVisible(isLocalKey); ui.genRevokeBtn->setVisible(isOpenPGP && isOwnKey); // update availability of buttons const auto userCanSignUserIDs = userHasCertificationKey(); ui.addUserIDBtn->setEnabled(canBeUsedForSecretKeyOperations(key)); ui.setPrimaryUserIDBtn->setEnabled(false); // requires a selected user ID ui.certifyBtn->setEnabled(isLocalKey && keyCanBeCertified && userCanSignUserIDs); ui.webOfTrustBtn->setEnabled(isLocalKey); ui.revokeCertificationsBtn->setEnabled(userCanSignUserIDs && isLocalKey); ui.revokeUserIDBtn->setEnabled(false); // requires a selected user ID ui.changeExpirationAction->setEnabled(canBeUsedForSecretKeyOperations(key)); ui.changePassphraseBtn->setEnabled(isSecretKeyStoredInKeyRing(key)); ui.genRevokeBtn->setEnabled(canBeUsedForSecretKeyOperations(key)); // update values of protocol-independent UI elements ui.validFromField->setValue(Formatting::creationDateString(key), Formatting::accessibleCreationDate(key)); ui.expiresField->setValue(Formatting::expirationDateString(key, i18nc("Valid until:", "unlimited")), Formatting::accessibleExpirationDate(key)); ui.fingerprintField->setValue(Formatting::prettyID(key.primaryFingerprint()), Formatting::accessibleHexID(key.primaryFingerprint())); if (DeVSCompliance::isCompliant()) { ui.complianceField->setValue(Kleo::Formatting::complianceStringForKey(key)); } } void CertificateDetailsWidget::Private::updateUserIDActions() { const auto userIDs = selectedUserIDs(ui.userIDTable); const auto singleUserID = userIDs.size() == 1 ? userIDs.front() : GpgME::UserID{}; const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0)); ui.setPrimaryUserIDBtn->setEnabled(!singleUserID.isNull() // && !isPrimaryUserID // && !Kleo::isRevokedOrExpired(singleUserID) // && canBeUsedForSecretKeyOperations(key)); ui.revokeUserIDBtn->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID)); } void CertificateDetailsWidget::Private::setUpUserIDTable() { ui.userIDTable->clear(); QStringList headers = {i18n("Email"), i18n("Name"), i18n("Trust Level"), i18n("Tags")}; ui.userIDTable->setColumnCount(headers.count()); ui.userIDTable->setColumnWidth(0, 200); ui.userIDTable->setColumnWidth(1, 200); ui.userIDTable->setHeaderLabels(headers); const auto uids = key.userIDs(); for (unsigned int i = 0; i < uids.size(); ++i) { const auto &uid = uids[i]; auto item = new QTreeWidgetItem; const QString toolTip = tofuTooltipString(uid); item->setData(0, Qt::UserRole, QVariant::fromValue(uid)); auto pMail = Kleo::Formatting::prettyEMail(uid); auto pName = Kleo::Formatting::prettyName(uid); item->setData(0, Qt::DisplayRole, pMail); item->setData(0, Qt::ToolTipRole, toolTip); item->setData(0, Qt::AccessibleTextRole, pMail.isEmpty() ? i18nc("text for screen readers for an empty email address", "no email") : pMail); item->setData(1, Qt::DisplayRole, pName); item->setData(1, Qt::ToolTipRole, toolTip); item->setData(2, Qt::DecorationRole, trustLevelIcon(uid)); item->setData(2, Qt::DisplayRole, trustLevelText(uid)); item->setData(2, Qt::ToolTipRole, toolTip); GpgME::Error err; QStringList tagList; for (const auto &tag : uid.remarks(Tags::tagKeys(), err)) { if (err) { qCWarning(KLEOPATRA_LOG) << "Getting remarks for user ID" << uid.id() << "failed:" << err; } tagList << QString::fromStdString(tag); } qCDebug(KLEOPATRA_LOG) << "tagList:" << tagList; const auto tags = tagList.join(QStringLiteral("; ")); item->setData(3, Qt::DisplayRole, tags); item->setData(3, Qt::ToolTipRole, toolTip); ui.userIDTable->addTopLevelItem(item); } if (!Tags::tagsEnabled()) { ui.userIDTable->hideColumn(3); } } void CertificateDetailsWidget::Private::setUpSMIMEAdressList() { ui.smimeAddressList->clear(); const auto *const emailField = attributeField(QStringLiteral("EMAIL")); // add email address from primary user ID if not listed already as attribute field if (!emailField) { const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); const QString dnEmail = dn[QStringLiteral("EMAIL")]; if (!dnEmail.isEmpty()) { ui.smimeAddressList->addItem(dnEmail); } } if (key.numUserIDs() > 1) { // iterate over the secondary user IDs #ifdef USE_RANGES for (const auto uids = key.userIDs(); const auto &uid : std::ranges::subrange(std::next(uids.begin()), uids.end())) { #else const auto uids = key.userIDs(); for (auto it = std::next(uids.begin()); it != uids.end(); ++it) { const auto &uid = *it; #endif const auto name = Kleo::Formatting::prettyName(uid); const auto email = Kleo::Formatting::prettyEMail(uid); QString itemText; if (name.isEmpty() && !email.isEmpty()) { // skip email addresses already listed in email attribute field if (emailField && email == emailField->value()) { continue; } itemText = email; } else { // S/MIME certificates sometimes contain urls where both // name and mail is empty. In that case we print whatever // the uid is as name. // // Can be ugly like (3:uri24:http://ca.intevation.org), but // this is better then showing an empty entry. itemText = QString::fromUtf8(uid.id()); } // avoid duplicate entries in the list if (ui.smimeAddressList->findItems(itemText, Qt::MatchExactly).empty()) { ui.smimeAddressList->addItem(itemText); } } } if (ui.smimeAddressList->count() == 0) { ui.smimeRelatedAddresses->setVisible(false); ui.smimeAddressList->setVisible(false); } } void CertificateDetailsWidget::Private::revokeUserID(const GpgME::UserID &userId) { const QString message = xi18nc("@info", "Do you really want to revoke the user ID%1 ?", QString::fromUtf8(userId.id())); auto confirmButton = KStandardGuiItem::ok(); confirmButton.setText(i18nc("@action:button", "Revoke User ID")); confirmButton.setToolTip({}); const auto choice = KMessageBox::questionTwoActions(q->window(), message, i18nc("@title:window", "Confirm Revocation"), confirmButton, KStandardGuiItem::cancel(), {}, KMessageBox::Notify | KMessageBox::WindowModal); if (choice != KMessageBox::ButtonCode::PrimaryAction) { return; } auto cmd = new Commands::RevokeUserIDCommand(userId); cmd->setParentWidget(q); connect(cmd, &Command::finished, q, [this]() { ui.userIDTable->setEnabled(true); // the Revoke User ID button will be updated by the key update updateKey(); }); ui.userIDTable->setEnabled(false); ui.revokeUserIDBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::revokeSelectedUserID() { const auto userIDs = selectedUserIDs(ui.userIDTable); if (userIDs.size() != 1) { return; } revokeUserID(userIDs.front()); } void CertificateDetailsWidget::Private::changeExpiration() { auto cmd = new Kleo::Commands::ChangeExpiryCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangeExpiryCommand::finished, q, [this]() { ui.changeExpirationAction->setEnabled(true); }); ui.changeExpirationAction->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::changePassphrase() { auto cmd = new Kleo::Commands::ChangePassphraseCommand(key); QObject::connect(cmd, &Kleo::Commands::ChangePassphraseCommand::finished, q, [this]() { ui.changePassphraseBtn->setEnabled(true); }); ui.changePassphraseBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::genRevokeCert() { auto cmd = new Kleo::Commands::GenRevokeCommand(key); QObject::connect(cmd, &Kleo::Commands::GenRevokeCommand::finished, q, [this]() { ui.genRevokeBtn->setEnabled(true); }); ui.genRevokeBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::refreshCertificate() { auto cmd = new Kleo::RefreshCertificateCommand{key}; QObject::connect(cmd, &Kleo::RefreshCertificateCommand::finished, q, [this]() { ui.refreshBtn->setEnabled(true); }); ui.refreshBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::certifyUserIDs() { const auto userIDs = selectedUserIDs(ui.userIDTable); auto cmd = userIDs.empty() ? new Kleo::Commands::CertifyCertificateCommand{key} // : new Kleo::Commands::CertifyCertificateCommand{userIDs}; QObject::connect(cmd, &Kleo::Commands::CertifyCertificateCommand::finished, q, [this]() { updateKey(); ui.certifyBtn->setEnabled(true); }); ui.certifyBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::revokeCertifications() { const auto userIDs = selectedUserIDs(ui.userIDTable); auto cmd = userIDs.empty() ? new Kleo::Commands::RevokeCertificationCommand{key} // : new Kleo::Commands::RevokeCertificationCommand{userIDs}; QObject::connect(cmd, &Kleo::Command::finished, q, [this]() { updateKey(); ui.revokeCertificationsBtn->setEnabled(true); }); ui.revokeCertificationsBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::webOfTrustClicked() { QScopedPointer dlg(new WebOfTrustDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::exportClicked() { QScopedPointer dlg(new ExportDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::addUserID() { auto cmd = new Kleo::Commands::AddUserIDCommand(key); QObject::connect(cmd, &Kleo::Commands::AddUserIDCommand::finished, q, [this]() { ui.addUserIDBtn->setEnabled(true); updateKey(); }); ui.addUserIDBtn->setEnabled(false); cmd->start(); } void CertificateDetailsWidget::Private::setPrimaryUserID(const GpgME::UserID &uid) { auto userId = uid; if (userId.isNull()) { const auto userIDs = selectedUserIDs(ui.userIDTable); if (userIDs.size() != 1) { return; } userId = userIDs.front(); } auto cmd = new Kleo::Commands::SetPrimaryUserIDCommand(userId); QObject::connect(cmd, &Kleo::Commands::SetPrimaryUserIDCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); // the Flag As Primary button will be updated by the key update updateKey(); }); ui.userIDTable->setEnabled(false); ui.setPrimaryUserIDBtn->setEnabled(false); cmd->start(); } namespace { void ensureThatKeyDetailsAreLoaded(GpgME::Key &key) { if (key.userID(0).numSignatures() == 0) { key.update(); } } } void CertificateDetailsWidget::Private::keysMayHaveChanged() { auto newKey = Kleo::KeyCache::instance()->findByFingerprint(key.primaryFingerprint()); if (!newKey.isNull()) { ensureThatKeyDetailsAreLoaded(newKey); setUpdatedKey(newKey); } } void CertificateDetailsWidget::Private::showTrustChainDialog() { QScopedPointer dlg(new TrustChainDialog(q)); dlg->setKey(key); dlg->exec(); } void CertificateDetailsWidget::Private::userIDTableContextMenuRequested(const QPoint &p) { const auto userIDs = selectedUserIDs(ui.userIDTable); const auto singleUserID = (userIDs.size() == 1) ? userIDs.front() : GpgME::UserID{}; const bool isPrimaryUserID = !singleUserID.isNull() && (ui.userIDTable->selectedItems().front() == ui.userIDTable->topLevelItem(0)); const bool canSignUserIDs = userHasCertificationKey(); const auto isLocalKey = !isRemoteKey(key); const auto keyCanBeCertified = Kleo::canBeCertified(key); auto menu = new QMenu(q); if (key.hasSecret()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("favorite")), i18nc("@action:inmenu", "Flag as Primary User ID"), q, [this, singleUserID]() { setPrimaryUserID(singleUserID); }); action->setEnabled(!singleUserID.isNull() // && !isPrimaryUserID // && !Kleo::isRevokedOrExpired(singleUserID) // && canBeUsedForSecretKeyOperations(key)); } { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Certify User IDs...") : i18ncp("@action:inmenu", "Certify User ID...", "Certify User IDs...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-sign")), actionText, q, [this]() { certifyUserIDs(); }); action->setEnabled(isLocalKey && keyCanBeCertified && canSignUserIDs); } if (Kleo::Commands::RevokeCertificationCommand::isSupported()) { const auto actionText = userIDs.empty() ? i18nc("@action:inmenu", "Revoke Certifications...") : i18ncp("@action:inmenu", "Revoke Certification...", "Revoke Certifications...", userIDs.size()); auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), actionText, q, [this]() { revokeCertifications(); }); action->setEnabled(isLocalKey && canSignUserIDs); } #ifdef MAILAKONADI_ENABLED if (key.hasSecret()) { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-export")), i18nc("@action:inmenu", "Publish at Mail Provider ..."), q, [this, singleUserID]() { auto cmd = new Kleo::Commands::ExportOpenPGPCertToProviderCommand(singleUserID); ui.userIDTable->setEnabled(false); connect(cmd, &Kleo::Commands::ExportOpenPGPCertToProviderCommand::finished, q, [this]() { ui.userIDTable->setEnabled(true); }); cmd->start(); }); action->setEnabled(!singleUserID.isNull()); } #endif // MAILAKONADI_ENABLED { auto action = menu->addAction(QIcon::fromTheme(QStringLiteral("view-certificate-revoke")), i18nc("@action:inmenu", "Revoke User ID"), q, [this, singleUserID]() { revokeUserID(singleUserID); }); action->setEnabled(!singleUserID.isNull() && canCreateCertifications(key) && canRevokeUserID(singleUserID)); } connect(menu, &QMenu::aboutToHide, menu, &QObject::deleteLater); menu->popup(ui.userIDTable->viewport()->mapToGlobal(p)); } void CertificateDetailsWidget::Private::showMoreDetails() { if (key.protocol() == GpgME::CMS) { auto cmd = new Kleo::Commands::DumpCertificateCommand(key); cmd->setParentWidget(q); cmd->setUseDialog(true); cmd->start(); } else { auto dlg = new SubKeysDialog{q}; dlg->setAttribute(Qt::WA_DeleteOnClose); dlg->setKey(key); dlg->open(); } } QString CertificateDetailsWidget::Private::tofuTooltipString(const GpgME::UserID &uid) const { const auto tofu = uid.tofuInfo(); if (tofu.isNull()) { return QString(); } QString html = QStringLiteral(""); const auto appendRow = [&html](const QString &lbl, const QString &val) { html += QStringLiteral( "" "" "" "") .arg(lbl, val); }; const auto appendHeader = [this, &html](const QString &hdr) { html += QStringLiteral("") .arg(q->palette().highlight().color().name(), q->palette().highlightedText().color().name(), hdr); }; const auto dateTime = [](long ts) { QLocale l; return ts == 0 ? i18n("never") : l.toString(QDateTime::fromSecsSinceEpoch(ts), QLocale::ShortFormat); }; appendHeader(i18n("Signing")); appendRow(i18n("First message"), dateTime(tofu.signFirst())); appendRow(i18n("Last message"), dateTime(tofu.signLast())); appendRow(i18n("Message count"), QString::number(tofu.signCount())); appendHeader(i18n("Encryption")); appendRow(i18n("First message"), dateTime(tofu.encrFirst())); appendRow(i18n("Last message"), dateTime(tofu.encrLast())); appendRow(i18n("Message count"), QString::number(tofu.encrCount())); html += QStringLiteral("
    %1:%2
    %3
    "); // Make sure the tooltip string is different for each UserID, even if the // data are the same, otherwise the tooltip is not updated and moved when // user moves mouse from one row to another. html += QStringLiteral("").arg(QString::fromUtf8(uid.id())); return html; } QIcon CertificateDetailsWidget::Private::trustLevelIcon(const GpgME::UserID &uid) const { if (updateInProgress) { return QIcon::fromTheme(QStringLiteral("emblem-question")); } switch (uid.validity()) { case GpgME::UserID::Unknown: case GpgME::UserID::Undefined: return QIcon::fromTheme(QStringLiteral("emblem-question")); case GpgME::UserID::Never: return QIcon::fromTheme(QStringLiteral("emblem-error")); case GpgME::UserID::Marginal: return QIcon::fromTheme(QStringLiteral("emblem-warning")); case GpgME::UserID::Full: case GpgME::UserID::Ultimate: return QIcon::fromTheme(QStringLiteral("emblem-success")); } return {}; } QString CertificateDetailsWidget::Private::trustLevelText(const GpgME::UserID &uid) const { return updateInProgress ? i18n("Updating...") : Formatting::validityShort(uid); } namespace { auto isGood(const GpgME::UserID::Signature &signature) { return signature.status() == GpgME::UserID::Signature::NoError // && !signature.isInvalid() // && 0x10 <= signature.certClass() && signature.certClass() <= 0x13; } auto accumulateTrustDomains(const std::vector &signatures) { return std::accumulate(std::begin(signatures), std::end(signatures), std::set(), [](auto domains, const auto &signature) { if (isGood(signature) && signature.isTrustSignature()) { domains.insert(Formatting::trustSignatureDomain(signature)); } return domains; }); } auto accumulateTrustDomains(const std::vector &userIds) { return std::accumulate(std::begin(userIds), std::end(userIds), std::set(), [](auto domains, const auto &userID) { const auto newDomains = accumulateTrustDomains(userID.signatures()); std::copy(std::begin(newDomains), std::end(newDomains), std::inserter(domains, std::end(domains))); return domains; }); } } void CertificateDetailsWidget::Private::setupPGPProperties() { setUpUserIDTable(); const auto trustDomains = accumulateTrustDomains(key.userIDs()); ui.trustedIntroducerField->setVisible(!trustDomains.empty()); ui.trustedIntroducerField->setValue(QStringList(std::begin(trustDomains), std::end(trustDomains)).join(u", ")); ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the key from external sources.")); } static QString formatDNToolTip(const Kleo::DN &dn) { QString html = QStringLiteral(""); const auto appendRow = [&html, dn](const QString &lbl, const QString &attr) { const QString val = dn[attr]; if (!val.isEmpty()) { html += QStringLiteral( "" "" "") .arg(lbl, val); } }; appendRow(i18n("Common Name"), QStringLiteral("CN")); appendRow(i18n("Organization"), QStringLiteral("O")); appendRow(i18n("Street"), QStringLiteral("STREET")); appendRow(i18n("City"), QStringLiteral("L")); appendRow(i18n("State"), QStringLiteral("ST")); appendRow(i18n("Country"), QStringLiteral("C")); html += QStringLiteral("
    %1:%2
    "); return html; } void CertificateDetailsWidget::Private::setupSMIMEProperties() { const auto ownerId = key.userID(0); const Kleo::DN dn(ownerId.id()); for (const auto &[attributeName, field] : ui.smimeAttributeFields) { const QString attributeValue = dn[attributeName]; field->setValue(attributeValue); field->setVisible(!attributeValue.isEmpty()); } ui.smimeTrustLevelField->setIcon(trustLevelIcon(ownerId)); ui.smimeTrustLevelField->setValue(trustLevelText(ownerId)); const Kleo::DN issuerDN(key.issuerName()); const QString issuerCN = issuerDN[QStringLiteral("CN")]; const QString issuer = issuerCN.isEmpty() ? QString::fromUtf8(key.issuerName()) : issuerCN; ui.smimeIssuerField->setValue(issuer); ui.smimeIssuerField->setToolTip(formatDNToolTip(issuerDN)); ui.showIssuerCertificateAction->setEnabled(!key.isRoot()); setUpSMIMEAdressList(); ui.refreshBtn->setToolTip(i18nc("@info:tooltip", "Update the CRLs and do a full validation check of the certificate.")); } void CertificateDetailsWidget::Private::showIssuerCertificate() { // there is either one or no parent key const auto parentKeys = KeyCache::instance()->findIssuers(key, KeyCache::NoOption); if (parentKeys.empty()) { KMessageBox::error(q, i18n("The issuer certificate could not be found locally.")); return; } auto cmd = new Kleo::Commands::DetailsCommand(parentKeys.front()); cmd->setParentWidget(q); cmd->start(); } void CertificateDetailsWidget::Private::copyFingerprintToClipboard() { if (auto clipboard = QGuiApplication::clipboard()) { clipboard->setText(QString::fromLatin1(key.primaryFingerprint())); } } CertificateDetailsWidget::CertificateDetailsWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } CertificateDetailsWidget::~CertificateDetailsWidget() = default; void CertificateDetailsWidget::Private::keyListDone(const GpgME::KeyListResult &, const std::vector &keys, const QString &, const GpgME::Error &) { updateInProgress = false; if (keys.size() != 1) { qCWarning(KLEOPATRA_LOG) << "Invalid keylist result in update."; return; } // As we listen for keysmayhavechanged we get the update // after updating the keycache. KeyCache::mutableInstance()->insert(keys); } void CertificateDetailsWidget::Private::updateKey() { key.update(); setUpdatedKey(key); } void CertificateDetailsWidget::Private::setUpdatedKey(const GpgME::Key &k) { key = k; setupCommonProperties(); if (key.protocol() == GpgME::OpenPGP) { setupPGPProperties(); } else { setupSMIMEProperties(); } } void CertificateDetailsWidget::setKey(const GpgME::Key &key) { if (key.protocol() == GpgME::CMS) { // For everything but S/MIME this should be quick // and we don't need to show another status. d->updateInProgress = true; } d->setUpdatedKey(key); // Run a keylistjob with full details (TOFU / Validate) QGpgME::KeyListJob *job = key.protocol() == GpgME::OpenPGP ? QGpgME::openpgp()->keyListJob(false, true, true) : QGpgME::smime()->keyListJob(false, true, true); auto ctx = QGpgME::Job::context(job); ctx->addKeyListMode(GpgME::WithTofu); ctx->addKeyListMode(GpgME::SignatureNotations); if (key.hasSecret()) { ctx->addKeyListMode(GpgME::WithSecret); } // Windows QGpgME new style connect problem makes this necessary. connect(job, SIGNAL(result(GpgME::KeyListResult, std::vector, QString, GpgME::Error)), this, SLOT(keyListDone(GpgME::KeyListResult, std::vector, QString, GpgME::Error))); - job->start(QStringList() << QLatin1String(key.primaryFingerprint())); + job->start(QStringList() << QLatin1StringView(key.primaryFingerprint())); } GpgME::Key CertificateDetailsWidget::key() const { return d->key; } #include "moc_certificatedetailswidget.cpp" diff --git a/src/dialogs/certifywidget.cpp b/src/dialogs/certifywidget.cpp index 0e8e6a865..04a353402 100644 --- a/src/dialogs/certifywidget.cpp +++ b/src/dialogs/certifywidget.cpp @@ -1,1084 +1,1084 @@ /* dialogs/certifywidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019, 2021 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "certifywidget.h" #include "view/infofield.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Q_DECLARE_METATYPE(GpgME::UserID) using namespace Kleo; using namespace GpgME; static QDebug operator<<(QDebug s, const GpgME::UserID &userID) { return s << Formatting::prettyUserID(userID); } namespace { // Maybe move this in its own file // based on code from StackOverflow class AnimatedExpander : public QWidget { Q_OBJECT public: explicit AnimatedExpander(const QString &title, const QString &accessibleTitle = {}, QWidget *parent = nullptr); void setContentLayout(QLayout *contentLayout); bool isExpanded() const; void setExpanded(bool expanded); private: static const int animationDuration = 300; QGridLayout mainLayout; QToolButton toggleButton; QFrame headerLine; QParallelAnimationGroup toggleAnimation; QWidget contentArea; }; AnimatedExpander::AnimatedExpander(const QString &title, const QString &accessibleTitle, QWidget *parent) : QWidget{parent} { #ifdef Q_OS_WIN // draw dotted focus frame if button has focus; otherwise, draw invisible frame using background color toggleButton.setStyleSheet( QStringLiteral("QToolButton { border: 1px solid palette(window); }" "QToolButton:focus { border: 1px dotted palette(window-text); }")); #else // this works with Breeze style because Breeze draws the focus frame when drawing CE_ToolButtonLabel // while the Windows styles (and Qt's common base style) draw the focus frame before drawing CE_ToolButtonLabel toggleButton.setStyleSheet(QStringLiteral("QToolButton { border: none; }")); #endif toggleButton.setToolButtonStyle(Qt::ToolButtonTextBesideIcon); toggleButton.setArrowType(Qt::ArrowType::RightArrow); toggleButton.setText(title); if (!accessibleTitle.isEmpty()) { toggleButton.setAccessibleName(accessibleTitle); } toggleButton.setCheckable(true); toggleButton.setChecked(false); headerLine.setFrameShape(QFrame::HLine); headerLine.setFrameShadow(QFrame::Sunken); headerLine.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Maximum); contentArea.setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Fixed); // start out collapsed contentArea.setMaximumHeight(0); contentArea.setMinimumHeight(0); contentArea.setVisible(false); // let the entire widget grow and shrink with its content toggleAnimation.addAnimation(new QPropertyAnimation(this, "minimumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(this, "maximumHeight")); toggleAnimation.addAnimation(new QPropertyAnimation(&contentArea, "maximumHeight")); mainLayout.setVerticalSpacing(0); mainLayout.setContentsMargins(0, 0, 0, 0); int row = 0; mainLayout.addWidget(&toggleButton, row, 0, 1, 1, Qt::AlignLeft); mainLayout.addWidget(&headerLine, row++, 2, 1, 1); mainLayout.addWidget(&contentArea, row, 0, 1, 3); setLayout(&mainLayout); connect(&toggleButton, &QToolButton::toggled, this, [this](const bool checked) { if (checked) { // make the content visible when expanding starts contentArea.setVisible(true); } // use instant animation if widget isn't visible (e.g. before widget is shown) const int duration = isVisible() ? animationDuration : 0; // update the size of the content area const auto collapsedHeight = sizeHint().height() - contentArea.maximumHeight(); const auto contentHeight = contentArea.layout()->sizeHint().height(); for (int i = 0; i < toggleAnimation.animationCount() - 1; ++i) { auto expanderAnimation = static_cast(toggleAnimation.animationAt(i)); expanderAnimation->setDuration(duration); expanderAnimation->setStartValue(collapsedHeight); expanderAnimation->setEndValue(collapsedHeight + contentHeight); } auto contentAnimation = static_cast(toggleAnimation.animationAt(toggleAnimation.animationCount() - 1)); contentAnimation->setDuration(duration); contentAnimation->setStartValue(0); contentAnimation->setEndValue(contentHeight); toggleButton.setArrowType(checked ? Qt::ArrowType::DownArrow : Qt::ArrowType::RightArrow); toggleAnimation.setDirection(checked ? QAbstractAnimation::Forward : QAbstractAnimation::Backward); toggleAnimation.start(); }); connect(&toggleAnimation, &QAbstractAnimation::finished, this, [this]() { // hide the content area when it is fully collapsed if (!toggleButton.isChecked()) { contentArea.setVisible(false); } }); } void AnimatedExpander::setContentLayout(QLayout *contentLayout) { delete contentArea.layout(); contentArea.setLayout(contentLayout); } bool AnimatedExpander::isExpanded() const { return toggleButton.isChecked(); } void AnimatedExpander::setExpanded(bool expanded) { toggleButton.setChecked(expanded); } class SecKeyFilter : public DefaultKeyFilter { public: SecKeyFilter() : DefaultKeyFilter() { setRevoked(DefaultKeyFilter::NotSet); setExpired(DefaultKeyFilter::NotSet); setHasSecret(DefaultKeyFilter::Set); setCanCertify(DefaultKeyFilter::Set); setIsOpenPGP(DefaultKeyFilter::Set); } bool matches(const GpgME::Key &key, Kleo::KeyFilter::MatchContexts contexts) const override { if (!(availableMatchContexts() & contexts)) { return false; } if (_detail::ByFingerprint()(key, mExcludedKey)) { return false; } return DefaultKeyFilter::matches(key, contexts); } void setExcludedKey(const GpgME::Key &key) { mExcludedKey = key; } private: GpgME::Key mExcludedKey; }; auto checkBoxSize(const QCheckBox *checkBox) { QStyleOptionButton opt; return checkBox->style()->sizeFromContents(QStyle::CT_CheckBox, &opt, QSize(), checkBox); } class TreeWidgetInternal : public TreeWidget { Q_OBJECT public: using TreeWidget::TreeWidget; protected: void focusInEvent(QFocusEvent *event) override { TreeWidget::focusInEvent(event); // queue the invokation, so that it happens after the widget itself got focus QMetaObject::invokeMethod(this, &TreeWidgetInternal::forceAccessibleFocusEventForCurrentItem, Qt::QueuedConnection); } bool edit(const QModelIndex &index, EditTrigger trigger, QEvent *event) override { if (event && event->type() == QEvent::KeyPress) { const auto *const keyEvent = static_cast(event); if (keyEvent->key() == Qt::Key_Space || keyEvent->key() == Qt::Key_Select) { // toggle checked state regardless of the index's column return TreeWidget::edit(index.siblingAtColumn(0), trigger, event); } } return TreeWidget::edit(index, trigger, event); } private: void forceAccessibleFocusEventForCurrentItem() { // force Qt to send a focus event for the current item to accessibility // tools; otherwise, the user has no idea which item is selected when the // list gets keyboard input focus const auto current = currentIndex(); setCurrentIndex({}); setCurrentIndex(current); } }; struct UserIDCheckState { GpgME::UserID userId; Qt::CheckState checkState; }; } class CertifyWidget::Private { public: enum Role { UserIdRole = Qt::UserRole }; enum Mode { SingleCertification, BulkCertification, }; enum TagsState { TagsMustBeChecked, TagsLoading, TagsLoaded, }; Private(CertifyWidget *qq) : q{qq} { auto mainLay = new QVBoxLayout{q}; { mInfoLabel = new QLabel{i18n("Verify the fingerprint, mark the user IDs you want to certify, " "and select the key you want to certify the user IDs with.
    " "Note: Only the fingerprint clearly identifies the key and its owner."), q}; mInfoLabel->setWordWrap(true); labelHelper.addLabel(mInfoLabel); mainLay->addWidget(mInfoLabel); } mainLay->addWidget(new KSeparator{Qt::Horizontal, q}); { auto grid = new QGridLayout; grid->setColumnStretch(1, 1); int row = -1; row++; mFprField = std::make_unique(i18n("Fingerprint:"), q); grid->addWidget(mFprField->label(), row, 0); grid->addLayout(mFprField->layout(), row, 1); row++; auto label = new QLabel{i18n("Certify with:"), q}; mSecKeySelect = new KeySelectionCombo{/* secretOnly= */ true, q}; mSecKeySelect->setKeyFilter(std::make_shared()); label->setBuddy(mSecKeySelect); grid->addWidget(label, row, 0); grid->addWidget(mSecKeySelect); mainLay->addLayout(grid); } mMissingOwnerTrustInfo = new KMessageWidget{q}; mSetOwnerTrustAction = new QAction{q}; mSetOwnerTrustAction->setText(i18nc("@action:button", "Set Owner Trust")); mSetOwnerTrustAction->setToolTip(i18nc("@info:tooltip", "Click to set the trust level of the selected certification key to ultimate trust. " "This is what you usually want to do for your own keys.")); connect(mSetOwnerTrustAction, &QAction::triggered, q, [this]() { setOwnerTrust(); }); mMissingOwnerTrustInfo->addAction(mSetOwnerTrustAction); mMissingOwnerTrustInfo->setVisible(false); mainLay->addWidget(mMissingOwnerTrustInfo); mainLay->addWidget(new KSeparator{Qt::Horizontal, q}); mBadCertificatesInfo = new KMessageWidget{q}; mBadCertificatesInfo->setMessageType(KMessageWidget::Warning); mBadCertificatesInfo->setIcon(QIcon::fromTheme(QStringLiteral("data-warning"), QIcon::fromTheme(QStringLiteral("dialog-warning")))); mBadCertificatesInfo->setText(i18nc("@info", "One or more certificates cannot be certified.")); mBadCertificatesInfo->setCloseButtonVisible(false); mBadCertificatesInfo->setVisible(false); mainLay->addWidget(mBadCertificatesInfo); userIdListView = new TreeWidget{q}; userIdListView->setAccessibleName(i18n("User IDs")); userIdListView->setEditTriggers(QAbstractItemView::NoEditTriggers); userIdListView->setSelectionMode(QAbstractItemView::SingleSelection); userIdListView->setRootIsDecorated(false); userIdListView->setUniformRowHeights(true); userIdListView->setAllColumnsShowFocus(false); userIdListView->setHeaderHidden(true); userIdListView->setHeaderLabels({i18nc("@title:column", "User ID")}); mainLay->addWidget(userIdListView, 1); // Setup the advanced area mAdvancedOptionsExpander = new AnimatedExpander{i18n("Advanced"), i18n("Show advanced options"), q}; mainLay->addWidget(mAdvancedOptionsExpander); auto advLay = new QVBoxLayout; mExportCB = new QCheckBox{q}; mExportCB->setText(i18n("Certify for everyone to see (exportable)")); mExportCB->setToolTip(xi18nc("@info:tooltip", "Check this option, if you want to share your certifications with others. " "If you just want to mark certificates as certified for yourself, then you can uncheck it.")); advLay->addWidget(mExportCB); { auto layout = new QHBoxLayout; mPublishCB = new QCheckBox{q}; mPublishCB->setText(i18n("Publish on keyserver afterwards")); mPublishCB->setToolTip(xi18nc("@info:tooltip", "Check this option, if you want to upload your certifications to a certificate " "directory after successful certification.")); mPublishCB->setEnabled(mExportCB->isChecked()); layout->addSpacing(checkBoxSize(mExportCB).width()); layout->addWidget(mPublishCB); advLay->addLayout(layout); } { auto tagsLay = new QHBoxLayout; auto label = new QLabel{i18n("Tags:"), q}; mTagsLE = new QLineEdit{q}; label->setBuddy(mTagsLE); const auto tooltip = i18n("You can use this to add additional info to a certification.") + QStringLiteral("

    ") + i18n("Tags created by anyone with full certification trust " "are shown in the keylist and can be searched."); label->setToolTip(tooltip); mTagsLE->setToolTip(tooltip); tagsLay->addWidget(label); tagsLay->addWidget(mTagsLE, 1); advLay->addLayout(tagsLay); } { auto layout = new QHBoxLayout; mExpirationCheckBox = new QCheckBox{q}; mExpirationCheckBox->setText(i18n("Expiration:")); mExpirationDateEdit = new KDateComboBox{q}; Kleo::setUpExpirationDateComboBox(mExpirationDateEdit, {QDate::currentDate().addDays(1), QDate{}}); mExpirationDateEdit->setDate(Kleo::defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration)); mExpirationDateEdit->setEnabled(mExpirationCheckBox->isChecked()); const auto tooltip = i18n("You can use this to set an expiration date for a certification.") + QStringLiteral("

    ") + i18n("By setting an expiration date, you can limit the validity of " "your certification to a certain amount of time. Once the expiration " "date has passed, your certification is no longer valid."); mExpirationCheckBox->setToolTip(tooltip); mExpirationDateEdit->setToolTip(tooltip); layout->addWidget(mExpirationCheckBox); layout->addWidget(mExpirationDateEdit, 1); advLay->addLayout(layout); } { mTrustSignatureCB = new QCheckBox{q}; mTrustSignatureWidgets.addWidget(mTrustSignatureCB); mTrustSignatureCB->setText(i18n("Certify as trusted introducer")); const auto tooltip = i18n("You can use this to certify a trusted introducer for a domain.") + QStringLiteral("

    ") + i18n("All certificates with email addresses belonging to the domain " "that have been certified by the trusted introducer are treated " "as certified, i.e. a trusted introducer acts as a kind of " "intermediate CA for a domain."); mTrustSignatureCB->setToolTip(tooltip); advLay->addWidget(mTrustSignatureCB); } { auto layout = new QHBoxLayout; auto label = new QLabel{i18n("Domain:"), q}; mTrustSignatureWidgets.addWidget(label); mTrustSignatureDomainLE = new QLineEdit{q}; mTrustSignatureWidgets.addWidget(mTrustSignatureDomainLE); mTrustSignatureDomainLE->setEnabled(mTrustSignatureCB->isChecked()); label->setBuddy(mTrustSignatureDomainLE); layout->addSpacing(checkBoxSize(mTrustSignatureCB).width()); layout->addWidget(label); layout->addWidget(mTrustSignatureDomainLE); advLay->addLayout(layout); } mAdvancedOptionsExpander->setContentLayout(advLay); connect(userIdListView, &QTreeWidget::itemChanged, q, [this](auto item, auto) { onItemChanged(item); }); connect(mExportCB, &QCheckBox::toggled, q, [this](bool on) { mPublishCB->setEnabled(on); }); connect(mSecKeySelect, &KeySelectionCombo::currentKeyChanged, q, [this](const GpgME::Key &) { updateSelectedUserIds(); updateTags(); checkOwnerTrust(); Q_EMIT q->changed(); }); connect(mExpirationCheckBox, &QCheckBox::toggled, q, [this](bool checked) { mExpirationDateEdit->setEnabled(checked); Q_EMIT q->changed(); }); connect(mExpirationDateEdit, &KDateComboBox::dateChanged, q, &CertifyWidget::changed); connect(mTrustSignatureCB, &QCheckBox::toggled, q, [this](bool on) { mTrustSignatureDomainLE->setEnabled(on); Q_EMIT q->changed(); }); connect(mTrustSignatureDomainLE, &QLineEdit::textChanged, q, &CertifyWidget::changed); loadConfig(true); } ~Private() = default; void loadConfig(bool loadAll = false) { const KConfigGroup conf(KSharedConfig::openConfig(), QStringLiteral("CertifySettings")); if (loadAll) { const Settings settings; mExpirationCheckBox->setChecked(settings.certificationValidityInDays() > 0); if (settings.certificationValidityInDays() > 0) { const QDate expirationDate = QDate::currentDate().addDays(settings.certificationValidityInDays()); mExpirationDateEdit->setDate(expirationDate > mExpirationDateEdit->maximumDate() // ? mExpirationDateEdit->maximumDate() // : expirationDate); } mSecKeySelect->setDefaultKey(conf.readEntry("LastKey", QString())); } switch (mMode) { case SingleCertification: { mExportCB->setChecked(conf.readEntry("ExportCheckState", false)); mPublishCB->setChecked(conf.readEntry("PublishCheckState", false)); mAdvancedOptionsExpander->setExpanded(conf.readEntry("AdvancedOptionsExpanded", false)); break; } case BulkCertification: { mExportCB->setChecked(conf.readEntry("BulkExportCheckState", true)); mPublishCB->setChecked(conf.readEntry("BulkPublishCheckState", false)); mAdvancedOptionsExpander->setExpanded(conf.readEntry("BulkAdvancedOptionsExpanded", true)); break; } } } void saveConfig() { - KConfigGroup conf{KSharedConfig::openConfig(), QLatin1String("CertifySettings")}; + KConfigGroup conf{KSharedConfig::openConfig(), QLatin1StringView("CertifySettings")}; if (!secKey().isNull()) { conf.writeEntry("LastKey", secKey().primaryFingerprint()); } switch (mMode) { case SingleCertification: { conf.writeEntry("ExportCheckState", mExportCB->isChecked()); conf.writeEntry("PublishCheckState", mPublishCB->isChecked()); conf.writeEntry("AdvancedOptionsExpanded", mAdvancedOptionsExpander->isExpanded()); break; } case BulkCertification: { conf.writeEntry("BulkExportCheckState", mExportCB->isChecked()); conf.writeEntry("BulkPublishCheckState", mPublishCB->isChecked()); conf.writeEntry("BulkAdvancedOptionsExpanded", mAdvancedOptionsExpander->isExpanded()); break; } } conf.sync(); } void setMode(Mode mode) { mMode = mode; switch (mMode) { case SingleCertification: break; case BulkCertification: { mInfoLabel->setText(i18nc("@info", "Verify the fingerprints, mark the user IDs you want to certify, " "and select the certificate you want to certify the user IDs with.
    " "Note: Only the fingerprints clearly identify the certificate and its owner.")); mFprField->setVisible(false); mTrustSignatureWidgets.setVisible(false); break; } } loadConfig(); } void setUpUserIdList(const std::vector &uids = {}) { userIdListView->clear(); if (mMode == SingleCertification) { userIdListView->setColumnCount(1); userIdListView->setHeaderHidden(true); // set header labels for accessibility tools to overwrite the default "1" userIdListView->setHeaderLabels({i18nc("@title:column", "User ID")}); for (const auto &uid : uids) { if (uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) { // Skip user IDs that cannot really be certified. continue; } auto item = new QTreeWidgetItem; item->setData(0, UserIdRole, QVariant::fromValue(uid)); item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyUserID(uid)); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setCheckState(0, Qt::Checked); userIdListView->addTopLevelItem(item); } } else { const QStringList headers = {i18nc("@title:column", "User ID"), i18nc("@title:column", "Fingerprint")}; userIdListView->setColumnCount(headers.count()); userIdListView->setHeaderHidden(false); userIdListView->setHeaderLabels(headers); for (const auto &key : mKeys) { const auto &uid = key.userID(0); auto item = new QTreeWidgetItem; item->setData(0, UserIdRole, QVariant::fromValue(uid)); item->setData(0, Qt::DisplayRole, Kleo::Formatting::prettyUserID(uid)); item->setData(1, Qt::DisplayRole, Kleo::Formatting::prettyID(key.primaryFingerprint())); item->setData(1, Qt::AccessibleTextRole, Kleo::Formatting::accessibleHexID(key.primaryFingerprint())); if ((key.protocol() != OpenPGP) || uid.isInvalid() || Kleo::isRevokedOrExpired(uid)) { item->setFlags(Qt::NoItemFlags); item->setCheckState(0, Qt::Unchecked); if (key.protocol() == CMS) { item->setData(0, Qt::ToolTipRole, i18nc("@info:tooltip", "S/MIME certificates cannot be certified.")); item->setData(1, Qt::ToolTipRole, i18nc("@info:tooltip", "S/MIME certificates cannot be certified.")); } else { item->setData(0, Qt::ToolTipRole, i18nc("@info:tooltip", "Expired or revoked certificates cannot be certified.")); item->setData(1, Qt::ToolTipRole, i18nc("@info:tooltip", "Expired or revoked certificates cannot be certified.")); } } else { item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setCheckState(0, Qt::Checked); } userIdListView->addTopLevelItem(item); } userIdListView->sortItems(0, Qt::AscendingOrder); userIdListView->resizeColumnToContents(0); userIdListView->resizeColumnToContents(1); } } void updateSelectedUserIds() { if (mMode == SingleCertification) { return; } if (userIdListView->topLevelItemCount() == 0) { return; } // restore check state of primary user ID of previous certification key if (!mCertificationKey.isNull()) { for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) { const auto uidItem = userIdListView->topLevelItem(i); const auto itemUserId = getUserId(uidItem); if (userIDBelongsToKey(itemUserId, mCertificationKey)) { uidItem->setCheckState(0, mCertificationKeyUserIDCheckState); uidItem->setFlags(Qt::ItemIsSelectable | Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); break; // we only show the primary user IDs } } } mCertificationKey = mSecKeySelect->currentKey(); // save and unset check state of primary user ID of current certification key if (!mCertificationKey.isNull()) { for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) { const auto uidItem = userIdListView->topLevelItem(i); const auto itemUserId = getUserId(uidItem); if (userIDBelongsToKey(itemUserId, mCertificationKey)) { mCertificationKeyUserIDCheckState = uidItem->checkState(0); if (mCertificationKeyUserIDCheckState) { uidItem->setCheckState(0, Qt::Unchecked); } uidItem->setFlags(Qt::ItemIsSelectable); break; // we only show the primary user IDs } } } } void updateTags() { struct ItemAndRemark { QTreeWidgetItem *item; QString remark; }; if (mTagsState != TagsLoaded) { return; } if (mTagsLE->isModified()) { return; } GpgME::Key remarkKey = mSecKeySelect->currentKey(); if (!remarkKey.isNull()) { std::vector itemsAndRemarks; // first choose the remark we want to prefill the Tags field with QString remark; for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) { const auto item = userIdListView->topLevelItem(i); if (item->isDisabled()) { continue; } const auto uid = getUserId(item); GpgME::Error err; const char *c_remark = uid.remark(remarkKey, err); const QString itemRemark = (!err && c_remark) ? QString::fromUtf8(c_remark) : QString{}; if (!itemRemark.isEmpty() && (itemRemark != remark)) { if (!remark.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Different remarks on user IDs. Taking last."; } remark = itemRemark; } itemsAndRemarks.push_back({item, itemRemark}); } // then select the user IDs with the chosen remark; this prevents overwriting existing // different remarks on the other user IDs (as long as the user doesn't select any of // the unselected user IDs with a different remark) if (!remark.isEmpty()) { for (const auto &[item, itemRemark] : itemsAndRemarks) { item->setCheckState(0, itemRemark == remark ? Qt::Checked : Qt::Unchecked); } } mTagsLE->setText(remark); } } void updateTrustSignatureDomain() { if (mMode == SingleCertification) { if (mTrustSignatureDomainLE->text().isEmpty() && certificate().numUserIDs() == 1) { // try to guess the domain to use for the trust signature const auto address = certificate().userID(0).addrSpec(); const auto atPos = address.find('@'); if (atPos != std::string::npos) { const auto domain = address.substr(atPos + 1); mTrustSignatureDomainLE->setText(QString::fromUtf8(domain.c_str(), domain.size())); } } } } void loadAllTags() { const auto keyWithoutTags = std::find_if(mKeys.cbegin(), mKeys.cend(), [](const auto &key) { return (key.protocol() == GpgME::OpenPGP) && !(key.keyListMode() & GpgME::SignatureNotations); }); const auto indexOfKeyWithoutTags = std::distance(mKeys.cbegin(), keyWithoutTags); if (indexOfKeyWithoutTags < signed(mKeys.size())) { auto loadTags = [this, indexOfKeyWithoutTags]() { Q_ASSERT(indexOfKeyWithoutTags < signed(mKeys.size())); // call update() on the reference to the vector element because it swaps key with the updated key mKeys[indexOfKeyWithoutTags].update(); loadAllTags(); }; QMetaObject::invokeMethod(q, loadTags, Qt::QueuedConnection); return; } mTagsState = TagsLoaded; QMetaObject::invokeMethod( q, [this]() { setUpWidget(); }, Qt::QueuedConnection); } bool ensureTagsLoaded() { Q_ASSERT(mTagsState != TagsLoading); if (mTagsState == TagsLoaded) { return true; } const auto allTagsAreLoaded = Kleo::all_of(mKeys, [](const auto &key) { return (key.protocol() != GpgME::OpenPGP) || (key.keyListMode() & GpgME::SignatureNotations); }); if (allTagsAreLoaded) { mTagsState = TagsLoaded; } else { mTagsState = TagsLoading; QMetaObject::invokeMethod( q, [this]() { loadAllTags(); }, Qt::QueuedConnection); } return mTagsState == TagsLoaded; } void setUpWidget() { if (!ensureTagsLoaded()) { return; } if (mMode == SingleCertification) { const auto key = certificate(); mFprField->setValue(QStringLiteral("") + Formatting::prettyID(key.primaryFingerprint()) + QStringLiteral(""), Formatting::accessibleHexID(key.primaryFingerprint())); setUpUserIdList(mUserIds.empty() ? key.userIDs() : mUserIds); auto keyFilter = std::make_shared(); keyFilter->setExcludedKey(key); mSecKeySelect->setKeyFilter(keyFilter); updateTrustSignatureDomain(); } else { // check for certificates that cannot be certified const auto haveBadCertificates = Kleo::any_of(mKeys, [](const auto &key) { const auto &uid = key.userID(0); return (key.protocol() != OpenPGP) || uid.isInvalid() || Kleo::isRevokedOrExpired(uid); }); if (haveBadCertificates) { mBadCertificatesInfo->animatedShow(); } setUpUserIdList(); } updateTags(); updateSelectedUserIds(); Q_EMIT q->changed(); } GpgME::Key certificate() const { Q_ASSERT(mMode == SingleCertification); return !mKeys.empty() ? mKeys.front() : Key{}; } void setCertificates(const std::vector &keys, const std::vector &uids) { mKeys = keys; mUserIds = uids; mTagsState = TagsMustBeChecked; setUpWidget(); } std::vector certificates() const { Q_ASSERT(mMode != SingleCertification); return mKeys; } GpgME::Key secKey() const { return mSecKeySelect->currentKey(); } GpgME::UserID getUserId(const QTreeWidgetItem *item) const { return item ? item->data(0, UserIdRole).value() : UserID{}; } void selectUserIDs(const std::vector &uids) { for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) { const auto uidItem = userIdListView->topLevelItem(i); const auto itemUserId = getUserId(uidItem); const bool userIdIsInList = Kleo::any_of(uids, [itemUserId](const auto &uid) { return Kleo::userIDsAreEqual(itemUserId, uid); }); uidItem->setCheckState(0, userIdIsInList ? Qt::Checked : Qt::Unchecked); } } std::vector selectedUserIDs() const { std::vector userIds; userIds.reserve(userIdListView->topLevelItemCount()); for (int i = 0, end = userIdListView->topLevelItemCount(); i < end; ++i) { const auto *const uidItem = userIdListView->topLevelItem(i); if (uidItem->checkState(0) == Qt::Checked) { userIds.push_back(getUserId(uidItem)); } } qCDebug(KLEOPATRA_LOG) << "Checked user IDs:" << userIds; return userIds; } bool exportableSelected() const { return mExportCB->isChecked(); } bool publishSelected() const { return mPublishCB->isChecked(); } QString tags() const { return mTagsLE->text().trimmed(); } bool isValid() const { static const QRegularExpression domainNameRegExp{QStringLiteral(R"(^\s*((xn--)?[a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}\s*$)"), QRegularExpression::CaseInsensitiveOption}; if (mTagsState != TagsLoaded) { return false; } // do not accept null keys if (mKeys.empty() || mSecKeySelect->currentKey().isNull()) { return false; } // do not accept empty list of user IDs const auto userIds = selectedUserIDs(); if (userIds.empty()) { return false; } // do not accept if any of the selected user IDs belongs to the certification key const auto certificationKey = mSecKeySelect->currentKey(); const auto userIdToCertifyBelongsToCertificationKey = std::any_of(userIds.cbegin(), userIds.cend(), [certificationKey](const auto &userId) { return Kleo::userIDBelongsToKey(userId, certificationKey); }); if (userIdToCertifyBelongsToCertificationKey) { return false; } if (mExpirationCheckBox->isChecked() && !mExpirationDateEdit->isValid()) { return false; } if (mTrustSignatureCB->isChecked() && !domainNameRegExp.match(mTrustSignatureDomainLE->text()).hasMatch()) { return false; } return true; } void checkOwnerTrust() { const auto secretKey = secKey(); if (secretKey.ownerTrust() != GpgME::Key::Ultimate) { mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Information); mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("question"))); mMissingOwnerTrustInfo->setText(i18n("Is this your own key?")); mSetOwnerTrustAction->setEnabled(true); mMissingOwnerTrustInfo->animatedShow(); } else { mMissingOwnerTrustInfo->animatedHide(); } } void setOwnerTrust() { mSetOwnerTrustAction->setEnabled(false); QGpgME::ChangeOwnerTrustJob *const j = QGpgME::openpgp()->changeOwnerTrustJob(); connect(j, &QGpgME::ChangeOwnerTrustJob::result, q, [this](const GpgME::Error &err) { if (err) { KMessageBox::error(q, i18n("

    Changing the certification trust of the key %1 failed:

    %2

    ", Formatting::formatForComboBox(secKey()), Formatting::errorAsString(err)), i18nc("@title:window", "Certification Trust Change Failed")); } if (err || err.isCanceled()) { mSetOwnerTrustAction->setEnabled(true); } else { mMissingOwnerTrustInfo->setMessageType(KMessageWidget::Positive); mMissingOwnerTrustInfo->setIcon(QIcon::fromTheme(QStringLiteral("checkmark"))); mMissingOwnerTrustInfo->setText(i18n("Owner trust set successfully.")); } }); j->start(secKey(), GpgME::Key::Ultimate); } void onItemChanged(QTreeWidgetItem *item) { Q_EMIT q->changed(); #ifndef QT_NO_ACCESSIBILITY if (item) { // assume that the checked state changed QAccessible::State st; st.checked = true; QAccessibleStateChangeEvent e(userIdListView, st); e.setChild(userIdListView->indexOfTopLevelItem(item)); QAccessible::updateAccessibility(&e); } #endif } public: CertifyWidget *const q; QLabel *mInfoLabel = nullptr; std::unique_ptr mFprField; KeySelectionCombo *mSecKeySelect = nullptr; KMessageWidget *mMissingOwnerTrustInfo = nullptr; KMessageWidget *mBadCertificatesInfo = nullptr; TreeWidget *userIdListView = nullptr; AnimatedExpander *mAdvancedOptionsExpander = nullptr; QCheckBox *mExportCB = nullptr; QCheckBox *mPublishCB = nullptr; QLineEdit *mTagsLE = nullptr; BulkStateChanger mTrustSignatureWidgets; QCheckBox *mTrustSignatureCB = nullptr; QLineEdit *mTrustSignatureDomainLE = nullptr; QCheckBox *mExpirationCheckBox = nullptr; KDateComboBox *mExpirationDateEdit = nullptr; QAction *mSetOwnerTrustAction = nullptr; LabelHelper labelHelper; Mode mMode = SingleCertification; std::vector mKeys; std::vector mUserIds; TagsState mTagsState = TagsMustBeChecked; GpgME::Key mCertificationKey; Qt::CheckState mCertificationKeyUserIDCheckState; }; CertifyWidget::CertifyWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } Kleo::CertifyWidget::~CertifyWidget() = default; void CertifyWidget::setCertificate(const GpgME::Key &key, const std::vector &uids) { Q_ASSERT(!key.isNull()); d->setMode(Private::SingleCertification); d->setCertificates({key}, uids); } GpgME::Key CertifyWidget::certificate() const { return d->certificate(); } void CertifyWidget::setCertificates(const std::vector &keys) { d->setMode(Private::BulkCertification); d->setCertificates(keys, {}); } std::vector CertifyWidget::certificates() const { return d->certificates(); } void CertifyWidget::selectUserIDs(const std::vector &uids) { d->selectUserIDs(uids); } std::vector CertifyWidget::selectedUserIDs() const { return d->selectedUserIDs(); } GpgME::Key CertifyWidget::secKey() const { return d->secKey(); } bool CertifyWidget::exportableSelected() const { return d->exportableSelected(); } QString CertifyWidget::tags() const { return d->tags(); } bool CertifyWidget::publishSelected() const { return d->publishSelected(); } bool CertifyWidget::trustSignatureSelected() const { return d->mTrustSignatureCB->isChecked(); } QString CertifyWidget::trustSignatureDomain() const { return d->mTrustSignatureDomainLE->text().trimmed(); } QDate CertifyWidget::expirationDate() const { return d->mExpirationCheckBox->isChecked() ? d->mExpirationDateEdit->date() : QDate{}; } bool CertifyWidget::isValid() const { return d->isValid(); } void CertifyWidget::saveState() const { d->saveConfig(); } #include "certifywidget.moc" #include "moc_certifywidget.cpp" diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp index dfafa2ead..6270a875c 100644 --- a/src/dialogs/exportdialog.cpp +++ b/src/dialogs/exportdialog.cpp @@ -1,213 +1,213 @@ /* SPDX-FileCopyrightText: 2017 Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "exportdialog.h" #include "kleopatra_debug.h" #include "view/waitwidget.h" #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; class ExportWidget::Private { public: Private(ExportWidget *qq) : q(qq) { } void setupUi(); GpgME::Key key; GpgME::Subkey subkey; QTextEdit *textEdit; WaitWidget *waitWidget; unsigned int flags; private: ExportWidget *const q; }; void ExportWidget::Private::setupUi() { auto vlay = new QVBoxLayout(q); vlay->setContentsMargins(0, 0, 0, 0); textEdit = new QTextEdit; textEdit->setVisible(false); textEdit->setReadOnly(true); auto fixedFont = QFont(QStringLiteral("Monospace")); fixedFont.setStyleHint(QFont::TypeWriter); textEdit->setFont(fixedFont); textEdit->setReadOnly(true); vlay->addWidget(textEdit); waitWidget = new WaitWidget; waitWidget->setText(i18n("Exporting ...")); vlay->addWidget(waitWidget); } ExportWidget::ExportWidget(QWidget *parent) : QWidget(parent) , d(new Private(this)) { d->setupUi(); } ExportWidget::~ExportWidget() { } static QString injectComments(const GpgME::Key &key, const QByteArray &data) { QString ret = QString::fromUtf8(data); if (key.protocol() != GpgME::OpenPGP) { return ret; } auto overView = Formatting::toolTip(key, Formatting::Fingerprint | Formatting::UserIDs | Formatting::Issuer | Formatting::Subject | Formatting::ExpiryDates | Formatting::CertificateType | Formatting::CertificateUsage); // Fixup the HTML coming from the toolTip for our own format. - overView.remove(QLatin1String("")); - overView.replace(QLatin1String(""), QLatin1String("\t")); - overView.replace(QLatin1String(""), QLatin1String("\n")); - overView.remove(QLatin1String("")); - overView.remove(QLatin1String("\n
    ")); - overView.replace(QLatin1String("<"), QLatin1String("<")); - overView.replace(QLatin1String(">"), QLatin1String(">")); + overView.remove(QLatin1StringView("")); + overView.replace(QLatin1StringView(""), QLatin1String("\t")); + overView.replace(QLatin1StringView(""), QLatin1String("\n")); + overView.remove(QLatin1StringView("")); + overView.remove(QLatin1StringView("\n
    ")); + overView.replace(QLatin1StringView("<"), QLatin1String("<")); + overView.replace(QLatin1StringView(">"), QLatin1String(">")); auto overViewLines = overView.split(QLatin1Char('\n')); // Format comments so that they fit for RFC 4880 auto comments = QStringLiteral("Comment: "); - comments += overViewLines.join(QLatin1String("\nComment: ")) + QLatin1Char('\n'); + comments += overViewLines.join(QLatin1StringView("\nComment: ")) + QLatin1Char('\n'); ret.insert(37 /* -----BEGIN PGP PUBLIC KEY BLOCK-----\n */, comments); return ret; } void ExportWidget::exportResult(const GpgME::Error &err, const QByteArray &data) { d->waitWidget->setVisible(false); d->textEdit->setVisible(true); if (err) { /* Should not happen. But well,.. */ d->textEdit->setText(i18nc("%1 is error message", "Failed to export: '%1'", Formatting::errorAsString(err))); } if (!d->flags) { d->textEdit->setText(injectComments(d->key, data)); } else { d->textEdit->setText(QString::fromUtf8(data)); } } void ExportWidget::setKey(const GpgME::Subkey &key, unsigned int flags) { d->waitWidget->setVisible(true); d->textEdit->setVisible(false); d->key = key.parent(); d->subkey = key; d->flags = flags; auto protocol = d->key.protocol() == GpgME::CMS ? QGpgME::smime() : QGpgME::openpgp(); auto job = protocol->publicKeyExportJob(true); connect(job, &QGpgME::ExportJob::result, this, &ExportWidget::exportResult); job->setExportFlags(flags); - job->start(QStringList() << QLatin1String(key.fingerprint()) + QLatin1Char('!')); + job->start(QStringList() << QLatin1StringView(key.fingerprint()) + QLatin1Char('!')); } void ExportWidget::setKey(const GpgME::Key &key, unsigned int flags) { d->waitWidget->setVisible(true); d->textEdit->setVisible(false); d->key = key; d->flags = flags; auto protocol = key.protocol() == GpgME::CMS ? QGpgME::smime() : QGpgME::openpgp(); auto job = protocol->publicKeyExportJob(true); connect(job, &QGpgME::ExportJob::result, this, &ExportWidget::exportResult); job->setExportFlags(flags); - job->start(QStringList() << QLatin1String(key.primaryFingerprint())); + job->start(QStringList() << QLatin1StringView(key.primaryFingerprint())); } GpgME::Key ExportWidget::key() const { return d->key; } ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent) , mWidget(new ExportWidget(this)) { KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("ExportDialog")); const auto size = dialog.readEntry("Size", QSize(600, 800)); if (size.isValid()) { resize(size); } setWindowTitle(i18nc("@title:window", "Export")); auto l = new QVBoxLayout(this); l->addWidget(mWidget); auto bbox = new QDialogButtonBox(this); auto btn = bbox->addButton(QDialogButtonBox::Close); connect(btn, &QPushButton::pressed, this, &QDialog::accept); l->addWidget(bbox); } ExportDialog::~ExportDialog() { KConfigGroup dialog(KSharedConfig::openStateConfig(), QStringLiteral("ExportDialog")); dialog.writeEntry("Size", size()); dialog.sync(); } void ExportDialog::setKey(const GpgME::Key &key, unsigned int flags) { mWidget->setKey(key, flags); } void ExportDialog::setKey(const GpgME::Subkey &key, unsigned int flags) { mWidget->setKey(key, flags); } GpgME::Key ExportDialog::key() const { return mWidget->key(); } #include "moc_exportdialog.cpp" diff --git a/src/dialogs/newopenpgpcertificatedetailsdialog.cpp b/src/dialogs/newopenpgpcertificatedetailsdialog.cpp index 8297cb3cb..6abda99d2 100644 --- a/src/dialogs/newopenpgpcertificatedetailsdialog.cpp +++ b/src/dialogs/newopenpgpcertificatedetailsdialog.cpp @@ -1,331 +1,331 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/newopenpgpcertificatedetailsdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "newopenpgpcertificatedetailsdialog.h" #include "nameandemailwidget.h" #include "newcertificatewizard/advancedsettingsdialog_p.h" #include "newcertificatewizard/keyalgo_p.h" #include "utils/keyparameters.h" #include "utils/scrollarea.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; class NewOpenPGPCertificateDetailsDialog::Private { friend class ::Kleo::NewOpenPGPCertificateDetailsDialog; NewOpenPGPCertificateDetailsDialog *const q; struct UI { QLabel *infoLabel; ScrollArea *scrollArea; NameAndEmailWidget *nameAndEmail; QCheckBox *withPassCheckBox; QPushButton *advancedButton; QDialogButtonBox *buttonBox; UI(QWidget *dialog) { auto mainLayout = new QVBoxLayout{dialog}; infoLabel = new QLabel{dialog}; infoLabel->setWordWrap(true); mainLayout->addWidget(infoLabel); mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}); scrollArea = new ScrollArea{dialog}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(dialog->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins(0, 0, 0, 0); nameAndEmail = new NameAndEmailWidget{dialog}; nameAndEmail->layout()->setContentsMargins(0, 0, 0, 0); scrollAreaLayout->addWidget(nameAndEmail); withPassCheckBox = new QCheckBox{i18n("Protect the generated key with a passphrase."), dialog}; withPassCheckBox->setToolTip( i18n("Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation.")); scrollAreaLayout->addWidget(withPassCheckBox); { auto layout = new QHBoxLayout; advancedButton = new QPushButton{i18n("Advanced Settings..."), dialog}; advancedButton->setAutoDefault(false); layout->addStretch(1); layout->addWidget(advancedButton); scrollAreaLayout->addLayout(layout); } scrollAreaLayout->addStretch(1); mainLayout->addWidget(scrollArea); mainLayout->addWidget(new KSeparator{Qt::Horizontal, dialog}); buttonBox = new QDialogButtonBox{QDialogButtonBox::Ok | QDialogButtonBox::Cancel, dialog}; mainLayout->addWidget(buttonBox); } } ui; public: explicit Private(NewOpenPGPCertificateDetailsDialog *qq) : q{qq} , ui{qq} , advancedSettingsDlg{new AdvancedSettingsDialog{qq}} , technicalParameters{KeyParameters::OpenPGP} { q->setWindowTitle(i18nc("title:window", "Create OpenPGP Certificate")); - const KConfigGroup config{KSharedConfig::openConfig(), QLatin1String("CertificateCreationWizard")}; + const KConfigGroup config{KSharedConfig::openConfig(), QLatin1StringView("CertificateCreationWizard")}; const auto attrOrder = config.readEntry("OpenPGPAttributeOrder", QStringList{}); - const auto nameIsRequired = attrOrder.contains(QLatin1String{"NAME!"}, Qt::CaseInsensitive); - const auto emailIsRequired = attrOrder.contains(QLatin1String{"EMAIL!"}, Qt::CaseInsensitive); + const auto nameIsRequired = attrOrder.contains(QLatin1StringView{"NAME!"}, Qt::CaseInsensitive); + const auto emailIsRequired = attrOrder.contains(QLatin1StringView{"EMAIL!"}, Qt::CaseInsensitive); ui.infoLabel->setText(nameIsRequired || emailIsRequired // ? i18n("Enter a name and an email address to use for the certificate.") : i18n("Enter a name and/or an email address to use for the certificate.")); ui.nameAndEmail->setNameIsRequired(nameIsRequired); ui.nameAndEmail->setNameLabel(config.readEntry("NAME_label")); ui.nameAndEmail->setNameHint(config.readEntry("NAME_hint", config.readEntry("NAME_placeholder"))); ui.nameAndEmail->setNamePattern(config.readEntry("NAME_regex")); ui.nameAndEmail->setEmailIsRequired(emailIsRequired); ui.nameAndEmail->setEmailLabel(config.readEntry("EMAIL_label")); ui.nameAndEmail->setEmailHint(config.readEntry("EMAIL_hint", config.readEntry("EMAIL_placeholder"))); ui.nameAndEmail->setEmailPattern(config.readEntry("EMAIL_regex")); Settings settings; ui.advancedButton->setVisible(!settings.hideAdvanced()); const auto conf = QGpgME::cryptoConfig(); const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints"); if (entry && entry->boolValue()) { qCDebug(KLEOPATRA_LOG) << "Disabling passphrase check box because of agent config."; ui.withPassCheckBox->setEnabled(false); ui.withPassCheckBox->setChecked(true); } else { ui.withPassCheckBox->setChecked(config.readEntry("WithPassphrase", false)); ui.withPassCheckBox->setEnabled(!config.isEntryImmutable("WithPassphrase")); } advancedSettingsDlg->setProtocol(GpgME::OpenPGP); updateTechnicalParameters(); // set key parameters to default values for OpenPGP connect(advancedSettingsDlg, &QDialog::accepted, q, [this]() { updateTechnicalParameters(); }); connect(ui.advancedButton, &QPushButton::clicked, q, [this]() { advancedSettingsDlg->open(); }); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); }); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); } private: KeyUsage keyUsage() const { KeyUsage usage; if (advancedSettingsDlg->signingAllowed()) { usage.setCanSign(true); } if (advancedSettingsDlg->encryptionAllowed() // && !is_ecdh(advancedSettingsDlg->subkeyType()) && !is_dsa(advancedSettingsDlg->keyType()) && !is_rsa(advancedSettingsDlg->subkeyType())) { usage.setCanEncrypt(true); } if (advancedSettingsDlg->authenticationAllowed()) { usage.setCanAuthenticate(true); } if (advancedSettingsDlg->certificationAllowed()) { usage.setCanCertify(true); } return usage; } KeyUsage subkeyUsage() const { KeyUsage usage; if (advancedSettingsDlg->encryptionAllowed() && (is_dsa(advancedSettingsDlg->keyType()) || is_rsa(advancedSettingsDlg->subkeyType()) || is_ecdh(advancedSettingsDlg->subkeyType()))) { Q_ASSERT(advancedSettingsDlg->subkeyType()); usage.setCanEncrypt(true); } return usage; } void updateTechnicalParameters() { technicalParameters = KeyParameters{KeyParameters::OpenPGP}; const auto keyType = advancedSettingsDlg->keyType(); technicalParameters.setKeyType(keyType); if (is_ecdsa(keyType) || is_eddsa(keyType)) { technicalParameters.setKeyCurve(advancedSettingsDlg->keyCurve()); } else if (const unsigned int strength = advancedSettingsDlg->keyStrength()) { technicalParameters.setKeyLength(strength); } technicalParameters.setKeyUsage(keyUsage()); const auto subkeyType = advancedSettingsDlg->subkeyType(); if (subkeyType) { technicalParameters.setSubkeyType(subkeyType); if (is_ecdh(subkeyType)) { technicalParameters.setSubkeyCurve(advancedSettingsDlg->subkeyCurve()); } else if (const unsigned int strength = advancedSettingsDlg->subkeyStrength()) { technicalParameters.setSubkeyLength(strength); } technicalParameters.setSubkeyUsage(subkeyUsage()); } if (advancedSettingsDlg->expiryDate().isValid()) { technicalParameters.setExpirationDate(advancedSettingsDlg->expiryDate()); } // name and email are set later } void setTechnicalParameters(const KeyParameters ¶meters) { advancedSettingsDlg->setKeyType(parameters.keyType()); advancedSettingsDlg->setKeyStrength(parameters.keyLength()); advancedSettingsDlg->setKeyCurve(parameters.keyCurve()); advancedSettingsDlg->setSubkeyType(parameters.subkeyType()); advancedSettingsDlg->setSubkeyStrength(parameters.subkeyLength()); advancedSettingsDlg->setSubkeyCurve(parameters.subkeyCurve()); advancedSettingsDlg->setSigningAllowed(parameters.keyUsage().canSign() || parameters.subkeyUsage().canSign()); advancedSettingsDlg->setEncryptionAllowed(parameters.keyUsage().canEncrypt() || parameters.subkeyUsage().canEncrypt()); advancedSettingsDlg->setCertificationAllowed(parameters.keyUsage().canCertify() || parameters.subkeyUsage().canCertify()); advancedSettingsDlg->setAuthenticationAllowed(parameters.keyUsage().canAuthenticate() || parameters.subkeyUsage().canAuthenticate()); advancedSettingsDlg->setExpiryDate(parameters.expirationDate()); } void checkAccept() { QStringList errors; if (ui.nameAndEmail->userID().isEmpty() && !ui.nameAndEmail->nameIsRequired() && !ui.nameAndEmail->emailIsRequired()) { errors.push_back(i18n("Enter a name or an email address.")); } const auto nameError = ui.nameAndEmail->nameError(); if (!nameError.isEmpty()) { errors.push_back(nameError); } const auto emailError = ui.nameAndEmail->emailError(); if (!emailError.isEmpty()) { errors.push_back(emailError); } if (errors.size() > 1) { KMessageBox::errorList(q, i18n("There is a problem."), errors); } else if (!errors.empty()) { KMessageBox::error(q, errors.first()); } else { q->accept(); } } private: AdvancedSettingsDialog *const advancedSettingsDlg; KeyParameters technicalParameters; }; NewOpenPGPCertificateDetailsDialog::NewOpenPGPCertificateDetailsDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d(new Private{this}) { } NewOpenPGPCertificateDetailsDialog::~NewOpenPGPCertificateDetailsDialog() = default; void NewOpenPGPCertificateDetailsDialog::setName(const QString &name) { d->ui.nameAndEmail->setName(name); } QString NewOpenPGPCertificateDetailsDialog::name() const { return d->ui.nameAndEmail->name(); } void NewOpenPGPCertificateDetailsDialog::setEmail(const QString &email) { d->ui.nameAndEmail->setEmail(email); } QString NewOpenPGPCertificateDetailsDialog::email() const { return d->ui.nameAndEmail->email(); } void Kleo::NewOpenPGPCertificateDetailsDialog::setKeyParameters(const Kleo::KeyParameters ¶meters) { setName(parameters.name()); const auto emails = parameters.emails(); if (!emails.empty()) { setEmail(emails.front()); } d->setTechnicalParameters(parameters); } KeyParameters NewOpenPGPCertificateDetailsDialog::keyParameters() const { // set name and email on a copy of the technical parameters auto parameters = d->technicalParameters; if (!name().isEmpty()) { parameters.setName(name()); } if (!email().isEmpty()) { parameters.setEmail(email()); } return parameters; } void Kleo::NewOpenPGPCertificateDetailsDialog::setProtectKeyWithPassword(bool protectKey) { d->ui.withPassCheckBox->setChecked(protectKey); } bool NewOpenPGPCertificateDetailsDialog::protectKeyWithPassword() const { return d->ui.withPassCheckBox->isChecked(); } #include "moc_newopenpgpcertificatedetailsdialog.cpp" diff --git a/src/dialogs/revokekeydialog.cpp b/src/dialogs/revokekeydialog.cpp index a7c2637d4..6554316c9 100644 --- a/src/dialogs/revokekeydialog.cpp +++ b/src/dialogs/revokekeydialog.cpp @@ -1,288 +1,288 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/revokekeydialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "revokekeydialog.h" #include "utils/accessibility.h" #include "view/errorlabel.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { class TextEdit : public QTextEdit { Q_OBJECT public: using QTextEdit::QTextEdit; Q_SIGNALS: void editingFinished(); protected: void focusOutEvent(QFocusEvent *event) override { Qt::FocusReason reason = event->reason(); if (reason != Qt::PopupFocusReason || !(QApplication::activePopupWidget() && QApplication::activePopupWidget()->parentWidget() == this)) { Q_EMIT editingFinished(); } QTextEdit::focusOutEvent(event); } }; } class RevokeKeyDialog::Private { friend class ::Kleo::RevokeKeyDialog; RevokeKeyDialog *const q; struct { QLabel *infoLabel = nullptr; QLabel *descriptionLabel = nullptr; TextEdit *description = nullptr; ErrorLabel *descriptionError = nullptr; QDialogButtonBox *buttonBox = nullptr; } ui; Key key; QButtonGroup reasonGroup; bool descriptionEditingInProgress = false; QString descriptionAccessibleName; public: Private(RevokeKeyDialog *qq) : q(qq) { q->setWindowTitle(i18nc("title:window", "Revoke Key")); auto mainLayout = new QVBoxLayout{q}; ui.infoLabel = new QLabel{q}; mainLayout->addWidget(ui.infoLabel); auto groupBox = new QGroupBox{i18nc("@title:group", "Reason for revocation"), q}; reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "No reason specified"), q}, static_cast(RevocationReason::Unspecified)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key has been compromised"), q}, static_cast(RevocationReason::Compromised)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is superseded"), q}, static_cast(RevocationReason::Superseded)); reasonGroup.addButton(new QRadioButton{i18nc("@option:radio", "Key is no longer used"), q}, static_cast(RevocationReason::NoLongerUsed)); reasonGroup.button(static_cast(RevocationReason::Unspecified))->setChecked(true); { auto boxLayout = new QVBoxLayout{groupBox}; for (auto radio : reasonGroup.buttons()) { boxLayout->addWidget(radio); } } mainLayout->addWidget(groupBox); { ui.descriptionLabel = new QLabel{i18nc("@label:textbox", "Description (optional):"), q}; ui.description = new TextEdit{q}; ui.description->setAcceptRichText(false); // do not accept Tab as input; this is better for accessibility and // tabulators are not really that useful in the description ui.description->setTabChangesFocus(true); ui.descriptionLabel->setBuddy(ui.description); ui.descriptionError = new ErrorLabel{q}; ui.descriptionError->setVisible(false); mainLayout->addWidget(ui.descriptionLabel); mainLayout->addWidget(ui.description); mainLayout->addWidget(ui.descriptionError); } connect(ui.description, &TextEdit::editingFinished, q, [this]() { onDescriptionEditingFinished(); }); connect(ui.description, &TextEdit::textChanged, q, [this]() { onDescriptionTextChanged(); }); ui.buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel); auto okButton = ui.buttonBox->button(QDialogButtonBox::Ok); okButton->setText(i18nc("@action:button", "Revoke Key")); okButton->setIcon(QIcon::fromTheme(QStringLiteral("edit-delete-remove"))); mainLayout->addWidget(ui.buttonBox); connect(ui.buttonBox, &QDialogButtonBox::accepted, q, [this]() { checkAccept(); }); connect(ui.buttonBox, &QDialogButtonBox::rejected, q, &QDialog::reject); restoreGeometry(); } ~Private() { saveGeometry(); } private: void saveGeometry() { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), QStringLiteral("RevokeKeyDialog")); cfgGroup.writeEntry("Size", q->size()); cfgGroup.sync(); } void restoreGeometry(const QSize &defaultSize = {}) { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), QStringLiteral("RevokeKeyDialog")); const QSize size = cfgGroup.readEntry("Size", defaultSize); if (size.isValid()) { q->resize(size); } } void checkAccept() { if (!descriptionHasAcceptableInput()) { KMessageBox::error(q, descriptionErrorMessage()); } else { q->accept(); } } bool descriptionHasAcceptableInput() const { - return !q->description().contains(QLatin1String{"\n\n"}); + return !q->description().contains(QLatin1StringView{"\n\n"}); } QString descriptionErrorMessage() const { QString message; if (!descriptionHasAcceptableInput()) { message = i18n("Error: The description must not contain empty lines."); } return message; } void updateDescriptionError() { const auto currentErrorMessage = ui.descriptionError->text(); const auto newErrorMessage = descriptionErrorMessage(); if (newErrorMessage == currentErrorMessage) { return; } if (currentErrorMessage.isEmpty() && descriptionEditingInProgress) { // delay showing the error message until editing is finished, so that we // do not annoy the user with an error message while they are still // entering the recipient; // on the other hand, we clear the error message immediately if it does // not apply anymore and we update the error message immediately if it // changed return; } ui.descriptionError->setVisible(!newErrorMessage.isEmpty()); ui.descriptionError->setText(newErrorMessage); updateAccessibleNameAndDescription(); } void updateAccessibleNameAndDescription() { // fall back to default accessible name if accessible name wasn't set explicitly if (descriptionAccessibleName.isEmpty()) { descriptionAccessibleName = getAccessibleName(ui.description); } const bool errorShown = ui.descriptionError->isVisible(); // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); // emulate this by setting the error message as accessible description of the input field const auto description = errorShown ? ui.descriptionError->text() : QString{}; if (ui.description->accessibleDescription() != description) { ui.description->setAccessibleDescription(description); } // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); // screen readers say something like "invalid entry" if this state is set; // emulate this by adding "invalid entry" to the accessible name of the input field // and its label - const auto name = errorShown ? descriptionAccessibleName + QLatin1String{", "} + invalidEntryText() // + const auto name = errorShown ? descriptionAccessibleName + QLatin1StringView{", "} + invalidEntryText() // : descriptionAccessibleName; if (ui.descriptionLabel->accessibleName() != name) { ui.descriptionLabel->setAccessibleName(name); } if (ui.description->accessibleName() != name) { ui.description->setAccessibleName(name); } } void onDescriptionTextChanged() { descriptionEditingInProgress = true; updateDescriptionError(); } void onDescriptionEditingFinished() { descriptionEditingInProgress = false; updateDescriptionError(); } }; RevokeKeyDialog::RevokeKeyDialog(QWidget *parent, Qt::WindowFlags f) : QDialog{parent, f} , d{new Private{this}} { } RevokeKeyDialog::~RevokeKeyDialog() = default; void RevokeKeyDialog::setKey(const GpgME::Key &key) { d->key = key; d->ui.infoLabel->setText(xi18n("You are about to revoke the following key:%1").arg(Formatting::summaryLine(key))); } GpgME::RevocationReason RevokeKeyDialog::reason() const { return static_cast(d->reasonGroup.checkedId()); } QString RevokeKeyDialog::description() const { static const QRegularExpression whitespaceAtEndOfLine{QStringLiteral(R"([ \t\r]+\n)")}; static const QRegularExpression trailingWhitespace{QStringLiteral(R"(\s*$)")}; return d->ui.description->toPlainText().remove(whitespaceAtEndOfLine).remove(trailingWhitespace); } #include "revokekeydialog.moc" #include "moc_revokekeydialog.cpp" diff --git a/src/dialogs/setinitialpindialog.cpp b/src/dialogs/setinitialpindialog.cpp index a8795bf2a..474125a40 100644 --- a/src/dialogs/setinitialpindialog.cpp +++ b/src/dialogs/setinitialpindialog.cpp @@ -1,199 +1,199 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/setinitialpindialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "setinitialpindialog.h" #include "ui_setinitialpindialog.h" #include #include #include #include #include // for Qt::escape #include using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; enum State { Unknown = 0, NotSet, AlreadySet, Ongoing, Ok, Failed, NumStates, }; const char *icons[] = { // PENDING(marc) use better icons, once available "", // Unknown "", // NotSet "security-medium", // AlreadySet "movie-process-working-kde", // Ongoing "security-high", // Ok "security-low", // Failed }; static_assert(sizeof icons / sizeof(*icons) == NumStates, ""); static_assert(sizeof("movie-") == 7, ""); static void update_widget(State state, bool delay, QLabel *resultLB, QLabel *lb, QPushButton *pb, QLabel *statusLB) { Q_ASSERT(state >= 0); Q_ASSERT(state < NumStates); const char *icon = icons[state]; if (qstrncmp(icon, "movie-", sizeof("movie-") - 1) == 0) { - resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1String(icon + sizeof("movie-")), KIconLoader::NoGroup)); + resultLB->setMovie(KIconLoader::global()->loadMovie(QLatin1StringView(icon + sizeof("movie-")), KIconLoader::NoGroup)); } else if (icon && *icon) { - resultLB->setPixmap(QIcon::fromTheme(QLatin1String(icon)).pixmap(32)); + resultLB->setPixmap(QIcon::fromTheme(QLatin1StringView(icon)).pixmap(32)); } else { resultLB->setPixmap(QPixmap()); } lb->setEnabled((state == NotSet || state == Failed) && !delay); pb->setEnabled((state == NotSet || state == Failed) && !delay); if (state == AlreadySet) { statusLB->setText( xi18nc("@info", "No NullPin found. If this PIN was not set by you personally, the card might have been tampered with.")); } } static QString format_error(const Error &err) { if (err.isCanceled()) { return i18nc("@info", "Canceled setting PIN."); } if (err) return xi18nc("@info", "There was an error setting the PIN: %1.", Formatting::errorAsString(err).toHtmlEscaped()); else { return i18nc("@info", "PIN set successfully."); } } class SetInitialPinDialog::Private { friend class ::Kleo::Dialogs::SetInitialPinDialog; SetInitialPinDialog *const q; public: explicit Private(SetInitialPinDialog *qq) : q(qq) , nksState(Unknown) , sigGState(Unknown) , ui(q) { } private: void slotNksButtonClicked() { nksState = Ongoing; ui.nksStatusLB->clear(); updateWidgets(); Q_EMIT q->nksPinRequested(); } void slotSigGButtonClicked() { sigGState = Ongoing; ui.sigGStatusLB->clear(); updateWidgets(); Q_EMIT q->sigGPinRequested(); } private: void updateWidgets() { update_widget(nksState, false, ui.nksResultIcon, ui.nksLB, ui.nksPB, ui.nksStatusLB); update_widget(sigGState, nksState == NotSet || nksState == Failed || nksState == Ongoing, ui.sigGResultIcon, ui.sigGLB, ui.sigGPB, ui.sigGStatusLB); ui.closePB()->setEnabled(q->isComplete()); ui.cancelPB()->setEnabled(!q->isComplete()); } private: State nksState, sigGState; struct UI : public Ui::SetInitialPinDialog { explicit UI(Dialogs::SetInitialPinDialog *qq) : Ui::SetInitialPinDialog() { setupUi(qq); closePB()->setEnabled(false); connect(closePB(), &QAbstractButton::clicked, qq, &QDialog::accept); } QAbstractButton *closePB() const { Q_ASSERT(dialogButtonBox); return dialogButtonBox->button(QDialogButtonBox::Close); } QAbstractButton *cancelPB() const { Q_ASSERT(dialogButtonBox); return dialogButtonBox->button(QDialogButtonBox::Cancel); } } ui; }; SetInitialPinDialog::SetInitialPinDialog(QWidget *p) : QDialog(p) , d(new Private(this)) { } SetInitialPinDialog::~SetInitialPinDialog() { } void SetInitialPinDialog::setNksPinPresent(bool on) { d->nksState = on ? AlreadySet : NotSet; d->updateWidgets(); } void SetInitialPinDialog::setSigGPinPresent(bool on) { d->sigGState = on ? AlreadySet : NotSet; d->updateWidgets(); } void SetInitialPinDialog::setNksPinSettingResult(const Error &err) { d->ui.nksStatusLB->setText(format_error(err)); d->nksState = (err.isCanceled() ? NotSet // : err ? Failed : Ok); d->updateWidgets(); } void SetInitialPinDialog::setSigGPinSettingResult(const Error &err) { d->ui.sigGStatusLB->setText(format_error(err)); d->sigGState = (err.isCanceled() ? NotSet // : err ? Failed : Ok); d->updateWidgets(); } bool SetInitialPinDialog::isComplete() const { return (d->nksState == Ok || d->nksState == AlreadySet); } #include "moc_setinitialpindialog.cpp" diff --git a/src/dialogs/updatenotification.cpp b/src/dialogs/updatenotification.cpp index 359522f9a..eb640d718 100644 --- a/src/dialogs/updatenotification.cpp +++ b/src/dialogs/updatenotification.cpp @@ -1,225 +1,225 @@ /* dialogs/updatenotification.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include "updatenotification.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; namespace { static void gpgconf_set_update_check(bool value) { auto conf = QGpgME::cryptoConfig(); auto entry = getCryptoConfigEntry(conf, "dirmngr", "allow-version-check"); if (!entry) { qCDebug(KLEOPATRA_LOG) << "allow-version-check entry not found"; return; } if (entry->boolValue() != value) { entry->setBoolValue(value); conf->sync(true); } } } // namespace void UpdateNotification::forceUpdateCheck(QWidget *parent) { auto proc = new QProcess; proc->setProgram(gnupgInstallPath() + QStringLiteral("/gpg-connect-agent.exe")); proc->setArguments({ QStringLiteral("--dirmngr"), QStringLiteral("loadswdb --force"), QStringLiteral("/bye"), }); auto progress = new QProgressDialog(i18n("Searching for updates..."), i18n("Cancel"), 0, 0, parent); progress->setMinimumDuration(0); progress->show(); connect(progress, &QProgressDialog::canceled, proc, [proc]() { proc->kill(); qCDebug(KLEOPATRA_LOG) << "Update force canceled. Output:" << QString::fromLocal8Bit(proc->readAllStandardOutput()) << "stderr:" << QString::fromLocal8Bit(proc->readAllStandardError()); }); connect(proc, &QProcess::finished, progress, [parent, progress, proc](int exitCode, QProcess::ExitStatus exitStatus) { qCDebug(KLEOPATRA_LOG) << "Update force exited with status:" << exitStatus << "code:" << exitCode; delete progress; proc->deleteLater(); UpdateNotification::checkUpdate(parent, exitStatus == QProcess::NormalExit); }); qCDebug(KLEOPATRA_LOG) << "Starting:" << proc->program() << "args" << proc->arguments(); proc->start(); } #ifdef Q_OS_WIN /* Extract the actual version number (conforming to the semanticversioning spec) * from the Version strings which might be used for Gpg4win / GnuPG VS-Desktop * which are optionally prefixed with some text followed by a dash * e.g. "Gpg4win-3.1.15-beta15"; see https://dev.gnupg.org/T5663 */ static const QByteArray extractVersionNumber(const QString &versionString) { - static const QRegularExpression catchSemVerRegExp{QLatin1String{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}}; + static const QRegularExpression catchSemVerRegExp{QLatin1StringView{R"(-([0-9]+(?:\.[0-9]+)*(?:-[.0-9A-Za-z-]+)?(?:\+[.0-9a-zA-Z-]+)?)$)"}}; const auto match = catchSemVerRegExp.match(versionString); const auto current = match.hasMatch() ? match.captured(1) : versionString; return current.toUtf8(); } #endif void UpdateNotification::checkUpdate(QWidget *parent, bool force) { #ifdef Q_OS_WIN KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); if (updatecfg.readEntry("NeverShow", false) && !force) { return; } // Gpg defaults to no update check. For Gpg4win we want this // enabled if the user does not explicitly disable update // checks neverShow would be true in that case or // we would have set AllowVersionCheck once and the user // explicitly removed that. if (force || updatecfg.readEntry("AllowVersionCheckSetOnce", false)) { gpgconf_set_update_check(true); updatecfg.writeEntry("AllowVersionCheckSetOnce", true); } GpgME::Error err; const auto lastshown = updatecfg.readEntry("LastShown", QDateTime()); if (!force && lastshown.isValid() && lastshown.addSecs(20 * 60 * 60) > QDateTime::currentDateTime()) { qDebug() << QDateTime::currentDateTime().addSecs(20 * 60 * 60); return; } const auto current = extractVersionNumber(KAboutData::applicationData().version()); const auto results = GpgME::SwdbResult::query("gpg4win", current.constData(), &err); if (err) { qCDebug(KLEOPATRA_LOG) << "update check failed: " << Formatting::errorAsString(err); return; } if (results.size() != 1) { /* Should not happen */ qCDebug(KLEOPATRA_LOG) << "more then one result"; return; } const auto result = results[0]; if (result.update()) { const QString newVersion = QStringLiteral("%1.%2.%3").arg(result.version().major).arg(result.version().minor).arg(result.version().patch); qCDebug(KLEOPATRA_LOG) << "Have update to version:" << newVersion; UpdateNotification notifier(parent, newVersion); notifier.exec(); updatecfg.writeEntry("LastShown", QDateTime::currentDateTime()); updatecfg.sync(); } else { qCDebug(KLEOPATRA_LOG) << "No update for:" << current; if (force) { KMessageBox::information(parent, i18nc("@info", "No update found in the available version database."), i18nc("@title", "Up to date")); } } #else Q_UNUSED(parent) Q_UNUSED(force) #endif } UpdateNotification::UpdateNotification(QWidget *parent, const QString &version) : QDialog(parent) { resize(400, 200); auto lay = new QGridLayout(this); auto logo = new QLabel; logo->setMaximumWidth(110); setAttribute(Qt::WA_QuitOnClose, false); KIconLoader *const il = KIconLoader::global(); const QString iconPath = il->iconPath(QStringLiteral("gpg4win"), KIconLoader::User); logo->setPixmap(QIcon(iconPath).pixmap(100, 100)); auto label = new HtmlLabel; const QString boldVersion = QStringLiteral("%1").arg(version); label->setHtml(i18nc("%1 is the version number", "Version %1 is available.", boldVersion) + QStringLiteral("

    ") + i18nc("Link to NEWS style changelog", "See the new features.")); label->setOpenExternalLinks(true); label->setTextInteractionFlags(Qt::TextBrowserInteraction); label->setWordWrap(true); setWindowTitle(i18nc("@title:window", "Update Available")); - setWindowIcon(QIcon(QLatin1String("gpg4win"))); + setWindowIcon(QIcon(QLatin1StringView("gpg4win"))); lay->addWidget(logo, 0, 0); lay->addWidget(label, 0, 1); const auto chk = new QCheckBox(i18n("Show this notification for future updates.")); lay->addWidget(chk, 1, 0, 1, -1); KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); chk->setChecked(!updatecfg.readEntry("NeverShow", false)); const auto bb = new QDialogButtonBox(); const auto b = bb->addButton(i18n("&Get update"), QDialogButtonBox::AcceptRole); b->setDefault(true); b->setIcon(QIcon::fromTheme(QStringLiteral("arrow-down"))); bb->addButton(QDialogButtonBox::Cancel); lay->addWidget(bb, 2, 0, 1, -1); connect(bb, &QDialogButtonBox::accepted, this, [this, chk]() { QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.gpg4win.org/download.html"))); KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); updatecfg.writeEntry("NeverShow", !chk->isChecked()); gpgconf_set_update_check(chk->isChecked()); QDialog::accept(); }); connect(bb, &QDialogButtonBox::rejected, this, [this, chk]() { KConfigGroup updatecfg(KSharedConfig::openConfig(), QStringLiteral("UpdateNotification")); updatecfg.writeEntry("NeverShow", !chk->isChecked()); gpgconf_set_update_check(chk->isChecked()); QDialog::reject(); }); } diff --git a/src/kleopatraapplication.cpp b/src/kleopatraapplication.cpp index de15e4413..b137e9f13 100644 --- a/src/kleopatraapplication.cpp +++ b/src/kleopatraapplication.cpp @@ -1,873 +1,873 @@ /* kleopatraapplication.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 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 "kleopatraapplication.h" #include "kleopatra_options.h" #include "mainwindow.h" #include "settings.h" #include "smimevalidationpreferences.h" #include "systrayicon.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "commands/checksumcreatefilescommand.h" #include "commands/checksumverifyfilescommand.h" #include "commands/decryptverifyfilescommand.h" #include "commands/detailscommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/lookupcertificatescommand.h" #include "commands/newcertificatesigningrequestcommand.h" #include "commands/newopenpgpcertificatecommand.h" #include "commands/signencryptfilescommand.h" #include "dialogs/updatenotification.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #if QT_CONFIG(graphicseffect) #include #endif #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif using namespace Kleo; using namespace Kleo::Commands; static void add_resources() { KIconLoader::global()->addAppDir(QStringLiteral("libkleopatra")); KIconLoader::global()->addAppDir(QStringLiteral("kwatchgnupg")); } static QList default_logging_options() { QList result; result.push_back("io"); return result; } namespace { class FocusFrame : public QFocusFrame { Q_OBJECT public: using QFocusFrame::QFocusFrame; protected: void paintEvent(QPaintEvent *event) override; }; static QRect effectiveWidgetRect(const QWidget *w) { // based on QWidgetPrivate::effectiveRectFor #if QT_CONFIG(graphicseffect) if (auto graphicsEffect = w->graphicsEffect(); graphicsEffect && graphicsEffect->isEnabled()) return graphicsEffect->boundingRectFor(w->rect()).toAlignedRect(); #endif // QT_CONFIG(graphicseffect) return w->rect(); } static QRect clipRect(const QWidget *w) { // based on QWidgetPrivate::clipRect if (!w->isVisible()) { return QRect(); } QRect r = effectiveWidgetRect(w); int ox = 0; int oy = 0; while (w && w->isVisible() && !w->isWindow() && w->parentWidget()) { ox -= w->x(); oy -= w->y(); w = w->parentWidget(); r &= QRect(ox, oy, w->width(), w->height()); } return r; } void FocusFrame::paintEvent(QPaintEvent *) { if (!widget()) { return; } QStylePainter p(this); QStyleOptionFocusRect option; initStyleOption(&option); const int vmargin = style()->pixelMetric(QStyle::PM_FocusFrameVMargin, &option); const int hmargin = style()->pixelMetric(QStyle::PM_FocusFrameHMargin, &option); const QRect rect = clipRect(widget()).adjusted(0, 0, hmargin * 2, vmargin * 2); p.setClipRect(rect); p.drawPrimitive(QStyle::PE_FrameFocusRect, option); } } class KleopatraApplication::Private { friend class ::KleopatraApplication; KleopatraApplication *const q; public: explicit Private(KleopatraApplication *qq) : q(qq) , ignoreNewInstance(true) , firstNewInstance(true) , sysTray(nullptr) , groupConfig{std::make_shared(QStringLiteral("kleopatragroupsrc"))} { } ~Private() { #ifndef QT_NO_SYSTEMTRAYICON delete sysTray; #endif } void setUpSysTrayIcon() { #ifndef QT_NO_SYSTEMTRAYICON Q_ASSERT(readerStatus); sysTray = new SysTrayIcon(); sysTray->setFirstCardWithNullPin(readerStatus->firstCardWithNullPin()); sysTray->setAnyCardCanLearnKeys(readerStatus->anyCardCanLearnKeys()); connect(readerStatus.get(), &SmartCard::ReaderStatus::firstCardWithNullPinChanged, sysTray, &SysTrayIcon::setFirstCardWithNullPin); connect(readerStatus.get(), &SmartCard::ReaderStatus::anyCardCanLearnKeysChanged, sysTray, &SysTrayIcon::setAnyCardCanLearnKeys); #endif } private: void connectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { connect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } connect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } void disconnectConfigureDialog() { if (configureDialog) { if (q->mainWindow()) { disconnect(configureDialog, SIGNAL(configCommitted()), q->mainWindow(), SLOT(slotConfigCommitted())); } disconnect(configureDialog, &ConfigureDialog::configCommitted, q, &KleopatraApplication::configurationChanged); } } public: bool ignoreNewInstance; bool firstNewInstance; QPointer focusFrame; QPointer configureDialog; QPointer groupsConfigDialog; QPointer mainWindow; std::unique_ptr readerStatus; #ifndef QT_NO_SYSTEMTRAYICON SysTrayIcon *sysTray; #endif std::shared_ptr groupConfig; std::shared_ptr keyCache; std::shared_ptr log; std::shared_ptr watcher; std::shared_ptr distroSettings; public: void setupKeyCache() { keyCache = KeyCache::mutableInstance(); keyCache->setRefreshInterval(SMimeValidationPreferences{}.refreshInterval()); watcher.reset(new FileSystemWatcher); watcher->whitelistFiles(gnupgFileWhitelist()); watcher->addPaths(gnupgFolderWhitelist()); watcher->setDelay(1000); keyCache->addFileSystemWatcher(watcher); keyCache->setGroupConfig(groupConfig); keyCache->setGroupsEnabled(Settings().groupsEnabled()); // always enable remarks (aka tags); in particular, this triggers a // relisting of the keys with signatures and signature notations // after the initial (fast) key listing keyCache->enableRemarks(true); } void setUpFilterManager() { if (!Settings{}.cmsEnabled()) { KeyFilterManager::instance()->alwaysFilterByProtocol(GpgME::OpenPGP); } } void setupLogging() { log = Log::mutableInstance(); const QByteArray envOptions = qgetenv("KLEOPATRA_LOGOPTIONS"); const bool logAll = envOptions.trimmed() == "all"; const QList options = envOptions.isEmpty() ? default_logging_options() : envOptions.split(','); const QByteArray dirNative = qgetenv("KLEOPATRA_LOGDIR"); if (dirNative.isEmpty()) { return; } const QString dir = QFile::decodeName(dirNative); const QString logFileName = QDir(dir).absoluteFilePath(QStringLiteral("kleopatra.log.%1").arg(QCoreApplication::applicationPid())); std::unique_ptr logFile(new QFile(logFileName)); if (!logFile->open(QIODevice::WriteOnly | QIODevice::Append)) { qCDebug(KLEOPATRA_LOG) << "Could not open file for logging: " << logFileName << "\nLogging disabled"; return; } log->setOutputDirectory(dir); if (logAll || options.contains("io")) { log->setIOLoggingEnabled(true); } qInstallMessageHandler(Log::messageHandler); if (logAll || options.contains("pipeio")) { KDPipeIODevice::setDebugLevel(KDPipeIODevice::Debug); } UiServer::setLogStream(log->logFile()); } void updateFocusFrame(QWidget *focusWidget) { if (focusWidget && focusWidget->inherits("QLabel") && focusWidget->window()->testAttribute(Qt::WA_KeyboardFocusChange)) { if (!focusFrame) { focusFrame = new FocusFrame{focusWidget}; } focusFrame->setWidget(focusWidget); } else if (focusFrame) { focusFrame->setWidget(nullptr); } } }; KleopatraApplication::KleopatraApplication(int &argc, char *argv[]) : QApplication(argc, argv) , d(new Private(this)) { // disable parent<->child navigation in tree views with left/right arrow keys // because this interferes with column by column navigation that is required // for accessibility setStyleSheet(QStringLiteral("QTreeView { arrow-keys-navigate-into-children: 0; }")); connect(this, &QApplication::focusChanged, this, [this](QWidget *, QWidget *now) { d->updateFocusFrame(now); }); } void KleopatraApplication::init() { #ifdef Q_OS_WIN QWindowsWindowFunctions::setWindowActivationBehavior(QWindowsWindowFunctions::AlwaysActivateWindow); #endif const auto blockedUrlSchemes = Settings{}.blockedUrlSchemes(); for (const auto &scheme : blockedUrlSchemes) { QDesktopServices::setUrlHandler(scheme, this, "blockUrl"); } add_resources(); DN::setAttributeOrder(Settings{}.attributeOrder()); /* Start the gpg-agent early, this is done explicitly * because on an empty keyring our keylistings wont start * the agent. In that case any assuan-connect calls to * the agent will fail. The requested start via the * connection is additionally done in case the gpg-agent * is killed while Kleopatra is running. */ startGpgAgent(); d->readerStatus.reset(new SmartCard::ReaderStatus); connect(d->readerStatus.get(), &SmartCard::ReaderStatus::startOfGpgAgentRequested, this, &KleopatraApplication::startGpgAgent); d->setupKeyCache(); d->setUpSysTrayIcon(); d->setUpFilterManager(); d->setupLogging(); #ifdef Q_OS_WIN if (!SystemInfo::isHighContrastModeActive()) { /* In high contrast mode we do not want our own colors */ new KColorSchemeManager(this); } #else new KColorSchemeManager(this); #endif #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->show(); #endif setQuitOnLastWindowClosed(false); // Sync config when we are about to quit connect(this, &QApplication::aboutToQuit, this, []() { KSharedConfig::openConfig()->sync(); }); } KleopatraApplication::~KleopatraApplication() { delete d->groupsConfigDialog; delete d->mainWindow; } namespace { using Func = void (KleopatraApplication::*)(const QStringList &, GpgME::Protocol); } void KleopatraApplication::slotActivateRequested(const QStringList &arguments, const QString &workingDirectory) { QCommandLineParser parser; kleopatra_options(&parser); QString err; if (!arguments.isEmpty() && !parser.parse(arguments)) { err = parser.errorText(); } else if (arguments.isEmpty()) { // KDBusServices omits the application name if no other // arguments are provided. In that case the parser prints // a warning. parser.parse(QStringList() << QCoreApplication::applicationFilePath()); } if (err.isEmpty()) { err = newInstance(parser, workingDirectory); } if (!err.isEmpty()) { KMessageBox::error(nullptr, err.toHtmlEscaped(), i18n("Failed to execute command")); Q_EMIT setExitValue(1); return; } Q_EMIT setExitValue(0); } QString KleopatraApplication::newInstance(const QCommandLineParser &parser, const QString &workingDirectory) { if (d->ignoreNewInstance) { qCDebug(KLEOPATRA_LOG) << "New instance ignored because of ignoreNewInstance"; return QString(); } QStringList files; const QDir cwd = QDir(workingDirectory); bool queryMode = parser.isSet(QStringLiteral("query")) || parser.isSet(QStringLiteral("search")); // Query and Search treat positional arguments differently, see below. if (!queryMode) { const auto positionalArguments = parser.positionalArguments(); for (const QString &file : positionalArguments) { // We do not check that file exists here. Better handle // these errors in the UI. if (QFileInfo(file).isAbsolute()) { files << file; } else { files << cwd.absoluteFilePath(file); } } } GpgME::Protocol protocol = GpgME::UnknownProtocol; if (parser.isSet(QStringLiteral("openpgp"))) { qCDebug(KLEOPATRA_LOG) << "found OpenPGP"; protocol = GpgME::OpenPGP; } if (parser.isSet(QStringLiteral("cms"))) { qCDebug(KLEOPATRA_LOG) << "found CMS"; if (protocol == GpgME::OpenPGP) { return i18n("Ambiguous protocol: --openpgp and --cms"); } protocol = GpgME::CMS; } // Check for Parent Window id WId parentId = 0; if (parser.isSet(QStringLiteral("parent-windowid"))) { #ifdef Q_OS_WIN // WId is not a portable type as it is a pointer type on Windows. // casting it from an integer is ok though as the values are guaranteed to // be compatible in the documentation. parentId = reinterpret_cast(parser.value(QStringLiteral("parent-windowid")).toUInt()); #else parentId = parser.value(QStringLiteral("parent-windowid")).toUInt(); #endif } // Handle openpgp4fpr URI scheme QString needle; if (queryMode) { needle = parser.positionalArguments().join(QLatin1Char(' ')); } - if (needle.startsWith(QLatin1String("openpgp4fpr:"))) { + if (needle.startsWith(QLatin1StringView("openpgp4fpr:"))) { needle.remove(0, 12); } // Check for --search command. if (parser.isSet(QStringLiteral("search"))) { // This is an extra command instead of a combination with the // similar query to avoid changing the older query commands behavior // and query's "show details if a certificate exist or search on a // keyserver" logic is hard to explain and use consistently. if (needle.isEmpty()) { return i18n("No search string specified for --search"); } auto const cmd = new LookupCertificatesCommand(needle, nullptr); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --query command if (parser.isSet(QStringLiteral("query"))) { if (needle.isEmpty()) { return i18n("No fingerprint argument specified for --query"); } auto cmd = Command::commandForQuery(needle); cmd->setParentWId(parentId); cmd->start(); return QString(); } // Check for --gen-key command if (parser.isSet(QStringLiteral("gen-key"))) { if (protocol == GpgME::CMS) { const Kleo::Settings settings{}; if (settings.cmsEnabled() && settings.cmsCertificateCreationAllowed()) { auto cmd = new NewCertificateSigningRequestCommand; cmd->setParentWId(parentId); cmd->start(); } else { return i18n("You are not allowed to create S/MIME certificate signing requests."); } } else { auto cmd = new NewOpenPGPCertificateCommand; cmd->setParentWId(parentId); cmd->start(); } return QString(); } // Check for --config command if (parser.isSet(QStringLiteral("config"))) { openConfigDialogWithForeignParent(parentId); return QString(); } struct FuncInfo { QString optionName; Func func; }; // While most of these options can be handled by the content autodetection // below it might be useful to override the autodetection if the input is in // doubt and you e.g. only want to import .asc files or fail and not decrypt them // if they are actually encrypted data. static const std::vector funcMap{ {QStringLiteral("import-certificate"), &KleopatraApplication::importCertificatesFromFile}, {QStringLiteral("encrypt"), &KleopatraApplication::encryptFiles}, {QStringLiteral("sign"), &KleopatraApplication::signFiles}, {QStringLiteral("encrypt-sign"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("sign-encrypt"), &KleopatraApplication::signEncryptFiles}, {QStringLiteral("decrypt"), &KleopatraApplication::decryptFiles}, {QStringLiteral("verify"), &KleopatraApplication::verifyFiles}, {QStringLiteral("decrypt-verify"), &KleopatraApplication::decryptVerifyFiles}, {QStringLiteral("checksum"), &KleopatraApplication::checksumFiles}, }; QString found; Func foundFunc = nullptr; for (const auto &[opt, fn] : funcMap) { if (parser.isSet(opt) && found.isEmpty()) { found = opt; foundFunc = fn; } else if (parser.isSet(opt)) { return i18n(R"(Ambiguous commands "%1" and "%2")", found, opt); } } QStringList errors; if (!found.isEmpty()) { if (files.empty()) { return i18n("No files specified for \"%1\" command", found); } qCDebug(KLEOPATRA_LOG) << "found" << found; (this->*foundFunc)(files, protocol); } else { if (files.empty()) { if (!(d->firstNewInstance && isSessionRestored())) { qCDebug(KLEOPATRA_LOG) << "openOrRaiseMainWindow"; openOrRaiseMainWindow(); } } else { for (const QString &fileName : std::as_const(files)) { QFileInfo fi(fileName); if (!fi.isReadable()) { errors << i18n("Cannot read \"%1\"", fileName); } } handleFiles(files, parentId); } } d->firstNewInstance = false; #ifdef Q_OS_WIN // On Windows we might be started from the // explorer in any working directory. E.g. // a double click on a file. To avoid preventing // the folder from deletion we set the // working directory to the users homedir. QDir::setCurrent(QDir::homePath()); #endif return errors.join(QLatin1Char('\n')); } void KleopatraApplication::handleFiles(const QStringList &files, WId parentId) { const QList allCmds = Command::commandsForFiles(files); for (Command *cmd : allCmds) { if (parentId) { cmd->setParentWId(parentId); } else { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } cmd->setParentWidget(mw); } if (dynamic_cast(cmd)) { openOrRaiseMainWindow(); } cmd->start(); } } #ifndef QT_NO_SYSTEMTRAYICON const SysTrayIcon *KleopatraApplication::sysTrayIcon() const { return d->sysTray; } SysTrayIcon *KleopatraApplication::sysTrayIcon() { return d->sysTray; } #endif const MainWindow *KleopatraApplication::mainWindow() const { return d->mainWindow; } MainWindow *KleopatraApplication::mainWindow() { return d->mainWindow; } void KleopatraApplication::setMainWindow(MainWindow *mainWindow) { if (mainWindow == d->mainWindow) { return; } d->disconnectConfigureDialog(); d->mainWindow = mainWindow; #ifndef QT_NO_SYSTEMTRAYICON d->sysTray->setMainWindow(mainWindow); #endif d->connectConfigureDialog(); } static void open_or_raise(QWidget *w) { #ifdef Q_OS_WIN if (w->isMinimized()) { qCDebug(KLEOPATRA_LOG) << __func__ << "unminimizing and raising window"; w->raise(); } else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "raising window"; w->raise(); #else if (w->isVisible()) { qCDebug(KLEOPATRA_LOG) << __func__ << "activating window"; KWindowSystem::updateStartupId(w->windowHandle()); KWindowSystem::activateWindow(w->windowHandle()); #endif } else { qCDebug(KLEOPATRA_LOG) << __func__ << "showing window"; w->show(); } } void KleopatraApplication::toggleMainWindowVisibility() { if (mainWindow()) { mainWindow()->setVisible(!mainWindow()->isVisible()); } else { openOrRaiseMainWindow(); } if (mainWindow()->isVisible()) { mainWindow()->exportWindow(); } else { mainWindow()->unexportWindow(); } } void KleopatraApplication::restoreMainWindow() { qCDebug(KLEOPATRA_LOG) << "restoring main window"; // Sanity checks if (!isSessionRestored()) { qCDebug(KLEOPATRA_LOG) << "Not in session restore"; return; } if (mainWindow()) { qCDebug(KLEOPATRA_LOG) << "Already have main window"; return; } auto mw = new MainWindow; if (KMainWindow::canBeRestored(1)) { // restore to hidden state, Mainwindow::readProperties() will // restore saved visibility. mw->restore(1, false); } mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } void KleopatraApplication::openOrRaiseMainWindow() { MainWindow *mw = mainWindow(); if (!mw) { mw = new MainWindow; mw->setAttribute(Qt::WA_DeleteOnClose); setMainWindow(mw); d->connectConfigureDialog(); } open_or_raise(mw); UpdateNotification::checkUpdate(mw); } void KleopatraApplication::openConfigDialogWithForeignParent(WId parentWId) { if (!d->configureDialog) { d->configureDialog = new ConfigureDialog; d->configureDialog->setAttribute(Qt::WA_DeleteOnClose); d->connectConfigureDialog(); } // This is similar to what the commands do. if (parentWId) { if (QWidget *pw = QWidget::find(parentWId)) { d->configureDialog->setParent(pw, d->configureDialog->windowFlags()); } else { d->configureDialog->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(d->configureDialog->windowHandle(), parentWId); } } open_or_raise(d->configureDialog); // If we have a parent we want to raise over it. if (parentWId) { d->configureDialog->raise(); } } void KleopatraApplication::openOrRaiseConfigDialog() { openConfigDialogWithForeignParent(0); } void KleopatraApplication::openOrRaiseGroupsConfigDialog(QWidget *parent) { if (!d->groupsConfigDialog) { d->groupsConfigDialog = new GroupsConfigDialog{parent}; d->groupsConfigDialog->setAttribute(Qt::WA_DeleteOnClose); } else { // reparent the dialog to ensure it's shown on top of the (modal) parent d->groupsConfigDialog->setParent(parent, Qt::Dialog); } open_or_raise(d->groupsConfigDialog); } #ifndef QT_NO_SYSTEMTRAYICON void KleopatraApplication::startMonitoringSmartCard() { Q_ASSERT(d->readerStatus); d->readerStatus->startMonitoring(); } #endif // QT_NO_SYSTEMTRAYICON void KleopatraApplication::importCertificatesFromFile(const QStringList &files, GpgME::Protocol /*proto*/) { openOrRaiseMainWindow(); if (!files.empty()) { mainWindow()->importCertificatesFromFile(files); } } void KleopatraApplication::encryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setEncryptionPolicy(Force); cmd->setSigningPolicy(Allow); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); cmd->setSigningPolicy(Force); cmd->setEncryptionPolicy(Deny); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::signEncryptFiles(const QStringList &files, GpgME::Protocol proto) { auto const cmd = new SignEncryptFilesCommand(files, nullptr); if (proto != GpgME::UnknownProtocol) { cmd->setProtocol(proto); } cmd->start(); } void KleopatraApplication::decryptFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Decrypt); cmd->start(); } void KleopatraApplication::verifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->setOperation(Verify); cmd->start(); } void KleopatraApplication::decryptVerifyFiles(const QStringList &files, GpgME::Protocol /*proto*/) { auto const cmd = new DecryptVerifyFilesCommand(files, nullptr); cmd->start(); } void KleopatraApplication::checksumFiles(const QStringList &files, GpgME::Protocol /*proto*/) { QStringList verifyFiles, createFiles; for (const QString &file : files) { if (isChecksumFile(file)) { verifyFiles << file; } else { createFiles << file; } } if (!verifyFiles.isEmpty()) { auto const cmd = new ChecksumVerifyFilesCommand(verifyFiles, nullptr); cmd->start(); } if (!createFiles.isEmpty()) { auto const cmd = new ChecksumCreateFilesCommand(createFiles, nullptr); cmd->start(); } } void KleopatraApplication::setIgnoreNewInstance(bool ignore) { d->ignoreNewInstance = ignore; } bool KleopatraApplication::ignoreNewInstance() const { return d->ignoreNewInstance; } void KleopatraApplication::blockUrl(const QUrl &url) { qCDebug(KLEOPATRA_LOG) << "Blocking URL" << url; KMessageBox::error(mainWindow(), i18n("Opening an external link is administratively prohibited."), i18n("Prohibited")); } void KleopatraApplication::startGpgAgent() { Kleo::launchGpgAgent(); } void KleopatraApplication::setDistributionSettings(const std::shared_ptr &settings) { d->distroSettings = settings; } std::shared_ptr KleopatraApplication::distributionSettings() const { return d->distroSettings; } #include "kleopatraapplication.moc" #include "moc_kleopatraapplication.cpp" diff --git a/src/kwatchgnupg/aboutdata.cpp b/src/kwatchgnupg/aboutdata.cpp index 772afcc97..5b3e6f8e1 100644 --- a/src/kwatchgnupg/aboutdata.cpp +++ b/src/kwatchgnupg/aboutdata.cpp @@ -1,45 +1,45 @@ /* aboutdata.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include "aboutdata.h" #include #include #include struct about_data { const KLazyLocalizedString name; const KLazyLocalizedString desc; const char *email; const char *web; }; static const about_data authors[] = { {kli18n("Steffen Hansen"), kli18n("Original Author"), "hansen@kde.org", nullptr}, }; AboutData::AboutData() : KAboutData(QStringLiteral("kwatchgnupg"), i18n("KWatchGnuPG"), QStringLiteral(KWATCHGNUPG_VERSION_STRING), i18n("GnuPG log viewer"), KAboutLicense::GPL, i18n("(c) 2004 Klar\xC3\xA4lvdalens Datakonsult AB\n")) { using ::authors; // using ::credits; for (unsigned int i = 0; i < sizeof authors / sizeof *authors; ++i) { addAuthor(KLocalizedString(authors[i].name).toString(), KLocalizedString(authors[i].desc).toString(), - QLatin1String(authors[i].email), - QLatin1String(authors[i].web)); + QLatin1StringView(authors[i].email), + QLatin1StringView(authors[i].web)); } } diff --git a/src/kwatchgnupg/kwatchgnupg.h b/src/kwatchgnupg/kwatchgnupg.h index a861267f9..1baf1957f 100644 --- a/src/kwatchgnupg/kwatchgnupg.h +++ b/src/kwatchgnupg/kwatchgnupg.h @@ -1,15 +1,15 @@ /* This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #define WATCHGNUPGBINARY QStringLiteral("watchgnupg") -#define WATCHGNUPGSOCKET QString(Kleo::gnupgHomeDirectory() + QLatin1String("/log-socket")) +#define WATCHGNUPGSOCKET QString(Kleo::gnupgHomeDirectory() + QLatin1StringView("/log-socket")) diff --git a/src/kwatchgnupg/kwatchgnupgconfig.cpp b/src/kwatchgnupg/kwatchgnupgconfig.cpp index 4aa15f4ca..e7970c005 100644 --- a/src/kwatchgnupg/kwatchgnupgconfig.cpp +++ b/src/kwatchgnupg/kwatchgnupgconfig.cpp @@ -1,199 +1,199 @@ /* kwatchgnupgconfig.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kwatchgnupgconfig.h" #include "kwatchgnupg.h" #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *log_levels[] = {"none", "basic", "advanced", "expert", "guru"}; static int log_level_to_int(const QString &loglevel) { - if (loglevel == QLatin1String("none")) { + if (loglevel == QLatin1StringView("none")) { return 0; - } else if (loglevel == QLatin1String("basic")) { + } else if (loglevel == QLatin1StringView("basic")) { return 1; - } else if (loglevel == QLatin1String("advanced")) { + } else if (loglevel == QLatin1StringView("advanced")) { return 2; - } else if (loglevel == QLatin1String("expert")) { + } else if (loglevel == QLatin1StringView("expert")) { return 3; - } else if (loglevel == QLatin1String("guru")) { + } else if (loglevel == QLatin1StringView("guru")) { return 4; } else { // default return 1; } } KWatchGnuPGConfig::KWatchGnuPGConfig(QWidget *parent) : QDialog(parent) { setWindowTitle(i18nc("@title:window", "Configure KWatchGnuPG")); auto mainLayout = new QVBoxLayout(this); mButtonBox = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); QPushButton *okButton = mButtonBox->button(QDialogButtonBox::Ok); okButton->setDefault(true); okButton->setShortcut(Qt::CTRL | Qt::Key_Return); connect(mButtonBox, &QDialogButtonBox::rejected, this, &KWatchGnuPGConfig::reject); auto top = new QWidget; mainLayout->addWidget(top); mainLayout->addWidget(mButtonBox); auto vlay = new QVBoxLayout(top); vlay->setContentsMargins(0, 0, 0, 0); auto group = new QGroupBox(i18n("WatchGnuPG"), top); vlay->addWidget(group); auto glay = new QGridLayout(group); glay->setColumnStretch(1, 1); int row = -1; ++row; mExeED = new Kleo::FileNameRequester(group); auto label = new QLabel(i18n("&Executable:"), group); label->setBuddy(mExeED); glay->addWidget(label, row, 0); glay->addWidget(mExeED, row, 1); connect(mExeED, &Kleo::FileNameRequester::fileNameChanged, this, &KWatchGnuPGConfig::slotChanged); ++row; mSocketED = new Kleo::FileNameRequester(group); label = new QLabel(i18n("&Socket:"), group); label->setBuddy(mSocketED); glay->addWidget(label, row, 0); glay->addWidget(mSocketED, row, 1); connect(mSocketED, &Kleo::FileNameRequester::fileNameChanged, this, &KWatchGnuPGConfig::slotChanged); ++row; mLogLevelCB = new QComboBox(group); mLogLevelCB->addItem(i18n("None")); mLogLevelCB->addItem(i18n("Basic")); mLogLevelCB->addItem(i18n("Advanced")); mLogLevelCB->addItem(i18n("Expert")); mLogLevelCB->addItem(i18n("Guru")); label = new QLabel(i18n("Default &log level:"), group); label->setBuddy(mLogLevelCB); glay->addWidget(label, row, 0); glay->addWidget(mLogLevelCB, row, 1); connect(mLogLevelCB, &QComboBox::activated, this, &KWatchGnuPGConfig::slotChanged); /******************* Log Window group *******************/ group = new QGroupBox(i18n("Log Window"), top); vlay->addWidget(group); glay = new QGridLayout(group); glay->setColumnStretch(1, 1); row = -1; ++row; mLoglenSB = new KPluralHandlingSpinBox(group); mLoglenSB->setRange(0, 1000000); mLoglenSB->setSingleStep(100); mLoglenSB->setSuffix(ki18ncp("history size spinbox suffix", " line", " lines")); mLoglenSB->setSpecialValueText(i18n("unlimited")); label = new QLabel(i18n("&History size:"), group); label->setBuddy(mLoglenSB); glay->addWidget(label, row, 0); glay->addWidget(mLoglenSB, row, 1); auto button = new QPushButton(i18n("Set &Unlimited"), group); glay->addWidget(button, row, 2); connect(mLoglenSB, &QSpinBox::valueChanged, this, &KWatchGnuPGConfig::slotChanged); connect(button, &QPushButton::clicked, this, &KWatchGnuPGConfig::slotSetHistorySizeUnlimited); ++row; mWordWrapCB = new QCheckBox(i18n("Enable &word wrapping"), group); mWordWrapCB->hide(); // QTextEdit doesn't support word wrapping in LogText mode glay->addWidget(mWordWrapCB, row, 0, 1, 3); connect(mWordWrapCB, &QCheckBox::clicked, this, &KWatchGnuPGConfig::slotChanged); vlay->addStretch(1); connect(okButton, &QPushButton::clicked, this, &KWatchGnuPGConfig::slotSave); } KWatchGnuPGConfig::~KWatchGnuPGConfig() { } void KWatchGnuPGConfig::slotSetHistorySizeUnlimited() { mLoglenSB->setValue(0); } void KWatchGnuPGConfig::loadConfig() { const KConfigGroup watchGnuPG(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); mExeED->setFileName(watchGnuPG.readEntry("Executable", WATCHGNUPGBINARY)); mSocketED->setFileName(watchGnuPG.readEntry("Socket", WATCHGNUPGSOCKET)); mLogLevelCB->setCurrentIndex(log_level_to_int(watchGnuPG.readEntry("LogLevel", "basic"))); const KConfigGroup logWindow(KSharedConfig::openConfig(), QStringLiteral("LogWindow")); mLoglenSB->setValue(logWindow.readEntry("MaxLogLen", 10000)); mWordWrapCB->setChecked(logWindow.readEntry("WordWrap", false)); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } void KWatchGnuPGConfig::saveConfig() { KConfigGroup watchGnuPG(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); watchGnuPG.writeEntry("Executable", mExeED->fileName()); watchGnuPG.writeEntry("Socket", mSocketED->fileName()); watchGnuPG.writeEntry("LogLevel", log_levels[mLogLevelCB->currentIndex()]); KConfigGroup logWindow(KSharedConfig::openConfig(), QStringLiteral("LogWindow")); logWindow.writeEntry("MaxLogLen", mLoglenSB->value()); logWindow.writeEntry("WordWrap", mWordWrapCB->isChecked()); KSharedConfig::openConfig()->sync(); mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(false); } void KWatchGnuPGConfig::slotChanged() { mButtonBox->button(QDialogButtonBox::Ok)->setEnabled(true); } void KWatchGnuPGConfig::slotSave() { saveConfig(); Q_EMIT reconfigure(); accept(); } #include "moc_kwatchgnupgconfig.cpp" diff --git a/src/kwatchgnupg/kwatchgnupgmainwin.cpp b/src/kwatchgnupg/kwatchgnupgmainwin.cpp index 7bd671523..3e7f44970 100644 --- a/src/kwatchgnupg/kwatchgnupgmainwin.cpp +++ b/src/kwatchgnupg/kwatchgnupgmainwin.cpp @@ -1,267 +1,267 @@ /* kwatchgnupgmainwin.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2001, 2002, 2004 Klar �vdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kwatchgnupgmainwin.h" #include "kwatchgnupg.h" #include "kwatchgnupgconfig.h" #include "tray.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include KWatchGnuPGMainWindow::KWatchGnuPGMainWindow(QWidget *parent) : KXmlGuiWindow(parent, Qt::Window) , mConfig(nullptr) { createActions(); createGUI(); mCentralWidget = new QTextEdit(this); mCentralWidget->setReadOnly(true); setCentralWidget(mCentralWidget); mWatcher = new KProcess; connect(mWatcher, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotWatcherExited(int, QProcess::ExitStatus))); connect(mWatcher, &QProcess::readyReadStandardOutput, this, &KWatchGnuPGMainWindow::slotReadStdout); slotReadConfig(); mSysTray = new KWatchGnuPGTray(this); QAction *act = mSysTray->action(QStringLiteral("quit")); if (act) { connect(act, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotQuit); } setAutoSaveSettings(); } KWatchGnuPGMainWindow::~KWatchGnuPGMainWindow() { delete mWatcher; } void KWatchGnuPGMainWindow::slotClear() { mCentralWidget->clear(); mCentralWidget->append(i18n("[%1] Log cleared", QDateTime::currentDateTime().toString(Qt::ISODate))); } void KWatchGnuPGMainWindow::createActions() { QAction *action = actionCollection()->addAction(QStringLiteral("clear_log")); action->setIcon(QIcon::fromTheme(QStringLiteral("edit-clear-history"))); action->setText(i18n("C&lear History")); connect(action, &QAction::triggered, this, &KWatchGnuPGMainWindow::slotClear); actionCollection()->setDefaultShortcut(action, QKeySequence(Qt::CTRL | Qt::Key_L)); (void)KStandardAction::saveAs(this, &KWatchGnuPGMainWindow::slotSaveAs, actionCollection()); (void)KStandardAction::close(this, &KWatchGnuPGMainWindow::close, actionCollection()); (void)KStandardAction::quit(this, &KWatchGnuPGMainWindow::slotQuit, actionCollection()); (void)KStandardAction::preferences(this, &KWatchGnuPGMainWindow::slotConfigure, actionCollection()); (void)KStandardAction::keyBindings(this, &KWatchGnuPGMainWindow::configureShortcuts, actionCollection()); (void)KStandardAction::configureToolbars(this, &KWatchGnuPGMainWindow::slotConfigureToolbars, actionCollection()); } void KWatchGnuPGMainWindow::configureShortcuts() { KShortcutsDialog::showDialog(actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, this); } void KWatchGnuPGMainWindow::slotConfigureToolbars() { KEditToolBar dlg(factory()); dlg.exec(); } void KWatchGnuPGMainWindow::startWatcher() { disconnect(mWatcher, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotWatcherExited(int, QProcess::ExitStatus))); if (mWatcher->state() == QProcess::Running) { mWatcher->kill(); while (mWatcher->state() == QProcess::Running) { qApp->processEvents(QEventLoop::ExcludeUserInputEvents); } mCentralWidget->append(i18n("[%1] Log stopped", QDateTime::currentDateTime().toString(Qt::ISODate))); mCentralWidget->ensureCursorVisible(); } mWatcher->clearProgram(); { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); *mWatcher << config.readEntry("Executable", WATCHGNUPGBINARY); *mWatcher << QStringLiteral("--force"); *mWatcher << config.readEntry("Socket", WATCHGNUPGSOCKET); } mWatcher->setOutputChannelMode(KProcess::OnlyStdoutChannel); mWatcher->start(); const bool ok = mWatcher->waitForStarted(); if (!ok) { KMessageBox::error(this, i18n("The watchgnupg logging process could not be started.\nPlease install watchgnupg somewhere in your $PATH.\nThis log window is " "unable to display any useful information.")); } else { mCentralWidget->append(i18n("[%1] Log started", QDateTime::currentDateTime().toString(Qt::ISODate))); mCentralWidget->ensureCursorVisible(); } connect(mWatcher, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotWatcherExited(int, QProcess::ExitStatus))); } void KWatchGnuPGMainWindow::setGnuPGConfig() { QStringList logclients; // Get config object QGpgME::CryptoConfig *const cconfig = QGpgME::cryptoConfig(); if (!cconfig) { return; } KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("WatchGnuPG")); const QStringList comps = cconfig->componentList(); for (QStringList::const_iterator it = comps.constBegin(); it != comps.constEnd(); ++it) { const QGpgME::CryptoConfigComponent *const comp = cconfig->component(*it); Q_ASSERT(comp); { QGpgME::CryptoConfigEntry *const entry = cconfig->entry(comp->name(), QStringLiteral("log-file")); if (entry) { - entry->setStringValue(QLatin1String("socket://") + config.readEntry("Socket", WATCHGNUPGSOCKET)); + entry->setStringValue(QLatin1StringView("socket://") + config.readEntry("Socket", WATCHGNUPGSOCKET)); logclients << QStringLiteral("%1 (%2)").arg(*it, comp->description()); } } { QGpgME::CryptoConfigEntry *const entry = cconfig->entry(comp->name(), QStringLiteral("debug-level")); if (entry) { entry->setStringValue(config.readEntry("LogLevel", "basic")); } } } cconfig->sync(true); if (logclients.isEmpty()) { KMessageBox::error(nullptr, i18n("There are no components available that support logging.")); } } void KWatchGnuPGMainWindow::slotWatcherExited(int, QProcess::ExitStatus) { if (KMessageBox::questionTwoActions(this, i18n("The watchgnupg logging process died.\nDo you want to try to restart it?"), QString(), KGuiItem(i18n("Try Restart")), KGuiItem(i18n("Do Not Try"))) == KMessageBox::ButtonCode::PrimaryAction) { mCentralWidget->append(i18n("====== Restarting logging process =====")); mCentralWidget->ensureCursorVisible(); startWatcher(); } else { KMessageBox::error(this, i18n("The watchgnupg logging process is not running.\nThis log window is unable to display any useful information.")); } } void KWatchGnuPGMainWindow::slotReadStdout() { if (!mWatcher) { return; } while (mWatcher->canReadLine()) { QString str = QString::fromUtf8(mWatcher->readLine()); if (str.endsWith(QLatin1Char('\n'))) { str.chop(1); } if (str.endsWith(QLatin1Char('\r'))) { str.chop(1); } mCentralWidget->append(str); mCentralWidget->ensureCursorVisible(); if (!isVisible()) { // Change tray icon to show something happened // PENDING(steffen) mSysTray->setAttention(true); } } } void KWatchGnuPGMainWindow::show() { mSysTray->setAttention(false); KMainWindow::show(); } void KWatchGnuPGMainWindow::slotSaveAs() { const QString filename = QFileDialog::getSaveFileName(this, i18n("Save Log to File")); if (filename.isEmpty()) { return; } QFile file(filename); if (file.open(QIODevice::WriteOnly)) { QTextStream(&file) << mCentralWidget->document()->toRawText(); } else KMessageBox::information(this, i18n("Could not save file %1: %2", filename, file.errorString())); } void KWatchGnuPGMainWindow::slotQuit() { disconnect(mWatcher, SIGNAL(finished(int, QProcess::ExitStatus)), this, SLOT(slotWatcherExited(int, QProcess::ExitStatus))); mWatcher->kill(); qApp->quit(); } void KWatchGnuPGMainWindow::slotConfigure() { if (!mConfig) { mConfig = new KWatchGnuPGConfig(this); mConfig->setObjectName(QLatin1StringView("config dialog")); connect(mConfig, &KWatchGnuPGConfig::reconfigure, this, &KWatchGnuPGMainWindow::slotReadConfig); } mConfig->loadConfig(); mConfig->exec(); } void KWatchGnuPGMainWindow::slotReadConfig() { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("LogWindow")); const int maxLogLen = config.readEntry("MaxLogLen", 10000); mCentralWidget->document()->setMaximumBlockCount(maxLogLen < 1 ? -1 : maxLogLen); setGnuPGConfig(); startWatcher(); } bool KWatchGnuPGMainWindow::queryClose() { if (!qApp->isSavingSession()) { hide(); return false; } return KMainWindow::queryClose(); } #include "moc_kwatchgnupgmainwin.cpp" diff --git a/src/mainwindow.cpp b/src/mainwindow.cpp index ed737201f..2b9ac575a 100644 --- a/src/mainwindow.cpp +++ b/src/mainwindow.cpp @@ -1,918 +1,918 @@ /* -*- mode: c++; c-basic-offset:4 -*- mainwindow.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 "aboutdata.h" #include "kleopatraapplication.h" #include "mainwindow.h" #include "settings.h" #include #include "view/keycacheoverlay.h" #include "view/keylistcontroller.h" #include "view/padwidget.h" #include "view/searchbar.h" #include "view/smartcardwidget.h" #include "view/tabwidget.h" #include "view/welcomewidget.h" #include "commands/decryptverifyfilescommand.h" #include "commands/importcertificatefromfilecommand.h" #include "commands/importcrlcommand.h" #include "commands/selftestcommand.h" #include "commands/signencryptfilescommand.h" #include "utils/action_data.h" #include "utils/clipboardmenu.h" #include "utils/detail_p.h" #include "utils/filedialog.h" #include "utils/gui-helper.h" #include "utils/keyexportdraghandler.h" #include #include "dialogs/updatenotification.h" // needed for GPGME_VERSION_NUMBER #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_UNIX #include #endif #include #include using namespace std::chrono_literals; using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; static KGuiItem KStandardGuiItem_quit() { static const QString app = KAboutData::applicationData().displayName(); KGuiItem item = KStandardGuiItem::quit(); item.setText(xi18nc("@action:button", "&Quit %1", app)); return item; } static KGuiItem KStandardGuiItem_close() { KGuiItem item = KStandardGuiItem::close(); item.setText(i18nc("@action:button", "Only &Close Window")); return item; } static bool isQuitting = false; namespace { static const std::vector mainViewActionNames = { QStringLiteral("view_certificate_overview"), QStringLiteral("manage_smartcard"), QStringLiteral("pad_view"), }; class CertificateView : public QWidget, public FocusFirstChild { Q_OBJECT public: CertificateView(QWidget *parent = nullptr) : QWidget{parent} , ui{this} { } SearchBar *searchBar() const { return ui.searchBar; } TabWidget *tabWidget() const { return ui.tabWidget; } void focusFirstChild(Qt::FocusReason reason) override { ui.searchBar->lineEdit()->setFocus(reason); } private: struct UI { TabWidget *tabWidget = nullptr; SearchBar *searchBar = nullptr; explicit UI(CertificateView *q) { auto vbox = new QVBoxLayout{q}; vbox->setSpacing(0); searchBar = new SearchBar{q}; vbox->addWidget(searchBar); tabWidget = new TabWidget{q}; vbox->addWidget(tabWidget); tabWidget->connectSearchBar(searchBar); } } ui; }; } class MainWindow::Private { friend class ::MainWindow; MainWindow *const q; public: explicit Private(MainWindow *qq); ~Private(); template void createAndStart() { (new T(this->currentView(), &this->controller))->start(); } template void createAndStart(QAbstractItemView *view) { (new T(view, &this->controller))->start(); } template void createAndStart(const QStringList &a) { (new T(a, this->currentView(), &this->controller))->start(); } template void createAndStart(const QStringList &a, QAbstractItemView *view) { (new T(a, view, &this->controller))->start(); } void closeAndQuit() { const QString app = KAboutData::applicationData().displayName(); const int rc = KMessageBox::questionTwoActionsCancel(q, xi18n("%1 may be used by other applications as a service." "You may instead want to close this window without exiting %1.", app), i18nc("@title:window", "Really Quit?"), KStandardGuiItem_close(), KStandardGuiItem_quit(), KStandardGuiItem::cancel(), - QLatin1String("really-quit-") + app.toLower()); + QLatin1StringView("really-quit-") + app.toLower()); if (rc == KMessageBox::Cancel) { return; } isQuitting = true; if (!q->close()) { return; } // WARNING: 'this' might be deleted at this point! if (rc == KMessageBox::ButtonCode::SecondaryAction) { qApp->quit(); } } void configureToolbars() { KEditToolBar dlg(q->factory()); dlg.exec(); } void editKeybindings() { KShortcutsDialog::showDialog(q->actionCollection(), KShortcutsEditor::LetterShortcutsAllowed, q); updateSearchBarClickMessage(); } void updateSearchBarClickMessage() { const QString shortcutStr = focusToClickSearchAction->shortcut().toString(); ui.searchTab->searchBar()->updateClickMessage(shortcutStr); } void updateStatusBar() { auto statusBar = std::make_unique(); auto settings = KleopatraApplication::instance()->distributionSettings(); bool showStatusbar = false; if (settings) { const QString statusline = settings->value(QStringLiteral("statusline"), {}).toString(); if (!statusline.isEmpty()) { auto customStatusLbl = new QLabel(statusline); statusBar->insertWidget(0, customStatusLbl); showStatusbar = true; } } if (DeVSCompliance::isActive()) { auto statusLbl = std::make_unique(DeVSCompliance::name()); if (!SystemInfo::isHighContrastModeActive()) { const auto color = KColorScheme(QPalette::Active, KColorScheme::View) .foreground(DeVSCompliance::isCompliant() ? KColorScheme::NormalText : KColorScheme::NegativeText) .color(); const auto background = KColorScheme(QPalette::Active, KColorScheme::View) .background(DeVSCompliance::isCompliant() ? KColorScheme::PositiveBackground : KColorScheme::NegativeBackground) .color(); statusLbl->setStyleSheet(QStringLiteral("QLabel { color: %1; background-color: %2; }").arg(color.name()).arg(background.name())); } statusBar->insertPermanentWidget(0, statusLbl.release()); showStatusbar = true; } if (showStatusbar) { q->setStatusBar(statusBar.release()); // QMainWindow takes ownership } else { q->setStatusBar(nullptr); } } void selfTest() { createAndStart(); } void configureGroups() { // open groups config dialog as independent top-level window KleopatraApplication::instance()->openOrRaiseGroupsConfigDialog(nullptr); } void showHandbook(); void gnupgLogViewer() { // Warning: Don't assume that the program needs to be in PATH. On Windows, it will also be found next to the calling process. if (!QProcess::startDetached(QStringLiteral("kwatchgnupg"), QStringList())) KMessageBox::error(q, i18n("Could not start the GnuPG Log Viewer (kwatchgnupg). " "Please check your installation."), i18n("Error Starting KWatchGnuPG")); } void forceUpdateCheck() { UpdateNotification::forceUpdateCheck(q); } void slotConfigCommitted(); void slotContextMenuRequested(QAbstractItemView *, const QPoint &p) { if (auto const menu = qobject_cast(q->factory()->container(QStringLiteral("listview_popup"), q))) { menu->exec(p); } else { qCDebug(KLEOPATRA_LOG) << "no \"listview_popup\" in kleopatra's ui.rc file"; } } void slotFocusQuickSearch() { ui.searchTab->searchBar()->lineEdit()->setFocus(); } void showView(const QString &actionName, QWidget *widget) { const auto coll = q->actionCollection(); if (coll) { for (const QString &name : mainViewActionNames) { if (auto action = coll->action(name)) { action->setChecked(name == actionName); } } } ui.stackWidget->setCurrentWidget(widget); if (auto ffci = dynamic_cast(widget)) { ffci->focusFirstChild(Qt::TabFocusReason); } } void showCertificateView() { if (KeyCache::instance()->keys().empty()) { showView(QStringLiteral("view_certificate_overview"), ui.welcomeWidget); } else { showView(QStringLiteral("view_certificate_overview"), ui.searchTab); } } void showSmartcardView() { showView(QStringLiteral("manage_smartcard"), ui.scWidget); } void showPadView() { if (!ui.padWidget) { ui.padWidget = new PadWidget; ui.stackWidget->addWidget(ui.padWidget); } showView(QStringLiteral("pad_view"), ui.padWidget); ui.stackWidget->resize(ui.padWidget->sizeHint()); } void restartDaemons() { Kleo::killDaemons(); } private: void setupActions(); QAbstractItemView *currentView() const { return ui.searchTab->tabWidget()->currentView(); } void keyListingDone() { const auto curWidget = ui.stackWidget->currentWidget(); if (curWidget == ui.scWidget || curWidget == ui.padWidget) { return; } showCertificateView(); } private: Kleo::KeyListController controller; bool firstShow : 1; struct UI { CertificateView *searchTab = nullptr; PadWidget *padWidget = nullptr; SmartCardWidget *scWidget = nullptr; WelcomeWidget *welcomeWidget = nullptr; QStackedWidget *stackWidget = nullptr; explicit UI(MainWindow *q); } ui; QAction *focusToClickSearchAction = nullptr; ClipboardMenu *clipboadMenu = nullptr; }; MainWindow::Private::UI::UI(MainWindow *q) : padWidget(nullptr) { auto mainWidget = new QWidget{q}; auto mainLayout = new QVBoxLayout(mainWidget); mainLayout->setContentsMargins({}); stackWidget = new QStackedWidget{q}; searchTab = new CertificateView{q}; stackWidget->addWidget(searchTab); new KeyCacheOverlay(mainWidget, q); scWidget = new SmartCardWidget{q}; stackWidget->addWidget(scWidget); welcomeWidget = new WelcomeWidget{q}; stackWidget->addWidget(welcomeWidget); mainLayout->addWidget(stackWidget); q->setCentralWidget(mainWidget); } MainWindow::Private::Private(MainWindow *qq) : q(qq) , controller(q) , firstShow(true) , ui(q) { KDAB_SET_OBJECT_NAME(controller); AbstractKeyListModel *flatModel = AbstractKeyListModel::createFlatKeyListModel(q); AbstractKeyListModel *hierarchicalModel = AbstractKeyListModel::createHierarchicalKeyListModel(q); KDAB_SET_OBJECT_NAME(flatModel); KDAB_SET_OBJECT_NAME(hierarchicalModel); #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0 auto keyExportDragHandler = std::make_shared(); flatModel->setDragHandler(keyExportDragHandler); hierarchicalModel->setDragHandler(keyExportDragHandler); #endif controller.setFlatModel(flatModel); controller.setHierarchicalModel(hierarchicalModel); controller.setTabWidget(ui.searchTab->tabWidget()); ui.searchTab->tabWidget()->setFlatModel(flatModel); ui.searchTab->tabWidget()->setHierarchicalModel(hierarchicalModel); #ifdef Q_OS_UNIX connect(KWaylandExtras::self(), &KWaylandExtras::windowExported, q, [this](const auto &window, const auto &token) { if (window == q->windowHandle()) { qputenv("PINENTRY_GEOM_HINT", QUrl::toPercentEncoding(token)); } }); q->exportWindow(); #endif setupActions(); ui.stackWidget->setCurrentWidget(ui.searchTab); if (auto action = q->actionCollection()->action(QStringLiteral("view_certificate_overview"))) { action->setChecked(true); } connect(&controller, SIGNAL(contextMenuRequested(QAbstractItemView *, QPoint)), q, SLOT(slotContextMenuRequested(QAbstractItemView *, QPoint))); connect(KeyCache::instance().get(), &KeyCache::keyListingDone, q, [this]() { keyListingDone(); }); q->createGUI(QStringLiteral("kleopatra.rc")); // make toolbar buttons accessible by keyboard auto toolbar = q->findChild(); if (toolbar) { auto toolbarButtons = toolbar->findChildren(); for (auto b : toolbarButtons) { b->setFocusPolicy(Qt::TabFocus); } // move toolbar and its child widgets before the central widget in the tab order; // this is necessary to make Shift+Tab work as expected forceSetTabOrder(q, toolbar); auto toolbarChildren = toolbar->findChildren(); std::for_each(std::rbegin(toolbarChildren), std::rend(toolbarChildren), [toolbar](auto w) { forceSetTabOrder(toolbar, w); }); } if (auto action = q->actionCollection()->action(QStringLiteral("help_whats_this"))) { delete action; } q->setAcceptDrops(true); // set default window size q->resize(QSize(1024, 500)); q->setAutoSaveSettings(); updateSearchBarClickMessage(); updateStatusBar(); if (KeyCache::instance()->initialized()) { keyListingDone(); } // delay setting the models to use the key cache so that the UI (including // the "Loading certificate cache..." overlay) is shown before the // blocking key cache initialization happens QMetaObject::invokeMethod( q, [flatModel, hierarchicalModel]() { flatModel->useKeyCache(true, KeyList::AllKeys); hierarchicalModel->useKeyCache(true, KeyList::AllKeys); }, Qt::QueuedConnection); } MainWindow::Private::~Private() { } MainWindow::MainWindow(QWidget *parent, Qt::WindowFlags flags) : KXmlGuiWindow(parent, flags) , d(new Private(this)) { } MainWindow::~MainWindow() { } void MainWindow::Private::setupActions() { KActionCollection *const coll = q->actionCollection(); const std::vector action_data = { // see keylistcontroller.cpp for more actions // Tools menu #ifndef Q_OS_WIN { "tools_start_kwatchgnupg", i18n("GnuPG Log Viewer"), QString(), "kwatchgnupg", q, [this](bool) { gnupgLogViewer(); }, QString(), }, #endif { "tools_restart_backend", i18nc("@action:inmenu", "Restart Background Processes"), i18nc("@info:tooltip", "Restart the background processes, e.g. after making changes to the configuration."), "view-refresh", q, [this](bool) { restartDaemons(); }, {}, }, // Help menu #ifdef Q_OS_WIN { "help_check_updates", i18n("Check for updates"), QString(), "gpg4win-compact", q, [this](bool) { forceUpdateCheck(); }, QString(), }, #endif // View menu { "view_certificate_overview", i18nc("@action show certificate overview", "Certificates"), i18n("Show certificate overview"), "view-certificate", q, [this](bool) { showCertificateView(); }, QString(), }, { "pad_view", i18nc("@action show input / output area for encrypting/signing resp. decrypting/verifying text", "Notepad"), i18n("Show pad for encrypting/decrypting and signing/verifying text"), "note", q, [this](bool) { showPadView(); }, QString(), }, { "manage_smartcard", i18nc("@action show smartcard management view", "Smartcards"), i18n("Show smartcard management"), "auth-sim-locked", q, [this](bool) { showSmartcardView(); }, QString(), }, // Settings menu { "settings_self_test", i18n("Perform Self-Test"), QString(), nullptr, q, [this](bool) { selfTest(); }, QString(), }, { "configure_groups", i18n("Configure Groups..."), QString(), "group", q, [this](bool) { configureGroups(); }, QString(), }}; make_actions_from_data(action_data, coll); if (!Settings().groupsEnabled()) { if (auto action = coll->action(QStringLiteral("configure_groups"))) { delete action; } } for (const QString &name : mainViewActionNames) { if (auto action = coll->action(name)) { action->setCheckable(true); } } KStandardAction::close(q, SLOT(close()), coll); KStandardAction::quit(q, SLOT(closeAndQuit()), coll); KStandardAction::configureToolbars(q, SLOT(configureToolbars()), coll); KStandardAction::keyBindings(q, SLOT(editKeybindings()), coll); KStandardAction::preferences(qApp, SLOT(openOrRaiseConfigDialog()), coll); focusToClickSearchAction = new QAction(i18n("Set Focus to Quick Search"), q); coll->addAction(QStringLiteral("focus_to_quickseach"), focusToClickSearchAction); coll->setDefaultShortcut(focusToClickSearchAction, QKeySequence(Qt::ALT | Qt::Key_Q)); connect(focusToClickSearchAction, SIGNAL(triggered(bool)), q, SLOT(slotFocusQuickSearch())); clipboadMenu = new ClipboardMenu(q); clipboadMenu->setMainWindow(q); clipboadMenu->clipboardMenu()->setIcon(QIcon::fromTheme(QStringLiteral("edit-paste"))); clipboadMenu->clipboardMenu()->setPopupMode(QToolButton::InstantPopup); coll->addAction(QStringLiteral("clipboard_menu"), clipboadMenu->clipboardMenu()); /* Add additional help actions for documentation */ const auto compendium = new DocAction(QIcon::fromTheme(QStringLiteral("gpg4win-compact")), i18n("Gpg4win Compendium"), i18nc("The Gpg4win compendium is only available" "at this point (24.7.2017) in german and english." "Please check with Gpg4win before translating this filename.", "gpg4win-compendium-en.pdf"), QStringLiteral("../share/gpg4win"), coll); coll->addAction(QStringLiteral("help_doc_compendium"), compendium); /* Documentation centered around the german approved VS-NfD mode for official * RESTRICTED communication. This is only available in some distributions with * the focus on official communications. */ const auto quickguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Quickguide"), i18nc("Only available in German and English. Leave to English for other languages.", "encrypt_and_sign_gnupgvsd_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_quickguide"), quickguide); const auto symguide = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Password-based encryption"), i18nc("Only available in German and English. Leave to English for other languages.", "symmetric_encryption_gnupgvsd_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_symenc"), symguide); const auto groups = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Group configuration"), i18nc("Only available in German and English. Leave to English for other languages.", "groupfeature_gnupgvsd_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_groups"), groups); #ifdef Q_OS_WIN const auto gpgol = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Mail encryption in Outlook"), i18nc("Only available in German and English. Leave to English for other languages. Only shown on Windows.", "gpgol_outlook_addin_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_gpgol"), gpgol); #endif /* The submenu with advanced topics */ const auto certmngmnt = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Certification Management"), i18nc("Only available in German and English. Leave to English for other languages.", "certification_management_gnupgvsd_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_cert_management"), certmngmnt); const auto smartcard = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("&Smartcard setup"), i18nc("Only available in German and English. Leave to English for other languages.", "smartcard_setup_gnupgvsd_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_smartcard"), smartcard); const auto man_gnupg = new DocAction(QIcon::fromTheme(QStringLiteral("help-contextual")), i18n("GnuPG Command&line"), QStringLiteral("gnupg_manual_en.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_gnupg"), man_gnupg); /* The secops */ const auto vsa10573 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10573"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10573-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_vsa10573"), vsa10573); const auto vsa10584 = new DocAction(QIcon::fromTheme(QStringLiteral("dvipdf")), i18n("SecOps VSA-10584"), i18nc("Only available in German and English. Leave to English for other languages.", "BSI-VSA-10584-ENG_secops-20220207.pdf"), QStringLiteral("../share/doc/gnupg-vsd"), coll); coll->addAction(QStringLiteral("help_doc_vsa10584"), vsa10584); q->setStandardToolBarMenuEnabled(true); controller.createActions(coll); ui.searchTab->tabWidget()->createActions(coll); } void MainWindow::Private::slotConfigCommitted() { controller.updateConfig(); updateStatusBar(); } void MainWindow::closeEvent(QCloseEvent *e) { // KMainWindow::closeEvent() insists on quitting the application, // so do not let it touch the event... qCDebug(KLEOPATRA_LOG); if (d->controller.hasRunningCommands()) { if (d->controller.shutdownWarningRequired()) { const int ret = KMessageBox::warningContinueCancel(this, i18n("There are still some background operations ongoing. " "These will be terminated when closing the window. " "Proceed?"), i18n("Ongoing Background Tasks")); if (ret != KMessageBox::Continue) { e->ignore(); return; } } d->controller.cancelCommands(); if (d->controller.hasRunningCommands()) { // wait for them to be finished: setEnabled(false); QEventLoop ev; QTimer::singleShot(100ms, &ev, &QEventLoop::quit); connect(&d->controller, &KeyListController::commandsExecuting, &ev, &QEventLoop::quit); ev.exec(); if (d->controller.hasRunningCommands()) qCWarning(KLEOPATRA_LOG) << "controller still has commands running, this may crash now..."; setEnabled(true); } } unexportWindow(); if (isQuitting || qApp->isSavingSession()) { d->ui.searchTab->tabWidget()->saveViews(KSharedConfig::openConfig().data()); KConfigGroup grp(KConfigGroup(KSharedConfig::openConfig(), autoSaveGroup())); saveMainWindowSettings(grp); e->accept(); } else { e->ignore(); hide(); } } void MainWindow::showEvent(QShowEvent *e) { KXmlGuiWindow::showEvent(e); if (d->firstShow) { d->ui.searchTab->tabWidget()->loadViews(KSharedConfig::openConfig().data()); d->firstShow = false; } if (!savedGeometry.isEmpty()) { restoreGeometry(savedGeometry); } } void MainWindow::hideEvent(QHideEvent *e) { savedGeometry = saveGeometry(); KXmlGuiWindow::hideEvent(e); } void MainWindow::importCertificatesFromFile(const QStringList &files) { if (!files.empty()) { d->createAndStart(files); } } static QStringList extract_local_files(const QMimeData *data) { const QList urls = data->urls(); // begin workaround KDE/Qt misinterpretation of text/uri-list QList::const_iterator end = urls.end(); if (urls.size() > 1 && !urls.back().isValid()) { --end; } // end workaround QStringList result; std::transform(urls.begin(), end, std::back_inserter(result), std::mem_fn(&QUrl::toLocalFile)); result.erase(std::remove_if(result.begin(), result.end(), std::mem_fn(&QString::isEmpty)), result.end()); return result; } static bool can_decode_local_files(const QMimeData *data) { if (!data) { return false; } return !extract_local_files(data).empty(); } void MainWindow::dragEnterEvent(QDragEnterEvent *e) { qCDebug(KLEOPATRA_LOG); if (can_decode_local_files(e->mimeData())) { e->acceptProposedAction(); } } void MainWindow::dropEvent(QDropEvent *e) { qCDebug(KLEOPATRA_LOG); if (!can_decode_local_files(e->mimeData())) { return; } e->setDropAction(Qt::CopyAction); const QStringList files = extract_local_files(e->mimeData()); KleopatraApplication::instance()->handleFiles(files); e->accept(); } void MainWindow::readProperties(const KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::readProperties(cg); setHidden(cg.readEntry("hidden", false)); } void MainWindow::saveProperties(KConfigGroup &cg) { qCDebug(KLEOPATRA_LOG); KXmlGuiWindow::saveProperties(cg); cg.writeEntry("hidden", isHidden()); } void MainWindow::exportWindow() { #ifdef Q_OS_UNIX (void)winId(); // Ensures that windowHandle() returns the window KWaylandExtras::self()->exportWindow(windowHandle()); #endif } void MainWindow::unexportWindow() { #ifdef Q_OS_UNIX KWaylandExtras::self()->unexportWindow(windowHandle()); #endif } #include "mainwindow.moc" #include "moc_mainwindow.cpp" diff --git a/src/newcertificatewizard/advancedsettingsdialog.cpp b/src/newcertificatewizard/advancedsettingsdialog.cpp index 0e42f2e70..7c69932b5 100644 --- a/src/newcertificatewizard/advancedsettingsdialog.cpp +++ b/src/newcertificatewizard/advancedsettingsdialog.cpp @@ -1,1017 +1,1017 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/advancedsettingsdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "advancedsettingsdialog_p.h" #include "keyalgo_p.h" #include "listwidget.h" #include "utils/expiration.h" #include "utils/gui-helper.h" #include "utils/scrollarea.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; static const char RSA_KEYSIZES_ENTRY[] = "RSAKeySizes"; static const char DSA_KEYSIZES_ENTRY[] = "DSAKeySizes"; static const char ELG_KEYSIZES_ENTRY[] = "ELGKeySizes"; static const char RSA_KEYSIZE_LABELS_ENTRY[] = "RSAKeySizeLabels"; static const char DSA_KEYSIZE_LABELS_ENTRY[] = "DSAKeySizeLabels"; static const char ELG_KEYSIZE_LABELS_ENTRY[] = "ELGKeySizeLabels"; static const char PGP_KEY_TYPE_ENTRY[] = "PGPKeyType"; static const char CMS_KEY_TYPE_ENTRY[] = "CMSKeyType"; // This should come from gpgme in the future // For now we only support the basic 2.1 curves and check // for GnuPG 2.1. The whole subkey / usage generation needs // new api and a reworked dialog. (ah 10.3.16) // EDDSA should be supported, too. static const QStringList curveNames{ {QStringLiteral("brainpoolP256r1")}, {QStringLiteral("brainpoolP384r1")}, {QStringLiteral("brainpoolP512r1")}, {QStringLiteral("NIST P-256")}, {QStringLiteral("NIST P-384")}, {QStringLiteral("NIST P-521")}, }; namespace { static void set_keysize(QComboBox *cb, unsigned int strength) { if (!cb) { return; } const int idx = cb->findData(static_cast(strength)); if (idx >= 0) { cb->setCurrentIndex(idx); } } static unsigned int get_keysize(const QComboBox *cb) { if (!cb) { return 0; } const int idx = cb->currentIndex(); if (idx < 0) { return 0; } return cb->itemData(idx).toInt(); } static void set_curve(QComboBox *cb, const QString &curve) { if (!cb) { return; } const int idx = cb->findText(curve, Qt::MatchFixedString); if (idx >= 0) { cb->setCurrentIndex(idx); } } static QString get_curve(const QComboBox *cb) { if (!cb) { return QString(); } return cb->currentText(); } // Extract the algo information from default_pubkey_algo format // // and put it into the return values size, algo and curve. // // Values look like: // RSA-2048 // rsa2048/cert,sign+rsa2048/enc // brainpoolP256r1+brainpoolP256r1 static void parseAlgoString(const QString &algoString, int *size, Subkey::PubkeyAlgo *algo, QString &curve) { const auto split = algoString.split(QLatin1Char('/')); - bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1String("enc")); + bool isEncrypt = split.size() == 2 && split[1].contains(QLatin1StringView("enc")); // Normalize const auto lowered = split[0].toLower().remove(QLatin1Char('-')); if (!algo || !size) { return; } *algo = Subkey::AlgoUnknown; - if (lowered.startsWith(QLatin1String("rsa"))) { + if (lowered.startsWith(QLatin1StringView("rsa"))) { *algo = Subkey::AlgoRSA; - } else if (lowered.startsWith(QLatin1String("dsa"))) { + } else if (lowered.startsWith(QLatin1StringView("dsa"))) { *algo = Subkey::AlgoDSA; - } else if (lowered.startsWith(QLatin1String("elg"))) { + } else if (lowered.startsWith(QLatin1StringView("elg"))) { *algo = Subkey::AlgoELG; } if (*algo != Subkey::AlgoUnknown) { bool ok; *size = QStringView(lowered).right(lowered.size() - 3).toInt(&ok); if (!ok) { qCWarning(KLEOPATRA_LOG) << "Could not extract size from: " << lowered; *size = 3072; } return; } // Now the ECC Algorithms - if (lowered.startsWith(QLatin1String("ed25519"))) { + if (lowered.startsWith(QLatin1StringView("ed25519"))) { // Special handling for this as technically // this is a cv25519 curve used for EDDSA if (isEncrypt) { - curve = QLatin1String("cv25519"); + curve = QLatin1StringView("cv25519"); *algo = Subkey::AlgoECDH; } else { curve = split[0]; *algo = Subkey::AlgoEDDSA; } return; } - if (lowered.startsWith(QLatin1String("cv25519")) // - || lowered.startsWith(QLatin1String("nist")) // - || lowered.startsWith(QLatin1String("brainpool")) // - || lowered.startsWith(QLatin1String("secp"))) { + if (lowered.startsWith(QLatin1StringView("cv25519")) // + || lowered.startsWith(QLatin1StringView("nist")) // + || lowered.startsWith(QLatin1StringView("brainpool")) // + || lowered.startsWith(QLatin1StringView("secp"))) { curve = split[0]; *algo = isEncrypt ? Subkey::AlgoECDH : Subkey::AlgoECDSA; return; } qCWarning(KLEOPATRA_LOG) << "Failed to parse default_pubkey_algo:" << algoString; } } struct AdvancedSettingsDialog::UI { QTabWidget *tabWidget = nullptr; QRadioButton *rsaRB = nullptr; QComboBox *rsaKeyStrengthCB = nullptr; QCheckBox *rsaSubCB = nullptr; QComboBox *rsaKeyStrengthSubCB = nullptr; QRadioButton *dsaRB = nullptr; QComboBox *dsaKeyStrengthCB = nullptr; QCheckBox *elgCB = nullptr; QComboBox *elgKeyStrengthCB = nullptr; QRadioButton *ecdsaRB = nullptr; QComboBox *ecdsaKeyCurvesCB = nullptr; QCheckBox *ecdhCB = nullptr; QComboBox *ecdhKeyCurvesCB = nullptr; QCheckBox *certificationCB = nullptr; QCheckBox *signingCB = nullptr; QCheckBox *encryptionCB = nullptr; QCheckBox *authenticationCB = nullptr; QCheckBox *expiryCB = nullptr; KDateComboBox *expiryDE = nullptr; ScrollArea *personalTab = nullptr; QGroupBox *uidGB = nullptr; Kleo::NewCertificateUi::ListWidget *uidLW = nullptr; QGroupBox *emailGB = nullptr; Kleo::NewCertificateUi::ListWidget *emailLW = nullptr; QGroupBox *dnsGB = nullptr; Kleo::NewCertificateUi::ListWidget *dnsLW = nullptr; QGroupBox *uriGB = nullptr; Kleo::NewCertificateUi::ListWidget *uriLW = nullptr; QDialogButtonBox *buttonBox = nullptr; UI(QDialog *parent) { parent->setWindowTitle(i18nc("@title:window", "Advanced Settings")); auto mainLayout = new QVBoxLayout{parent}; tabWidget = new QTabWidget{parent}; { auto technicalTab = new ScrollArea{tabWidget}; technicalTab->setFocusPolicy(Qt::NoFocus); technicalTab->setFrameStyle(QFrame::NoFrame); technicalTab->setBackgroundRole(parent->backgroundRole()); technicalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); technicalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto tabLayout = qobject_cast(technicalTab->widget()->layout()); { auto groupBox = new QGroupBox{i18nc("@title:group", "Key Material"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; rsaRB = new QRadioButton{i18nc("@option:radio", "RSA"), groupBox}; rsaRB->setChecked(false); groupBoxGrid->addWidget(rsaRB, row, 0, 1, 2); rsaKeyStrengthCB = new QComboBox{groupBox}; rsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthCB, row, 2, 1, 1); row++; auto subKeyIndentation = new QSpacerItem(13, 13, QSizePolicy::Fixed, QSizePolicy::Minimum); groupBoxGrid->addItem(subKeyIndentation, row, 0, 1, 1); rsaSubCB = new QCheckBox{i18nc("@option:check", "+ RSA"), groupBox}; rsaSubCB->setEnabled(true); groupBoxGrid->addWidget(rsaSubCB, row, 1, 1, 1); rsaKeyStrengthSubCB = new QComboBox{groupBox}; rsaKeyStrengthSubCB->setEnabled(false); groupBoxGrid->addWidget(rsaKeyStrengthSubCB, row, 2, 1, 1); row++; dsaRB = new QRadioButton{i18nc("@option:radio", "DSA"), groupBox}; groupBoxGrid->addWidget(dsaRB, row, 0, 1, 2); dsaKeyStrengthCB = new QComboBox{groupBox}; dsaKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(dsaKeyStrengthCB, row, 2, 1, 1); row++; elgCB = new QCheckBox{i18nc("@option:check", "+ Elgamal"), groupBox}; elgCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); elgCB->setEnabled(true); groupBoxGrid->addWidget(elgCB, row, 1, 1, 1); elgKeyStrengthCB = new QComboBox{groupBox}; elgKeyStrengthCB->setEnabled(false); groupBoxGrid->addWidget(elgKeyStrengthCB, row, 2, 1, 1); row++; ecdsaRB = new QRadioButton{i18nc("@option:radio", "ECDSA"), groupBox}; groupBoxGrid->addWidget(ecdsaRB, row, 0, 1, 2); ecdsaKeyCurvesCB = new QComboBox{groupBox}; ecdsaKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdsaKeyCurvesCB, row, 2, 1, 1); row++; ecdhCB = new QCheckBox{i18nc("@option:check", "+ ECDH"), groupBox}; ecdhCB->setToolTip(i18nc("@info:tooltip", "This subkey is required for encryption.")); ecdhCB->setEnabled(true); groupBoxGrid->addWidget(ecdhCB, row, 1, 1, 1); ecdhKeyCurvesCB = new QComboBox{groupBox}; ecdhKeyCurvesCB->setEnabled(false); groupBoxGrid->addWidget(ecdhKeyCurvesCB, row, 2, 1, 1); groupBoxGrid->setColumnStretch(3, 1); tabLayout->addWidget(groupBox); } { auto groupBox = new QGroupBox{i18nc("@title:group", "Certificate Usage"), technicalTab}; auto groupBoxGrid = new QGridLayout{groupBox}; int row = 0; signingCB = new QCheckBox{i18nc("@option:check", "Signing"), groupBox}; signingCB->setChecked(true); groupBoxGrid->addWidget(signingCB, row, 0, 1, 1); certificationCB = new QCheckBox{i18nc("@option:check", "Certification"), groupBox}; groupBoxGrid->addWidget(certificationCB, row, 1, 1, 1); row++; encryptionCB = new QCheckBox{i18nc("@option:check", "Encryption"), groupBox}; encryptionCB->setChecked(true); groupBoxGrid->addWidget(encryptionCB, row, 0, 1, 1); authenticationCB = new QCheckBox{i18nc("@option:check", "Authentication"), groupBox}; groupBoxGrid->addWidget(authenticationCB, row, 1, 1, 1); row++; { auto hbox = new QHBoxLayout; expiryCB = new QCheckBox{i18nc("@option:check", "Valid until:"), groupBox}; hbox->addWidget(expiryCB); expiryDE = new KDateComboBox(groupBox); hbox->addWidget(expiryDE, 1); groupBoxGrid->addLayout(hbox, row, 0, 1, 2); } tabLayout->addWidget(groupBox); } tabLayout->addStretch(1); tabWidget->addTab(technicalTab, i18nc("@title:tab", "Technical Details")); } { personalTab = new ScrollArea{tabWidget}; personalTab->setFocusPolicy(Qt::NoFocus); personalTab->setFrameStyle(QFrame::NoFrame); personalTab->setBackgroundRole(parent->backgroundRole()); personalTab->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); personalTab->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(personalTab->widget()->layout()); auto tabGrid = new QGridLayout; uidGB = new QGroupBox{i18nc("@title:group", "Additional User IDs"), personalTab}; { auto layout = new QVBoxLayout{uidGB}; uidLW = new Kleo::NewCertificateUi::ListWidget{uidGB}; layout->addWidget(uidLW); } tabGrid->addWidget(uidGB, 0, 0, 1, 2); emailGB = new QGroupBox{i18nc("@title:group", "EMail Addresses"), personalTab}; { auto layout = new QVBoxLayout{emailGB}; emailLW = new Kleo::NewCertificateUi::ListWidget{emailGB}; layout->addWidget(emailLW); } tabGrid->addWidget(emailGB, 2, 0, 2, 1); dnsGB = new QGroupBox{i18nc("@title:group", "DNS Names"), personalTab}; { auto layout = new QVBoxLayout{dnsGB}; dnsLW = new Kleo::NewCertificateUi::ListWidget{dnsGB}; layout->addWidget(dnsLW); } tabGrid->addWidget(dnsGB, 2, 1, 1, 1); uriGB = new QGroupBox{i18nc("@title:group", "URIs"), personalTab}; { auto layout = new QVBoxLayout{uriGB}; uriLW = new Kleo::NewCertificateUi::ListWidget{uriGB}; layout->addWidget(uriLW); } tabGrid->addWidget(uriGB, 3, 1, 1, 1); scrollAreaLayout->addLayout(tabGrid); tabWidget->addTab(personalTab, i18nc("@title:tab", "Personal Details")); } mainLayout->addWidget(tabWidget); buttonBox = new QDialogButtonBox{parent}; buttonBox->setStandardButtons(QDialogButtonBox::Cancel | QDialogButtonBox::Ok); mainLayout->addWidget(buttonBox); } }; AdvancedSettingsDialog::AdvancedSettingsDialog(QWidget *parent) : QDialog{parent} , ui{new UI{this}} , mECCSupported{engineIsVersion(2, 1, 0)} , mEdDSASupported{engineIsVersion(2, 1, 15)} { qRegisterMetaType("Subkey::PubkeyAlgo"); Kleo::setUpExpirationDateComboBox(ui->expiryDE); if (unlimitedValidityIsAllowed()) { ui->expiryDE->setEnabled(ui->expiryCB->isChecked()); } else { ui->expiryCB->setEnabled(false); ui->expiryCB->setChecked(true); } ui->emailLW->setDefaultValue(i18n("new email")); ui->dnsLW->setDefaultValue(i18n("new dns name")); ui->uriLW->setDefaultValue(i18n("new uri")); fillKeySizeComboBoxen(); connect(ui->rsaRB, &QAbstractButton::toggled, ui->rsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->rsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->rsaSubCB, &QAbstractButton::toggled, ui->rsaKeyStrengthSubCB, &QWidget::setEnabled); connect(ui->rsaSubCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->dsaRB, &QAbstractButton::toggled, ui->dsaKeyStrengthCB, &QWidget::setEnabled); connect(ui->dsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->elgCB, &QAbstractButton::toggled, ui->elgKeyStrengthCB, &QWidget::setEnabled); connect(ui->elgCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdsaRB, &QAbstractButton::toggled, ui->ecdsaKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdsaRB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->ecdhCB, &QAbstractButton::toggled, ui->ecdhKeyCurvesCB, &QWidget::setEnabled); connect(ui->ecdhCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotKeyMaterialSelectionChanged); connect(ui->signingCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotSigningAllowedToggled); connect(ui->encryptionCB, &QAbstractButton::toggled, this, &AdvancedSettingsDialog::slotEncryptionAllowedToggled); connect(ui->expiryCB, &QAbstractButton::toggled, this, [this](bool checked) { ui->expiryDE->setEnabled(checked); if (checked && !ui->expiryDE->isValid()) { setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration)); } }); connect(ui->buttonBox, &QDialogButtonBox::accepted, this, &AdvancedSettingsDialog::accept); connect(ui->buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); } AdvancedSettingsDialog::~AdvancedSettingsDialog() = default; bool AdvancedSettingsDialog::unlimitedValidityIsAllowed() const { return !Kleo::maximumExpirationDate().isValid(); } void AdvancedSettingsDialog::setProtocol(GpgME::Protocol proto) { if (protocol == proto) { return; } protocol = proto; loadDefaults(); } void AdvancedSettingsDialog::setAdditionalUserIDs(const QStringList &items) { ui->uidLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalUserIDs() const { return ui->uidLW->items(); } void AdvancedSettingsDialog::setAdditionalEMailAddresses(const QStringList &items) { ui->emailLW->setItems(items); } QStringList AdvancedSettingsDialog::additionalEMailAddresses() const { return ui->emailLW->items(); } void AdvancedSettingsDialog::setDnsNames(const QStringList &items) { ui->dnsLW->setItems(items); } QStringList AdvancedSettingsDialog::dnsNames() const { return ui->dnsLW->items(); } void AdvancedSettingsDialog::setUris(const QStringList &items) { ui->uriLW->setItems(items); } QStringList AdvancedSettingsDialog::uris() const { return ui->uriLW->items(); } void AdvancedSettingsDialog::setKeyStrength(unsigned int strength) { set_keysize(ui->rsaKeyStrengthCB, strength); set_keysize(ui->dsaKeyStrengthCB, strength); } unsigned int AdvancedSettingsDialog::keyStrength() const { return ui->dsaRB->isChecked() ? get_keysize(ui->dsaKeyStrengthCB) : ui->rsaRB->isChecked() ? get_keysize(ui->rsaKeyStrengthCB) : 0; } void AdvancedSettingsDialog::setKeyType(Subkey::PubkeyAlgo algo) { QRadioButton *const rb = is_rsa(algo) ? ui->rsaRB : is_dsa(algo) ? ui->dsaRB : is_ecdsa(algo) || is_eddsa(algo) ? ui->ecdsaRB : nullptr; if (rb) { rb->setChecked(true); } } Subkey::PubkeyAlgo AdvancedSettingsDialog::keyType() const { return ui->dsaRB->isChecked() ? Subkey::AlgoDSA : ui->rsaRB->isChecked() ? Subkey::AlgoRSA - : ui->ecdsaRB->isChecked() ? ui->ecdsaKeyCurvesCB->currentText() == QLatin1String("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA + : ui->ecdsaRB->isChecked() ? ui->ecdsaKeyCurvesCB->currentText() == QLatin1StringView("ed25519") ? Subkey::AlgoEDDSA : Subkey::AlgoECDSA : Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setKeyCurve(const QString &curve) { set_curve(ui->ecdsaKeyCurvesCB, curve); } QString AdvancedSettingsDialog::keyCurve() const { return get_curve(ui->ecdsaKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyType(Subkey::PubkeyAlgo algo) { ui->elgCB->setChecked(is_elg(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); ui->ecdhCB->setChecked(is_ecdh(algo)); } Subkey::PubkeyAlgo AdvancedSettingsDialog::subkeyType() const { if (ui->elgCB->isChecked()) { return Subkey::AlgoELG_E; } else if (ui->rsaSubCB->isChecked()) { return Subkey::AlgoRSA; } else if (ui->ecdhCB->isChecked()) { return Subkey::AlgoECDH; } return Subkey::AlgoUnknown; } void AdvancedSettingsDialog::setSubkeyCurve(const QString &curve) { set_curve(ui->ecdhKeyCurvesCB, curve); } QString AdvancedSettingsDialog::subkeyCurve() const { return get_curve(ui->ecdhKeyCurvesCB); } void AdvancedSettingsDialog::setSubkeyStrength(unsigned int strength) { if (subkeyType() == Subkey::AlgoRSA) { set_keysize(ui->rsaKeyStrengthSubCB, strength); } else { set_keysize(ui->elgKeyStrengthCB, strength); } } unsigned int AdvancedSettingsDialog::subkeyStrength() const { if (subkeyType() == Subkey::AlgoRSA) { return get_keysize(ui->rsaKeyStrengthSubCB); } return get_keysize(ui->elgKeyStrengthCB); } void AdvancedSettingsDialog::setSigningAllowed(bool on) { ui->signingCB->setChecked(on); } bool AdvancedSettingsDialog::signingAllowed() const { return ui->signingCB->isChecked(); } void AdvancedSettingsDialog::setEncryptionAllowed(bool on) { ui->encryptionCB->setChecked(on); } bool AdvancedSettingsDialog::encryptionAllowed() const { return ui->encryptionCB->isChecked(); } void AdvancedSettingsDialog::setCertificationAllowed(bool on) { ui->certificationCB->setChecked(on); } bool AdvancedSettingsDialog::certificationAllowed() const { return ui->certificationCB->isChecked(); } void AdvancedSettingsDialog::setAuthenticationAllowed(bool on) { ui->authenticationCB->setChecked(on); } bool AdvancedSettingsDialog::authenticationAllowed() const { return ui->authenticationCB->isChecked(); } QDate AdvancedSettingsDialog::forceDateIntoAllowedRange(QDate date) const { const auto minDate = ui->expiryDE->minimumDate(); if (minDate.isValid() && date < minDate) { date = minDate; } const auto maxDate = ui->expiryDE->maximumDate(); if (maxDate.isValid() && date > maxDate) { date = maxDate; } return date; } void AdvancedSettingsDialog::setExpiryDate(QDate date) { if (date.isValid()) { ui->expiryDE->setDate(forceDateIntoAllowedRange(date)); } else { // check if unlimited validity is allowed if (unlimitedValidityIsAllowed()) { ui->expiryDE->setDate(date); } } if (ui->expiryCB->isEnabled()) { ui->expiryCB->setChecked(ui->expiryDE->isValid()); } } QDate AdvancedSettingsDialog::expiryDate() const { return ui->expiryCB->isChecked() ? ui->expiryDE->date() : QDate{}; } void AdvancedSettingsDialog::slotKeyMaterialSelectionChanged() { const unsigned int algo = keyType(); const unsigned int sk_algo = subkeyType(); if (protocol == OpenPGP) { // first update the enabled state, but only if key type is not forced if (!keyTypeImmutable) { ui->elgCB->setEnabled(is_dsa(algo)); ui->rsaSubCB->setEnabled(is_rsa(algo)); ui->ecdhCB->setEnabled(is_ecdsa(algo) || is_eddsa(algo)); if (is_rsa(algo)) { ui->encryptionCB->setEnabled(true); ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setEnabled(false); } else { ui->encryptionCB->setEnabled(true); } } else if (is_dsa(algo)) { ui->encryptionCB->setEnabled(false); } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setEnabled(true); ui->authenticationCB->setEnabled(true); ui->encryptionCB->setEnabled(false); } } // then update the checked state if (sender() == ui->dsaRB || sender() == ui->rsaRB || sender() == ui->ecdsaRB) { ui->elgCB->setChecked(is_dsa(algo)); ui->ecdhCB->setChecked(is_ecdsa(algo) || is_eddsa(algo)); ui->rsaSubCB->setChecked(is_rsa(algo)); } if (is_rsa(algo)) { ui->encryptionCB->setChecked(true); ui->signingCB->setChecked(true); if (is_rsa(sk_algo)) { ui->encryptionCB->setChecked(true); } } else if (is_dsa(algo)) { if (is_elg(sk_algo)) { ui->encryptionCB->setChecked(true); } else { ui->encryptionCB->setChecked(false); } } else if (is_ecdsa(algo) || is_eddsa(algo)) { ui->signingCB->setChecked(true); ui->encryptionCB->setChecked(is_ecdh(sk_algo)); } } else { // assert( is_rsa( keyType() ) ); // it can happen through misconfiguration by the admin that no key type is selectable at all } } void AdvancedSettingsDialog::slotSigningAllowedToggled(bool on) { if (!on && protocol == CMS && !encryptionAllowed()) { setEncryptionAllowed(true); } } void AdvancedSettingsDialog::slotEncryptionAllowedToggled(bool on) { if (!on && protocol == CMS && !signingAllowed()) { setSigningAllowed(true); } } static void fill_combobox(QComboBox &cb, const QList &sizes, const QStringList &labels) { cb.clear(); for (int i = 0, end = sizes.size(); i != end; ++i) { const int size = std::abs(sizes[i]); /* As we respect the defaults configurable in GnuPG, and we also have configurable * defaults in Kleopatra its difficult to print out "default" here. To avoid confusion * about that its better not to show any default indication. */ cb.addItem(i < labels.size() && !labels[i].trimmed().isEmpty() ? i18ncp("%2: some admin-supplied text, %1: key size in bits", "%2 (1 bit)", "%2 (%1 bits)", size, labels[i].trimmed()) : i18ncp("%1: key size in bits", "1 bit", "%1 bits", size), size); if (sizes[i] < 0) { cb.setCurrentIndex(cb.count() - 1); } } } void AdvancedSettingsDialog::fillKeySizeComboBoxen() { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); QList rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << 2048 << -3072 << 4096); if (DeVSCompliance::isActive()) { rsaKeySizes = config.readEntry(RSA_KEYSIZES_ENTRY, QList() << -3072 << 4096); } const QList dsaKeySizes = config.readEntry(DSA_KEYSIZES_ENTRY, QList() << -2048); const QList elgKeySizes = config.readEntry(ELG_KEYSIZES_ENTRY, QList() << -2048 << 3072 << 4096); const QStringList rsaKeySizeLabels = config.readEntry(RSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList dsaKeySizeLabels = config.readEntry(DSA_KEYSIZE_LABELS_ENTRY, QStringList()); const QStringList elgKeySizeLabels = config.readEntry(ELG_KEYSIZE_LABELS_ENTRY, QStringList()); fill_combobox(*ui->rsaKeyStrengthCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->rsaKeyStrengthSubCB, rsaKeySizes, rsaKeySizeLabels); fill_combobox(*ui->dsaKeyStrengthCB, dsaKeySizes, dsaKeySizeLabels); fill_combobox(*ui->elgKeyStrengthCB, elgKeySizes, elgKeySizeLabels); if (mEdDSASupported) { // If supported we recommend cv25519 ui->ecdsaKeyCurvesCB->addItem(QStringLiteral("ed25519")); ui->ecdhKeyCurvesCB->addItem(QStringLiteral("cv25519")); } ui->ecdhKeyCurvesCB->addItems(curveNames); ui->ecdsaKeyCurvesCB->addItems(curveNames); } // Try to load the default key type from GnuPG void AdvancedSettingsDialog::loadDefaultGnuPGKeyType() { const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = getCryptoConfigEntry(conf, protocol == CMS ? "gpgsm" : "gpg", "default_pubkey_algo"); if (!entry) { qCDebug(KLEOPATRA_LOG) << "GnuPG does not have default key type. Fallback to RSA"; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); return; } qCDebug(KLEOPATRA_LOG) << "Have default key type: " << entry->stringValue(); // Format is [/usage]+[/usage] const auto split = entry->stringValue().split(QLatin1Char('+')); int size = 0; Subkey::PubkeyAlgo algo = Subkey::AlgoUnknown; QString curve; parseAlgoString(split[0], &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setKeyType(algo); if (is_rsa(algo) || is_elg(algo) || is_dsa(algo)) { setKeyStrength(size); } else { setKeyCurve(curve); } { auto algoString = (split.size() == 2) ? split[1] : split[0]; // If it has no usage we assume encrypt subkey if (!algoString.contains(QLatin1Char('/'))) { algoString += QStringLiteral("/enc"); } parseAlgoString(algoString, &size, &algo, curve); if (algo == Subkey::AlgoUnknown) { setSubkeyType(Subkey::AlgoRSA); return; } setSubkeyType(algo); if (is_rsa(algo) || is_elg(algo)) { setSubkeyStrength(size); } else { setSubkeyCurve(curve); } } } void AdvancedSettingsDialog::loadDefaultKeyType() { if (protocol != CMS && protocol != OpenPGP) { return; } const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); - const QString entry = protocol == CMS ? QLatin1String(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); + const QString entry = protocol == CMS ? QLatin1StringView(CMS_KEY_TYPE_ENTRY) : QLatin1String(PGP_KEY_TYPE_ENTRY); const QString keyType = config.readEntry(entry).trimmed().toUpper(); - if (protocol == OpenPGP && keyType == QLatin1String("DSA")) { + if (protocol == OpenPGP && keyType == QLatin1StringView("DSA")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoUnknown); - } else if (protocol == OpenPGP && keyType == QLatin1String("DSA+ELG")) { + } else if (protocol == OpenPGP && keyType == QLatin1StringView("DSA+ELG")) { setKeyType(Subkey::AlgoDSA); setSubkeyType(Subkey::AlgoELG_E); } else if (keyType.isEmpty() && engineIsVersion(2, 1, 17)) { loadDefaultGnuPGKeyType(); } else { - if (!keyType.isEmpty() && keyType != QLatin1String("RSA")) + if (!keyType.isEmpty() && keyType != QLatin1StringView("RSA")) qCWarning(KLEOPATRA_LOG) << "invalid value \"" << qPrintable(keyType) << "\" for entry \"[CertificateCreationWizard]" << qPrintable(entry) << "\""; setKeyType(Subkey::AlgoRSA); setSubkeyType(Subkey::AlgoRSA); } keyTypeImmutable = config.isEntryImmutable(entry); } void AdvancedSettingsDialog::loadDefaultExpiration() { if (protocol != OpenPGP) { return; } if (unlimitedValidityIsAllowed()) { setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::NoExpiration)); } else { setExpiryDate(defaultExpirationDate(ExpirationOnUnlimitedValidity::InternalDefaultExpiration)); } } void AdvancedSettingsDialog::loadDefaults() { loadDefaultKeyType(); loadDefaultExpiration(); updateWidgetVisibility(); } void AdvancedSettingsDialog::updateWidgetVisibility() { // Personal Details Page if (protocol == OpenPGP) { // ### hide until multi-uid is implemented if (ui->tabWidget->indexOf(ui->personalTab) != -1) { ui->tabWidget->removeTab(ui->tabWidget->indexOf(ui->personalTab)); } } else { if (ui->tabWidget->indexOf(ui->personalTab) == -1) { ui->tabWidget->addTab(ui->personalTab, i18nc("@title:tab", "Personal Details")); } } ui->uidGB->setVisible(protocol == OpenPGP); ui->uidGB->setEnabled(false); ui->uidGB->setToolTip(i18nc("@info:tooltip", "Adding more than one user ID is not yet implemented.")); ui->emailGB->setVisible(protocol == CMS); ui->dnsGB->setVisible(protocol == CMS); ui->uriGB->setVisible(protocol == CMS); // Technical Details Page ui->ecdhCB->setVisible(mECCSupported); ui->ecdhKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaKeyCurvesCB->setVisible(mECCSupported); ui->ecdsaRB->setVisible(mECCSupported); if (mEdDSASupported) { // We use the same radio button for EdDSA as we use for // ECDSA GnuPG does the same and this is really super technical // land. ui->ecdsaRB->setText(QStringLiteral("ECDSA/EdDSA")); } const bool deVsHack = DeVSCompliance::isActive(); if (deVsHack) { // GnuPG Provides no API to query which keys are compliant for // a mode. If we request a different one it will error out so // we have to remove the options. // // Does anyone want to use NIST anyway? int i; while ((i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdsaKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdsaKeyCurvesCB->removeItem(i); } while ((i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("NIST"), Qt::MatchStartsWith)) != -1 || (i = ui->ecdhKeyCurvesCB->findText(QStringLiteral("25519"), Qt::MatchEndsWith)) != -1) { ui->ecdhKeyCurvesCB->removeItem(i); } } ui->certificationCB->setVisible(protocol == OpenPGP); // gpgsm limitation? ui->authenticationCB->setVisible(protocol == OpenPGP); if (keyTypeImmutable) { ui->rsaRB->setEnabled(false); ui->rsaSubCB->setEnabled(false); ui->dsaRB->setEnabled(false); ui->elgCB->setEnabled(false); ui->ecdsaRB->setEnabled(false); ui->ecdhCB->setEnabled(false); // force usage if key type is forced ui->certificationCB->setEnabled(false); ui->signingCB->setEnabled(false); ui->encryptionCB->setEnabled(false); ui->authenticationCB->setEnabled(false); } else { ui->rsaRB->setEnabled(true); ui->rsaSubCB->setEnabled(protocol == OpenPGP); ui->dsaRB->setEnabled(protocol == OpenPGP && !deVsHack); ui->elgCB->setEnabled(protocol == OpenPGP && !deVsHack); ui->ecdsaRB->setEnabled(protocol == OpenPGP); ui->ecdhCB->setEnabled(protocol == OpenPGP); if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setEnabled(false); } if (protocol == CMS) { ui->encryptionCB->setEnabled(true); ui->rsaKeyStrengthSubCB->setEnabled(false); } } if (protocol == OpenPGP) { // OpenPGP keys must have certify capability ui->certificationCB->setChecked(true); } if (protocol == CMS) { ui->rsaSubCB->setChecked(false); } ui->expiryDE->setVisible(protocol == OpenPGP); ui->expiryCB->setVisible(protocol == OpenPGP); slotKeyMaterialSelectionChanged(); } void AdvancedSettingsDialog::setInitialFocus() { // first try the key type radio buttons if (focusFirstCheckedButton({ui->rsaRB, ui->dsaRB, ui->ecdsaRB})) { return; } // then try the usage check boxes and the expiration check box if (focusFirstEnabledButton({ui->signingCB, ui->certificationCB, ui->encryptionCB, ui->authenticationCB, ui->expiryCB})) { return; } // finally, focus the OK button ui->buttonBox->button(QDialogButtonBox::Ok)->setFocus(); } void AdvancedSettingsDialog::accept() { if (!Kleo::isValidExpirationDate(expiryDate())) { KMessageBox::error(this, i18nc("@info", "Error: %1", Kleo::validityPeriodHint())); return; } QDialog::accept(); } void AdvancedSettingsDialog::showEvent(QShowEvent *event) { if (isFirstShowEvent) { setInitialFocus(); isFirstShowEvent = false; } QDialog::showEvent(event); } #include "moc_advancedsettingsdialog_p.cpp" diff --git a/src/newcertificatewizard/enterdetailspage.cpp b/src/newcertificatewizard/enterdetailspage.cpp index d232f694a..e31cfcfc2 100644 --- a/src/newcertificatewizard/enterdetailspage.cpp +++ b/src/newcertificatewizard/enterdetailspage.cpp @@ -1,538 +1,538 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/enterdetailspage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "enterdetailspage_p.h" #include "advancedsettingsdialog_p.h" #include "utils/scrollarea.h" #include "utils/userinfo.h" #include "utils/validation.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; static void set_tab_order(const QList &wl) { kdtools::for_each_adjacent_pair(wl.begin(), wl.end(), [](QWidget *w1, QWidget *w2) { QWidget::setTabOrder(w1, w2); }); } static QString pgpLabel(const QString &attr) { - if (attr == QLatin1String("NAME")) { + if (attr == QLatin1StringView("NAME")) { return i18n("Name"); } - if (attr == QLatin1String("EMAIL")) { + if (attr == QLatin1StringView("EMAIL")) { return i18n("EMail"); } return QString(); } static QString attributeLabel(const QString &attr, bool pgp) { if (attr.isEmpty()) { return QString(); } const QString label = pgp ? pgpLabel(attr) : Kleo::DN::attributeNameToLabel(attr); if (!label.isEmpty()) if (pgp) { return label; } else return i18nc("Format string for the labels in the \"Your Personal Data\" page", "%1 (%2)", label, attr); else { return attr; } } static QString attributeFromKey(QString key) { return key.remove(QLatin1Char('!')); } struct EnterDetailsPage::UI { QGridLayout *gridLayout = nullptr; QLabel *nameLB = nullptr; QLineEdit *nameLE = nullptr; QLabel *nameRequiredLB = nullptr; QLabel *emailLB = nullptr; QLineEdit *emailLE = nullptr; QLabel *emailRequiredLB = nullptr; QCheckBox *withPassCB = nullptr; QLineEdit *resultLE = nullptr; QLabel *errorLB = nullptr; QPushButton *advancedPB = nullptr; UI(QWizardPage *parent) { parent->setTitle(i18nc("@title", "Enter Details")); auto mainLayout = new QVBoxLayout{parent}; const auto margins = mainLayout->contentsMargins(); mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); gridLayout = new QGridLayout; int row = 0; nameLB = new QLabel{i18n("Real name:"), parent}; nameLE = new QLineEdit{parent}; nameRequiredLB = new QLabel{i18n("(required)"), parent}; gridLayout->addWidget(nameLB, row, 0, 1, 1); gridLayout->addWidget(nameLE, row, 1, 1, 1); gridLayout->addWidget(nameRequiredLB, row, 2, 1, 1); row++; emailLB = new QLabel{i18n("EMail address:"), parent}; emailLE = new QLineEdit{parent}; emailRequiredLB = new QLabel{i18n("(required)"), parent}; gridLayout->addWidget(emailLB, row, 0, 1, 1); gridLayout->addWidget(emailLE, row, 1, 1, 1); gridLayout->addWidget(emailRequiredLB, row, 2, 1, 1); row++; withPassCB = new QCheckBox{i18n("Protect the generated key with a passphrase."), parent}; withPassCB->setToolTip(i18n("Encrypts the secret key with an unrecoverable passphrase. You will be asked for the passphrase during key generation.")); gridLayout->addWidget(withPassCB, row, 1, 1, 2); scrollAreaLayout->addLayout(gridLayout); auto verticalSpacer = new QSpacerItem{20, 40, QSizePolicy::Minimum, QSizePolicy::Expanding}; scrollAreaLayout->addItem(verticalSpacer); resultLE = new QLineEdit{parent}; resultLE->setFrame(false); resultLE->setAlignment(Qt::AlignCenter); resultLE->setReadOnly(true); scrollAreaLayout->addWidget(resultLE); auto horizontalLayout = new QHBoxLayout; errorLB = new QLabel{parent}; QSizePolicy sizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Preferred); sizePolicy.setHorizontalStretch(0); sizePolicy.setVerticalStretch(0); sizePolicy.setHeightForWidth(errorLB->sizePolicy().hasHeightForWidth()); errorLB->setSizePolicy(sizePolicy); QPalette palette; QBrush brush(QColor(255, 0, 0, 255)); brush.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Active, QPalette::WindowText, brush); palette.setBrush(QPalette::Inactive, QPalette::WindowText, brush); QBrush brush1(QColor(114, 114, 114, 255)); brush1.setStyle(Qt::SolidPattern); palette.setBrush(QPalette::Disabled, QPalette::WindowText, brush1); errorLB->setPalette(palette); errorLB->setTextFormat(Qt::RichText); horizontalLayout->addWidget(errorLB); advancedPB = new QPushButton{i18n("Advanced Settings..."), parent}; advancedPB->setAutoDefault(false); horizontalLayout->addWidget(advancedPB); scrollAreaLayout->addLayout(horizontalLayout); mainLayout->addWidget(scrollArea); } }; EnterDetailsPage::EnterDetailsPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} , dialog{new AdvancedSettingsDialog{this}} { setObjectName(QLatin1StringView("Kleo__NewCertificateUi__EnterDetailsPage")); Settings settings; if (settings.hideAdvanced()) { setSubTitle(i18n("Please enter your personal details below.")); } else { setSubTitle(i18n("Please enter your personal details below. If you want more control over the parameters, click on the Advanced Settings button.")); } ui->advancedPB->setVisible(!settings.hideAdvanced()); ui->resultLE->setFocusPolicy(Qt::NoFocus); // set errorLB to have a fixed height of two lines: ui->errorLB->setText(QStringLiteral("2
    1")); ui->errorLB->setFixedHeight(ui->errorLB->minimumSizeHint().height()); ui->errorLB->clear(); connect(ui->advancedPB, &QPushButton::clicked, this, &EnterDetailsPage::slotAdvancedSettingsClicked); connect(ui->resultLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); // The email doesn't necessarily show up in ui->resultLE: connect(ui->emailLE, &QLineEdit::textChanged, this, &QWizardPage::completeChanged); registerDialogPropertiesAsFields(); registerField(QStringLiteral("dn"), ui->resultLE); registerField(QStringLiteral("name"), ui->nameLE); registerField(QStringLiteral("email"), ui->emailLE); registerField(QStringLiteral("protectedKey"), ui->withPassCB); setCommitPage(true); setButtonText(QWizard::CommitButton, i18nc("@action", "Create")); const auto conf = QGpgME::cryptoConfig(); if (!conf) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain cryptoConfig."; return; } const auto entry = getCryptoConfigEntry(conf, "gpg-agent", "enforce-passphrase-constraints"); if (entry && entry->boolValue()) { qCDebug(KLEOPATRA_LOG) << "Disabling passphrace cb because of agent config."; ui->withPassCB->setEnabled(false); ui->withPassCB->setChecked(true); } else { const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); ui->withPassCB->setChecked(config.readEntry("WithPassphrase", false)); ui->withPassCB->setEnabled(!config.isEntryImmutable("WithPassphrase")); } } EnterDetailsPage::~EnterDetailsPage() = default; void EnterDetailsPage::initializePage() { updateForm(); ui->withPassCB->setVisible(pgp()); dialog->setProtocol(pgp() ? OpenPGP : CMS); } void EnterDetailsPage::cleanupPage() { saveValues(); } void EnterDetailsPage::registerDialogPropertiesAsFields() { const QMetaObject *const mo = dialog->metaObject(); for (unsigned int i = mo->propertyOffset(), end = i + mo->propertyCount(); i != end; ++i) { const QMetaProperty mp = mo->property(i); if (mp.isValid()) { - registerField(QLatin1String(mp.name()), dialog, mp.name(), SIGNAL(accepted())); + registerField(QLatin1StringView(mp.name()), dialog, mp.name(), SIGNAL(accepted())); } } } void EnterDetailsPage::saveValues() { for (const Line &line : std::as_const(lineList)) { savedValues[attributeFromKey(line.attr)] = line.edit->text().trimmed(); } } void EnterDetailsPage::clearForm() { qDeleteAll(dynamicWidgets); dynamicWidgets.clear(); lineList.clear(); ui->nameLE->hide(); ui->nameLE->clear(); ui->nameLB->hide(); ui->nameRequiredLB->hide(); ui->emailLE->hide(); ui->emailLE->clear(); ui->emailLB->hide(); ui->emailRequiredLB->hide(); } static int row_index_of(QWidget *w, QGridLayout *l) { const int idx = l->indexOf(w); int r, c, rs, cs; l->getItemPosition(idx, &r, &c, &rs, &cs); return r; } static QLineEdit * adjust_row(QGridLayout *l, int row, const QString &label, const QString &preset, const std::shared_ptr &validator, bool readonly, bool required) { Q_ASSERT(l); Q_ASSERT(row >= 0); Q_ASSERT(row < l->rowCount()); auto lb = qobject_cast(l->itemAtPosition(row, 0)->widget()); Q_ASSERT(lb); auto le = qobject_cast(l->itemAtPosition(row, 1)->widget()); Q_ASSERT(le); lb->setBuddy(le); // For better accessibility auto reqLB = qobject_cast(l->itemAtPosition(row, 2)->widget()); Q_ASSERT(reqLB); lb->setText(i18nc("interpunctation for labels", "%1:", label)); le->setText(preset); reqLB->setText(required ? i18n("(required)") : i18n("(optional)")); if (validator) { le->setValidator(validator.get()); } le->setReadOnly(readonly && le->hasAcceptableInput()); lb->show(); le->show(); reqLB->show(); return le; } static int add_row(QGridLayout *l, QList *wl) { Q_ASSERT(l); Q_ASSERT(wl); const int row = l->rowCount(); QWidget *w1, *w2, *w3; l->addWidget(w1 = new QLabel(l->parentWidget()), row, 0); l->addWidget(w2 = new QLineEdit(l->parentWidget()), row, 1); l->addWidget(w3 = new QLabel(l->parentWidget()), row, 2); wl->push_back(w1); wl->push_back(w2); wl->push_back(w3); return row; } void EnterDetailsPage::updateForm() { clearForm(); const auto settings = Kleo::Settings{}; const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); QStringList attrOrder = config.readEntry(pgp() ? "OpenPGPAttributeOrder" : "DNAttributeOrder", QStringList()); if (attrOrder.empty()) { if (pgp()) { attrOrder << QStringLiteral("NAME") << QStringLiteral("EMAIL"); } else { attrOrder << QStringLiteral("CN!") << QStringLiteral("L") << QStringLiteral("OU") << QStringLiteral("O") << QStringLiteral("C") << QStringLiteral("EMAIL!"); } } QList widgets; widgets.push_back(ui->nameLE); widgets.push_back(ui->emailLE); QMap lines; for (const QString &rawKey : std::as_const(attrOrder)) { const QString key = rawKey.trimmed().toUpper(); const QString attr = attributeFromKey(key); if (attr.isEmpty()) { continue; } const QString preset = savedValues.value(attr, config.readEntry(attr, QString())); const bool required = key.endsWith(QLatin1Char('!')); const bool readonly = config.isEntryImmutable(attr); - const QString label = config.readEntry(attr + QLatin1String("_label"), attributeLabel(attr, pgp())); - const QString regex = config.readEntry(attr + QLatin1String("_regex")); - const QString placeholder = config.readEntry(attr + QLatin1String{"_placeholder"}); + const QString label = config.readEntry(attr + QLatin1StringView("_label"), attributeLabel(attr, pgp())); + const QString regex = config.readEntry(attr + QLatin1StringView("_regex")); + const QString placeholder = config.readEntry(attr + QLatin1StringView{"_placeholder"}); int row; bool known = true; std::shared_ptr validator; - if (attr == QLatin1String("EMAIL")) { + if (attr == QLatin1StringView("EMAIL")) { row = row_index_of(ui->emailLE, ui->gridLayout); validator = regex.isEmpty() ? Validation::email() : Validation::email(regex); - } else if (attr == QLatin1String("NAME") || attr == QLatin1String("CN")) { - if ((pgp() && attr == QLatin1String("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { + } else if (attr == QLatin1StringView("NAME") || attr == QLatin1String("CN")) { + if ((pgp() && attr == QLatin1StringView("CN")) || (!pgp() && attr == QLatin1String("NAME"))) { continue; } if (pgp()) { validator = regex.isEmpty() ? Validation::pgpName() : Validation::pgpName(regex); } row = row_index_of(ui->nameLE, ui->gridLayout); } else { known = false; row = add_row(ui->gridLayout, &dynamicWidgets); } if (!validator && !regex.isEmpty()) { validator = std::make_shared(QRegularExpression{regex}); } QLineEdit *le = adjust_row(ui->gridLayout, row, label, preset, validator, readonly, required); le->setPlaceholderText(placeholder); const Line line = {key, label, regex, le, validator}; lines[row] = line; if (!known) { widgets.push_back(le); } // don't connect twice: disconnect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); connect(le, &QLineEdit::textChanged, this, &EnterDetailsPage::slotUpdateResultLabel); } // create lineList in visual order, so requirementsAreMet() // complains from top to bottom: lineList.reserve(lines.count()); std::copy(lines.cbegin(), lines.cend(), std::back_inserter(lineList)); widgets.push_back(ui->withPassCB); widgets.push_back(ui->advancedPB); const bool prefillName = (pgp() && settings.prefillName()) || (!pgp() && settings.prefillCN()); if (ui->nameLE->text().isEmpty() && prefillName) { ui->nameLE->setText(userFullName()); } if (ui->emailLE->text().isEmpty() && settings.prefillEmail()) { ui->emailLE->setText(userEmailAddress()); } slotUpdateResultLabel(); set_tab_order(widgets); } QString EnterDetailsPage::cmsDN() const { DN dn; for (QList::const_iterator it = lineList.begin(), end = lineList.end(); it != end; ++it) { const QString text = it->edit->text().trimmed(); if (text.isEmpty()) { continue; } QString attr = attributeFromKey(it->attr); - if (attr == QLatin1String("EMAIL")) { + if (attr == QLatin1StringView("EMAIL")) { continue; } if (const char *const oid = oidForAttributeName(attr)) { attr = QString::fromUtf8(oid); } dn.append(DN::Attribute(attr, text)); } return dn.dn(); } QString EnterDetailsPage::pgpUserID() const { return Formatting::prettyNameAndEMail(OpenPGP, QString(), ui->nameLE->text().trimmed(), ui->emailLE->text().trimmed(), QString()); } static bool has_intermediate_input(const QLineEdit *le) { QString text = le->text(); int pos = le->cursorPosition(); const QValidator *const v = le->validator(); return v && v->validate(text, pos) == QValidator::Intermediate; } static bool requirementsAreMet(const QList &list, QString &error) { bool allEmpty = true; for (const auto &line : list) { const QLineEdit *le = line.edit; if (!le) { continue; } const QString key = line.attr; qCDebug(KLEOPATRA_LOG) << "requirementsAreMet(): checking" << key << "against" << le->text() << ":"; if (le->text().trimmed().isEmpty()) { if (key.endsWith(QLatin1Char('!'))) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is required, but empty.", line.label); } else error = xi18nc("@info", "%1 is required, but empty." "Local Admin rule: %2", line.label, line.regex); return false; } } else if (has_intermediate_input(le)) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is incomplete.", line.label); } else error = xi18nc("@info", "%1 is incomplete." "Local Admin rule: %2", line.label, line.regex); return false; } else if (!le->hasAcceptableInput()) { if (line.regex.isEmpty()) { error = xi18nc("@info", "%1 is invalid.", line.label); } else error = xi18nc("@info", "%1 is invalid." "Local Admin rule: %2", line.label, line.regex); return false; } else { allEmpty = false; } } // Ensure that at least one value is acceptable return !allEmpty; } bool EnterDetailsPage::isComplete() const { QString error; const bool ok = requirementsAreMet(lineList, error); ui->errorLB->setText(error); return ok; } void EnterDetailsPage::slotAdvancedSettingsClicked() { dialog->exec(); } void EnterDetailsPage::slotUpdateResultLabel() { ui->resultLE->setText(pgp() ? pgpUserID() : cmsDN()); } #include "moc_enterdetailspage_p.cpp" diff --git a/src/newcertificatewizard/keycreationpage.cpp b/src/newcertificatewizard/keycreationpage.cpp index 3644ac98a..92697652e 100644 --- a/src/newcertificatewizard/keycreationpage.cpp +++ b/src/newcertificatewizard/keycreationpage.cpp @@ -1,249 +1,249 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/keycreationpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "keycreationpage_p.h" #include "keyalgo_p.h" #include "kleopatraapplication.h" #include "utils/keyparameters.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::NewCertificateUi; using namespace GpgME; struct KeyCreationPage::UI { UI(QWizardPage *parent) { parent->setTitle(i18nc("@title", "Creating Key Pair...")); auto mainLayout = new QVBoxLayout{parent}; auto label = new QLabel{i18n("The process of creating a key requires large amounts of random numbers. This may require several minutes..."), parent}; label->setWordWrap(true); mainLayout->addWidget(label); } }; KeyCreationPage::KeyCreationPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} { setObjectName(QString::fromUtf8("Kleo__NewCertificateUi__KeyCreationPage")); } KeyCreationPage::~KeyCreationPage() = default; bool KeyCreationPage::isComplete() const { return !job; } void KeyCreationPage::initializePage() { startJob(); } void KeyCreationPage::startJob() { const auto proto = pgp() ? QGpgME::openpgp() : QGpgME::smime(); if (!proto) { return; } QGpgME::KeyGenerationJob *const j = proto->keyGenerationJob(); if (!j) { return; } if (!protectedKey() && pgp()) { auto ctx = QGpgME::Job::context(j); ctx->setPassphraseProvider(&mEmptyPassphraseProvider); ctx->setPinentryMode(Context::PinentryLoopback); } connect(j, &QGpgME::KeyGenerationJob::result, this, &KeyCreationPage::slotResult); if (const Error err = j->start(createGnupgKeyParms())) setField(QStringLiteral("error"), i18n("Could not start key pair creation: %1", Formatting::errorAsString(err))); else { job = j; } } KeyUsage KeyCreationPage::keyUsage() const { KeyUsage usage; if (signingAllowed()) { usage.setCanSign(true); } if (encryptionAllowed() && !is_ecdh(subkeyType()) && !is_dsa(keyType()) && !is_rsa(subkeyType())) { usage.setCanEncrypt(true); } if (authenticationAllowed()) { usage.setCanAuthenticate(true); } if (!usage.value() && certificationAllowed()) { /* Empty usages cause an error so we need to * add at least certify if nothing else is selected */ usage.setCanCertify(true); } return usage; } KeyUsage KeyCreationPage::subkeyUsage() const { KeyUsage usage; if (encryptionAllowed() && (is_dsa(keyType()) || is_rsa(subkeyType()) || is_ecdh(subkeyType()))) { Q_ASSERT(subkeyType()); usage.setCanEncrypt(true); } return usage; } QString KeyCreationPage::createGnupgKeyParms() const { KeyParameters keyParameters(pgp() ? KeyParameters::OpenPGP : KeyParameters::CMS); keyParameters.setKeyType(keyType()); if (is_ecdsa(keyType()) || is_eddsa(keyType())) { keyParameters.setKeyCurve(keyCurve()); } else if (const unsigned int strength = keyStrength()) { keyParameters.setKeyLength(strength); } keyParameters.setKeyUsage(keyUsage()); if (subkeyType()) { keyParameters.setSubkeyType(subkeyType()); if (is_ecdh(subkeyType())) { keyParameters.setSubkeyCurve(subkeyCurve()); } else if (const unsigned int strength = subkeyStrength()) { keyParameters.setSubkeyLength(strength); } keyParameters.setSubkeyUsage(subkeyUsage()); } if (pgp()) { if (expiryDate().isValid()) { keyParameters.setExpirationDate(expiryDate()); } if (!name().isEmpty()) { keyParameters.setName(name()); } if (!email().isEmpty()) { keyParameters.setEmail(email()); } } else { keyParameters.setDN(dn()); keyParameters.setEmail(email()); const auto addesses{additionalEMailAddresses()}; for (const QString &email : addesses) { keyParameters.addEmail(email); } const auto dnsN{dnsNames()}; for (const QString &dns : dnsN) { keyParameters.addDomainName(dns); } const auto urisList{uris()}; for (const QString &uri : urisList) { keyParameters.addURI(uri); } } const QString result = keyParameters.toString(); qCDebug(KLEOPATRA_LOG) << '\n' << result; return result; } void KeyCreationPage::slotResult(const GpgME::KeyGenerationResult &result, const QByteArray &request, const QString &auditLog) { Q_UNUSED(auditLog) if (result.error().code() || (pgp() && !result.fingerprint())) { setField(QStringLiteral("error"), result.error().isCanceled() ? i18n("Operation canceled.") : i18n("Could not create key pair: %1", Formatting::errorAsString(result.error()))); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else if (pgp()) { setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.\n" "Fingerprint: %1", Formatting::prettyID(result.fingerprint()))); } else { QFile file(tmpDir().absoluteFilePath(QStringLiteral("request.p10"))); if (!file.open(QIODevice::WriteOnly)) { setField(QStringLiteral("error"), i18n("Could not write output file %1: %2", file.fileName(), file.errorString())); setField(QStringLiteral("url"), QString()); setField(QStringLiteral("result"), QString()); } else { file.write(request); setField(QStringLiteral("error"), QString()); setField(QStringLiteral("url"), QUrl::fromLocalFile(file.fileName()).toString()); setField(QStringLiteral("result"), i18n("Key pair created successfully.")); } } // Ensure that we have the key in the keycache if (pgp() && !result.error().code() && result.fingerprint()) { auto ctx = Context::createForProtocol(OpenPGP); if (ctx) { // Check is pretty useless something very buggy in that case. Error e; const auto key = ctx->key(result.fingerprint(), e, true); if (!key.isNull()) { KeyCache::mutableInstance()->insert(key); } else { qCDebug(KLEOPATRA_LOG) << "Failed to find newly generated key."; } delete ctx; } } setField(QStringLiteral("fingerprint"), result.fingerprint() ? QString::fromLatin1(result.fingerprint()) : QString()); job = nullptr; Q_EMIT completeChanged(); const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); if (config.readEntry("SkipResultPage", false)) { if (result.fingerprint()) { KleopatraApplication::instance()->slotActivateRequested(QStringList() << QStringLiteral("kleopatra") << QStringLiteral("--query") - << QLatin1String(result.fingerprint()), + << QLatin1StringView(result.fingerprint()), QString()); QMetaObject::invokeMethod(wizard(), "close", Qt::QueuedConnection); } else { QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } } else { QMetaObject::invokeMethod(wizard(), "next", Qt::QueuedConnection); } } #include "moc_keycreationpage_p.cpp" diff --git a/src/newcertificatewizard/resultpage.cpp b/src/newcertificatewizard/resultpage.cpp index 094243b42..5f8abd98d 100644 --- a/src/newcertificatewizard/resultpage.cpp +++ b/src/newcertificatewizard/resultpage.cpp @@ -1,369 +1,369 @@ /* -*- mode: c++; c-basic-offset:4 -*- newcertificatewizard/resultpage.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016, 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "resultpage_p.h" #include "commands/exportcertificatecommand.h" #include "commands/exportopenpgpcertstoservercommand.h" #include "commands/exportsecretkeycommand.h" #include "utils/dragqueen.h" #include "utils/email.h" #include "utils/filedialog.h" #include "utils/scrollarea.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::NewCertificateUi; using namespace GpgME; struct ResultPage::UI { QTextBrowser *resultTB = nullptr; QTextBrowser *errorTB = nullptr; DragQueen *dragQueen = nullptr; QPushButton *restartWizardPB = nullptr; QGroupBox *nextStepsGB = nullptr; QPushButton *saveRequestToFilePB = nullptr; QPushButton *sendRequestByEMailPB = nullptr; QPushButton *makeBackupPB = nullptr; QPushButton *sendCertificateByEMailPB = nullptr; QPushButton *uploadToKeyserverPB = nullptr; QPushButton *createRevocationRequestPB = nullptr; QPushButton *createSigningCertificatePB = nullptr; QPushButton *createEncryptionCertificatePB = nullptr; UI(QWizardPage *parent) { auto mainLayout = new QVBoxLayout{parent}; const auto margins = mainLayout->contentsMargins(); mainLayout->setContentsMargins(margins.left(), 0, margins.right(), 0); auto scrollArea = new ScrollArea{parent}; scrollArea->setFocusPolicy(Qt::NoFocus); scrollArea->setFrameStyle(QFrame::NoFrame); scrollArea->setBackgroundRole(parent->backgroundRole()); scrollArea->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); scrollArea->setSizeAdjustPolicy(QScrollArea::AdjustToContents); auto scrollAreaLayout = qobject_cast(scrollArea->widget()->layout()); scrollAreaLayout->setContentsMargins(0, margins.top(), 0, margins.bottom()); auto resultGB = new QGroupBox{i18nc("@title:group", "Result"), parent}; auto resultGBLayout = new QHBoxLayout{resultGB}; resultTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(resultTB); errorTB = new QTextBrowser{resultGB}; resultGBLayout->addWidget(errorTB); dragQueen = new Kleo::DragQueen{resultGB}; dragQueen->setToolTip(i18n("Drag this icon to your mail application's composer to attach the request to a mail.")); dragQueen->setAlignment(Qt::AlignCenter); resultGBLayout->addWidget(dragQueen); scrollAreaLayout->addWidget(resultGB); restartWizardPB = new QPushButton{i18n("Restart This Wizard (Keeps Your Parameters)"), parent}; scrollAreaLayout->addWidget(restartWizardPB); nextStepsGB = new QGroupBox{i18nc("@title:group", "Next Steps"), parent}; auto nextStepsGBLayout = new QVBoxLayout{nextStepsGB}; saveRequestToFilePB = new QPushButton{i18n("Save Certificate Request To File..."), nextStepsGB}; nextStepsGBLayout->addWidget(saveRequestToFilePB); sendRequestByEMailPB = new QPushButton{i18n("Send Certificate Request By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendRequestByEMailPB); makeBackupPB = new QPushButton{i18n("Make a Backup Of Your Key Pair..."), nextStepsGB}; nextStepsGBLayout->addWidget(makeBackupPB); sendCertificateByEMailPB = new QPushButton{i18n("Send Public Key By EMail..."), nextStepsGB}; nextStepsGBLayout->addWidget(sendCertificateByEMailPB); uploadToKeyserverPB = new QPushButton{i18n("Upload Public Key To Directory Service..."), nextStepsGB}; nextStepsGBLayout->addWidget(uploadToKeyserverPB); createRevocationRequestPB = new QPushButton{i18n("Create Revocation Request..."), nextStepsGB}; nextStepsGBLayout->addWidget(createRevocationRequestPB); createSigningCertificatePB = new QPushButton{i18n("Create Signing Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createSigningCertificatePB); createEncryptionCertificatePB = new QPushButton{i18n("Create Encryption Certificate With Same Parameters"), nextStepsGB}; nextStepsGBLayout->addWidget(createEncryptionCertificatePB); scrollAreaLayout->addWidget(nextStepsGB); mainLayout->addWidget(scrollArea); } }; ResultPage::ResultPage(QWidget *p) : WizardPage{p} , ui{new UI{this}} , initialized{false} , successfullyCreatedSigningCertificate{false} , successfullyCreatedEncryptionCertificate{false} { setObjectName(QString::fromUtf8("Kleo__NewCertificateUi__ResultPage")); connect(ui->saveRequestToFilePB, &QPushButton::clicked, this, &ResultPage::slotSaveRequestToFile); connect(ui->sendRequestByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendRequestByEMail); connect(ui->sendCertificateByEMailPB, &QPushButton::clicked, this, &ResultPage::slotSendCertificateByEMail); connect(ui->uploadToKeyserverPB, &QPushButton::clicked, this, &ResultPage::slotUploadCertificateToDirectoryServer); connect(ui->makeBackupPB, &QPushButton::clicked, this, &ResultPage::slotBackupCertificate); connect(ui->createRevocationRequestPB, &QPushButton::clicked, this, &ResultPage::slotCreateRevocationRequest); connect(ui->createSigningCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateSigningCertificate); connect(ui->createEncryptionCertificatePB, &QPushButton::clicked, this, &ResultPage::slotCreateEncryptionCertificate); ui->dragQueen->setPixmap(QIcon::fromTheme(QStringLiteral("kleopatra")).pixmap(64, 64)); registerField(QStringLiteral("error"), ui->errorTB, "plainText"); registerField(QStringLiteral("result"), ui->resultTB, "plainText"); registerField(QStringLiteral("url"), ui->dragQueen, "url"); // hidden field, since QWizard can't deal with non-widget-backed fields... auto le = new QLineEdit(this); le->hide(); registerField(QStringLiteral("fingerprint"), le); } ResultPage::~ResultPage() = default; void ResultPage::initializePage() { const bool error = isError(); if (error) { setTitle(i18nc("@title", "Key Creation Failed")); setSubTitle(i18n("Key pair creation failed. Please find details about the failure below.")); } else { setTitle(i18nc("@title", "Key Pair Successfully Created")); setSubTitle(i18n("Your new key pair was created successfully. Please find details on the result and some suggested next steps below.")); } ui->resultTB->setVisible(!error); ui->errorTB->setVisible(error); ui->dragQueen->setVisible(!error && !pgp()); ui->restartWizardPB->setVisible(error); ui->nextStepsGB->setVisible(!error); ui->saveRequestToFilePB->setVisible(!pgp()); ui->makeBackupPB->setVisible(pgp()); ui->createRevocationRequestPB->setVisible(pgp() && false); // not implemented ui->sendCertificateByEMailPB->setVisible(pgp()); ui->sendRequestByEMailPB->setVisible(!pgp()); ui->uploadToKeyserverPB->setVisible(pgp()); if (!error && !pgp()) { if (signingAllowed() && !encryptionAllowed()) { successfullyCreatedSigningCertificate = true; } else if (!signingAllowed() && encryptionAllowed()) { successfullyCreatedEncryptionCertificate = true; } else { successfullyCreatedEncryptionCertificate = successfullyCreatedSigningCertificate = true; } } ui->createSigningCertificatePB->setVisible(successfullyCreatedEncryptionCertificate && !successfullyCreatedSigningCertificate); ui->createEncryptionCertificatePB->setVisible(successfullyCreatedSigningCertificate && !successfullyCreatedEncryptionCertificate); if (error) { wizard()->setOptions(wizard()->options() & ~QWizard::NoCancelButtonOnLastPage); } else { wizard()->setOptions(wizard()->options() | QWizard::NoCancelButtonOnLastPage); } if (!initialized) { connect(ui->restartWizardPB, &QAbstractButton::clicked, this, [this]() { restartAtEnterDetailsPage(); }); } initialized = true; } bool ResultPage::isError() const { return !ui->errorTB->document()->isEmpty(); } bool ResultPage::isComplete() const { return !isError(); } Key ResultPage::key() const { return KeyCache::instance()->findByFingerprint(fingerprint().toLatin1().constData()); } void ResultPage::slotSaveRequestToFile() { QString fileName = FileDialog::getSaveFileName(this, i18nc("@title", "Save Request"), QStringLiteral("imp"), i18n("PKCS#10 Requests (*.p10)")); if (fileName.isEmpty()) { return; } - if (!fileName.endsWith(QLatin1String(".p10"), Qt::CaseInsensitive)) { - fileName += QLatin1String(".p10"); + if (!fileName.endsWith(QLatin1StringView(".p10"), Qt::CaseInsensitive)) { + fileName += QLatin1StringView(".p10"); } QFile src(QUrl(url()).toLocalFile()); if (!src.copy(fileName)) KMessageBox::error(this, xi18nc("@info", "Could not copy temporary file %1 " "to file %2: %3", src.fileName(), fileName, src.errorString()), i18nc("@title", "Error Saving Request")); else KMessageBox::information(this, xi18nc("@info", "Successfully wrote request to %1." "You should now send the request to the Certification Authority (CA).", fileName), i18nc("@title", "Request Saved")); } void ResultPage::slotSendRequestByEMail() { if (pgp()) { return; } const KConfigGroup config(KSharedConfig::openConfig(), QStringLiteral("CertificateCreationWizard")); invokeMailer(config.readEntry("CAEmailAddress"), // to i18n("Please process this certificate."), // subject i18n("Please process this certificate and inform the sender about the location to fetch the resulting certificate.\n\nThanks,\n"), // body QFileInfo{QUrl(url()).toLocalFile()}); // attachment KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then drag the Kleopatra icon and drop " "it on the message compose window of your mail client." "If that does not work, either, save the request to a file, and then attach that."), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-mailto-troubles")); } void ResultPage::slotSendCertificateByEMail() { if (!pgp() || exportCertificateCommand) { return; } auto cmd = new ExportCertificateCommand(key()); connect(cmd, &ExportCertificateCommand::finished, this, &ResultPage::slotSendCertificateByEMailContinuation); - cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1String(".asc"))); + cmd->setOpenPGPFileName(tmpDir().absoluteFilePath(fingerprint() + QLatin1StringView(".asc"))); cmd->start(); exportCertificateCommand = cmd; } void ResultPage::slotSendCertificateByEMailContinuation() { if (!exportCertificateCommand) { return; } // ### better error handling? const QString fileName = exportCertificateCommand->openPGPFileName(); qCDebug(KLEOPATRA_LOG) << "fileName" << fileName; exportCertificateCommand = nullptr; if (fileName.isEmpty()) { return; } invokeMailer(i18n("My new public OpenPGP key"), // subject i18n("Please find attached my new public OpenPGP key."), // body QFileInfo{fileName}); KMessageBox::information(this, xi18nc("@info", "Kleopatra tried to send a mail via your default mail client." "Some mail clients are known not to support attachments when invoked this way." "If your mail client does not have an attachment, then attach the file %1 manually.", fileName), i18nc("@title", "Sending Mail"), QStringLiteral("newcertificatewizard-openpgp-mailto-troubles")); } void ResultPage::slotUploadCertificateToDirectoryServer() { if (pgp()) { (new ExportOpenPGPCertsToServerCommand(key()))->start(); } } void ResultPage::slotBackupCertificate() { if (pgp()) { (new ExportSecretKeyCommand(key()))->start(); } } void ResultPage::slotCreateRevocationRequest() { } void ResultPage::slotCreateSigningCertificate() { if (successfullyCreatedSigningCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::slotCreateEncryptionCertificate() { if (successfullyCreatedEncryptionCertificate) { return; } toggleSignEncryptAndRestart(); } void ResultPage::toggleSignEncryptAndRestart() { if (!wizard()) { return; } if (KMessageBox::warningContinueCancel(this, i18nc("@info", "This operation will delete the certification request. " "Please make sure that you have sent or saved it before proceeding."), i18nc("@title", "Certification Request About To Be Deleted")) != KMessageBox::Continue) { return; } const bool sign = signingAllowed(); const bool encr = encryptionAllowed(); setField(QStringLiteral("signingAllowed"), !sign); setField(QStringLiteral("encryptionAllowed"), !encr); restartAtEnterDetailsPage(); } #include "moc_resultpage_p.cpp" diff --git a/src/selftest/gpgconfcheck.cpp b/src/selftest/gpgconfcheck.cpp index 74b6f547a..fe3a6fa2b 100644 --- a/src/selftest/gpgconfcheck.cpp +++ b/src/selftest/gpgconfcheck.cpp @@ -1,88 +1,88 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/gpgconfcheck.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "gpgconfcheck.h" #include "implementation_p.h" #include #include #include "kleopatra_debug.h" #include #include #include using namespace Kleo; using namespace Kleo::_detail; namespace { class GpgConfCheck : public SelfTestImplementation { QString m_component; public: explicit GpgConfCheck(const char *component) - : SelfTestImplementation(i18nc("@title", "%1 Configuration Check", component && *component ? QLatin1String(component) : QLatin1String("gpgconf"))) - , m_component(QLatin1String(component)) + : SelfTestImplementation(i18nc("@title", "%1 Configuration Check", component && *component ? QLatin1StringView(component) : QLatin1String("gpgconf"))) + , m_component(QLatin1StringView(component)) { runTest(); } void runTest() { const auto conf = QGpgME::cryptoConfig(); QString message; m_passed = true; if (!conf) { message = QStringLiteral("Could not be started."); m_passed = false; } else if (m_component.isEmpty() && conf->componentList().empty()) { message = QStringLiteral("Could not list components."); m_passed = false; } else if (!m_component.isEmpty()) { const auto comp = conf->component(m_component); if (!comp) { message = QStringLiteral("Binary could not be found."); m_passed = false; } else if (comp->groupList().empty()) { // If we don't have any group it means that list-options // for this component failed. message = QStringLiteral("The configuration file is invalid."); m_passed = false; } } if (!m_passed) { m_error = i18nc("self-test did not pass", "Failed"); m_explanation = i18n( "There was an error executing the GnuPG configuration self-check for %2:\n" " %1\n" "You might want to execute \"gpgconf %3\" on the command line.\n", message, m_component.isEmpty() ? QStringLiteral("GnuPG") : m_component, QStringLiteral("--check-options ") + (m_component.isEmpty() ? QString() : m_component)); // To avoid modifying the l10n m_explanation.replace(QLatin1Char('\n'), QStringLiteral("
    ")); } } }; } std::shared_ptr Kleo::makeGpgConfCheckConfigurationSelfTest(const char *component) { return std::shared_ptr(new GpgConfCheck(component)); } diff --git a/src/selftest/libkleopatrarccheck.cpp b/src/selftest/libkleopatrarccheck.cpp index 0704f6faa..d7eb06d6d 100644 --- a/src/selftest/libkleopatrarccheck.cpp +++ b/src/selftest/libkleopatrarccheck.cpp @@ -1,66 +1,66 @@ /* -*- mode: c++; c-basic-offset:4 -*- selftest/libkleopatrarccheck.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "libkleopatrarccheck.h" #include "implementation_p.h" #include #include #include using namespace Kleo; using namespace Kleo::_detail; namespace { class LibKleopatraRcCheck : public SelfTestImplementation { public: explicit LibKleopatraRcCheck() : SelfTestImplementation(i18nc("@title", "Config File 'libkleopatrarc'")) { runTest(); } void runTest() { QStringList errors; ArchiveDefinition::getArchiveDefinitions(errors); ChecksumDefinition::getChecksumDefinitions(errors); m_passed = errors.empty(); if (m_passed) { return; } m_error = i18n("Errors found"); // The building of the following string is a bit of a hack to avoid // that xi18nc does not escape the html tags while not breaking // the historic string. m_explanation = xi18nc("@info", "Kleopatra detected the following errors in the libkleopatrarc configuration:" "%1", QStringLiteral("%1")) - .arg(QStringLiteral("
    1. ") + errors.join(QLatin1String("
    2. ")) + QStringLiteral("
    ")); + .arg(QStringLiteral("
    1. ") + errors.join(QLatin1StringView("
    2. ")) + QStringLiteral("
    ")); } ///* reimp */ bool canFixAutomatically() const { return false; } }; } std::shared_ptr Kleo::makeLibKleopatraRcSelfTest() { return std::shared_ptr(new LibKleopatraRcCheck); } diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index 9ce183fae..3ba33fea9 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,1223 +1,1223 @@ /* -*- mode: c++; c-basic-offset:4 -*- smartcard/readerstatus.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "readerstatus.h" #include "deviceinfowatcher.h" #include #include #include #include #include #include #include #include #include #include #include #include #include "netkeycard.h" #include "openpgpcard.h" #include "p15card.h" #include "pivcard.h" #include #include #include #include #include #include "utils/kdtoolsglobal.h" #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace GpgME; static ReaderStatus *self = nullptr; #define xtoi_1(p) (*(p) <= '9' ? (*(p) - '0') : *(p) <= 'F' ? (*(p) - 'A' + 10) : (*(p) - 'a' + 10)) #define xtoi_2(p) ((xtoi_1(p) * 16) + xtoi_1((p) + 1)) Q_DECLARE_METATYPE(GpgME::Error) namespace { static bool gpgHasMultiCardMultiAppSupport() { return !(engineInfo(GpgME::GpgEngine).engineVersion() < "2.3.0"); } static QDebug operator<<(QDebug s, const std::vector> &v) { using pair = std::pair; s << '('; for (const pair &p : v) { s << "status(" << QString::fromStdString(p.first) << ") =" << QString::fromStdString(p.second) << '\n'; } return s << ')'; } struct CardApp { std::string serialNumber; std::string appName; }; static void logUnexpectedStatusLine(const std::pair &line, const std::string &prefix = std::string(), const std::string &command = std::string()) { qCWarning(KLEOPATRA_LOG) << (!prefix.empty() ? QString::fromStdString(prefix + ": ") : QString()) << "Unexpected status line" - << (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1String(":")) << QString::fromStdString(line.first) - << QString::fromStdString(line.second); + << (!command.empty() ? QString::fromStdString(" on " + command + ":") : QLatin1StringView(":")) + << QString::fromStdString(line.first) << QString::fromStdString(line.second); } static int parse_app_version(const std::string &s) { return std::atoi(s.c_str()); } static Card::PinState parse_pin_state(const QString &s) { bool ok; int i = s.toInt(&ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "Failed to parse pin state" << s; return Card::UnknownPinState; } switch (i) { case -4: return Card::NullPin; case -3: return Card::PinBlocked; case -2: return Card::NoPin; case -1: return Card::UnknownPinState; default: if (i < 0) { return Card::UnknownPinState; } else { return Card::PinOk; } } } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return Assuan::sendStatusCommand(gpgAgent, cmd.c_str(), err); } static const std::string getAttribute(std::shared_ptr &gpgAgent, const char *attribute, const char *versionHint) { Error err; const auto result = scd_getattr_status(gpgAgent, attribute, err); if (err) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "Querying for attribute" << attribute << "not yet supported; needs GnuPG" << versionHint; } else { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR " << attribute << " failed:" << err; } return std::string(); } return result; } enum GetCardsAndAppsOptions { WithReportedAppOrder, WithStableAppOrder, }; static std::vector getCardsAndApps(std::shared_ptr &gpgAgent, GetCardsAndAppsOptions options, Error &err) { std::vector result; if (gpgHasMultiCardMultiAppSupport()) { const std::string command = "SCD GETINFO all_active_apps"; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return result; } for (const auto &statusLine : statusLines) { if (statusLine.first == "SERIALNO") { const auto serialNumberAndApps = QByteArray::fromStdString(statusLine.second).split(' '); if (serialNumberAndApps.size() >= 2) { const auto serialNumber = serialNumberAndApps[0]; auto apps = serialNumberAndApps.mid(1); if (options == WithStableAppOrder) { // sort the apps to get a stable order independently of the currently selected application std::sort(apps.begin(), apps.end()); } for (const auto &app : apps) { qCDebug(KLEOPATRA_LOG) << "getCardsAndApps(): Found card" << serialNumber << "with app" << app; result.push_back({serialNumber.toStdString(), app.toStdString()}); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } else { logUnexpectedStatusLine(statusLine, "getCardsAndApps()", command); } } } else { // use SCD SERIALNO to get the currently active card const auto serialNumber = Assuan::sendStatusCommand(gpgAgent, "SCD SERIALNO", err); if (err) { return result; } // use SCD GETATTR APPTYPE to find out which app is active auto appName = scd_getattr_status(gpgAgent, "APPTYPE", err); std::transform(appName.begin(), appName.end(), appName.begin(), [](unsigned char c) { return std::tolower(c); }); if (err) { return result; } result.push_back({serialNumber, appName}); } return result; } static std::string switchCard(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const std::string command = "SCD SWITCHCARD " + serialNumber; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second == serialNumber) { return serialNumber; } qCWarning(KLEOPATRA_LOG) << "switchCard():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber << ")"; return std::string(); } static std::string switchApp(std::shared_ptr &gpgAgent, const std::string &serialNumber, const std::string &appName, Error &err) { const std::string command = "SCD SWITCHAPP " + appName; const auto statusLines = Assuan::sendStatusLinesCommand(gpgAgent, command.c_str(), err); if (err) { return std::string(); } if (statusLines.size() == 1 && statusLines[0].first == "SERIALNO" && statusLines[0].second.find(serialNumber + ' ' + appName) == 0) { return appName; } qCWarning(KLEOPATRA_LOG) << "switchApp():" << command << "returned" << statusLines << "(expected:" << "SERIALNO " + serialNumber + ' ' + appName + "..." << ")"; return std::string(); } static std::vector getCardApps(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { const auto cardApps = getCardsAndApps(gpgAgent, WithReportedAppOrder, err); if (err) { return {}; } std::vector apps; kdtools::transform_if( cardApps.begin(), cardApps.end(), std::back_inserter(apps), [](const auto &cardApp) { return cardApp.appName; }, [serialNumber](const auto &cardApp) { return cardApp.serialNumber == serialNumber; }); qCDebug(KLEOPATRA_LOG) << __func__ << "apps:" << apps; return apps; } static void switchCardBackToOpenPGPApp(std::shared_ptr &gpgAgent, const std::string &serialNumber, Error &err) { if (!gpgHasMultiCardMultiAppSupport()) { return; } const auto apps = getCardApps(gpgAgent, serialNumber, err); if (err || apps.empty() || apps[0] == OpenPGPCard::AppName) { return; } if (Kleo::contains(apps, OpenPGPCard::AppName)) { switchApp(gpgAgent, serialNumber, OpenPGPCard::AppName, err); } } static const char *get_openpgp_card_manufacturer_from_serial_number(const std::string &serialno) { qCDebug(KLEOPATRA_LOG) << "get_openpgp_card_manufacturer_from_serial_number(" << serialno.c_str() << ")"; const bool isProperOpenPGPCardSerialNumber = serialno.size() == 32 && serialno.substr(0, 12) == "D27600012401"; if (isProperOpenPGPCardSerialNumber) { const char *sn = serialno.c_str(); const int manufacturerId = xtoi_2(sn + 16) * 256 + xtoi_2(sn + 18); switch (manufacturerId) { case 0x0001: return "PPC Card Systems"; case 0x0002: return "Prism"; case 0x0003: return "OpenFortress"; case 0x0004: return "Wewid"; case 0x0005: return "ZeitControl"; case 0x0006: return "Yubico"; case 0x0007: return "OpenKMS"; case 0x0008: return "LogoEmail"; case 0x002A: return "Magrathea"; case 0x1337: return "Warsaw Hackerspace"; case 0xF517: return "FSIJ"; /* 0x0000 and 0xFFFF are defined as test cards per spec, 0xFF00 to 0xFFFE are assigned for use with randomly created serial numbers. */ case 0x0000: case 0xffff: return "test card"; default: return (manufacturerId & 0xff00) == 0xff00 ? "unmanaged S/N range" : "unknown"; } } else { return "unknown"; } } static std::vector get_openpgp_card_supported_algorithms_announced_by_card(std::shared_ptr &gpgAgent) { static constexpr std::string_view cardSlotPrefix = "OPENPGP.1 "; static const std::map algoMapping = { {"cv25519", "curve25519"}, {"cv448", "curve448"}, {"ed25519", "curve25519"}, {"ed448", "curve448"}, {"x448", "curve448"}, }; Error err; const auto lines = Assuan::sendStatusLinesCommand(gpgAgent, "SCD GETATTR KEY-ATTR-INFO", err); if (err) { return {}; } std::vector algos; kdtools::transform_if( lines.cbegin(), lines.cend(), std::back_inserter(algos), [](const auto &line) { auto algo = line.second.substr(cardSlotPrefix.size()); // map a few algorithms to the standard names used by us const auto mapping = algoMapping.find(algo); if (mapping != algoMapping.end()) { algo = mapping->second; } return algo; }, [](const auto &line) { // only consider KEY-ATTR-INFO status lines for the first card slot; // for now, we assume that all card slots support the same algorithms return line.first == "KEY-ATTR-INFO" && line.second.starts_with(cardSlotPrefix); }); // remove duplicate algorithms std::sort(algos.begin(), algos.end()); algos.erase(std::unique(algos.begin(), algos.end()), algos.end()); qCDebug(KLEOPATRA_LOG) << __func__ << "returns" << algos; return algos; } static std::vector get_openpgp_card_supported_algorithms(Card *card, std::shared_ptr &gpgAgent) { // first ask the smart card for the supported algorithms const std::vector announcedAlgos = get_openpgp_card_supported_algorithms_announced_by_card(gpgAgent); if (!announcedAlgos.empty()) { return announcedAlgos; } // otherwise, fall back to hard-coded lists if ((card->cardType() == "yubikey") && (card->cardVersion() >= 0x050203)) { return { "rsa2048", "rsa3072", "rsa4096", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "curve25519", }; } else if ((card->cardType() == "zeitcontrol") && (card->appVersion() >= 0x0304)) { return { "rsa2048", "rsa3072", "rsa4096", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", }; } return {"rsa2048", "rsa3072", "rsa4096"}; } static bool isOpenPGPCardSerialNumber(const std::string &serialNumber) { return serialNumber.size() == 32 && serialNumber.substr(0, 12) == "D27600012401"; } static const std::string getDisplaySerialNumber(std::shared_ptr &gpgAgent, Error &err) { const auto displaySerialNumber = scd_getattr_status(gpgAgent, "$DISPSERIALNO", err); if (err && err.code() != GPG_ERR_INV_NAME) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR $DISPSERIALNO failed:" << err; } return displaySerialNumber; } static void setDisplaySerialNumber(Card *card, std::shared_ptr &gpgAgent) { static const QRegularExpression leadingZeros(QStringLiteral("^0*")); Error err; const QString displaySerialNumber = QString::fromStdString(getDisplaySerialNumber(gpgAgent, err)); if (err) { card->setDisplaySerialNumber(QString::fromStdString(card->serialNumber())); return; } if (isOpenPGPCardSerialNumber(card->serialNumber()) && displaySerialNumber.size() == 12) { // add a space between manufacturer id and card id for OpenPGP cards card->setDisplaySerialNumber(displaySerialNumber.left(4) + QLatin1Char(' ') + displaySerialNumber.right(8)); } else { card->setDisplaySerialNumber(displaySerialNumber); } return; } static void learnCardKeyStubs(const Card *card, std::shared_ptr &gpg_agent) { for (const KeyPairInfo &keyInfo : card->keyInfos()) { if (!keyInfo.grip.empty()) { Error err; const auto command = std::string("READKEY --card --no-data -- ") + keyInfo.keyRef; (void)Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; } } } } static void handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pgpCard = new OpenPGPCard(*ci); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err.code()) { ci->setStatus(Card::CardError); return; } pgpCard->setCardInfo(info); if (pgpCard->manufacturer().empty()) { // fallback in case MANUFACTURER is not yet included in the card info pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } setDisplaySerialNumber(pgpCard, gpg_agent); learnCardKeyStubs(pgpCard, gpg_agent); pgpCard->setSupportedAlgorithms(get_openpgp_card_supported_algorithms(pgpCard, gpg_agent)); ci.reset(pgpCard); } static void readKeyPairInfoFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READKEY --info-only -- ") + keyRef; const auto keyPairInfoLines = Assuan::sendStatusLinesCommand(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } // this adds the key algorithm (and the key creation date, but that seems to be unset for PIV) to the existing key pair information pivCard->setCardInfo(keyPairInfoLines); } static void readCertificateFromPIVCard(const std::string &keyRef, PIVCard *pivCard, const std::shared_ptr &gpg_agent) { Error err; const std::string command = std::string("SCD READCERT ") + keyRef; const std::string certificateData = Assuan::sendDataCommand(gpg_agent, command.c_str(), err); if (err && err.code() != GPG_ERR_NOT_FOUND) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } if (certificateData.empty()) { qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): No certificate stored on card"; return; } qCDebug(KLEOPATRA_LOG) << "readCertificateFromPIVCard(" << QString::fromStdString(keyRef) << "): Found certificate stored on card"; pivCard->setCertificateData(keyRef, certificateData); } static void handle_piv_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pivCard = new PIVCard(*ci); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); setDisplaySerialNumber(pivCard, gpg_agent); for (const KeyPairInfo &keyInfo : pivCard->keyInfos()) { if (!keyInfo.grip.empty()) { readKeyPairInfoFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyInfo.keyRef, pivCard, gpg_agent); } } learnCardKeyStubs(pivCard, gpg_agent); ci.reset(pivCard); } static void handle_p15_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto p15Card = new P15Card(*ci); auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } const auto fprs = Assuan::sendStatusLinesCommand(gpg_agent, "SCD GETATTR KEY-FPR", err); if (!err) { info.insert(info.end(), fprs.begin(), fprs.end()); } p15Card->setCardInfo(info); learnCardKeyStubs(p15Card, gpg_agent); setDisplaySerialNumber(p15Card, gpg_agent); ci.reset(p15Card); } static void handle_netkey_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto nkCard = new NetKeyCard(*ci); ci.reset(nkCard); ci->setAppVersion(parse_app_version(scd_getattr_status(gpg_agent, "NKS-VERSION", err))); if (err.code()) { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR NKS-VERSION failed:" << err; ci->setErrorMsg(QStringLiteral("NKS-VERSION failed: ") + Formatting::errorAsString(err)); return; } if (ci->appVersion() < 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 (or later) card, giving up. Version:" << ci->appVersion(); ci->setErrorMsg(QStringLiteral("NetKey v%1 cards are not supported.").arg(ci->appVersion())); return; } setDisplaySerialNumber(nkCard, gpg_agent); // the following only works for NKS v3... const auto chvStatus = QString::fromStdString(scd_getattr_status(gpg_agent, "CHV-STATUS", err)).split(QLatin1Char(' ')); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "Running SCD GETATTR CHV-STATUS failed:" << err; ci->setErrorMsg(QStringLiteral("CHV-Status failed: ") + Formatting::errorAsString(err)); return; } std::vector states; states.reserve(chvStatus.count()); // CHV Status for NKS v3 is // Pin1 (Normal pin) Pin2 (Normal PUK) // SigG1 SigG PUK. int num = 0; for (const auto &state : chvStatus) { const auto parsed = parse_pin_state(state); states.push_back(parsed); if (parsed == Card::NullPin) { if (num == 0) { ci->setHasNullPin(true); } } ++num; } nkCard->setPinStates(states); const auto info = Assuan::sendStatusLinesCommand(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } nkCard->setCardInfo(info); learnCardKeyStubs(nkCard, gpg_agent); } static std::shared_ptr get_card_status(const std::string &serialNumber, const std::string &appName, std::shared_ptr &gpg_agent) { qCDebug(KLEOPATRA_LOG) << "get_card_status(" << serialNumber << ',' << appName << ',' << gpg_agent.get() << ')'; auto ci = std::shared_ptr(new Card()); if (gpgHasMultiCardMultiAppSupport()) { // select card Error err; const auto result = switchCard(gpg_agent, serialNumber, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching card failed"; ci->setStatus(Card::CardError); return ci; } ci->setStatus(Card::CardPresent); } else { ci->setStatus(Card::CardPresent); } if (gpgHasMultiCardMultiAppSupport()) { // select app Error err; const auto result = switchApp(gpg_agent, serialNumber, appName, err); if (err) { if (err.code() == GPG_ERR_CARD_NOT_PRESENT || err.code() == GPG_ERR_CARD_REMOVED) { ci->setStatus(Card::NoCard); } else { ci->setStatus(Card::CardError); } return ci; } if (result.empty()) { qCWarning(KLEOPATRA_LOG) << "get_card_status: switching app failed"; ci->setStatus(Card::CardError); return ci; } } ci->setSerialNumber(serialNumber); ci->setSigningKeyRef(getAttribute(gpg_agent, "$SIGNKEYID", "2.2.18")); ci->setEncryptionKeyRef(getAttribute(gpg_agent, "$ENCRKEYID", "2.2.18")); // Handle different card types if (appName == NetKeyCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found Netkey card" << ci->serialNumber().c_str() << "end"; handle_netkey_card(ci, gpg_agent); } else if (appName == OpenPGPCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; ci->setAuthenticationKeyRef(OpenPGPCard::pgpAuthKeyRef()); handle_openpgp_card(ci, gpg_agent); } else if (appName == PIVCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found PIV card" << ci->serialNumber().c_str() << "end"; handle_piv_card(ci, gpg_agent); } else if (appName == P15Card::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found P15 card" << ci->serialNumber().c_str() << "end"; handle_p15_card(ci, gpg_agent); } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName; } if (gpgHasMultiCardMultiAppSupport() && appName != OpenPGPCard::AppName) { // switch the card app back to OpenPGP; errors are ignored GpgME::Error dummy; switchCardBackToOpenPGPApp(gpg_agent, serialNumber, dummy); } return ci; } static bool isCardNotPresentError(const GpgME::Error &err) { // see fixup_scd_errors() in gpg-card.c return err && ((err.code() == GPG_ERR_CARD_NOT_PRESENT) || ((err.code() == GPG_ERR_ENODEV || err.code() == GPG_ERR_CARD_REMOVED) && (err.sourceID() == GPG_ERR_SOURCE_SCD))); } static std::vector> update_cardinfo(std::shared_ptr &gpgAgent) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo()"; // ensure that a card is present and that all cards are properly set up { Error err; const char *command = (gpgHasMultiCardMultiAppSupport()) ? "SCD SERIALNO --all" : "SCD SERIALNO"; const std::string serialno = Assuan::sendStatusCommand(gpgAgent, command, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector>(); } else { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } } Error err; const std::vector cardApps = getCardsAndApps(gpgAgent, WithStableAppOrder, err); if (err) { if (isCardNotPresentError(err)) { qCDebug(KLEOPATRA_LOG) << "update_cardinfo: No card present"; return std::vector>(); } else { qCWarning(KLEOPATRA_LOG) << "Getting active apps on all inserted cards failed:" << err; auto ci = std::shared_ptr(new Card()); ci->setStatus(Card::CardError); return std::vector>(1, ci); } } std::vector> cards; for (const auto &cardApp : cardApps) { const auto card = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); cards.push_back(card); } return cards; } } // namespace struct Transaction { CardApp cardApp; QByteArray command; QPointer receiver; ReaderStatus::TransactionFunc slot; AssuanTransaction *assuanTransaction; }; static const Transaction updateTransaction = {{"__all__", "__all__"}, "__update__", nullptr, nullptr, nullptr}; static const Transaction quitTransaction = {{"__all__", "__all__"}, "__quit__", nullptr, nullptr, nullptr}; namespace { class ReaderStatusThread : public QThread { Q_OBJECT public: explicit ReaderStatusThread(QObject *parent = nullptr) : QThread(parent) , m_gnupgHomePath(Kleo::gnupgHomeDirectory()) , m_transactions(1, updateTransaction) // force initial scan { connect(this, &ReaderStatusThread::oneTransactionFinished, this, &ReaderStatusThread::slotOneTransactionFinished); } std::vector> cardInfos() const { const QMutexLocker locker(&m_mutex); return m_cardInfos; } Card::Status cardStatus(unsigned int slot) const { const QMutexLocker locker(&m_mutex); if (slot < m_cardInfos.size()) { return m_cardInfos[slot]->status(); } else { return Card::NoCard; } } void addTransaction(const Transaction &t) { const QMutexLocker locker(&m_mutex); m_transactions.push_back(t); m_waitForTransactions.wakeOne(); } Q_SIGNALS: void firstCardWithNullPinChanged(const std::string &serialNumber); void anyCardCanLearnKeysChanged(bool); void cardAdded(const std::string &serialNumber, const std::string &appName); void cardChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); void updateFinished(); void oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: void deviceStatusChanged(const QByteArray &details) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::deviceStatusChanged(" << details << ")"; addTransaction(updateTransaction); } void ping() { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[GUI]::ping()"; addTransaction(updateTransaction); } void stop() { const QMutexLocker locker(&m_mutex); m_transactions.push_front(quitTransaction); m_waitForTransactions.wakeOne(); } private Q_SLOTS: void slotOneTransactionFinished(const GpgME::Error &err) { std::list ft; KDAB_SYNCHRONIZED(m_mutex) ft.splice(ft.begin(), m_finishedTransactions); for (const Transaction &t : std::as_const(ft)) if (t.receiver && t.slot) { QMetaObject::invokeMethod( t.receiver, [&t, &err]() { t.slot(err); }, Qt::DirectConnection); } } private: void run() override { while (true) { std::shared_ptr gpgAgent; CardApp cardApp; QByteArray command; bool nullSlot = false; AssuanTransaction *assuanTransaction = nullptr; std::list item; std::vector> oldCards; while (!KeyCache::instance()->initialized()) { qCDebug(KLEOPATRA_LOG) << "Waiting for Keycache to be initialized."; sleep(1); } Error err; std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return; } gpgAgent = std::shared_ptr(c.release()); KDAB_SYNCHRONIZED(m_mutex) { while (m_transactions.empty()) { // go to sleep waiting for more work: qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: waiting for commands"; m_waitForTransactions.wait(&m_mutex); } // splice off the first transaction without // copying, so we own it without really importing // it into this thread (the QPointer isn't // thread-safe): item.splice(item.end(), m_transactions, m_transactions.begin()); // make local copies of the interesting stuff so // we can release the mutex again: cardApp = item.front().cardApp; command = item.front().command; nullSlot = !item.front().slot; // we take ownership of the assuan transaction std::swap(assuanTransaction, item.front().assuanTransaction); oldCards = m_cardInfos; } qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread[2nd]: new iteration command=" << command << " ; nullSlot=" << nullSlot; // now, let's see what we got: if (nullSlot && command == quitTransaction.command) { return; // quit } if ((nullSlot && command == updateTransaction.command)) { bool anyError = false; if (cardApp.serialNumber == "__all__" || cardApp.appName == "__all__") { std::vector> newCards = update_cardinfo(gpgAgent); KDAB_SYNCHRONIZED(m_mutex) { m_cardInfos = newCards; } bool anyLC = false; std::string firstCardWithNullPin; for (const auto &newCard : newCards) { const auto serialNumber = newCard->serialNumber(); const auto appName = newCard->appName(); const auto matchingOldCard = std::find_if(oldCards.cbegin(), oldCards.cend(), [serialNumber, appName](const std::shared_ptr &card) { return card->serialNumber() == serialNumber && card->appName() == appName; }); if (matchingOldCard == oldCards.cend()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added"; Q_EMIT cardAdded(serialNumber, appName); } else { if (*newCard != **matchingOldCard) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed"; Q_EMIT cardChanged(serialNumber, appName); } oldCards.erase(matchingOldCard); } if (newCard->canLearnKeys()) { anyLC = true; } if (newCard->hasNullPin() && firstCardWithNullPin.empty()) { firstCardWithNullPin = newCard->serialNumber(); } if (newCard->status() == Card::CardError) { anyError = true; } } for (const auto &oldCard : oldCards) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << oldCard->serialNumber() << "with app" << oldCard->appName() << "was removed"; Q_EMIT cardRemoved(oldCard->serialNumber(), oldCard->appName()); } Q_EMIT firstCardWithNullPinChanged(firstCardWithNullPin); Q_EMIT anyCardCanLearnKeysChanged(anyLC); } else { auto updatedCard = get_card_status(cardApp.serialNumber, cardApp.appName, gpgAgent); const auto serialNumber = updatedCard->serialNumber(); const auto appName = updatedCard->appName(); bool cardWasAdded = false; bool cardWasChanged = false; KDAB_SYNCHRONIZED(m_mutex) { const auto matchingCard = std::find_if(m_cardInfos.begin(), m_cardInfos.end(), [serialNumber, appName](const auto &card) { return card->serialNumber() == serialNumber && card->appName() == appName; }); if (matchingCard == m_cardInfos.end()) { m_cardInfos.push_back(updatedCard); cardWasAdded = true; } else { cardWasChanged = (*updatedCard != **matchingCard); m_cardInfos[std::distance(m_cardInfos.begin(), matchingCard)] = updatedCard; } if (updatedCard->status() == Card::CardError) { anyError = true; } } if (cardWasAdded) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "was added"; Q_EMIT cardAdded(serialNumber, appName); } else if (cardWasChanged) { qCDebug(KLEOPATRA_LOG) << "ReaderStatusThread: Card" << serialNumber << "with app" << appName << "changed"; Q_EMIT cardChanged(serialNumber, appName); } } if (anyError) { gpgAgent.reset(); } Q_EMIT updateFinished(); } else { GpgME::Error err; if (gpgHasMultiCardMultiAppSupport()) { switchCard(gpgAgent, cardApp.serialNumber, err); if (!err) { switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err); } } if (!err) { if (assuanTransaction) { (void)Assuan::sendCommand(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)Assuan::sendCommand(gpgAgent, command.constData(), err); } } KDAB_SYNCHRONIZED(m_mutex) // splice 'item' into m_finishedTransactions: m_finishedTransactions.splice(m_finishedTransactions.end(), item); Q_EMIT oneTransactionFinished(err); } } } private: mutable QMutex m_mutex; QWaitCondition m_waitForTransactions; const QString m_gnupgHomePath; // protected by m_mutex: std::vector> m_cardInfos; std::list m_transactions, m_finishedTransactions; }; } class ReaderStatus::Private : ReaderStatusThread { friend class Kleo::SmartCard::ReaderStatus; ReaderStatus *const q; public: explicit Private(ReaderStatus *qq) : ReaderStatusThread(qq) , q(qq) , watcher() { KDAB_SET_OBJECT_NAME(watcher); qRegisterMetaType("Kleo::SmartCard::Card::Status"); qRegisterMetaType("GpgME::Error"); connect(this, &::ReaderStatusThread::cardAdded, q, &ReaderStatus::cardAdded); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::cardRemoved, q, &ReaderStatus::cardRemoved); connect(this, &::ReaderStatusThread::updateFinished, q, &ReaderStatus::updateFinished); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); if (DeviceInfoWatcher::isSupported()) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using new DeviceInfoWatcher"; connect(&devInfoWatcher, &DeviceInfoWatcher::statusChanged, this, &::ReaderStatusThread::deviceStatusChanged); } else { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::Private: Using deprecated FileSystemWatcher"; watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } } ~Private() override { stop(); if (!wait(100)) { terminate(); wait(); } } private: std::string firstCardWithNullPinImpl() const { const auto cis = cardInfos(); const auto firstWithNullPin = std::find_if(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->hasNullPin(); }); return firstWithNullPin != cis.cend() ? (*firstWithNullPin)->serialNumber() : std::string(); } bool anyCardCanLearnKeysImpl() const { const auto cis = cardInfos(); return std::any_of(cis.cbegin(), cis.cend(), [](const std::shared_ptr &ci) { return ci->canLearnKeys(); }); } private: FileSystemWatcher watcher; DeviceInfoWatcher devInfoWatcher; }; ReaderStatus::ReaderStatus(QObject *parent) : QObject(parent) , d(new Private(this)) { self = this; qRegisterMetaType("std::string"); } ReaderStatus::~ReaderStatus() { self = nullptr; } // slot void ReaderStatus::startMonitoring() { d->start(); if (DeviceInfoWatcher::isSupported()) { connect(&d->devInfoWatcher, &DeviceInfoWatcher::startOfGpgAgentRequested, this, &ReaderStatus::startOfGpgAgentRequested); d->devInfoWatcher.start(); } } // static ReaderStatus *ReaderStatus::mutableInstance() { return self; } // static const ReaderStatus *ReaderStatus::instance() { return self; } Card::Status ReaderStatus::cardStatus(unsigned int slot) const { return d->cardStatus(slot); } std::string ReaderStatus::firstCardWithNullPin() const { return d->firstCardWithNullPinImpl(); } bool ReaderStatus::anyCardCanLearnKeys() const { return d->anyCardCanLearnKeysImpl(); } void ReaderStatus::startSimpleTransaction(const std::shared_ptr &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot) { const CardApp cardApp = {card->serialNumber(), card->appName()}; const Transaction t = {cardApp, command, receiver, slot, nullptr}; d->addTransaction(t); } void ReaderStatus::startTransaction(const std::shared_ptr &card, const QByteArray &command, QObject *receiver, const TransactionFunc &slot, std::unique_ptr transaction) { const CardApp cardApp = {card->serialNumber(), card->appName()}; const Transaction t = {cardApp, command, receiver, slot, transaction.release()}; d->addTransaction(t); } void ReaderStatus::updateStatus() { d->ping(); } void ReaderStatus::updateCard(const std::string &serialNumber, const std::string &appName) { const CardApp cardApp = {serialNumber, appName}; const Transaction t = {cardApp, updateTransaction.command, nullptr, nullptr, nullptr}; d->addTransaction(t); } std::vector> ReaderStatus::getCards() const { return d->cardInfos(); } std::shared_ptr ReaderStatus::getCard(const std::string &serialNumber, const std::string &appName) const { for (const auto &card : d->cardInfos()) { if (card->serialNumber() == serialNumber && card->appName() == appName) { qCDebug(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Found card with serial number" << serialNumber << "and app" << appName; return card; } } qCWarning(KLEOPATRA_LOG) << "ReaderStatus::getCard() - Did not find card with serial number" << serialNumber << "and app" << appName; return std::shared_ptr(); } // static std::string ReaderStatus::switchCard(std::shared_ptr &ctx, const std::string &serialNumber, Error &err) { return ::switchCard(ctx, serialNumber, err); } // static std::string ReaderStatus::switchApp(std::shared_ptr &ctx, const std::string &serialNumber, const std::string &appName, Error &err) { return ::switchApp(ctx, serialNumber, appName, err); } // static Error ReaderStatus::switchCardAndApp(const std::string &serialNumber, const std::string &appName) { Error err; if (!(engineInfo(GpgEngine).engineVersion() < "2.3.0")) { std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(c.release()); const auto resultSerialNumber = switchCard(assuanContext, serialNumber, err); if (err || resultSerialNumber != serialNumber) { qCWarning(KLEOPATRA_LOG) << "Switching to card" << QString::fromStdString(serialNumber) << "failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } const auto resultAppName = switchApp(assuanContext, serialNumber, appName, err); if (err || resultAppName != appName) { qCWarning(KLEOPATRA_LOG) << "Switching card to" << QString::fromStdString(appName) << "app failed"; if (!err) { err = Error::fromCode(GPG_ERR_UNEXPECTED); } return err; } } return err; } // static Error ReaderStatus::switchCardBackToOpenPGPApp(const std::string &serialNumber) { Error err; if (gpgHasMultiCardMultiAppSupport()) { std::unique_ptr c = Context::createForEngine(AssuanEngine, &err); if (err.code() == GPG_ERR_NOT_SUPPORTED) { return err; } auto assuanContext = std::shared_ptr(c.release()); ::switchCardBackToOpenPGPApp(assuanContext, serialNumber, err); } return err; } #include "readerstatus.moc" #include "moc_readerstatus.cpp" diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp index e4c312264..4852566de 100644 --- a/src/uiserver/assuanserverconnection.cpp +++ b/src/uiserver/assuanserverconnection.cpp @@ -1,1493 +1,1493 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/assuanserverconnection.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 */ #ifndef QT_NO_CAST_TO_ASCII #define QT_NO_CAST_TO_ASCII #endif #ifndef QT_NO_CAST_FROM_ASCII #define QT_NO_CAST_FROM_ASCII #endif #include #include #include "assuancommand.h" #include "assuanserverconnection.h" #include "sessiondata.h" #include #include #include #include #include #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 #include #include #include #include #ifdef __GLIBCXX__ #include // for is_sorted #endif #ifdef Q_OS_WIN #include #include #else #include #include #endif using namespace Kleo; static const unsigned int INIT_SOCKET_FLAGS = 3; // says info assuan... // static int(*USE_DEFAULT_HANDLER)(assuan_context_t,char*) = 0; static const int FOR_READING = 0; static const unsigned int MAX_ACTIVE_FDS = 32; static void my_assuan_release(assuan_context_t ctx) { if (ctx) { assuan_release(ctx); } } // std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server: using AssuanContextBase = std::shared_ptr::type>; struct AssuanContext : AssuanContextBase { AssuanContext() : AssuanContextBase() { } explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) { } void reset(assuan_context_t ctx = nullptr) { AssuanContextBase::reset(ctx, &my_assuan_release); } }; static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const char *err_msg) { return assuan_process_done(ctx, assuan_set_error(ctx, err, err_msg)); } static inline gpg_error_t assuan_process_done_msg(assuan_context_t ctx, gpg_error_t err, const QString &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.toUtf8().constData()); } static std::map upcase_option(const char *option, std::map options) { std::string value; bool value_found = false; auto it = options.begin(); while (it != options.end()) if (qstricmp(it->first.c_str(), option) == 0) { value = it->second; options.erase(it++); value_found = true; } else { ++it; } if (value_found) { options[option] = value; } return options; } static std::map parse_commandline(const char *line) { std::map result; if (line) { const char *begin = line; const char *lastEQ = nullptr; while (*line) { if (*line == ' ' || *line == '\t') { if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[std::string(begin, line - begin)] = std::string(); } } begin = line + 1; } else if (*line == '=') { if (line == begin) throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("No option name given")); else { lastEQ = line; } } ++line; } if (begin != line) { if (begin[0] == '-' && begin[1] == '-') { begin += 2; // skip initial "--" } if (lastEQ && lastEQ > begin) { result[std::string(begin, lastEQ - begin)] = hexdecode(std::string(lastEQ + 1, line - (lastEQ + 1))); } else { result[begin] = std::string(); } } } return result; } static WId wid_from_string(const QString &winIdStr, bool *ok = nullptr) { return static_cast(winIdStr.toULongLong(ok, 16)); } static void apply_window_id(QWidget *widget, const QString &winIdStr) { if (!widget || winIdStr.isEmpty()) { return; } bool ok = false; const WId wid = wid_from_string(winIdStr, &ok); if (!ok) { qCDebug(KLEOPATRA_LOG) << "window-id value" << wid << "doesn't look like a number"; return; } if (QWidget *pw = QWidget::find(wid)) { widget->setParent(pw, widget->windowFlags()); } else { widget->setAttribute(Qt::WA_NativeWindow, true); KWindowSystem::setMainWindow(widget->windowHandle(), wid); } } // // // AssuanServerConnection: // // class AssuanServerConnection::Private : public QObject { Q_OBJECT friend class ::Kleo::AssuanServerConnection; friend class ::Kleo::AssuanCommandFactory; friend class ::Kleo::AssuanCommand; AssuanServerConnection *const q; public: Private(assuan_fd_t fd_, const std::vector> &factories_, AssuanServerConnection *qq); ~Private() override; Q_SIGNALS: void startKeyManager(); public Q_SLOTS: void slotReadActivity(int) { Q_ASSERT(ctx); int done = false; if (const int err = assuan_process_next(ctx.get(), &done) || done) { // if ( err == -1 || gpg_err_code(err) == GPG_ERR_EOF ) { topHalfDeletion(); if (nohupedCommands.empty()) { bottomHalfDeletion(); } //} else { // assuan_process_done( ctx.get(), err ); // return; //} } } int startCommandBottomHalf(); private: void nohupDone(AssuanCommand *cmd) { const auto it = std::find_if(nohupedCommands.begin(), nohupedCommands.end(), [cmd](const std::shared_ptr &other) { return other.get() == cmd; }); Q_ASSERT(it != nohupedCommands.end()); nohupedCommands.erase(it); if (nohupedCommands.empty() && closed) { bottomHalfDeletion(); } } void commandDone(AssuanCommand *cmd) { if (!cmd || cmd != currentCommand.get()) { return; } currentCommand.reset(); } void topHalfDeletion() { if (currentCommand) { currentCommand->canceled(); } if (fd != ASSUAN_INVALID_FD) { #if defined(Q_OS_WIN) CloseHandle(fd); #else ::close(fd); #endif } notifiers.clear(); closed = true; } void bottomHalfDeletion() { if (sessionId) { SessionDataHandler::instance()->exitSession(sessionId); } cleanup(); const QPointer that = this; Q_EMIT q->closed(q); if (that) { // still there q->deleteLater(); } } private: static gpg_error_t reset_handler(assuan_context_t ctx_, char *) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); conn.reset(); return 0; } static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (key && key[0] == '-' && key[1] == '-') { key += 2; // skip "--" } conn.options[key] = QString::fromUtf8(value); return 0; // return gpg_error( GPG_ERR_UNKNOWN_OPTION ); } static gpg_error_t session_handler(assuan_context_t ctx_, char *line) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); const QString str = QString::fromUtf8(line); static const QRegularExpression rx(QRegularExpression::anchoredPattern(uR"((\d+)(?:\s+(.*))?)")); const QRegularExpressionMatch match = rx.match(str); if (!match.hasMatch()) { static const QString errorString = i18n("Parse error"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } bool ok = false; if (const qulonglong id = match.captured(1).toULongLong(&ok)) { if (ok && id <= std::numeric_limits::max()) { SessionDataHandler::instance()->enterSession(id); conn.sessionId = id; } else { static const QString errorString = i18n("Parse error: numeric session id too large"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_SYNTAX), errorString); } } const QString cap2 = match.captured(2); if (!cap2.isEmpty()) { conn.sessionTitle = cap2; } qCDebug(KLEOPATRA_LOG) << "session_handler: " << "id=" << static_cast(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle); return assuan_process_done(ctx_, 0); } static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line) { if (!QByteArray(line).trimmed().isEmpty()) { static const QString errorString = i18n("CAPABILITIES does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } static const char capabilities[] = "SENDER=info\n" "RECIPIENT=info\n" "SESSION\n"; return assuan_process_done(ctx_, assuan_send_data(ctx_, capabilities, sizeof capabilities - 1)); } static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (qstrcmp(line, "version") == 0) { static const char version[] = "Kleopatra " KLEOPATRA_VERSION_STRING; return assuan_process_done(ctx_, assuan_send_data(ctx_, version, sizeof version - 1)); } QByteArray ba; if (qstrcmp(line, "pid") == 0) { ba = QByteArray::number(QCoreApplication::applicationPid()); } else if (qstrcmp(line, "options") == 0) { ba = conn.dumpOptions(); } else if (qstrcmp(line, "x-mementos") == 0) { ba = conn.dumpMementos(); } else if (qstrcmp(line, "senders") == 0) { ba = conn.dumpSenders(); } else if (qstrcmp(line, "recipients") == 0) { ba = conn.dumpRecipients(); } else if (qstrcmp(line, "x-files") == 0) { ba = conn.dumpFiles(); } else { static const QString errorString = i18n("Unknown value for WHAT"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } return assuan_process_done(ctx_, assuan_send_data(ctx_, ba.constData(), ba.size())); } static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_KEYMANAGER does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startKeyManagerRequested(); return assuan_process_done(ctx_, 0); } static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); if (line && *line) { static const QString errorString = i18n("START_CONFDIALOG does not take arguments"); return assuan_process_done_msg(ctx_, gpg_error(GPG_ERR_ASS_PARAMETER), errorString); } Q_EMIT conn.q->startConfigDialogRequested(); return assuan_process_done(ctx_, 0); } template struct Input_or_Output : std::conditional { }; // format: TAG (FD|FD=\d+|FILE=...) template static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); char *binOpt = strstr(line_, "--binary"); if (binOpt && !in) { /* Note there is also --armor and --base64 allowed but we don't need * to parse those because they are default. * We remove it here so that it is not parsed as an Option.*/ memset(binOpt, ' ', 8); } try { /*const*/ std::map options = upcase_option("FD", upcase_option("FILE", parse_commandline(line_))); if (options.size() < 1 || options.size() > 2) { throw gpg_error(GPG_ERR_ASS_SYNTAX); } std::shared_ptr::type> io; if (options.count("FD")) { if (options.count("FILE")) { throw gpg_error(GPG_ERR_CONFLICT); } assuan_fd_t fd = ASSUAN_INVALID_FD; const std::string fdstr = options["FD"]; if (fdstr.empty()) { if (const gpg_error_t err = assuan_receivefd(conn.ctx.get(), &fd)) { throw err; } } else { #if defined(Q_OS_WIN) fd = (assuan_fd_t)std::stoi(fdstr); #else fd = std::stoi(fdstr); #endif } io = Input_or_Output::type::createFromPipeDevice(fd, in ? i18n("Message #%1", (conn.*which).size() + 1) : QString()); options.erase("FD"); } else if (options.count("FILE")) { if (options.count("FD")) { throw gpg_error(GPG_ERR_CONFLICT); } const QString filePath = QFile::decodeName(options["FILE"].c_str()); if (filePath.isEmpty()) { throw Exception(gpg_error(GPG_ERR_ASS_SYNTAX), i18n("Empty file path")); } const QFileInfo fi(filePath); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.isFile()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only files are allowed in INPUT/OUTPUT FILE")); } else { io = Input_or_Output::type::createFromFile(fi.absoluteFilePath(), true); } options.erase("FILE"); } else { throw gpg_error(GPG_ERR_ASS_PARAMETER); } if (options.size()) { throw gpg_error(GPG_ERR_UNKNOWN_OPTION); } (conn.*which).push_back(io); if (binOpt && !in) { auto out = reinterpret_cast(io.get()); out->setBinaryOpt(true); qCDebug(KLEOPATRA_LOG) << "Configured output for binary data"; } qCDebug(KLEOPATRA_LOG) << "AssuanServerConnection: added" << io->label(); return assuan_process_done(conn.ctx.get(), 0); } catch (const GpgME::Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().c_str()); } catch (const std::exception &) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_ASS_SYNTAX)); } catch (const gpg_error_t &e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), "unknown exception caught"); } } static gpg_error_t input_handler(assuan_context_t ctx, char *line) { return IO_handler(ctx, line, &Private::inputs); } static gpg_error_t output_handler(assuan_context_t ctx, char *line) { return IO_handler(ctx, line, &Private::outputs); } static gpg_error_t message_handler(assuan_context_t ctx, char *line) { return IO_handler(ctx, line, &Private::messages); } static gpg_error_t file_handler(assuan_context_t ctx_, char *line) { Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); try { const QFileInfo fi(QFile::decodeName(hexdecode(line).c_str())); if (!fi.isAbsolute()) { throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Only absolute file paths are allowed")); } if (!fi.exists()) { throw gpg_error(GPG_ERR_ENOENT); } if (!fi.isReadable() || (fi.isDir() && !fi.isExecutable())) { throw gpg_error(GPG_ERR_EPERM); } conn.files.push_back(fi.absoluteFilePath()); return assuan_process_done(conn.ctx.get(), 0); } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error().encodedError(), e.message().toUtf8().constData()); } catch (const gpg_error_t &e) { return assuan_process_done(conn.ctx.get(), e); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("unknown exception caught").toUtf8().constData()); } } static bool parse_informative(const char *&begin, GpgME::Protocol &protocol) { protocol = GpgME::UnknownProtocol; bool informative = false; const char *pos = begin; while (true) { while (*pos == ' ' || *pos == '\t') { ++pos; } if (qstrnicmp(pos, "--info", strlen("--info")) == 0) { informative = true; pos += strlen("--info"); if (*pos == '=') { ++pos; break; } } else if (qstrnicmp(pos, "--protocol=", strlen("--protocol=")) == 0) { pos += strlen("--protocol="); if (qstrnicmp(pos, "OpenPGP", strlen("OpenPGP")) == 0) { protocol = GpgME::OpenPGP; pos += strlen("OpenPGP"); } else if (qstrnicmp(pos, "CMS", strlen("CMS")) == 0) { protocol = GpgME::CMS; pos += strlen("CMS"); } else { ; } } else if (qstrncmp(pos, "-- ", strlen("-- ")) == 0) { pos += 3; while (*pos == ' ' || *pos == '\t') { ++pos; } break; } else { break; } } begin = pos; return informative; } template static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { Q_ASSERT(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); if (!line || !*line) { return assuan_process_done(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG)); } const char *begin = line; const char *const end = begin + qstrlen(line); GpgME::Protocol proto = GpgME::UnknownProtocol; const bool informative = parse_informative(begin, proto); if (!(conn.*mp).empty() && informative != (conn.*info)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_CONFLICT), i18n("Cannot mix --info with non-info SENDER or RECIPIENT").toUtf8().constData()); KMime::Types::Mailbox mb; if (!KMime::HeaderParsing::parseMailbox(begin, end, mb)) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Argument is not a valid RFC-2822 mailbox").toUtf8().constData()); if (begin != end) return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_INV_ARG), i18n("Garbage after valid RFC-2822 mailbox detected").toUtf8().constData()); (conn.*info) = informative; (conn.*mp).push_back(mb); const QString email = mb.addrSpec().asString(); (void)assuan_write_line(conn.ctx.get(), qPrintable(QString::asprintf("# ok, parsed as \"%s\"", qPrintable(email)))); if (sender && !informative) { return AssuanCommandFactory::_handle(conn.ctx.get(), line, "PREP_SIGN"); } else { return assuan_process_done(ctx, 0); } } static gpg_error_t recipient_handler(assuan_context_t ctx, char *line) { return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line); } static gpg_error_t sender_handler(assuan_context_t ctx, char *line) { return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true); } QByteArray dumpOptions() const { QByteArray result; for (auto it = options.begin(), end = options.end(); it != end; ++it) { result += it->first.c_str() + it->second.toString().toUtf8() + '\n'; } return result; } static QByteArray dumpStringList(const QStringList &sl) { return sl.join(QLatin1Char('\n')).toUtf8(); } template static QByteArray dumpStringList(const T_container &c) { QStringList sl; std::copy(c.begin(), c.end(), std::back_inserter(sl)); return dumpStringList(sl); } template static QByteArray dumpMailboxes(const T_container &c) { QStringList sl; std::transform(c.begin(), c.end(), std::back_inserter(sl), [](typename T_container::const_reference val) { return val.prettyAddress(); }); return dumpStringList(sl); } QByteArray dumpSenders() const { return dumpMailboxes(senders); } QByteArray dumpRecipients() const { return dumpMailboxes(recipients); } QByteArray dumpMementos() const { QByteArray result; for (auto it = mementos.begin(), end = mementos.end(); it != end; ++it) { char buf[2 + 2 * sizeof(void *) + 2]; sprintf(buf, "0x%p\n", (void *)it->second.get()); buf[sizeof(buf) - 1] = '\0'; result += it->first + QByteArray::fromRawData(buf, sizeof buf); } return result; } QByteArray dumpFiles() const { QStringList rv; rv.reserve(files.size()); std::copy(files.cbegin(), files.cend(), std::back_inserter(rv)); return dumpStringList(rv); } void cleanup(); void reset() { options.clear(); senders.clear(); informativeSenders = false; recipients.clear(); informativeRecipients = false; sessionTitle.clear(); sessionId = 0; mementos.clear(); files.clear(); std::for_each(inputs.begin(), inputs.end(), std::mem_fn(&Input::finalize)); inputs.clear(); std::for_each(outputs.begin(), outputs.end(), std::mem_fn(&Output::finalize)); outputs.clear(); std::for_each(messages.begin(), messages.end(), std::mem_fn(&Input::finalize)); messages.clear(); bias = GpgME::UnknownProtocol; } assuan_fd_t fd; AssuanContext ctx; bool closed : 1; bool cryptoCommandsEnabled : 1; bool commandWaitingForCryptoCommandsEnabled : 1; bool currentCommandIsNohup : 1; bool informativeSenders; // address taken, so no : 1 bool informativeRecipients; // address taken, so no : 1 GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; std::vector> notifiers; std::vector> factories; // sorted: _detail::ByName std::shared_ptr currentCommand; std::vector> nohupedCommands; std::map options; std::vector senders, recipients; std::vector> inputs, messages; std::vector> outputs; std::vector files; std::map> mementos; }; void AssuanServerConnection::Private::cleanup() { Q_ASSERT(nohupedCommands.empty()); reset(); currentCommand.reset(); currentCommandIsNohup = false; commandWaitingForCryptoCommandsEnabled = false; notifiers.clear(); ctx.reset(); fd = ASSUAN_INVALID_FD; } AssuanServerConnection::Private::Private(assuan_fd_t fd_, const std::vector> &factories_, AssuanServerConnection *qq) : QObject() , q(qq) , fd(fd_) , closed(false) , cryptoCommandsEnabled(false) , commandWaitingForCryptoCommandsEnabled(false) , currentCommandIsNohup(false) , informativeSenders(false) , informativeRecipients(false) , bias(GpgME::UnknownProtocol) , sessionId(0) , factories(factories_) { #ifdef __GLIBCXX__ Q_ASSERT(__gnu_cxx::is_sorted(factories_.begin(), factories_.end(), _detail::ByName())); #endif if (fd == ASSUAN_INVALID_FD) { throw Exception(gpg_error(GPG_ERR_INV_ARG), "pre-assuan_init_socket_server_ext"); } { assuan_context_t naked_ctx = nullptr; if (const gpg_error_t err = assuan_new(&naked_ctx)) { throw Exception(err, "assuan_new"); } ctx.reset(naked_ctx); } if (const gpg_error_t err = assuan_init_socket_server(ctx.get(), fd, INIT_SOCKET_FLAGS)) throw Exception(err, "assuan_init_socket_server_ext"); // for callbacks, associate the context with this connection: assuan_set_pointer(ctx.get(), this); FILE *const logFile = Log::instance()->logFile(); assuan_set_log_stream(ctx.get(), logFile ? logFile : stderr); // register FDs with the event loop: assuan_fd_t fds[MAX_ACTIVE_FDS]; const int numFDs = assuan_get_active_fds(ctx.get(), FOR_READING, fds, MAX_ACTIVE_FDS); Q_ASSERT(numFDs != -1); // == 1 if (!numFDs || fds[0] != fd) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fd, QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } notifiers.reserve(notifiers.size() + numFDs); for (int i = 0; i < numFDs; ++i) { const std::shared_ptr sn(new QSocketNotifier((intptr_t)fds[i], QSocketNotifier::Read), std::mem_fn(&QObject::deleteLater)); connect(sn.get(), &QSocketNotifier::activated, this, &Private::slotReadActivity); notifiers.push_back(sn); } // register our INPUT/OUTPUT/MESSGAE/FILE handlers: if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, "")) throw Exception(err, "register \"INPUT\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, "")) throw Exception(err, "register \"MESSAGE\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, "")) throw Exception(err, "register \"OUTPUT\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, "")) throw Exception(err, "register \"FILE\" handler"); // register user-defined commands: for (std::shared_ptr fac : std::as_const(factories)) if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), "")) throw Exception(err, std::string("register \"") + fac->name() + "\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, "")) throw Exception(err, "register \"GETINFO\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, "")) throw Exception(err, "register \"START_KEYMANAGER\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, "")) throw Exception(err, "register \"START_CONFDIALOG\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, "")) throw Exception(err, "register \"RECIPIENT\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, "")) throw Exception(err, "register \"SENDER\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, "")) throw Exception(err, "register \"SESSION\" handler"); if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, "")) throw Exception(err, "register \"CAPABILITIES\" handler"); assuan_set_hello_line(ctx.get(), "GPG UI server (Kleopatra/" KLEOPATRA_VERSION_STRING ") ready to serve"); // assuan_set_hello_line( ctx.get(), GPG UI server (qApp->applicationName() + " v" + kapp->applicationVersion() + "ready to serve" ) // some notifiers we're interested in: if (const gpg_error_t err = assuan_register_reset_notify(ctx.get(), reset_handler)) { throw Exception(err, "register reset notify"); } if (const gpg_error_t err = assuan_register_option_handler(ctx.get(), option_handler)) { throw Exception(err, "register option handler"); } // and last, we need to call assuan_accept, which doesn't block // (d/t INIT_SOCKET_FLAGS), but performs vital connection // establishing handling: if (const gpg_error_t err = assuan_accept(ctx.get())) { throw Exception(err, "assuan_accept"); } } AssuanServerConnection::Private::~Private() { cleanup(); } AssuanServerConnection::AssuanServerConnection(assuan_fd_t fd, const std::vector> &factories, QObject *p) : QObject(p) , d(new Private(fd, factories, this)) { } AssuanServerConnection::~AssuanServerConnection() { } void AssuanServerConnection::enableCryptoCommands(bool on) { if (on == d->cryptoCommandsEnabled) { return; } d->cryptoCommandsEnabled = on; if (d->commandWaitingForCryptoCommandsEnabled) { QTimer::singleShot(0, d.get(), &Private::startCommandBottomHalf); } } // // // AssuanCommand: // // namespace Kleo { class InquiryHandler : public QObject { Q_OBJECT public: explicit InquiryHandler(const char *keyword_, QObject *p = nullptr) : QObject(p) , keyword(keyword_) { } static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen) { Q_ASSERT(cb_data); auto this_ = static_cast(cb_data); Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(buffer), buflen), this_->keyword); std::free(buffer); delete this_; return 0; } private: const char *keyword; Q_SIGNALS: void signal(int rc, const QByteArray &data, const QByteArray &keyword); }; } // namespace Kleo class AssuanCommand::Private { public: Private() : informativeRecipients(false) , informativeSenders(false) , bias(GpgME::UnknownProtocol) , done(false) , nohup(false) { } std::map options; std::vector> inputs, messages; std::vector> outputs; std::vector files; std::vector recipients, senders; bool informativeRecipients, informativeSenders; GpgME::Protocol bias; QString sessionTitle; unsigned int sessionId; QByteArray utf8ErrorKeepAlive; AssuanContext ctx; bool done; bool nohup; }; AssuanCommand::AssuanCommand() : d(new Private) { } AssuanCommand::~AssuanCommand() { } int AssuanCommand::start() { try { if (const int err = doStart()) if (!d->done) { done(err); } return 0; } catch (const Exception &e) { if (!d->done) { done(e.error_code(), e.message()); } return 0; } catch (const GpgME::Exception &e) { if (!d->done) { done(e.error(), QString::fromLocal8Bit(e.message().c_str())); } return 0; } catch (const std::exception &e) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unexpected exception: %1", QString::fromLocal8Bit(e.what()))); } return 0; } catch (...) { if (!d->done) { done(makeError(GPG_ERR_INTERNAL), i18n("Caught unknown exception - please report this error to the developers.")); } return 0; } } void AssuanCommand::canceled() { d->done = true; doCanceled(); } // static int AssuanCommand::makeError(int code) { return makeGnuPGError(code); } bool AssuanCommand::hasOption(const char *opt) const { return d->options.count(opt); } QVariant AssuanCommand::option(const char *opt) const { const auto it = d->options.find(opt); if (it == d->options.end()) { return QVariant(); } else { return it->second; } } const std::map &AssuanCommand::options() const { return d->options; } namespace { template std::vector keys(const std::map &map) { std::vector result; result.resize(map.size()); for (typename std::map::const_iterator it = map.begin(), end = map.end(); it != end; ++it) { result.push_back(it->first); } return result; } } const std::map> &AssuanCommand::mementos() const { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); const AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); return conn.mementos; } bool AssuanCommand::hasMemento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { return SessionDataHandler::instance()->sessionData(id)->mementos.count(tag) || mementos().count(tag); } else { return mementos().count(tag); } } std::shared_ptr AssuanCommand::memento(const QByteArray &tag) const { if (const unsigned int id = sessionId()) { const std::shared_ptr sdh = SessionDataHandler::instance(); const std::shared_ptr sd = sdh->sessionData(id); const auto it = sd->mementos.find(tag); if (it != sd->mementos.end()) { return it->second; } } const auto it = mementos().find(tag); if (it == mementos().end()) { return std::shared_ptr(); } else { return it->second; } } QByteArray AssuanCommand::registerMemento(const std::shared_ptr &mem) { const QByteArray tag = QByteArray::number(reinterpret_cast(mem.get()), 36); return registerMemento(tag, mem); } QByteArray AssuanCommand::registerMemento(const QByteArray &tag, const std::shared_ptr &mem) { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos[tag] = mem; } else { conn.mementos[tag] = mem; } return tag; } void AssuanCommand::removeMemento(const QByteArray &tag) { // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); conn.mementos.erase(tag); if (const unsigned int id = sessionId()) { SessionDataHandler::instance()->sessionData(id)->mementos.erase(tag); } } const std::vector> &AssuanCommand::inputs() const { return d->inputs; } const std::vector> &AssuanCommand::messages() const { return d->messages; } const std::vector> &AssuanCommand::outputs() const { return d->outputs; } QStringList AssuanCommand::fileNames() const { QStringList rv; rv.reserve(d->files.size()); std::copy(d->files.cbegin(), d->files.cend(), std::back_inserter(rv)); return rv; } unsigned int AssuanCommand::numFiles() const { return d->files.size(); } void AssuanCommand::sendStatus(const char *keyword, const QString &text) { sendStatusEncoded(keyword, text.toUtf8().constData()); } void AssuanCommand::sendStatusEncoded(const char *keyword, const std::string &text) { if (d->nohup) { return; } if (const int err = assuan_write_status(d->ctx.get(), keyword, text.c_str())) { throw Exception(err, i18n("Cannot send \"%1\" status", QString::fromLatin1(keyword))); } } void AssuanCommand::sendData(const QByteArray &data, bool moreToCome) { if (d->nohup) { return; } if (const gpg_error_t err = assuan_send_data(d->ctx.get(), data.constData(), data.size())) { throw Exception(err, i18n("Cannot send data")); } if (!moreToCome) if (const gpg_error_t err = assuan_send_data(d->ctx.get(), nullptr, 0)) { // flush throw Exception(err, i18n("Cannot flush data")); } } int AssuanCommand::inquire(const char *keyword, QObject *receiver, const char *slot, unsigned int maxSize) { Q_ASSERT(keyword); Q_ASSERT(receiver); Q_ASSERT(slot); if (d->nohup) { return makeError(GPG_ERR_INV_OP); } std::unique_ptr ih(new InquiryHandler(keyword, receiver)); receiver->connect(ih.get(), SIGNAL(signal(int, QByteArray, QByteArray)), slot); if (const gpg_error_t err = assuan_inquire_ext(d->ctx.get(), keyword, maxSize, InquiryHandler::handler, ih.get())) { return err; } ih.release(); return 0; } void AssuanCommand::done(const GpgME::Error &err, const QString &details) { if (d->ctx && !d->done && !details.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Error: " << details; d->utf8ErrorKeepAlive = details.toUtf8(); if (!d->nohup) { assuan_set_error(d->ctx.get(), err.encodedError(), d->utf8ErrorKeepAlive.constData()); } } done(err); } void AssuanCommand::done(const GpgME::Error &err) { if (!d->ctx) { qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called with NULL ctx."; return; } if (d->done) { qCDebug(KLEOPATRA_LOG) << Formatting::errorAsString(err) << ": called twice!"; return; } d->done = true; std::for_each(d->messages.begin(), d->messages.end(), std::mem_fn(&Input::finalize)); std::for_each(d->inputs.begin(), d->inputs.end(), std::mem_fn(&Input::finalize)); std::for_each(d->outputs.begin(), d->outputs.end(), std::mem_fn(&Output::finalize)); d->messages.clear(); d->inputs.clear(); d->outputs.clear(); d->files.clear(); // oh, hack :( Q_ASSERT(assuan_get_pointer(d->ctx.get())); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(d->ctx.get())); if (d->nohup) { conn.nohupDone(this); return; } const gpg_error_t rc = assuan_process_done(d->ctx.get(), err.encodedError()); if (gpg_err_code(rc) != GPG_ERR_NO_ERROR) qFatal("AssuanCommand::done: assuan_process_done returned error %d (%s)", static_cast(rc), gpg_strerror(rc)); d->utf8ErrorKeepAlive.clear(); conn.commandDone(this); } void AssuanCommand::setNohup(bool nohup) { d->nohup = nohup; } bool AssuanCommand::isNohup() const { return d->nohup; } bool AssuanCommand::isDone() const { return d->done; } QString AssuanCommand::sessionTitle() const { return d->sessionTitle; } unsigned int AssuanCommand::sessionId() const { return d->sessionId; } bool AssuanCommand::informativeSenders() const { return d->informativeSenders; } bool AssuanCommand::informativeRecipients() const { return d->informativeRecipients; } const std::vector &AssuanCommand::recipients() const { return d->recipients; } const std::vector &AssuanCommand::senders() const { return d->senders; } gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { Q_ASSERT(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); try { const auto it = std::lower_bound(conn.factories.begin(), conn.factories.end(), commandName, _detail::ByName()); kleo_assert(it != conn.factories.end()); kleo_assert(*it); kleo_assert(qstricmp((*it)->name(), commandName) == 0); const std::shared_ptr cmd = (*it)->create(); kleo_assert(cmd); cmd->d->ctx = conn.ctx; cmd->d->options = conn.options; cmd->d->inputs.swap(conn.inputs); kleo_assert(conn.inputs.empty()); cmd->d->messages.swap(conn.messages); kleo_assert(conn.messages.empty()); cmd->d->outputs.swap(conn.outputs); kleo_assert(conn.outputs.empty()); cmd->d->files.swap(conn.files); kleo_assert(conn.files.empty()); cmd->d->senders.swap(conn.senders); kleo_assert(conn.senders.empty()); cmd->d->recipients.swap(conn.recipients); kleo_assert(conn.recipients.empty()); cmd->d->informativeRecipients = conn.informativeRecipients; cmd->d->informativeSenders = conn.informativeSenders; cmd->d->bias = conn.bias; cmd->d->sessionTitle = conn.sessionTitle; cmd->d->sessionId = conn.sessionId; const std::map cmdline_options = parse_commandline(line); for (auto it = cmdline_options.begin(), end = cmdline_options.end(); it != end; ++it) { cmd->d->options[it->first] = QString::fromUtf8(it->second.c_str()); } bool nohup = false; if (cmd->d->options.count("nohup")) { if (!cmd->d->options["nohup"].toString().isEmpty()) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_ASS_PARAMETER), "--nohup takes no argument"); } nohup = true; cmd->d->options.erase("nohup"); } conn.currentCommand = cmd; conn.currentCommandIsNohup = nohup; QTimer::singleShot(0, &conn, &AssuanServerConnection::Private::startCommandBottomHalf); return 0; } catch (const Exception &e) { return assuan_process_done_msg(conn.ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(conn.ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } int AssuanServerConnection::Private::startCommandBottomHalf() { commandWaitingForCryptoCommandsEnabled = currentCommand && !cryptoCommandsEnabled; if (!cryptoCommandsEnabled) { return 0; } const std::shared_ptr cmd = currentCommand; if (!cmd) { return 0; } currentCommand.reset(); const bool nohup = currentCommandIsNohup; currentCommandIsNohup = false; try { if (const int err = cmd->start()) { if (cmd->isDone()) { return err; } else { return assuan_process_done(ctx.get(), err); } } if (cmd->isDone()) { return 0; } if (nohup) { cmd->setNohup(true); nohupedCommands.push_back(cmd); return assuan_process_done_msg(ctx.get(), 0, "Command put in the background to continue executing after connection end."); } else { currentCommand = cmd; return 0; } } catch (const Exception &e) { return assuan_process_done_msg(ctx.get(), e.error_code(), e.message()); } catch (const std::exception &e) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), e.what()); } catch (...) { return assuan_process_done_msg(ctx.get(), gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception")); } } // // // AssuanCommand convenience methods // // /*! Checks the \c --mode parameter. \returns The parameter as an AssuanCommand::Mode enum value. If no \c --mode was given, or it's value wasn't recognized, throws an Kleo::Exception. */ AssuanCommand::Mode AssuanCommand::checkMode() const { if (!hasOption("mode")) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --mode option missing")); } const QString modeString = option("mode").toString().toLower(); - if (modeString == QLatin1String("filemanager")) { + if (modeString == QLatin1StringView("filemanager")) { return FileManager; } - if (modeString == QLatin1String("email")) { + if (modeString == QLatin1StringView("email")) { return EMail; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid mode: \"%1\"", modeString)); } /*! Checks the \c --protocol parameter. \returns The parameter as a GpgME::Protocol enum value. If \c --protocol was given, but has an invalid value, throws an Kleo::Exception. If no \c --protocol was given, checks the connection bias, if available, otherwise, in FileManager mode, returns GpgME::UnknownProtocol, but if \a mode == \c EMail, throws an Kleo::Exception instead. */ GpgME::Protocol AssuanCommand::checkProtocol(Mode mode, int options) const { if (!hasOption("protocol")) if (d->bias != GpgME::UnknownProtocol) { return d->bias; } else if (mode == AssuanCommand::EMail && (options & AllowProtocolMissing) == 0) { throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("Required --protocol option missing")); } else { return GpgME::UnknownProtocol; } else if (mode == AssuanCommand::FileManager) { throw Exception(makeError(GPG_ERR_INV_FLAG), i18n("--protocol is not allowed here")); } const QString protocolString = option("protocol").toString().toLower(); - if (protocolString == QLatin1String("openpgp")) { + if (protocolString == QLatin1StringView("openpgp")) { return GpgME::OpenPGP; } - if (protocolString == QLatin1String("cms")) { + if (protocolString == QLatin1StringView("cms")) { return GpgME::CMS; } throw Exception(makeError(GPG_ERR_INV_ARG), i18n("invalid protocol \"%1\"", protocolString)); } void AssuanCommand::doApplyWindowID(QWidget *widget) const { if (!widget || !hasOption("window-id")) { return; } apply_window_id(widget, option("window-id").toString()); } WId AssuanCommand::parentWId() const { return wid_from_string(option("window-id").toString()); } #include "assuanserverconnection.moc" #include "moc_assuanserverconnection.cpp" diff --git a/src/uiserver/echocommand.cpp b/src/uiserver/echocommand.cpp index 28a19fd06..1cd1bbeee 100644 --- a/src/uiserver/echocommand.cpp +++ b/src/uiserver/echocommand.cpp @@ -1,188 +1,188 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/echocommand.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 "echocommand.h" #include #include #include #include #include #include #include #include #include using namespace Kleo; static const char option_prefix[] = "prefix"; class EchoCommand::Private { public: int operationsInFlight = 0; QByteArray buffer; }; EchoCommand::EchoCommand() : QObject() , AssuanCommandMixin() , d(new Private) { } EchoCommand::~EchoCommand() { } int EchoCommand::doStart() { const std::vector> in = inputs(), msg = messages(); const std::vector> out = outputs(); if (!in.empty() && out.empty()) { return makeError(GPG_ERR_NOT_SUPPORTED); } if (!msg.empty()) { return makeError(GPG_ERR_NOT_SUPPORTED); } if (hasOption(option_prefix) && !option(option_prefix).toByteArray().isEmpty()) { return makeError(GPG_ERR_NOT_IMPLEMENTED); } std::string keyword; if (hasOption("inquire")) { keyword = option("inquire").toString().toStdString(); if (keyword.empty()) { return makeError(GPG_ERR_INV_ARG); } } const std::string output = option("text").toString().toStdString(); // aaand ACTION: // 1. echo the command line though the status channel - sendStatus("ECHO", output.empty() ? QString() : QLatin1String(output.c_str())); + sendStatus("ECHO", output.empty() ? QString() : QLatin1StringView(output.c_str())); // 2. if --inquire was given, inquire more data from the client: if (!keyword.empty()) { if (const int err = inquire(keyword.c_str(), this, SLOT(slotInquireData(int, QByteArray)))) { return err; } else { ++d->operationsInFlight; } } // 3. if INPUT was given, start the data pump for input->output if (const std::shared_ptr i = in.at(0)->ioDevice()) { const std::shared_ptr o = out.at(0)->ioDevice(); ++d->operationsInFlight; connect(i.get(), &QIODevice::readyRead, this, &EchoCommand::slotInputReadyRead); connect(o.get(), &QIODevice::bytesWritten, this, &EchoCommand::slotOutputBytesWritten); if (i->bytesAvailable()) { slotInputReadyRead(); } } if (!d->operationsInFlight) { done(); } return 0; } void EchoCommand::doCanceled() { } void EchoCommand::slotInquireData(int rc, const QByteArray &data) { --d->operationsInFlight; if (rc) { done(rc); return; } try { - sendStatus("ECHOINQ", QLatin1String(data)); + sendStatus("ECHOINQ", QLatin1StringView(data)); if (!d->operationsInFlight) { done(); } } catch (const Exception &e) { done(e.error(), e.message()); } catch (const std::exception &e) { done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignCommand::Private::slotMicAlgDetermined: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignCommand::Private::slotMicAlgDetermined")); } } void EchoCommand::slotInputReadyRead() { const std::shared_ptr in = inputs().at(0)->ioDevice(); Q_ASSERT(in); QByteArray buffer; buffer.resize(in->bytesAvailable()); const qint64 read = in->read(buffer.data(), buffer.size()); if (read == -1) { done(makeError(GPG_ERR_EIO)); return; } if (read == 0 || (!in->isSequential() && read == in->size())) { in->close(); } buffer.resize(read); d->buffer += buffer; slotOutputBytesWritten(); } void EchoCommand::slotOutputBytesWritten() { const std::shared_ptr out = outputs().at(0)->ioDevice(); Q_ASSERT(out); if (!d->buffer.isEmpty()) { if (out->bytesToWrite()) { return; } const qint64 written = out->write(d->buffer); if (written == -1) { done(makeError(GPG_ERR_EIO)); return; } d->buffer.remove(0, written); } if (out->isOpen() && d->buffer.isEmpty() && !inputs().at(0)->ioDevice()->isOpen()) { out->close(); if (!--d->operationsInFlight) { done(); } } } #include "moc_echocommand.cpp" diff --git a/src/uiserver/prepencryptcommand.cpp b/src/uiserver/prepencryptcommand.cpp index 6b8fcefe2..b777c96df 100644 --- a/src/uiserver/prepencryptcommand.cpp +++ b/src/uiserver/prepencryptcommand.cpp @@ -1,143 +1,143 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/prepencryptcommand.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 "prepencryptcommand.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; class PrepEncryptCommand::Private : public QObject { Q_OBJECT private: friend class ::Kleo::PrepEncryptCommand; PrepEncryptCommand *const q; public: explicit Private(PrepEncryptCommand *qq) : q(qq) , controller() { } private: void checkForErrors() const; public Q_SLOTS: void slotRecipientsResolved(); void slotError(int, const QString &); private: std::shared_ptr controller; }; PrepEncryptCommand::PrepEncryptCommand() : AssuanCommandMixin() , d(new Private(this)) { } PrepEncryptCommand::~PrepEncryptCommand() { } void PrepEncryptCommand::Private::checkForErrors() const { if (!q->inputs().empty() || !q->outputs().empty() || !q->messages().empty()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("INPUT/OUTPUT/MESSAGE may only be given after PREP_ENCRYPT")); if (q->numFiles()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("PREP_ENCRYPT is an email mode command, connection seems to be in filemanager mode")); if (!q->senders().empty() && !q->informativeSenders()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("SENDER may not be given prior to PREP_ENCRYPT, except with --info")); if (q->recipients().empty() || q->informativeRecipients()) throw Exception(makeError(GPG_ERR_MISSING_VALUE), i18n("No recipients given, or only with --info")); } int PrepEncryptCommand::doStart() { removeMemento(NewSignEncryptEMailController::mementoName()); d->checkForErrors(); d->controller.reset(new NewSignEncryptEMailController(shared_from_this())); d->controller->setEncrypting(true); const QString session = sessionTitle(); if (!session.isEmpty()) { d->controller->setSubject(session); } if (hasOption("protocol")) // --protocol is optional for PREP_ENCRYPT { d->controller->setProtocol(checkProtocol(EMail)); } d->controller->setSigning(hasOption("expect-sign")); QObject::connect(d->controller.get(), &NewSignEncryptEMailController::certificatesResolved, d.get(), &Private::slotRecipientsResolved); QObject::connect(d->controller.get(), SIGNAL(error(int, QString)), d.get(), SLOT(slotError(int, QString))); d->controller->startResolveCertificates(recipients(), senders()); return 0; } void PrepEncryptCommand::Private::slotRecipientsResolved() { // hold local std::shared_ptr to member as q->done() deletes *this const std::shared_ptr cont = controller; QPointer that(this); try { - q->sendStatus("PROTOCOL", QLatin1String(controller->protocolAsString())); + q->sendStatus("PROTOCOL", QLatin1StringView(controller->protocolAsString())); q->registerMemento(NewSignEncryptEMailController::mementoName(), make_typed_memento(controller)); q->done(); return; } catch (const Exception &e) { q->done(e.error(), e.message()); } catch (const std::exception &e) { q->done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in PrepEncryptCommand::Private::slotRecipientsResolved: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { q->done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in PrepEncryptCommand::Private::slotRecipientsResolved")); } if (that) { // isn't this always deleted here and thus unnecessary? q->removeMemento(NewSignEncryptEMailController::mementoName()); } cont->cancel(); } void PrepEncryptCommand::Private::slotError(int err, const QString &details) { q->done(err, details); } void PrepEncryptCommand::doCanceled() { if (d->controller) { d->controller->cancel(); } } #include "prepencryptcommand.moc" diff --git a/src/uiserver/prepsigncommand.cpp b/src/uiserver/prepsigncommand.cpp index 07f245722..aa1cd6397 100644 --- a/src/uiserver/prepsigncommand.cpp +++ b/src/uiserver/prepsigncommand.cpp @@ -1,173 +1,173 @@ /* -*- mode: c++; c-basic-offset:4 -*- uiserver/prepsigncommand.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 "prepsigncommand.h" #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; class PrepSignCommand::Private : public QObject { Q_OBJECT private: friend class ::Kleo::PrepSignCommand; PrepSignCommand *const q; public: explicit Private(PrepSignCommand *qq) : q(qq) , controller() { } private: void checkForErrors() const; void connectController(); public Q_SLOTS: void slotSignersResolved(); void slotError(int, const QString &); private: std::shared_ptr controller; }; PrepSignCommand::PrepSignCommand() : AssuanCommandMixin() , d(new Private(this)) { } PrepSignCommand::~PrepSignCommand() { } void PrepSignCommand::Private::checkForErrors() const { if (!q->inputs().empty() || !q->outputs().empty() || !q->messages().empty()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("INPUT/OUTPUT/MESSAGE may only be given after PREP_SIGN")); if (q->numFiles()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("PREP_SIGN is an email mode command, connection seems to be in filemanager mode")); if (q->senders().empty()) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("No SENDER given")); const auto m = q->mementoContent>(NewSignEncryptEMailController::mementoName()); if (m && m->isSigning()) { if (q->hasOption("protocol")) if (m->protocol() != q->checkProtocol(EMail)) throw Exception(makeError(GPG_ERR_CONFLICT), i18n("Protocol given conflicts with protocol determined by PREP_ENCRYPT in this session")); // ### check that any SENDER here is the same as the one for PREP_ENCRYPT // ### ditto RECIPIENT } } void PrepSignCommand::Private::connectController() { auto ptr = controller.get(); connect(ptr, &NewSignEncryptEMailController::certificatesResolved, this, &PrepSignCommand::Private::slotSignersResolved); connect(ptr, &Controller::error, this, &PrepSignCommand::Private::slotError); } int PrepSignCommand::doStart() { d->checkForErrors(); const auto seec = mementoContent>(NewSignEncryptEMailController::mementoName()); if (seec && seec->isSigning()) { // reuse the controller from a previous PREP_ENCRYPT --expect-sign, if available: d->controller = seec; d->connectController(); seec->setExecutionContext(shared_from_this()); if (seec->areCertificatesResolved()) { QTimer::singleShot(0, d.get(), &Private::slotSignersResolved); } else { kleo_assert(seec->isResolvingInProgress()); } } else { // use a new controller d->controller.reset(new NewSignEncryptEMailController(shared_from_this())); const QString session = sessionTitle(); if (!session.isEmpty()) { d->controller->setSubject(session); } if (hasOption("protocol")) // --protocol is optional for PREP_SIGN { d->controller->setProtocol(checkProtocol(EMail)); } d->controller->setEncrypting(false); d->controller->setSigning(true); d->connectController(); d->controller->startResolveCertificates(recipients(), senders()); } return 0; } void PrepSignCommand::Private::slotSignersResolved() { // hold local std::shared_ptr to member as q->done() deletes *this const std::shared_ptr cont = controller; QPointer that(this); try { - q->sendStatus("PROTOCOL", QLatin1String(controller->protocolAsString())); + q->sendStatus("PROTOCOL", QLatin1StringView(controller->protocolAsString())); q->registerMemento(NewSignEncryptEMailController::mementoName(), make_typed_memento(controller)); q->done(); return; } catch (const Exception &e) { q->done(e.error(), e.message()); } catch (const std::exception &e) { q->done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in PrepSignCommand::Private::slotRecipientsResolved: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { q->done(makeError(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in PrepSignCommand::Private::slotRecipientsResolved")); } if (that) { // isn't this always deleted here and thus unnecessary? q->removeMemento(NewSignEncryptEMailController::mementoName()); } cont->cancel(); } void PrepSignCommand::Private::slotError(int err, const QString &details) { q->done(err, details); } void PrepSignCommand::doCanceled() { if (d->controller) { d->controller->cancel(); } } #include "prepsigncommand.moc" diff --git a/src/utils/action_data.cpp b/src/utils/action_data.cpp index e52ac6fc6..ea984cbb0 100644 --- a/src/utils/action_data.cpp +++ b/src/utils/action_data.cpp @@ -1,57 +1,57 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/action_data.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 "action_data.h" #include #include #include #include #include QAction *Kleo::createAction(const action_data &ad, KActionCollection *coll) { QAction *const a = ad.actionType == KFToggleAction ? new KToggleAction(coll) : new QAction(coll); - a->setObjectName(QLatin1String(ad.name)); + a->setObjectName(QLatin1StringView(ad.name)); a->setText(ad.text); if (!ad.tooltip.isEmpty()) { a->setToolTip(ad.tooltip); } if (ad.icon) { - a->setIcon(QIcon::fromTheme(QLatin1String(ad.icon))); + a->setIcon(QIcon::fromTheme(QLatin1StringView(ad.icon))); } if (ad.receiver && ad.func) { if (ad.actionType == KFToggleAction) { QObject::connect(a, &KToggleAction::toggled, ad.receiver, ad.func); } else { QObject::connect(a, &QAction::triggered, ad.receiver, ad.func); } } a->setEnabled(ad.actionState == Enabled); - coll->addAction(QLatin1String(ad.name), a); + coll->addAction(QLatin1StringView(ad.name), a); return a; } QAction *Kleo::make_action_from_data(const action_data &ad, KActionCollection *coll) { QAction *const a = createAction(ad, coll); if (!ad.shortcut.isEmpty()) { coll->setDefaultShortcut(a, QKeySequence(ad.shortcut)); } return a; } void Kleo::make_actions_from_data(const std::vector &data, KActionCollection *coll) { for (const auto &actionData : data) { - coll->addAction(QLatin1String(actionData.name), make_action_from_data(actionData, coll)); + coll->addAction(QLatin1StringView(actionData.name), make_action_from_data(actionData, coll)); } } diff --git a/src/utils/applicationstate.cpp b/src/utils/applicationstate.cpp index 2c683adc8..22cdcde45 100644 --- a/src/utils/applicationstate.cpp +++ b/src/utils/applicationstate.cpp @@ -1,38 +1,38 @@ /* utils/applicationstate.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "applicationstate.h" #include #include #include #include QString ApplicationState::lastUsedExportDirectory() { QString ret; - const KConfigGroup stateConfig{KSharedConfig::openStateConfig(), QLatin1String("Export")}; + const KConfigGroup stateConfig{KSharedConfig::openStateConfig(), QLatin1StringView("Export")}; ret = stateConfig.readEntry("LastDirectory"); if (ret.isEmpty()) { // try the normal config for backward compatibility - const KConfigGroup config{KSharedConfig::openConfig(), QLatin1String("ExportDialog")}; + const KConfigGroup config{KSharedConfig::openConfig(), QLatin1StringView("ExportDialog")}; ret = config.readEntry("LastDirectory"); } return ret.isEmpty() ? QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation) : ret; } void ApplicationState::setLastUsedExportDirectory(const QString &path) { const QFileInfo fi{path}; - KConfigGroup stateConfig{KSharedConfig::openStateConfig(), QLatin1String("Export")}; + KConfigGroup stateConfig{KSharedConfig::openStateConfig(), QLatin1StringView("Export")}; stateConfig.writeEntry("LastDirectory", fi.isDir() ? fi.absoluteFilePath() : fi.absolutePath()); } diff --git a/src/utils/archivedefinition.cpp b/src/utils/archivedefinition.cpp index 6a68e71d5..3ffcf065a 100644 --- a/src/utils/archivedefinition.cpp +++ b/src/utils/archivedefinition.cpp @@ -1,477 +1,477 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/archivedefinition.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009, 2010 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "archivedefinition.h" #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; static QMutex installPathMutex; Q_GLOBAL_STATIC(QString, _installPath) QString ArchiveDefinition::installPath() { const QMutexLocker locker(&installPathMutex); QString *const ip = _installPath(); if (ip->isEmpty()) { if (QCoreApplication::instance()) { *ip = QCoreApplication::applicationDirPath(); } else { qCWarning(KLEOPATRA_LOG) << "called before QCoreApplication was constructed"; } } return *ip; } void ArchiveDefinition::setInstallPath(const QString &ip) { const QMutexLocker locker(&installPathMutex); *_installPath() = ip; } // Archive Definition #N groups -static const QLatin1String ID_ENTRY("id"); -static const QLatin1String NAME_ENTRY("Name"); -static const QLatin1String PACK_COMMAND_ENTRY("pack-command"); -static const QLatin1String PACK_COMMAND_OPENPGP_ENTRY("pack-command-openpgp"); -static const QLatin1String PACK_COMMAND_CMS_ENTRY("pack-command-cms"); -static const QLatin1String UNPACK_COMMAND_ENTRY("unpack-command"); -static const QLatin1String UNPACK_COMMAND_OPENPGP_ENTRY("unpack-command-openpgp"); -static const QLatin1String UNPACK_COMMAND_CMS_ENTRY("unpack-command-cms"); -static const QLatin1String EXTENSIONS_ENTRY("extensions"); -static const QLatin1String EXTENSIONS_OPENPGP_ENTRY("extensions-openpgp"); -static const QLatin1String EXTENSIONS_CMS_ENTRY("extensions-cms"); -static const QLatin1String FILE_PLACEHOLDER("%f"); -static const QLatin1String FILE_PLACEHOLDER_7BIT("%F"); -static const QLatin1String INSTALLPATH_PLACEHOLDER("%I"); -static const QLatin1String NULL_SEPARATED_STDIN_INDICATOR("0|"); +static const QLatin1StringView ID_ENTRY("id"); +static const QLatin1StringView NAME_ENTRY("Name"); +static const QLatin1StringView PACK_COMMAND_ENTRY("pack-command"); +static const QLatin1StringView PACK_COMMAND_OPENPGP_ENTRY("pack-command-openpgp"); +static const QLatin1StringView PACK_COMMAND_CMS_ENTRY("pack-command-cms"); +static const QLatin1StringView UNPACK_COMMAND_ENTRY("unpack-command"); +static const QLatin1StringView UNPACK_COMMAND_OPENPGP_ENTRY("unpack-command-openpgp"); +static const QLatin1StringView UNPACK_COMMAND_CMS_ENTRY("unpack-command-cms"); +static const QLatin1StringView EXTENSIONS_ENTRY("extensions"); +static const QLatin1StringView EXTENSIONS_OPENPGP_ENTRY("extensions-openpgp"); +static const QLatin1StringView EXTENSIONS_CMS_ENTRY("extensions-cms"); +static const QLatin1StringView FILE_PLACEHOLDER("%f"); +static const QLatin1StringView FILE_PLACEHOLDER_7BIT("%F"); +static const QLatin1StringView INSTALLPATH_PLACEHOLDER("%I"); +static const QLatin1StringView NULL_SEPARATED_STDIN_INDICATOR("0|"); static const QLatin1Char NEWLINE_SEPARATED_STDIN_INDICATOR('|'); namespace { class ArchiveDefinitionError : public Kleo::Exception { const QString m_id; public: ArchiveDefinitionError(const QString &id, const QString &message) : Kleo::Exception(GPG_ERR_INV_PARAMETER, i18n("Error in archive definition %1: %2", id, message), MessageOnly) , m_id(id) { } ~ArchiveDefinitionError() throw() override { } const QString &archiveDefinitionId() const { return m_id; } }; } static QString try_extensions(const QString &path) { static const char exts[][4] = { "", "exe", "bat", "bin", "cmd", }; static const size_t numExts = sizeof exts / sizeof *exts; for (unsigned int i = 0; i < numExts; ++i) { - const QFileInfo fi(path + QLatin1Char('.') + QLatin1String(exts[i])); + const QFileInfo fi(path + QLatin1Char('.') + QLatin1StringView(exts[i])); if (fi.exists()) { return fi.filePath(); } } return QString(); } static void parse_command(QString cmdline, const QString &id, const QString &whichCommand, QString *command, QStringList *prefix, QStringList *suffix, ArchiveDefinition::ArgumentPassingMethod *method, bool parseFilePlaceholder) { Q_ASSERT(prefix); Q_ASSERT(suffix); Q_ASSERT(method); KShell::Errors errors; QStringList l; if (cmdline.startsWith(NULL_SEPARATED_STDIN_INDICATOR)) { *method = ArchiveDefinition::NullSeparatedInputFile; cmdline.remove(0, 2); } else if (cmdline.startsWith(NEWLINE_SEPARATED_STDIN_INDICATOR)) { *method = ArchiveDefinition::NewlineSeparatedInputFile; cmdline.remove(0, 1); } else { *method = ArchiveDefinition::CommandLine; } if (*method != ArchiveDefinition::CommandLine && cmdline.contains(FILE_PLACEHOLDER)) { throw ArchiveDefinitionError(id, i18n("Cannot use both %f and | in '%1'", whichCommand)); } - cmdline.replace(FILE_PLACEHOLDER, QLatin1String("__files_go_here__")) + cmdline.replace(FILE_PLACEHOLDER, QLatin1StringView("__files_go_here__")) .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__")) .replace(FILE_PLACEHOLDER_7BIT, QStringLiteral("__file7Bit_go_here__")); l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors); l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER); l = l.replaceInStrings(QStringLiteral("__file7Bit_go_here__"), FILE_PLACEHOLDER_7BIT); - if (l.indexOf(QRegularExpression(QLatin1String(".*__path_goes_here__.*"))) >= 0) { + if (l.indexOf(QRegularExpression(QLatin1StringView(".*__path_goes_here__.*"))) >= 0) { l = l.replaceInStrings(QStringLiteral("__path_goes_here__"), ArchiveDefinition::installPath()); } if (errors == KShell::BadQuoting) { throw ArchiveDefinitionError(id, i18n("Quoting error in '%1' entry", whichCommand)); } if (errors == KShell::FoundMeta) { throw ArchiveDefinitionError(id, i18n("'%1' too complex (would need shell)", whichCommand)); } qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << l; if (l.empty()) { throw ArchiveDefinitionError(id, i18n("'%1' entry is empty/missing", whichCommand)); } const QFileInfo fi1(l.front()); if (fi1.isAbsolute()) { *command = try_extensions(l.front()); } else { *command = QStandardPaths::findExecutable(fi1.fileName()); } if (command->isEmpty()) { throw ArchiveDefinitionError(id, i18n("'%1' empty or not found", whichCommand)); } if (parseFilePlaceholder) { const int idx1 = l.indexOf(FILE_PLACEHOLDER); if (idx1 < 0) { // none -> append *prefix = l.mid(1); } else { *prefix = l.mid(1, idx1 - 1); *suffix = l.mid(idx1 + 1); } } else { *prefix = l.mid(1); } switch (*method) { case ArchiveDefinition::CommandLine: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << *command << *prefix << FILE_PLACEHOLDER << *suffix; break; case ArchiveDefinition::NewlineSeparatedInputFile: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << "find | " << *command << *prefix; break; case ArchiveDefinition::NullSeparatedInputFile: qCDebug(KLEOPATRA_LOG) << "ArchiveDefinition[" << id << ']' << "find -print0 | " << *command << *prefix; break; case ArchiveDefinition::NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); break; } } namespace { class KConfigBasedArchiveDefinition : public ArchiveDefinition { public: explicit KConfigBasedArchiveDefinition(const KConfigGroup &group) : ArchiveDefinition(group.readEntryUntranslated(ID_ENTRY), group.readEntry(NAME_ENTRY)) { if (id().isEmpty()) { throw ArchiveDefinitionError(group.name(), i18n("'%1' entry is empty/missing", ID_ENTRY)); } QStringList extensions; QString extensionsKey; // extensions(-openpgp) if (group.hasKey(EXTENSIONS_OPENPGP_ENTRY)) { extensionsKey = EXTENSIONS_OPENPGP_ENTRY; } else { extensionsKey = EXTENSIONS_ENTRY; } extensions = group.readEntry(extensionsKey, QStringList()); if (extensions.empty()) { throw ArchiveDefinitionError(id(), i18n("'%1' entry is empty/missing", extensionsKey)); } setExtensions(OpenPGP, extensions); // extensions(-cms) if (group.hasKey(EXTENSIONS_CMS_ENTRY)) { extensionsKey = EXTENSIONS_CMS_ENTRY; } else { extensionsKey = EXTENSIONS_ENTRY; } extensions = group.readEntry(extensionsKey, QStringList()); if (extensions.empty()) { throw ArchiveDefinitionError(id(), i18n("'%1' entry is empty/missing", extensionsKey)); } setExtensions(CMS, extensions); ArgumentPassingMethod method; // pack-command(-openpgp) if (group.hasKey(PACK_COMMAND_OPENPGP_ENTRY)) parse_command(group.readEntry(PACK_COMMAND_OPENPGP_ENTRY), id(), PACK_COMMAND_OPENPGP_ENTRY, &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true); else parse_command(group.readEntry(PACK_COMMAND_ENTRY), id(), PACK_COMMAND_ENTRY, &m_packCommand[OpenPGP], &m_packPrefixArguments[OpenPGP], &m_packPostfixArguments[OpenPGP], &method, true); setPackCommandArgumentPassingMethod(OpenPGP, method); // pack-command(-cms) if (group.hasKey(PACK_COMMAND_CMS_ENTRY)) parse_command(group.readEntry(PACK_COMMAND_CMS_ENTRY), id(), PACK_COMMAND_CMS_ENTRY, &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true); else parse_command(group.readEntry(PACK_COMMAND_ENTRY), id(), PACK_COMMAND_ENTRY, &m_packCommand[CMS], &m_packPrefixArguments[CMS], &m_packPostfixArguments[CMS], &method, true); setPackCommandArgumentPassingMethod(CMS, method); QStringList dummy; // unpack-command(-openpgp) if (group.hasKey(UNPACK_COMMAND_OPENPGP_ENTRY)) parse_command(group.readEntry(UNPACK_COMMAND_OPENPGP_ENTRY), id(), UNPACK_COMMAND_OPENPGP_ENTRY, &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false); else parse_command(group.readEntry(UNPACK_COMMAND_ENTRY), id(), UNPACK_COMMAND_ENTRY, &m_unpackCommand[OpenPGP], &m_unpackArguments[OpenPGP], &dummy, &method, false); if (method != CommandLine) { throw ArchiveDefinitionError(id(), i18n("cannot use argument passing on standard input for unpack-command")); } // unpack-command(-cms) if (group.hasKey(UNPACK_COMMAND_CMS_ENTRY)) parse_command(group.readEntry(UNPACK_COMMAND_CMS_ENTRY), id(), UNPACK_COMMAND_CMS_ENTRY, &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false); else parse_command(group.readEntry(UNPACK_COMMAND_ENTRY), id(), UNPACK_COMMAND_ENTRY, &m_unpackCommand[CMS], &m_unpackArguments[CMS], &dummy, &method, false); if (method != CommandLine) { throw ArchiveDefinitionError(id(), i18n("cannot use argument passing on standard input for unpack-command")); } } private: QString doGetPackCommand(GpgME::Protocol p) const override { return m_packCommand[p]; } QString doGetUnpackCommand(GpgME::Protocol p) const override { return m_unpackCommand[p]; } QStringList doGetPackArguments(GpgME::Protocol p, const QStringList &files) const override { return m_packPrefixArguments[p] + files + m_packPostfixArguments[p]; } QStringList doGetUnpackArguments(GpgME::Protocol p, const QString &file) const override { QStringList copy = m_unpackArguments[p]; if (copy.contains(FILE_PLACEHOLDER_7BIT)) { /* This is a crutch for missing a way to provide Unicode arguments * to gpgtar unless gpgtar offers a unicode interface we have * no defined way to provide non 7Bit arguments. So we filter out * the chars and replace them by _ to avoid completely broken * folder names when unpacking. This is only relevant for the * unpacked folder and does not effect files in the archive. */ - const QRegularExpression non7Bit(QLatin1String(R"([^\x{0000}-\x{007F}])")); + const QRegularExpression non7Bit(QLatin1StringView(R"([^\x{0000}-\x{007F}])")); QString underscore_filename = file; underscore_filename.replace(non7Bit, QStringLiteral("_")); copy.replaceInStrings(FILE_PLACEHOLDER_7BIT, underscore_filename); } copy.replaceInStrings(FILE_PLACEHOLDER, file); return copy; } private: QString m_packCommand[2], m_unpackCommand[2]; QStringList m_packPrefixArguments[2], m_packPostfixArguments[2]; QStringList m_unpackArguments[2]; }; } ArchiveDefinition::ArchiveDefinition(const QString &id, const QString &label) : m_id(id) , m_label(label) { m_packCommandMethod[GpgME::OpenPGP] = m_packCommandMethod[GpgME::CMS] = CommandLine; } ArchiveDefinition::~ArchiveDefinition() { } QString ArchiveDefinition::stripExtension(GpgME::Protocol p, const QString &filePath) const { checkProtocol(p); for (const auto &ext : std::as_const(m_extensions[p])) { if (filePath.endsWith(QLatin1Char('.') + ext)) { return filePath.chopped(ext.size() + 1); } } return filePath; } static QByteArray make_input(const QStringList &files, char sep) { QByteArray result; for (const QString &file : files) { #ifdef Q_OS_WIN // As encoding is more complicated on windows with different // 8 bit codepages we always use UTF-8 here and add this as an // option in the libkleopatrarc.desktop archive definition. result += file.toUtf8(); #else result += QFile::encodeName(file); #endif result += sep; } return result; } std::shared_ptr ArchiveDefinition::createInputFromPackCommand(GpgME::Protocol p, const QStringList &files) const { checkProtocol(p); const QString base = heuristicBaseDirectory(files); if (base.isEmpty()) { throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", files.join(QLatin1Char('\n')))); } qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << files << ") ->" << base; const QStringList relative = makeRelativeTo(base, files); qCDebug(KLEOPATRA_LOG) << "relative" << relative; switch (m_packCommandMethod[p]) { case CommandLine: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, relative), QDir(base)); case NewlineSeparatedInputFile: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, QStringList()), QDir(base), make_input(relative, '\n')); case NullSeparatedInputFile: return Input::createFromProcessStdOut(doGetPackCommand(p), doGetPackArguments(p, QStringList()), QDir(base), make_input(relative, '\0')); case NumArgumentPassingMethods: Q_ASSERT(!"Should not happen"); } return std::shared_ptr(); // make compiler happy } std::shared_ptr ArchiveDefinition::createOutputFromUnpackCommand(GpgME::Protocol p, const QString &file, const QDir &wd) const { checkProtocol(p); const QFileInfo fi(file); return Output::createFromProcessStdIn(doGetUnpackCommand(p), doGetUnpackArguments(p, fi.absoluteFilePath()), wd); } // static std::vector> ArchiveDefinition::getArchiveDefinitions() { QStringList errors; return getArchiveDefinitions(errors); } // static std::vector> ArchiveDefinition::getArchiveDefinitions(QStringList &errors) { std::vector> result; KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc")); const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #"))); result.reserve(groups.size()); for (const QString &group : groups) try { const std::shared_ptr ad(new KConfigBasedArchiveDefinition(KConfigGroup(config, group))); result.push_back(ad); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << e.what(); errors.push_back(QString::fromLocal8Bit(e.what())); } catch (...) { errors.push_back(i18n("Caught unknown exception in group %1", group)); } return result; } void ArchiveDefinition::checkProtocol(GpgME::Protocol p) const { kleo_assert(p == GpgME::OpenPGP || p == GpgME::CMS); } diff --git a/src/utils/dragqueen.cpp b/src/utils/dragqueen.cpp index b98fc5eed..ff46e26e5 100644 --- a/src/utils/dragqueen.cpp +++ b/src/utils/dragqueen.cpp @@ -1,202 +1,202 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/dragqueen.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 "dragqueen.h" #include #include #include #include #include #include #include #include using namespace Kleo; namespace { class MimeDataProxy : public QMimeData { Q_OBJECT public: explicit MimeDataProxy(QMimeData *source) : QMimeData() , m_source(source) { } QStringList formats() const override { if (m_source) { return m_source->formats(); } else { return QStringList(); } } bool hasFormat(const QString &format) const override { return m_source && m_source->hasFormat(format); } protected: QVariant retrieveData(const QString &format, QMetaType type) const override { if (!m_source) { return QVariant(); } // Doesn't work, is protected: // return m_source->retrieveData( format, type ); switch (type.id()) { case QMetaType::QString: - if (format == QLatin1String("text/plain")) { + if (format == QLatin1StringView("text/plain")) { return m_source->text(); } - if (format == QLatin1String("text/html")) { + if (format == QLatin1StringView("text/html")) { return m_source->html(); } break; case QMetaType::QColor: - if (format == QLatin1String("application/x-color")) { + if (format == QLatin1StringView("application/x-color")) { return m_source->colorData(); } break; case QMetaType::QImage: - if (format == QLatin1String("application/x-qt-image")) { + if (format == QLatin1StringView("application/x-qt-image")) { return m_source->imageData(); } break; case QMetaType::QVariantList: case QMetaType::QUrl: - if (format == QLatin1String("text/uri-list")) { + if (format == QLatin1StringView("text/uri-list")) { const QList urls = m_source->urls(); if (urls.size() == 1) { return urls.front(); } QList result; std::copy(urls.begin(), urls.end(), std::back_inserter(result)); return result; } break; default: break; } QVariant v = m_source->data(format); v.convert(type); return v; } private: QPointer m_source; }; } DragQueen::DragQueen(QWidget *p, Qt::WindowFlags f) : QLabel(p, f) , m_data() , m_dragStartPosition() { } DragQueen::DragQueen(const QString &t, QWidget *p, Qt::WindowFlags f) : QLabel(t, p, f) , m_data() , m_dragStartPosition() { } DragQueen::~DragQueen() { delete m_data; } void DragQueen::setUrl(const QString &url) { auto data = new QMimeData; QList urls; urls.push_back(QUrl(url)); data->setUrls(urls); setMimeData(data); } QString DragQueen::url() const { if (!m_data || !m_data->hasUrls()) { return QString(); } const QList urls = m_data->urls(); if (urls.empty()) { return QString(); } return urls.front().toString(); } void DragQueen::setMimeData(QMimeData *data) { if (data == m_data) { return; } delete m_data; m_data = data; } QMimeData *DragQueen::mimeData() const { return m_data; } void DragQueen::mousePressEvent(QMouseEvent *e) { #ifndef QT_NO_DRAGANDDROP if (m_data && e->button() == Qt::LeftButton) { m_dragStartPosition = e->pos(); } #endif QLabel::mousePressEvent(e); } static QPoint calculate_hot_spot(const QPoint &mouse, const QSize &pix, const QLabel *label) { const Qt::Alignment align = label->alignment(); const int margin = label->margin(); const QRect cr = label->contentsRect().adjusted(margin, margin, -margin, -margin); const QRect rect = QStyle::alignedRect(QApplication::layoutDirection(), align, pix, cr); return mouse - rect.topLeft(); } void DragQueen::mouseMoveEvent(QMouseEvent *e) { #ifndef QT_NO_DRAGANDDROP if (m_data // && (e->buttons() & Qt::LeftButton) // && (m_dragStartPosition - e->pos()).manhattanLength() > QApplication::startDragDistance()) { auto drag = new QDrag(this); const QPixmap pix = pixmap(); if (!pix.isNull()) { drag->setPixmap(pix); drag->setHotSpot(calculate_hot_spot(e->pos(), pix.size(), this)); } drag->setMimeData(new MimeDataProxy(m_data)); drag->exec(); } else { #endif QLabel::mouseMoveEvent(e); #ifndef QT_NO_DRAGANDDROP } #endif } #include "dragqueen.moc" #include "moc_dragqueen.cpp" diff --git a/src/utils/expiration.cpp b/src/utils/expiration.cpp index cc2293038..494f9b310 100644 --- a/src/utils/expiration.cpp +++ b/src/utils/expiration.cpp @@ -1,143 +1,143 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/expiration.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 "expiration.h" #include #include #include QDate Kleo::maximumAllowedDate() { static const QDate maxAllowedDate{2106, 2, 5}; return maxAllowedDate; } QDate Kleo::minimumExpirationDate() { return expirationDateRange().minimum; } QDate Kleo::maximumExpirationDate() { return expirationDateRange().maximum; } Kleo::DateRange Kleo::expirationDateRange() { Kleo::DateRange range; const auto settings = Kleo::Settings{}; const auto today = QDate::currentDate(); const auto minimumExpiry = std::max(1, settings.validityPeriodInDaysMin()); range.minimum = std::min(today.addDays(minimumExpiry), maximumAllowedDate()); const auto maximumExpiry = settings.validityPeriodInDaysMax(); if (maximumExpiry >= 0) { range.maximum = std::min(std::max(today.addDays(maximumExpiry), range.minimum), maximumAllowedDate()); } return range; } QDate Kleo::defaultExpirationDate(Kleo::ExpirationOnUnlimitedValidity onUnlimitedValidity) { QDate expirationDate; const auto settings = Kleo::Settings{}; const auto defaultExpirationInDays = settings.validityPeriodInDays(); if (defaultExpirationInDays > 0) { expirationDate = QDate::currentDate().addDays(defaultExpirationInDays); } else if (defaultExpirationInDays < 0 || onUnlimitedValidity == ExpirationOnUnlimitedValidity::InternalDefaultExpiration) { expirationDate = QDate::currentDate().addYears(3); } const auto allowedRange = expirationDateRange(); expirationDate = std::max(expirationDate, allowedRange.minimum); if (allowedRange.maximum.isValid()) { expirationDate = std::min(expirationDate, allowedRange.maximum); } return expirationDate; } bool Kleo::isValidExpirationDate(const QDate &date) { const auto allowedRange = expirationDateRange(); if (date.isValid()) { return (date >= allowedRange.minimum // && ((allowedRange.maximum.isValid() && date <= allowedRange.maximum) // || (!allowedRange.maximum.isValid() && date <= maximumAllowedDate()))); } else { return !allowedRange.maximum.isValid(); } } static QString dateToString(const QDate &date, QWidget *widget) { // workaround for QLocale using "yy" way too often for years // stolen from KDateComboBox auto locale = widget ? widget->locale() : QLocale{}; const auto dateFormat = (locale .dateFormat(QLocale::ShortFormat) // - .replace(QLatin1String{"yy"}, QLatin1String{"yyyy"}) - .replace(QLatin1String{"yyyyyyyy"}, QLatin1String{"yyyy"})); + .replace(QLatin1StringView{"yy"}, QLatin1String{"yyyy"}) + .replace(QLatin1StringView{"yyyyyyyy"}, QLatin1String{"yyyy"})); return locale.toString(date, dateFormat); } static QString validityPeriodHint(const Kleo::DateRange &dateRange, QWidget *widget) { // the minimum date is always valid if (dateRange.maximum.isValid()) { if (dateRange.maximum == dateRange.minimum) { return i18nc("@info", "The date cannot be changed."); } else { return i18nc("@info ... between and .", "Enter a date between %1 and %2.", dateToString(dateRange.minimum, widget), dateToString(dateRange.maximum, widget)); } } else { return i18nc("@info ... between and .", "Enter a date between %1 and %2.", dateToString(dateRange.minimum, widget), dateToString(Kleo::maximumAllowedDate(), widget)); } } QString Kleo::validityPeriodHint() { return ::validityPeriodHint(expirationDateRange(), nullptr); } void Kleo::setUpExpirationDateComboBox(KDateComboBox *dateCB, const Kleo::DateRange &range) { const auto dateRange = range.minimum.isValid() ? range : expirationDateRange(); // enable warning on invalid or not allowed dates dateCB->setOptions(KDateComboBox::EditDate | KDateComboBox::SelectDate | KDateComboBox::DatePicker | KDateComboBox::DateKeywords | KDateComboBox::WarnOnInvalid); const auto hintAndErrorMessage = validityPeriodHint(dateRange, dateCB); dateCB->setDateRange(dateRange.minimum, dateRange.maximum.isValid() ? dateRange.maximum : maximumAllowedDate(), hintAndErrorMessage, hintAndErrorMessage); if (dateRange.minimum == dateRange.maximum) { // only one date is allowed, so that changing it no sense dateCB->setEnabled(false); } dateCB->setToolTip(hintAndErrorMessage); const QDate today = QDate::currentDate(); dateCB->setDateMap({ {today.addYears(3), i18nc("@item:inlistbox", "Three years from now")}, {today.addYears(2), i18nc("@item:inlistbox", "Two years from now")}, {today.addYears(1), i18nc("@item:inlistbox", "One year from now")}, }); } diff --git a/src/utils/keyparameters.cpp b/src/utils/keyparameters.cpp index 83db94796..7d472a453 100644 --- a/src/utils/keyparameters.cpp +++ b/src/utils/keyparameters.cpp @@ -1,394 +1,394 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/keyparameters.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "keyparameters.h" #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace GpgME; namespace { QString encodeDomainName(const QString &domain) { const QByteArray encodedDomain = QUrl::toAce(domain); return encodedDomain.isEmpty() ? domain : QString::fromLatin1(encodedDomain); } QString encodeEmail(const QString &email) { const int at = email.lastIndexOf(QLatin1Char('@')); if (at < 0) { return email; } return email.left(at + 1) + encodeDomainName(email.mid(at + 1)); } } class KeyParameters::Private { friend class ::Kleo::KeyParameters; Protocol protocol; Subkey::PubkeyAlgo keyType = Subkey::AlgoUnknown; QString cardKeyRef; unsigned int keyLength = 0; QString keyCurve; KeyUsage keyUsage; Subkey::PubkeyAlgo subkeyType = Subkey::AlgoUnknown; unsigned int subkeyLength = 0; QString subkeyCurve; KeyUsage subkeyUsage; QString name; QString comment; QString dn; std::vector emailAdresses; std::vector domainNames; std::vector uris; std::vector designatedRevokers; QDate expirationDate; public: explicit Private(Protocol proto) : protocol(proto) { } }; KeyParameters::KeyParameters() : KeyParameters{NoProtocol} { } KeyParameters::KeyParameters(Protocol protocol) : d{new Private{protocol}} { } KeyParameters::~KeyParameters() = default; KeyParameters::KeyParameters(const KeyParameters &other) : d{new Private{*other.d}} { } KeyParameters &KeyParameters::operator=(const KeyParameters &other) { *d = *other.d; return *this; } KeyParameters::KeyParameters(KeyParameters &&other) = default; KeyParameters &KeyParameters::operator=(KeyParameters &&other) = default; KeyParameters::Protocol KeyParameters::protocol() const { return d->protocol; } void KeyParameters::setKeyType(Subkey::PubkeyAlgo type) { d->keyType = type; } GpgME::Subkey::PubkeyAlgo KeyParameters::keyType() const { return d->keyType; } void KeyParameters::setCardKeyRef(const QString &cardKeyRef) { d->cardKeyRef = cardKeyRef; } QString KeyParameters::cardKeyRef() const { return d->cardKeyRef; } void KeyParameters::setKeyLength(unsigned int length) { d->keyLength = length; } unsigned int KeyParameters::keyLength() const { return d->keyLength; } void KeyParameters::setKeyCurve(const QString &curve) { d->keyCurve = curve; } QString KeyParameters::keyCurve() const { return d->keyCurve; } void KeyParameters::setKeyUsage(const KeyUsage &usage) { d->keyUsage = usage; } KeyUsage KeyParameters::keyUsage() const { return d->keyUsage; } void KeyParameters::setSubkeyType(Subkey::PubkeyAlgo type) { d->subkeyType = type; } Subkey::PubkeyAlgo KeyParameters::subkeyType() const { return d->subkeyType; } void KeyParameters::setSubkeyLength(unsigned int length) { d->subkeyLength = length; } unsigned int KeyParameters::subkeyLength() const { return d->subkeyLength; } void KeyParameters::setSubkeyCurve(const QString &curve) { d->subkeyCurve = curve; } QString KeyParameters::subkeyCurve() const { return d->subkeyCurve; } void KeyParameters::setSubkeyUsage(const KeyUsage &usage) { d->subkeyUsage = usage; } KeyUsage KeyParameters::subkeyUsage() const { return d->subkeyUsage; } void KeyParameters::setExpirationDate(const QDate &date) { d->expirationDate = date; } QDate KeyParameters::expirationDate() const { return d->expirationDate; } void KeyParameters::setName(const QString &name) { d->name = name; } QString KeyParameters::name() const { return d->name; } void KeyParameters::setComment(const QString &comment) { d->comment = comment; } QString KeyParameters::comment() const { return d->comment; } void KeyParameters::setDN(const QString &dn) { d->dn = dn; } QString KeyParameters::dn() const { return d->dn; } void KeyParameters::setEmail(const QString &email) { d->emailAdresses = {email}; } void KeyParameters::addEmail(const QString &email) { d->emailAdresses.push_back(email); } std::vector KeyParameters::emails() const { return d->emailAdresses; } void KeyParameters::addDomainName(const QString &domain) { d->domainNames.push_back(domain); } std::vector KeyParameters::domainNames() const { return d->domainNames; } void KeyParameters::addURI(const QString &uri) { d->uris.push_back(uri); } std::vector KeyParameters::uris() const { return d->uris; } void KeyParameters::addDesignatedRevoker(const QString &fpr) { d->designatedRevokers.push_back(fpr); } std::vector KeyParameters::designatedRevokers() const { return d->designatedRevokers; } namespace { QString serialize(Subkey::PubkeyAlgo algo) { return QString::fromLatin1(Subkey::publicKeyAlgorithmAsString(algo)); } QString serialize(unsigned int number) { return QString::number(number); } QString serialize(KeyUsage keyUsage) { QStringList usages; if (keyUsage.canSign()) { usages << QStringLiteral("sign"); } if (keyUsage.canEncrypt()) { usages << QStringLiteral("encrypt"); } if (keyUsage.canAuthenticate()) { usages << QStringLiteral("auth"); } if (keyUsage.canCertify()) { usages << QStringLiteral("cert"); } return usages.join(QLatin1Char{' '}); } QString serialize(const QDate &date) { return date.toString(Qt::ISODate); } QString serialize(const char *key, const QString &value) { return QString::fromLatin1(key) + QLatin1Char(':') + value; } } QString KeyParameters::toString() const { QStringList keyParameters; - keyParameters.push_back(QLatin1String("")); + keyParameters.push_back(QLatin1StringView("")); if (d->protocol == OpenPGP) { // for backward compatibility with GnuPG 2.0 and earlier keyParameters.push_back(QStringLiteral("%ask-passphrase")); } // add Key-Type as first parameter if (!d->cardKeyRef.isEmpty()) { - keyParameters.push_back(serialize("Key-Type", QLatin1String{"card:"} + d->cardKeyRef)); + keyParameters.push_back(serialize("Key-Type", QLatin1StringView{"card:"} + d->cardKeyRef)); } else if (d->keyType != Subkey::AlgoUnknown) { keyParameters.push_back(serialize("Key-Type", serialize(d->keyType))); } else { qCWarning(KLEOPATRA_LOG) << "KeyParameters::toString(): Key type is unset/empty"; } if (d->keyLength) { keyParameters.push_back(serialize("Key-Length", serialize(d->keyLength))); } if (!d->keyCurve.isEmpty()) { keyParameters.push_back(serialize("Key-Curve", d->keyCurve)); } keyParameters.push_back(serialize("Key-Usage", serialize(d->keyUsage))); if (d->subkeyType != Subkey::AlgoUnknown) { keyParameters.push_back(serialize("Subkey-Type", serialize(d->subkeyType))); if (d->subkeyUsage.value()) { keyParameters.push_back(serialize("Subkey-Usage", serialize(d->subkeyUsage))); } if (d->subkeyLength) { keyParameters.push_back(serialize("Subkey-Length", serialize(d->subkeyLength))); } if (!d->subkeyCurve.isEmpty()) { keyParameters.push_back(serialize("Subkey-Curve", d->subkeyCurve)); } } if (d->expirationDate.isValid()) { keyParameters.push_back(serialize("Expire-Date", serialize(d->expirationDate))); } if (!d->name.isEmpty()) { keyParameters.push_back(serialize("Name-Real", d->name)); } if (!d->comment.isEmpty()) { keyParameters.push_back(serialize("Name-Comment", d->comment)); } if (!d->dn.isEmpty()) { keyParameters.push_back(serialize("Name-DN", d->dn)); } std::transform(std::cbegin(d->emailAdresses), std::cend(d->emailAdresses), std::back_inserter(keyParameters), [this](const auto &email) { return serialize("Name-Email", (d->protocol == CMS) ? encodeEmail(email) : email); }); std::transform(std::cbegin(d->domainNames), std::cend(d->domainNames), std::back_inserter(keyParameters), [](const auto &domain) { return serialize("Name-DNS", encodeDomainName(domain)); }); std::transform(std::cbegin(d->uris), std::cend(d->uris), std::back_inserter(keyParameters), [](const auto &uri) { return serialize("Name-URI", uri); }); std::transform(std::cbegin(d->designatedRevokers), std::cend(d->designatedRevokers), std::back_inserter(keyParameters), [](const auto &designatedRevoker) { return serialize("Revoker", designatedRevoker); }); - keyParameters.push_back(QLatin1String("")); + keyParameters.push_back(QLatin1StringView("")); return keyParameters.join(QLatin1Char('\n')); } diff --git a/src/utils/log.cpp b/src/utils/log.cpp index 6b951d83a..b2124deb7 100644 --- a/src/utils/log.cpp +++ b/src/utils/log.cpp @@ -1,159 +1,159 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/log.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "log.h" #include "iodevicelogger.h" #include #include #include #include #include #include #include using namespace Kleo; class Log::Private { Log *const q; public: explicit Private(Log *qq) : q(qq) , m_ioLoggingEnabled(false) , m_logFile(nullptr) { Q_UNUSED(q); } ~Private(); bool m_ioLoggingEnabled; QString m_outputDirectory; FILE *m_logFile; }; Log::Private::~Private() { if (m_logFile) { fclose(m_logFile); } } void Log::messageHandler(QtMsgType type, const QMessageLogContext &ctx, const QString &msg) { const QString formattedMessage = qFormatLogMessage(type, ctx, msg); const QByteArray local8str = formattedMessage.toLocal8Bit(); FILE *const file = Log::instance()->logFile(); if (!file) { fprintf(stderr, "Log::messageHandler[!file]: %s", local8str.constData()); return; } qint64 toWrite = local8str.size(); while (toWrite > 0) { const qint64 written = fprintf(file, "%s", local8str.constData()); if (written == -1) { return; } toWrite -= written; } // append newline: while (fprintf(file, "\n") == 0) ; fflush(file); } std::shared_ptr Log::instance() { return mutableInstance(); } std::shared_ptr Log::mutableInstance() { static std::weak_ptr self; try { return std::shared_ptr(self); } catch (const std::bad_weak_ptr &) { const std::shared_ptr s(new Log); self = s; return s; } } Log::Log() : d(new Private(this)) { } Log::~Log() { } FILE *Log::logFile() const { return d->m_logFile; } void Log::setIOLoggingEnabled(bool enabled) { d->m_ioLoggingEnabled = enabled; } bool Log::ioLoggingEnabled() const { return d->m_ioLoggingEnabled; } QString Log::outputDirectory() const { return d->m_outputDirectory; } void Log::setOutputDirectory(const QString &path) { if (d->m_outputDirectory == path) { return; } d->m_outputDirectory = path; Q_ASSERT(!d->m_logFile); - const QString lfn = path + QLatin1String("/kleo-log"); + const QString lfn = path + QLatin1StringView("/kleo-log"); d->m_logFile = fopen(QDir::toNativeSeparators(lfn).toLocal8Bit().constData(), "a"); Q_ASSERT(d->m_logFile); } std::shared_ptr Log::createIOLogger(const std::shared_ptr &io, const QString &prefix, OpenMode mode) const { if (!d->m_ioLoggingEnabled) { return io; } std::shared_ptr logger(new IODeviceLogger(io)); const QString timestamp = QDateTime::currentDateTime().toString(QStringLiteral("yyMMdd-hhmmss")); const QString fn = d->m_outputDirectory + QLatin1Char('/') + prefix + QLatin1Char('-') + timestamp + QLatin1Char('-') + KRandom::randomString(4); std::shared_ptr file(new QFile(fn)); if (!file->open(QIODevice::WriteOnly)) { throw Exception(gpg_error(GPG_ERR_EIO), i18n("Log Error: Could not open log file \"%1\" for writing.", fn)); } if (mode & Read) { logger->setReadLogDevice(file); } else { // Write logger->setWriteLogDevice(file); } return logger; } diff --git a/src/utils/output.cpp b/src/utils/output.cpp index a85c8fbab..d35d4336d 100644 --- a/src/utils/output.cpp +++ b/src/utils/output.cpp @@ -1,827 +1,827 @@ /* -*- 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 "cached.h" #include "detail_p.h" #include "input_p.h" #include "kdpipeiodevice.h" #include "kleo_assert.h" #include "log.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::Options options_, OverwritePolicy::Policy pol) : policy(pol) , parentWidget(p) , options{options_} { } OverwritePolicy::Policy policy; QWidget *parentWidget; OverwritePolicy::Options options; }; OverwritePolicy::OverwritePolicy(Policy initialPolicy) : d{new Private{nullptr, {}, initialPolicy}} { } OverwritePolicy::OverwritePolicy(QWidget *parent, OverwritePolicy::Options options) : d{new Private{parent, options, Ask}} { } OverwritePolicy::~OverwritePolicy() = default; OverwritePolicy::Policy OverwritePolicy::policy() const { return d->policy; } void OverwritePolicy::setPolicy(Policy policy) { d->policy = policy; } 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> 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> 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: QString m_fileName; std::shared_ptr 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::make_shared(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)); } 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 OverwritePolicy::obtainOverwritePermission(const QString &fileName) { switch (d->policy) { case OverwritePolicy::Ask: break; case OverwritePolicy::Overwrite: return fileName; case OverwritePolicy::Rename: { return suggestFileName(fileName); } case OverwritePolicy::Skip: return {}; case OverwritePolicy::Cancel: qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Called although user canceled operation"; return {}; } OverwriteDialog::Options options = OverwriteDialog::AllowRename; if (d->options & MultipleFiles) { options |= OverwriteDialog::MultipleItems | OverwriteDialog::AllowSkip; } OverwriteDialog dialog{d->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: d->policy = OverwritePolicy::Cancel; return {}; case OverwriteDialog::AutoSkip: d->policy = OverwritePolicy::Skip; [[fallthrough]]; case OverwriteDialog::Skip: return {}; case OverwriteDialog::OverwriteAll: d->policy = OverwritePolicy::Overwrite; [[fallthrough]]; case OverwriteDialog::Overwrite: return fileName; case OverwriteDialog::Rename: return dialog.newFileName(); case OverwriteDialog::AutoRename: { d->policy = 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 = m_tmpFile->oldFileName(); remover.file = tmpFileName; 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); 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"))) { + if (tmpFileName.startsWith(QLatin1StringView("UNC"))) { tmpFileName.replace(0, strlen("UNC"), QLatin1Char('/')); remover.file = tmpFileName; } 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; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "renaming succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } return; } qCDebug(KLEOPATRA_LOG) << this << "renaming failed"; if (QFile::exists(m_fileName)) { const auto newFileName = m_policy->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 << "renaming" << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "renaming succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } return; } qCDebug(KLEOPATRA_LOG) << this << "renaming 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/path-helper.cpp b/src/utils/path-helper.cpp index f8117279b..e07c985d8 100644 --- a/src/utils/path-helper.cpp +++ b/src/utils/path-helper.cpp @@ -1,195 +1,195 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/path-helper.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "path-helper.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #ifdef Q_OS_WIN #include #endif #include using namespace Kleo; static QString commonPrefix(const QString &s1, const QString &s2) { return QString(s1.data(), std::mismatch(s1.data(), s1.data() + std::min(s1.size(), s2.size()), s2.data()).first - s1.data()); } static QString longestCommonPrefix(const QStringList &sl) { if (sl.empty()) { return QString(); } QString result = sl.front(); for (const QString &s : sl) { result = commonPrefix(s, result); } return result; } QString Kleo::heuristicBaseDirectory(const QStringList &fileNames) { QStringList dirs; for (const QString &fileName : fileNames) { dirs.push_back(QFileInfo(fileName).path() + QLatin1Char('/')); } qCDebug(KLEOPATRA_LOG) << "dirs" << dirs; const QString candidate = longestCommonPrefix(dirs); /* Special case handling for Outlook and KMail attachment temporary path. * This is otherwise something like: * c:\users\username\AppData\Local\Microsoft\Windows\INetCache\ * Content.Outlook\ADSDFG9\foo.txt * * For KMail it is usually /tmp/messageviewer/foo * * Both are paths that are unlikely to be the target path to save the * decrypted attachment. * * This is very common when encrypted attachments are opened * within Outlook or KMail. */ if (candidate.contains(QStringLiteral("Content.Outlook")) // || candidate.contains(QStringLiteral("messageviewer"))) { return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } const int idx = candidate.lastIndexOf(QLatin1Char('/')); return candidate.left(idx); } QStringList Kleo::makeRelativeTo(const QString &base, const QStringList &fileNames) { if (base.isEmpty()) { return fileNames; } else { return makeRelativeTo(QDir(base), fileNames); } } QStringList Kleo::makeRelativeTo(const QDir &baseDir, const QStringList &fileNames) { QStringList rv; rv.reserve(fileNames.size()); std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(rv), [&baseDir](const QString &file) { return baseDir.relativeFilePath(file); }); return rv; } QString Kleo::stripSuffix(const QString &fileName) { const QFileInfo fi(fileName); return fi.dir().filePath(fi.completeBaseName()); } bool Kleo::isWritable(const QFileInfo &fi) { #ifdef Q_OS_WIN if (fi.isDir()) { - QTemporaryFile dummy{fi.absoluteFilePath() + QLatin1String{"/tempXXXXXX"}}; + QTemporaryFile dummy{fi.absoluteFilePath() + QLatin1StringView{"/tempXXXXXX"}}; const auto fileCreated = dummy.open(); if (!fileCreated) { qCDebug(KLEOPATRA_LOG) << "Failed to create test file in folder" << fi.absoluteFilePath(); } return fileCreated; } #endif return fi.isWritable(); } #ifdef Q_OS_WIN void Kleo::recursivelyRemovePath(const QString &path) { const QFileInfo fi(path); if (fi.isDir()) { QDir dir(path); const auto dirs{dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden)}; for (const QString &fname : dirs) { recursivelyRemovePath(dir.filePath(fname)); } const QString dirName = fi.fileName(); dir.cdUp(); if (!dir.rmdir(dirName)) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove directory %1", path)); } } else { QFile file(path); if (!file.remove()) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove file %1: %2", path, file.errorString())); } } } bool Kleo::recursivelyCopy(const QString &src, const QString &dest) { QDir srcDir(src); if (!srcDir.exists()) { return false; } QDir destDir(dest); if (!destDir.exists() && !destDir.mkdir(dest)) { return false; } for (const auto &file : srcDir.entryList(QDir::Files | QDir::Hidden)) { const QString srcName = src + QLatin1Char('/') + file; const QString destName = dest + QLatin1Char('/') + file; if (!QFile::copy(srcName, destName)) { return false; } } for (const auto &dir : srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden)) { const QString srcName = src + QLatin1Char('/') + dir; const QString destName = dest + QLatin1Char('/') + dir; if (!recursivelyCopy(srcName, destName)) { return false; } } return true; } bool Kleo::moveDir(const QString &src, const QString &dest) { // Need an existing path to query the device const QString parentDest = QFileInfo(dest).dir().absolutePath(); const auto srcDevice = QStorageInfo(src).device(); if (!srcDevice.isEmpty() // && srcDevice == QStorageInfo(parentDest).device() // && QFile::rename(src, dest)) { qCDebug(KLEOPATRA_LOG) << "Renamed" << src << "to" << dest; return true; } // first copy if (!recursivelyCopy(src, dest)) { return false; } // Then delete original recursivelyRemovePath(src); return true; } #endif diff --git a/src/utils/scrollarea.cpp b/src/utils/scrollarea.cpp index 5620db02d..4fe7d05e7 100644 --- a/src/utils/scrollarea.cpp +++ b/src/utils/scrollarea.cpp @@ -1,103 +1,103 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/scrollarea.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "scrollarea.h" #include #include #include #include #include using namespace Kleo; ScrollArea::ScrollArea(QWidget *parent) : QScrollArea{parent} { auto w = new QWidget; - w->setObjectName(QLatin1String("scrollarea_widget")); + w->setObjectName(QLatin1StringView("scrollarea_widget")); new QVBoxLayout{w}; setWidget(w); setWidgetResizable(true); w->installEventFilter(this); connect(qApp, &QApplication::focusChanged, this, [this](QWidget *old, QWidget *now) { Q_UNUSED(old); ensureWidgetVisible(now); }); } ScrollArea::~ScrollArea() { widget()->removeEventFilter(this); } QSize ScrollArea::minimumSizeHint() const { const int fw = frameWidth(); QSize sz{2 * fw, 2 * fw}; sz += {widget()->minimumSizeHint().width(), 0}; if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setWidth(sz.width() + verticalScrollBar()->sizeHint().width()); } if (horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setHeight(sz.height() + horizontalScrollBar()->sizeHint().height()); } return QScrollArea::minimumSizeHint().expandedTo(sz); } QSize ScrollArea::sizeHint() const { const int fw = frameWidth(); QSize sz{2 * fw, 2 * fw}; sz += viewportSizeHint(); if (verticalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setWidth(sz.width() + verticalScrollBar()->sizeHint().width()); } if (horizontalScrollBarPolicy() != Qt::ScrollBarAlwaysOff) { sz.setHeight(sz.height() + horizontalScrollBar()->sizeHint().height()); } sz = QScrollArea::sizeHint().expandedTo(sz); return sz; } void ScrollArea::adjustSizeOfWindowBy(const QSize &extent) { if (auto w = window()) { const auto currentSize = w->size(); // we limit the automatic size adjustment to 2/3 of the screen's size const auto maxWindowSize = screen()->geometry().size() * 2 / 3; const auto newWindowSize = currentSize.expandedTo((currentSize + extent).boundedTo(maxWindowSize)); if (newWindowSize != currentSize) { w->resize(newWindowSize); } } } bool ScrollArea::eventFilter(QObject *obj, QEvent *ev) { if (ev->type() == QEvent::Resize && obj == widget() && sizeAdjustPolicy() == AdjustToContents) { const auto *const event = static_cast(ev); if (event->size().height() > event->oldSize().height()) { const auto currentViewportHeight = viewport()->height(); const auto wantedViewportHeight = event->size().height(); const auto wantedAdditionalHeight = wantedViewportHeight - currentViewportHeight; if (wantedAdditionalHeight > 0) { adjustSizeOfWindowBy(QSize{0, wantedAdditionalHeight}); } } } return QScrollArea::eventFilter(obj, ev); } #include "moc_scrollarea.cpp" diff --git a/src/utils/validation.cpp b/src/utils/validation.cpp index 2bb457b55..c6d58cc66 100644 --- a/src/utils/validation.cpp +++ b/src/utils/validation.cpp @@ -1,122 +1,122 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/validation.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "validation.h" #include #include #include "kleopatra_debug.h" #include using namespace Kleo; namespace { template class TrimmingValidator : public Validator { public: using Validator::Validator; QValidator::State validate(QString &str, int &pos) const override { auto trimmed = str.trimmed(); auto posCopy = pos; return Validator::validate(trimmed, posCopy); } }; template class EmptyIsAcceptableValidator : public Validator { public: using Validator::Validator; QValidator::State validate(QString &str, int &pos) const override { if (str.isEmpty()) { return QValidator::Acceptable; } return Validator::validate(str, pos); } }; class EMailValidator : public QValidator { public: EMailValidator() : QValidator{} { } State validate(QString &str, int &pos) const override { Q_UNUSED(pos) if (KEmailAddress::isValidSimpleAddress(str)) { return Acceptable; } return Intermediate; } }; std::shared_ptr regularExpressionValidator(Validation::Flags flags, const QString ®exp) { if (flags & Validation::Required) { return std::make_shared>(QRegularExpression{regexp}); } else { return std::make_shared>>(QRegularExpression{regexp}); } } } std::shared_ptr Validation::email(Flags flags) { if (flags & Required) { return std::make_shared>(); } else { return std::make_shared>>(); } } std::shared_ptr Validation::email(const QString &addRX, Flags flags) { return MultiValidator::create({email(flags), regularExpressionValidator(flags, addRX)}); } std::shared_ptr Validation::pgpName(Flags flags) { // this regular expression is modeled after gnupg/g10/keygen.c:ask_user_id: - static const QString name_rx{QLatin1String{"[^0-9<>][^<>@]{4,}"}}; + static const QString name_rx{QLatin1StringView{"[^0-9<>][^<>@]{4,}"}}; return regularExpressionValidator(flags, name_rx); } std::shared_ptr Validation::pgpName(const QString &addRX, Flags flags) { return MultiValidator::create({pgpName(flags), regularExpressionValidator(flags, addRX)}); } std::shared_ptr Validation::simpleName(Flags flags) { - static const QString name_rx{QLatin1String{"[^<>@]*"}}; + static const QString name_rx{QLatin1StringView{"[^<>@]*"}}; return std::shared_ptr{regularExpressionValidator(flags, name_rx)}; } std::shared_ptr Validation::simpleName(const QString &additionalRegExp, Flags flags) { return MultiValidator::create({simpleName(flags), regularExpressionValidator(flags, additionalRegExp)}); } diff --git a/src/utils/windowsprocessdevice.cpp b/src/utils/windowsprocessdevice.cpp index 1ca3ee5a9..be69a01a2 100644 --- a/src/utils/windowsprocessdevice.cpp +++ b/src/utils/windowsprocessdevice.cpp @@ -1,436 +1,436 @@ /* utils/windowsprocessdevice.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2019 g 10code GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #ifdef WIN32 #include "windowsprocessdevice.h" #include "kleopatra_debug.h" #include #include #include #include /* This is the amount of data GPGME reads at once */ #define PIPEBUF_SIZE 16384 using namespace Kleo; static void CloseHandleX(HANDLE &h) { if (h && h != INVALID_HANDLE_VALUE) { if (!CloseHandle(h)) { qCWarning(KLEOPATRA_LOG) << "CloseHandle failed!"; } h = nullptr; } } class WindowsProcessDevice::Private { public: ~Private(); Private(const QString &path, const QStringList &args, const QString &wd) : mPath(path) , mArgs(args) , mWorkingDirectory(wd) , mStdInRd(nullptr) , mStdInWr(nullptr) , mStdOutRd(nullptr) , mStdOutWr(nullptr) , mStdErrRd(nullptr) , mStdErrWr(nullptr) , mProc(nullptr) , mThread(nullptr) , mEnded(false) { } bool start(QIODevice::OpenMode mode); qint64 write(const char *data, qint64 size) { if (size < 0 || (size >> 32)) { qCDebug(KLEOPATRA_LOG) << "Invalid write"; return -1; } if (!mStdInWr) { qCDebug(KLEOPATRA_LOG) << "Write to closed or read only device"; return -1; } DWORD dwWritten; if (!WriteFile(mStdInWr, data, (DWORD)size, &dwWritten, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to write"; return -1; } if (dwWritten != size) { qCDebug(KLEOPATRA_LOG) << "Failed to write everything"; return -1; } return size; } qint64 read(char *data, qint64 maxSize) { if (!mStdOutRd) { qCDebug(KLEOPATRA_LOG) << "Read of closed or write only device"; return -1; } if (!maxSize) { return 0; } DWORD exitCode = 0; if (GetExitCodeProcess(mProc, &exitCode)) { if (exitCode != STILL_ACTIVE) { if (exitCode) { qCDebug(KLEOPATRA_LOG) << "Non zero exit code"; mError = readAllStdErr(); return -1; } mEnded = true; qCDebug(KLEOPATRA_LOG) << "Process finished with code " << exitCode; } } else { qCDebug(KLEOPATRA_LOG) << "GetExitCodeProcess Failed"; } if (mEnded) { DWORD avail = 0; if (!PeekNamedPipe(mStdOutRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return -1; } if (!avail) { qCDebug(KLEOPATRA_LOG) << "Process ended and nothing more in pipe"; return 0; } } DWORD dwRead; if (!ReadFile(mStdOutRd, data, (DWORD)maxSize, &dwRead, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to read"; return -1; } return dwRead; } QString readAllStdErr() { QString ret; if (!mStdErrRd) { qCDebug(KLEOPATRA_LOG) << "Read of closed stderr"; } DWORD dwRead = 0; do { char buf[4096]; DWORD avail; if (!PeekNamedPipe(mStdErrRd, nullptr, 0, nullptr, &avail, nullptr)) { qCDebug(KLEOPATRA_LOG) << "Failed to peek pipe"; return ret; } if (!avail) { return ret; } ReadFile(mStdErrRd, buf, 4096, &dwRead, nullptr); if (dwRead) { QByteArray ba(buf, dwRead); ret += QString::fromLocal8Bit(ba); } } while (dwRead); return ret; } void close() { if (mProc && mProc != INVALID_HANDLE_VALUE) { TerminateProcess(mProc, 0xf291); CloseHandleX(mProc); } } QString errorString() { return mError; } void closeWriteChannel() { CloseHandleX(mStdInWr); } private: QString mPath; QStringList mArgs; QString mWorkingDirectory; QString mError; HANDLE mStdInRd; HANDLE mStdInWr; HANDLE mStdOutRd; HANDLE mStdOutWr; HANDLE mStdErrRd; HANDLE mStdErrWr; HANDLE mProc; HANDLE mThread; bool mEnded; }; WindowsProcessDevice::WindowsProcessDevice(const QString &path, const QStringList &args, const QString &wd) : d(new Private(path, args, wd)) { } bool WindowsProcessDevice::open(QIODevice::OpenMode mode) { bool ret = d->start(mode); if (ret) { setOpenMode(mode); } return ret; } qint64 WindowsProcessDevice::readData(char *data, qint64 maxSize) { return d->read(data, maxSize); } qint64 WindowsProcessDevice::writeData(const char *data, qint64 maxSize) { return d->write(data, maxSize); } bool WindowsProcessDevice::isSequential() const { return true; } void WindowsProcessDevice::closeWriteChannel() { d->closeWriteChannel(); } void WindowsProcessDevice::close() { d->close(); QIODevice::close(); } QString getLastErrorString() { wchar_t *lpMsgBuf = nullptr; DWORD dw = GetLastError(); FormatMessageW(FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS, NULL, dw, MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT), (wchar_t *)&lpMsgBuf, 0, NULL); QString ret = QString::fromWCharArray(lpMsgBuf); LocalFree(lpMsgBuf); return ret; } WindowsProcessDevice::Private::~Private() { if (mProc && mProc != INVALID_HANDLE_VALUE) { close(); } CloseHandleX(mThread); CloseHandleX(mStdInRd); CloseHandleX(mStdInWr); CloseHandleX(mStdOutRd); CloseHandleX(mStdOutWr); CloseHandleX(mStdErrRd); CloseHandleX(mStdErrWr); } static QString qt_create_commandline(const QString &program, const QStringList &arguments, const QString &nativeArguments) { QString args; if (!program.isEmpty()) { QString programName = program; if (!programName.startsWith(QLatin1Char('\"')) && !programName.endsWith(QLatin1Char('\"')) && programName.contains(QLatin1Char(' '))) programName = QLatin1Char('\"') + programName + QLatin1Char('\"'); programName.replace(QLatin1Char('/'), QLatin1Char('\\')); // add the prgram as the first arg ... it works better args = programName + QLatin1Char(' '); } for (int i = 0; i < arguments.size(); ++i) { QString tmp = arguments.at(i); // Quotes are escaped and their preceding backslashes are doubled. - tmp.replace(QRegularExpression(QLatin1String(R"--((\\*)")--")), QLatin1String(R"--(\1\1\")--")); + tmp.replace(QRegularExpression(QLatin1StringView(R"--((\\*)")--")), QLatin1String(R"--(\1\1\")--")); if (tmp.isEmpty() || tmp.contains(QLatin1Char(' ')) || tmp.contains(QLatin1Char('\t'))) { // The argument must not end with a \ since this would be interpreted // as escaping the quote -- rather put the \ behind the quote: e.g. // rather use "foo"\ than "foo\" int i = tmp.length(); while (i > 0 && tmp.at(i - 1) == QLatin1Char('\\')) --i; tmp.insert(i, QLatin1Char('"')); tmp.prepend(QLatin1Char('"')); } args += QLatin1Char(' ') + tmp; } if (!nativeArguments.isEmpty()) { if (!args.isEmpty()) args += QLatin1Char(' '); args += nativeArguments; } return args; } bool WindowsProcessDevice::Private::start(QIODevice::OpenMode mode) { if (mode != QIODevice::ReadOnly // && mode != QIODevice::WriteOnly // && mode != QIODevice::ReadWrite) { qCDebug(KLEOPATRA_LOG) << "Unsupported open mode " << mode; return false; } SECURITY_ATTRIBUTES saAttr; ZeroMemory(&saAttr, sizeof(SECURITY_ATTRIBUTES)); saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); saAttr.bInheritHandle = TRUE; saAttr.lpSecurityDescriptor = NULL; // Create the pipes if (!CreatePipe(&mStdOutRd, &mStdOutWr, &saAttr, PIPEBUF_SIZE) // || !CreatePipe(&mStdErrRd, &mStdErrWr, &saAttr, 0) // || !CreatePipe(&mStdInRd, &mStdInWr, &saAttr, PIPEBUF_SIZE)) { qCDebug(KLEOPATRA_LOG) << "Failed to create pipes"; mError = getLastErrorString(); return false; } // Ensure only the proper handles are inherited if (!SetHandleInformation(mStdOutRd, HANDLE_FLAG_INHERIT, 0) // || !SetHandleInformation(mStdErrRd, HANDLE_FLAG_INHERIT, 0) // || !SetHandleInformation(mStdInWr, HANDLE_FLAG_INHERIT, 0)) { qCDebug(KLEOPATRA_LOG) << "Failed to set inherit flag"; mError = getLastErrorString(); return false; } PROCESS_INFORMATION piProcInfo; ZeroMemory(&piProcInfo, sizeof(PROCESS_INFORMATION)); STARTUPINFO siStartInfo; ZeroMemory(&siStartInfo, sizeof(STARTUPINFO)); siStartInfo.cb = sizeof(STARTUPINFO); siStartInfo.hStdError = mStdErrWr; siStartInfo.hStdOutput = mStdOutWr; siStartInfo.hStdInput = mStdInRd; siStartInfo.dwFlags |= STARTF_USESTDHANDLES; const auto args = qt_create_commandline(mPath, mArgs, QString()); wchar_t *cmdLine = wcsdup(reinterpret_cast(args.utf16())); const wchar_t *proc = reinterpret_cast(mPath.utf16()); const QString nativeWorkingDirectory = QDir::toNativeSeparators(mWorkingDirectory); const wchar_t *wd = reinterpret_cast(nativeWorkingDirectory.utf16()); /* Filter out the handles to inherit only the three handles which we want to * inherit. As a Qt Application we have a multitude of open handles which * may or may not be inheritable. Testing has shown that this reduced about * thirty handles. Open File handles in our child application can also cause the * read pipe not to be closed correcly on exit. */ SIZE_T size; bool suc = InitializeProcThreadAttributeList(NULL, 1, 0, &size) || GetLastError() == ERROR_INSUFFICIENT_BUFFER; if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to get Attribute List size"; mError = getLastErrorString(); return false; } LPPROC_THREAD_ATTRIBUTE_LIST attributeList = reinterpret_cast(HeapAlloc(GetProcessHeap(), 0, size)); if (!attributeList) { qCDebug(KLEOPATRA_LOG) << "Failed to Allocate Attribute List"; return false; } suc = InitializeProcThreadAttributeList(attributeList, 1, 0, &size); if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to Initalize Attribute List"; mError = getLastErrorString(); return false; } HANDLE handles[3]; handles[0] = mStdOutWr; handles[1] = mStdErrWr; handles[2] = mStdInRd; suc = UpdateProcThreadAttribute(attributeList, 0, PROC_THREAD_ATTRIBUTE_HANDLE_LIST, handles, 3 * sizeof(HANDLE), NULL, NULL); if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to Update Attribute List"; mError = getLastErrorString(); return false; } STARTUPINFOEX info; ZeroMemory(&info, sizeof(info)); info.StartupInfo = siStartInfo; info.StartupInfo.cb = sizeof(info); // You have to know this,.. info.lpAttributeList = attributeList; // Now lets start qCDebug(KLEOPATRA_LOG) << "Spawning:" << args; suc = CreateProcessW(proc, cmdLine, // command line NULL, // process security attributes NULL, // primary thread security attributes TRUE, // handles are inherited CREATE_NO_WINDOW | EXTENDED_STARTUPINFO_PRESENT, // creation flags NULL, // use parent's environment wd, // use parent's current directory &info.StartupInfo, // STARTUPINFO pointer &piProcInfo); // receives PROCESS_INFORMATION DeleteProcThreadAttributeList(attributeList); HeapFree(GetProcessHeap(), 0, attributeList); CloseHandleX(mStdOutWr); CloseHandleX(mStdErrWr); CloseHandleX(mStdInRd); free(cmdLine); if (!suc) { qCDebug(KLEOPATRA_LOG) << "Failed to create process"; mError = getLastErrorString(); return false; } mProc = piProcInfo.hProcess; mThread = piProcInfo.hThread; if (mode == QIODevice::WriteOnly) { CloseHandleX(mStdOutRd); } if (mode == QIODevice::ReadOnly) { CloseHandleX(mStdInWr); } return true; } QString WindowsProcessDevice::errorString() { return d->errorString(); } #endif #include "moc_windowsprocessdevice.cpp" diff --git a/src/view/formtextinput.cpp b/src/view/formtextinput.cpp index 1988c566e..101df11c8 100644 --- a/src/view/formtextinput.cpp +++ b/src/view/formtextinput.cpp @@ -1,419 +1,419 @@ /* view/formtextinput.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "formtextinput.h" #include "errorlabel.h" #include "utils/accessibility.h" #include #include #include #include #include #include "kleopatra_debug.h" namespace { auto defaultValueRequiredErrorMessage() { return i18n("Error: Enter a value."); } auto defaultInvalidEntryErrorMessage() { return i18n("Error: Enter a value in the correct format."); } } namespace Kleo::_detail { class FormTextInputBase::Private { FormTextInputBase *q; public: enum Error { EntryOK, EntryMissing, // a required entry is missing InvalidEntry // the validator doesn't accept the entry }; Private(FormTextInputBase *q) : q{q} , mValueRequiredErrorMessage{defaultValueRequiredErrorMessage()} , mInvalidEntryErrorMessage{defaultInvalidEntryErrorMessage()} { } QString annotatedIfRequired(const QString &text) const; void updateLabel(); void setLabelText(const QString &text, const QString &accessibleName); void setHint(const QString &text, const QString &accessibleDescription); QString errorMessage(Error error) const; QString accessibleErrorMessage(Error error) const; void updateError(); QString accessibleDescription() const; void updateAccessibleNameAndDescription(); QPointer mLabel; QPointer mHintLabel; QPointer mWidget; QPointer mErrorLabel; std::shared_ptr mValidator; QString mLabelText; QString mAccessibleName; QString mValueRequiredErrorMessage; QString mAccessibleValueRequiredErrorMessage; QString mInvalidEntryErrorMessage; QString mAccessibleInvalidEntryErrorMessage; Error mError = EntryOK; bool mRequired = false; bool mEditingInProgress = false; }; QString FormTextInputBase::Private::annotatedIfRequired(const QString &text) const { return mRequired ? i18nc("@label label text (required)", "%1 (required)", text) // : text; } void FormTextInputBase::Private::updateLabel() { if (mLabel) { mLabel->setText(annotatedIfRequired(mLabelText)); } } void FormTextInputBase::Private::setLabelText(const QString &text, const QString &accessibleName) { mLabelText = text; mAccessibleName = accessibleName.isEmpty() ? text : accessibleName; updateLabel(); updateAccessibleNameAndDescription(); } void FormTextInputBase::Private::setHint(const QString &text, const QString &accessibleDescription) { if (!mHintLabel) { return; } mHintLabel->setVisible(!text.isEmpty()); mHintLabel->setText(text); mHintLabel->setAccessibleName(accessibleDescription.isEmpty() ? text : accessibleDescription); updateAccessibleNameAndDescription(); } namespace { QString decoratedError(const QString &text) { return text.isEmpty() ? QString() : i18nc("@info", "Error: %1", text); } } QString FormTextInputBase::Private::errorMessage(Error error) const { switch (error) { case EntryOK: return {}; case EntryMissing: return mValueRequiredErrorMessage; case InvalidEntry: return mInvalidEntryErrorMessage; } return {}; } QString FormTextInputBase::Private::accessibleErrorMessage(Error error) const { switch (error) { case EntryOK: return {}; case EntryMissing: return mAccessibleValueRequiredErrorMessage; case InvalidEntry: return mAccessibleInvalidEntryErrorMessage; } return {}; } void FormTextInputBase::Private::updateError() { if (!mErrorLabel) { return; } if (mRequired && !q->hasValue()) { mError = EntryMissing; } else if (!q->hasAcceptableInput()) { mError = InvalidEntry; } else { mError = EntryOK; } const auto currentErrorMessage = mErrorLabel->text(); const auto newErrorMessage = decoratedError(errorMessage(mError)); if (newErrorMessage == currentErrorMessage) { return; } if (currentErrorMessage.isEmpty() && mEditingInProgress) { // delay showing the error message until editing is finished, so that we // do not annoy the user with an error message while they are still // entering the recipient; // on the other hand, we clear the error message immediately if it does // not apply anymore and we update the error message immediately if it // changed return; } mErrorLabel->setVisible(!newErrorMessage.isEmpty()); mErrorLabel->setText(newErrorMessage); mErrorLabel->setAccessibleName(decoratedError(accessibleErrorMessage(mError))); updateAccessibleNameAndDescription(); } QString FormTextInputBase::Private::accessibleDescription() const { QString description; if (mHintLabel) { // get the explicitly set accessible hint text description = mHintLabel->accessibleName(); } if (description.isEmpty()) { // fall back to the default accessible description of the input widget description = getAccessibleDescription(mWidget); } return description; } void FormTextInputBase::Private::updateAccessibleNameAndDescription() { // fall back to default accessible name if accessible name wasn't set explicitly if (mAccessibleName.isEmpty()) { mAccessibleName = getAccessibleName(mWidget); } const bool errorShown = mErrorLabel && mErrorLabel->isVisible(); // Qt does not support "described-by" relations (like WCAG's "aria-describedby" relationship attribute); // emulate this by setting the hint text and, if the error is shown, the error message as accessible // description of the input field - const auto description = errorShown ? accessibleDescription() + QLatin1String{" "} + mErrorLabel->accessibleName() // + const auto description = errorShown ? accessibleDescription() + QLatin1StringView{" "} + mErrorLabel->accessibleName() // : accessibleDescription(); if (mWidget && mWidget->accessibleDescription() != description) { mWidget->setAccessibleDescription(description); } // Qt does not support IA2's "invalid entry" state (like WCAG's "aria-invalid" state attribute); // screen readers say something like "invalid entry" if this state is set; // emulate this by adding "invalid entry" to the accessible name of the input field // and its label QString name = annotatedIfRequired(mAccessibleName); if (errorShown) { - name += QLatin1String{", "} + invalidEntryText(); + name += QLatin1StringView{", "} + invalidEntryText(); }; if (mLabel && mLabel->accessibleName() != name) { mLabel->setAccessibleName(name); } if (mWidget && mWidget->accessibleName() != name) { mWidget->setAccessibleName(name); } } FormTextInputBase::FormTextInputBase() : d{new Private{this}} { } FormTextInputBase::~FormTextInputBase() = default; QWidget *FormTextInputBase::widget() const { return d->mWidget; } QLabel *FormTextInputBase::label() const { return d->mLabel; } QLabel *FormTextInputBase::hintLabel() const { return d->mHintLabel; } ErrorLabel *FormTextInputBase::errorLabel() const { return d->mErrorLabel; } void FormTextInputBase::setLabelText(const QString &text, const QString &accessibleName) { d->setLabelText(text, accessibleName); } void FormTextInputBase::setHint(const QString &text, const QString &accessibleDescription) { d->setHint(text, accessibleDescription); } void FormTextInputBase::setIsRequired(bool required) { d->mRequired = required; d->updateLabel(); d->updateAccessibleNameAndDescription(); } bool FormTextInputBase::isRequired() const { return d->mRequired; } void FormTextInputBase::setValidator(const std::shared_ptr &validator) { Q_ASSERT(!validator || !validator->parent()); d->mValidator = validator; } void FormTextInputBase::setValueRequiredErrorMessage(const QString &text, const QString &accessibleText) { if (text.isEmpty()) { d->mValueRequiredErrorMessage = defaultValueRequiredErrorMessage(); } else { d->mValueRequiredErrorMessage = text; } if (accessibleText.isEmpty()) { d->mAccessibleValueRequiredErrorMessage = d->mValueRequiredErrorMessage; } else { d->mAccessibleValueRequiredErrorMessage = accessibleText; } } void FormTextInputBase::setInvalidEntryErrorMessage(const QString &text, const QString &accessibleText) { if (text.isEmpty()) { d->mInvalidEntryErrorMessage = defaultInvalidEntryErrorMessage(); } else { d->mInvalidEntryErrorMessage = text; } if (accessibleText.isEmpty()) { d->mAccessibleInvalidEntryErrorMessage = d->mInvalidEntryErrorMessage; } else { d->mAccessibleInvalidEntryErrorMessage = accessibleText; } } void FormTextInputBase::setToolTip(const QString &toolTip) { if (d->mLabel) { d->mLabel->setToolTip(toolTip); } if (d->mWidget) { d->mWidget->setToolTip(toolTip); } } void FormTextInputBase::setWidget(QWidget *widget) { auto parent = widget ? widget->parentWidget() : nullptr; d->mWidget = widget; d->mLabel = new QLabel{parent}; d->mLabel->setTextFormat(Qt::PlainText); d->mLabel->setWordWrap(true); QFont font = d->mLabel->font(); font.setBold(true); d->mLabel->setFont(font); d->mLabel->setBuddy(d->mWidget); d->mHintLabel = new QLabel{parent}; d->mHintLabel->setWordWrap(true); d->mHintLabel->setTextFormat(Qt::PlainText); // set widget as buddy of hint label, so that the label isn't considered unrelated d->mHintLabel->setBuddy(d->mWidget); d->mHintLabel->setVisible(false); d->mErrorLabel = new ErrorLabel{parent}; d->mErrorLabel->setWordWrap(true); d->mErrorLabel->setTextFormat(Qt::PlainText); // set widget as buddy of error label, so that the label isn't considered unrelated d->mErrorLabel->setBuddy(d->mWidget); d->mErrorLabel->setVisible(false); connectWidget(); } void FormTextInputBase::setEnabled(bool enabled) { if (d->mLabel) { d->mLabel->setEnabled(enabled); } if (d->mWidget) { d->mWidget->setEnabled(enabled); } if (d->mErrorLabel) { d->mErrorLabel->setVisible(enabled && !d->mErrorLabel->text().isEmpty()); } } QString FormTextInputBase::currentError() const { if (d->mError) { return d->errorMessage(d->mError); } return {}; } bool FormTextInputBase::validate(const QString &text, int pos) const { QString textCopy = text; if (d->mValidator && d->mValidator->validate(textCopy, pos) != QValidator::Acceptable) { return false; } return true; } void FormTextInputBase::onTextChanged() { d->mEditingInProgress = true; d->updateError(); } void FormTextInputBase::onEditingFinished() { d->mEditingInProgress = false; d->updateError(); } } template<> bool Kleo::FormTextInput::hasValue() const { const auto w = widget(); return w && !w->text().trimmed().isEmpty(); } template<> bool Kleo::FormTextInput::hasAcceptableInput() const { const auto w = widget(); return w && validate(w->text(), w->cursorPosition()); } template<> void Kleo::FormTextInput::connectWidget() { const auto w = widget(); QObject::connect(w, &QLineEdit::editingFinished, w, [this]() { onEditingFinished(); }); QObject::connect(w, &QLineEdit::textChanged, w, [this]() { onTextChanged(); }); } diff --git a/src/view/htmllabel.cpp b/src/view/htmllabel.cpp index 8c7c452a1..144520211 100644 --- a/src/view/htmllabel.cpp +++ b/src/view/htmllabel.cpp @@ -1,153 +1,153 @@ /* view/htmllabel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "htmllabel.h" #include "anchorcache_p.h" #include #include using namespace Kleo; class HtmlLabel::Private { HtmlLabel *q; public: Private(HtmlLabel *qq) : q{qq} { } void updateText(const QString &newText = {}); AnchorCache mAnchorCache; QColor linkColor; }; void HtmlLabel::Private::updateText(const QString &newText) { - static const QString styleTemplate{QLatin1String{""}}; + static const QString styleTemplate{QLatin1StringView{""}}; if (newText.isEmpty() && q->text().isEmpty()) { return; } const auto styleTag = styleTemplate.arg(linkColor.isValid() ? linkColor.name() : q->palette().link().color().name()); if (newText.isEmpty()) { q->setText(styleTag + q->text().mid(styleTag.size())); } else { q->setText(styleTag + newText); } mAnchorCache.setText(q->text()); } HtmlLabel::HtmlLabel(QWidget *parent) : HtmlLabel{{}, parent} { } HtmlLabel::HtmlLabel(const QString &html, QWidget *parent) : QLabel{parent} , d{new Private{this}} { setTextFormat(Qt::RichText); setTextInteractionFlags(Qt::TextBrowserInteraction); setHtml(html); } HtmlLabel::~HtmlLabel() = default; void HtmlLabel::setHtml(const QString &html) { if (html.isEmpty()) { clear(); d->mAnchorCache.clear(); return; } d->updateText(html); } void HtmlLabel::setLinkColor(const QColor &color) { d->linkColor = color; d->updateText(); } int HtmlLabel::numberOfAnchors() const { return d->mAnchorCache.size(); } QString HtmlLabel::anchorText(int index) const { if (index >= 0 && index < d->mAnchorCache.size()) { return d->mAnchorCache[index].text; } return {}; } QString HtmlLabel::anchorHref(int index) const { if (index >= 0 && index < d->mAnchorCache.size()) { return d->mAnchorCache[index].href; } return {}; } void HtmlLabel::activateAnchor(int index) { // based on QWidgetTextControlPrivate::activateLinkUnderCursor if (index < 0 || index >= d->mAnchorCache.size()) { return; } const auto &anchor = d->mAnchorCache[index]; if (anchor.href.isEmpty()) { return; } if (hasFocus()) { // move cursor just before the anchor and clear the selection setSelection(anchor.start, 0); // focus the anchor focusNextPrevChild(true); } else { // clear the selection moving the cursor just after the anchor setSelection(anchor.end, 0); } if (openExternalLinks()) { QDesktopServices::openUrl(QUrl{anchor.href}); } else { Q_EMIT linkActivated(anchor.href); } } int HtmlLabel::selectedAnchor() const { return d->mAnchorCache.findAnchor(selectionStart()); } bool HtmlLabel::focusNextPrevChild(bool next) { const bool result = QLabel::focusNextPrevChild(next); if (hasFocus() && QAccessible::isActive()) { const int anchorIndex = selectedAnchor(); if (anchorIndex >= 0) { QAccessibleEvent focusEvent(this, QAccessible::Focus); focusEvent.setChild(anchorIndex); QAccessible::updateAccessibility(&focusEvent); } } return result; } #include "moc_htmllabel.cpp" diff --git a/src/view/openpgpkeycardwidget.cpp b/src/view/openpgpkeycardwidget.cpp index 174f78d0a..c9e74b2d0 100644 --- a/src/view/openpgpkeycardwidget.cpp +++ b/src/view/openpgpkeycardwidget.cpp @@ -1,266 +1,266 @@ /* view/openpgpkeycardwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "openpgpkeycardwidget.h" #include "commands/detailscommand.h" #include "smartcard/card.h" #include "smartcard/keypairinfo.h" #include "smartcard/openpgpcard.h" #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace SmartCard; namespace { struct KeyWidgets { std::string cardKeyRef; std::string keyFingerprint; KeyPairInfo keyInfo; QLabel *keyTitleLabel = nullptr; QLabel *keyInfoLabel = nullptr; QPushButton *showCertificateDetailsButton = nullptr; QPushButton *generateButton = nullptr; QPushButton *createCSRButton = nullptr; }; KeyWidgets createKeyWidgets(const KeyPairInfo &keyInfo, QWidget *parent) { KeyWidgets keyWidgets; keyWidgets.keyTitleLabel = new QLabel{OpenPGPCard::keyDisplayName(keyInfo.keyRef), parent}; keyWidgets.keyInfoLabel = new QLabel{parent}; keyWidgets.keyInfoLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); keyWidgets.showCertificateDetailsButton = new QPushButton{i18nc("@action:button", "Show Details"), parent}; keyWidgets.showCertificateDetailsButton->setToolTip(i18nc("@action:tooltip", "Show detailed information about this key")); keyWidgets.showCertificateDetailsButton->setEnabled(false); keyWidgets.generateButton = new QPushButton{i18nc("@action:button", "Generate Key"), parent}; keyWidgets.generateButton->setEnabled(false); if (keyInfo.canCertify() || keyInfo.canSign() || keyInfo.canAuthenticate()) { keyWidgets.createCSRButton = new QPushButton{i18nc("@action:button", "Create CSR"), parent}; keyWidgets.createCSRButton->setToolTip(i18nc("@info:tooltip", "Create a certificate signing request for this key")); keyWidgets.createCSRButton->setEnabled(false); } return keyWidgets; } } class OpenPGPKeyCardWidget::Private { public: explicit Private(OpenPGPKeyCardWidget *q); ~Private() = default; void setAllowedActions(Actions actions); void update(const Card *card = nullptr); private: void updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card); void updateKeyWidgets(const std::string &openPGPKeyRef); void showCertificateDetails(const std::string &openPGPKeyRef); private: OpenPGPKeyCardWidget *const q; Actions mAllowedActions = AllActions; std::map mKeyWidgets; }; OpenPGPKeyCardWidget::Private::Private(OpenPGPKeyCardWidget *q) : q{q} { auto grid = new QGridLayout{q}; grid->setContentsMargins(0, 0, 0, 0); for (const auto &keyInfo : OpenPGPCard::supportedKeys()) { const KeyWidgets keyWidgets = createKeyWidgets(keyInfo, q); const std::string keyRef = keyInfo.keyRef; connect(keyWidgets.showCertificateDetailsButton, &QPushButton::clicked, q, [this, keyRef]() { showCertificateDetails(keyRef); }); connect(keyWidgets.generateButton, &QPushButton::clicked, q, [q, keyRef]() { Q_EMIT q->generateKeyRequested(keyRef); }); if (keyWidgets.createCSRButton) { connect(keyWidgets.createCSRButton, &QPushButton::clicked, q, [q, keyRef]() { Q_EMIT q->createCSRRequested(keyRef); }); } const int row = grid->rowCount(); grid->addWidget(keyWidgets.keyTitleLabel, row, 0, Qt::AlignTop); grid->addWidget(keyWidgets.keyInfoLabel, row, 1, Qt::AlignTop); auto buttons = new QHBoxLayout; buttons->addWidget(keyWidgets.showCertificateDetailsButton); buttons->addWidget(keyWidgets.generateButton); if (keyWidgets.createCSRButton) { buttons->addWidget(keyWidgets.createCSRButton); } buttons->addStretch(1); grid->addLayout(buttons, row, 2, Qt::AlignTop); mKeyWidgets.insert({keyInfo.keyRef, keyWidgets}); } grid->setColumnStretch(grid->columnCount(), 1); } void OpenPGPKeyCardWidget::Private::setAllowedActions(Actions actions) { mAllowedActions = actions; update(); } void OpenPGPKeyCardWidget::Private::update(const Card *card) { if (card) { updateCachedValues(OpenPGPCard::pgpSigKeyRef(), card->signingKeyRef(), card); updateCachedValues(OpenPGPCard::pgpEncKeyRef(), card->encryptionKeyRef(), card); updateCachedValues(OpenPGPCard::pgpAuthKeyRef(), card->authenticationKeyRef(), card); } updateKeyWidgets(OpenPGPCard::pgpSigKeyRef()); updateKeyWidgets(OpenPGPCard::pgpEncKeyRef()); updateKeyWidgets(OpenPGPCard::pgpAuthKeyRef()); } void OpenPGPKeyCardWidget::Private::updateCachedValues(const std::string &openPGPKeyRef, const std::string &cardKeyRef, const Card *card) { KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef); widgets.cardKeyRef = cardKeyRef; widgets.keyFingerprint = card->keyFingerprint(openPGPKeyRef); widgets.keyInfo = card->keyInfo(cardKeyRef); } void OpenPGPKeyCardWidget::Private::updateKeyWidgets(const std::string &openPGPKeyRef) { const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef); const auto cardSupportsKey = !widgets.cardKeyRef.empty(); widgets.keyTitleLabel->setVisible(cardSupportsKey); widgets.keyInfoLabel->setVisible(cardSupportsKey); widgets.showCertificateDetailsButton->setVisible(cardSupportsKey); widgets.generateButton->setVisible(cardSupportsKey && (mAllowedActions & Action::GenerateKey)); if (widgets.createCSRButton) { widgets.createCSRButton->setVisible(cardSupportsKey && (mAllowedActions & Action::CreateCSR)); } if (!cardSupportsKey) { return; } widgets.showCertificateDetailsButton->setEnabled(false); if (widgets.keyFingerprint.empty()) { widgets.keyInfoLabel->setTextFormat(Qt::RichText); widgets.keyInfoLabel->setText(i18nc("@info", "No key")); widgets.generateButton->setText(i18nc("@action:button", "Generate Key")); widgets.generateButton->setToolTip(i18nc("@info:tooltip", "Generate a key for this card slot")); if (widgets.createCSRButton) { widgets.createCSRButton->setEnabled(false); } } else { QStringList lines; if (widgets.keyFingerprint.size() >= 16) { const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16); const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid}); if (subkeys.empty() || subkeys[0].isNull()) { widgets.keyInfoLabel->setTextFormat(Qt::RichText); lines.push_back(i18nc("@info", "Public key not found locally")); widgets.keyInfoLabel->setToolTip({}); } else { // force interpretation of text as plain text to avoid problems with HTML in user IDs widgets.keyInfoLabel->setTextFormat(Qt::PlainText); QStringList toolTips; toolTips.reserve(subkeys.size()); for (const auto &sub : subkeys) { // Yep you can have one subkey associated with multiple primary keys. const GpgME::Key key = sub.parent(); toolTips << Formatting::toolTip(key, Formatting::Validity | Formatting::ExpiryDates | Formatting::UserIDs | Formatting::Fingerprint); const auto uids = key.userIDs(); for (const auto &uid : uids) { lines.push_back(Formatting::prettyUserID(uid)); } } - widgets.keyInfoLabel->setToolTip(toolTips.join(QLatin1String("
    "))); + widgets.keyInfoLabel->setToolTip(toolTips.join(QLatin1StringView("
    "))); widgets.showCertificateDetailsButton->setEnabled(true); } } else { widgets.keyInfoLabel->setTextFormat(Qt::RichText); lines.push_back(i18nc("@info", "Invalid fingerprint")); } const QString fingerprint = widgets.keyInfoLabel->textFormat() == Qt::RichText - ? Formatting::prettyID(widgets.keyFingerprint.c_str()).replace(QLatin1Char(' '), QLatin1String(" ")) + ? Formatting::prettyID(widgets.keyFingerprint.c_str()).replace(QLatin1Char(' '), QLatin1StringView(" ")) : Formatting::prettyID(widgets.keyFingerprint.c_str()); lines.insert(0, fingerprint); - const auto lineSeparator = widgets.keyInfoLabel->textFormat() == Qt::PlainText ? QLatin1String("\n") : QLatin1String("
    "); + const auto lineSeparator = widgets.keyInfoLabel->textFormat() == Qt::PlainText ? QLatin1StringView("\n") : QLatin1String("
    "); widgets.keyInfoLabel->setText(lines.join(lineSeparator)); widgets.generateButton->setText(i18nc("@action:button", "Regenerate Key")); widgets.generateButton->setToolTip(i18nc("@info:tooltip", "Generate a new key for this card slot replacing the existing key")); if (widgets.createCSRButton) { widgets.createCSRButton->setEnabled(DeVSCompliance::algorithmIsCompliant(widgets.keyInfo.algorithm)); } } widgets.generateButton->setEnabled(!widgets.generateButton->isHidden()); } void OpenPGPKeyCardWidget::Private::showCertificateDetails(const std::string &openPGPKeyRef) { const KeyWidgets &widgets = mKeyWidgets.at(openPGPKeyRef); if (widgets.keyFingerprint.size() >= 16) { const std::string keyid = widgets.keyFingerprint.substr(widgets.keyFingerprint.size() - 16); const auto subkeys = KeyCache::instance()->findSubkeysByKeyID({keyid}); if (!subkeys.empty() && !subkeys[0].isNull()) { auto cmd = new Commands::DetailsCommand(subkeys[0].parent()); cmd->setParentWidget(q); cmd->start(); return; } } KMessageBox::error(q, i18nc("@info", "Sorry, I cannot find the key with fingerprint %1.", Formatting::prettyID(widgets.keyFingerprint.c_str()))); } OpenPGPKeyCardWidget::OpenPGPKeyCardWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this]() { d->update(); }); } OpenPGPKeyCardWidget::~OpenPGPKeyCardWidget() = default; void OpenPGPKeyCardWidget::setAllowedActions(Actions actions) { d->setAllowedActions(actions); } void OpenPGPKeyCardWidget::update(const Card *card) { d->update(card); } #include "moc_openpgpkeycardwidget.cpp" diff --git a/src/view/p15cardwidget.cpp b/src/view/p15cardwidget.cpp index a9e2ae609..eaee83a1e 100644 --- a/src/view/p15cardwidget.cpp +++ b/src/view/p15cardwidget.cpp @@ -1,196 +1,196 @@ /* view/p15cardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021 g10 Code GmbH SPDX-FileContributor: Andre Heinecke SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "p15cardwidget.h" #include "openpgpkeycardwidget.h" #include "settings.h" #include "smartcard/openpgpcard.h" #include "smartcard/p15card.h" #include "smartcard/readerstatus.h" #include "commands/learncardkeyscommand.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" using namespace Kleo; using namespace Kleo::SmartCard; using namespace Kleo::Commands; P15CardWidget::P15CardWidget(QWidget *parent) : QWidget{parent} , mVersionLabel{new QLabel{this}} , mSerialNumber{new QLabel{this}} , mStatusLabel{new QLabel{this}} , mOpenPGPKeysSection{new QWidget{this}} , mOpenPGPKeysWidget{new OpenPGPKeyCardWidget{this}} { // Set up the scroll area auto myLayout = new QVBoxLayout(this); myLayout->setContentsMargins(0, 0, 0, 0); auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); myLayout->addWidget(area); auto areaWidget = new QWidget; area->setWidget(areaWidget); auto areaVLay = new QVBoxLayout(areaWidget); auto cardInfoGrid = new QGridLayout; { int row = 0; // Version and Serialnumber cardInfoGrid->addWidget(mVersionLabel, row++, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0); cardInfoGrid->addWidget(mSerialNumber, row++, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction | Qt::TextSelectableByKeyboard); cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); } areaVLay->addLayout(cardInfoGrid); areaVLay->addWidget(mStatusLabel); mStatusLabel->setVisible(false); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); { auto l = new QVBoxLayout{mOpenPGPKeysSection}; l->setContentsMargins(0, 0, 0, 0); l->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("OpenPGP keys:")))); mOpenPGPKeysWidget->setAllowedActions(OpenPGPKeyCardWidget::NoAction); l->addWidget(mOpenPGPKeysWidget); l->addWidget(new KSeparator(Qt::Horizontal)); } mOpenPGPKeysSection->setVisible(false); areaVLay->addWidget(mOpenPGPKeysSection); areaVLay->addStretch(1); } P15CardWidget::~P15CardWidget() = default; void P15CardWidget::searchPGPFpr(const std::string &fpr) { /* Only do auto import from LDAP */ auto conf = QGpgME::cryptoConfig(); Q_ASSERT(conf); - if (!Settings().alwaysSearchCardOnKeyserver() && !Kleo::keyserver().startsWith(QLatin1String{"ldap"})) { + if (!Settings().alwaysSearchCardOnKeyserver() && !Kleo::keyserver().startsWith(QLatin1StringView{"ldap"})) { return; } mStatusLabel->setText(i18n("Searching in directory service...")); mStatusLabel->setVisible(true); qCDebug(KLEOPATRA_LOG) << "Looking for:" << fpr.c_str() << "on ldap server"; QGpgME::KeyListJob *job = QGpgME::openpgp()->keyListJob(true); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this, fpr]() { qCDebug(KLEOPATRA_LOG) << "Updating key info after changes"; ReaderStatus::mutableInstance()->updateStatus(); mOpenPGPKeysWidget->update(nullptr); }); connect(job, &QGpgME::KeyListJob::result, job, [this](GpgME::KeyListResult, std::vector keys, QString, GpgME::Error) { if (keys.size() == 1) { auto importJob = QGpgME::openpgp()->importFromKeyserverJob(); qCDebug(KLEOPATRA_LOG) << "Importing: " << keys[0].primaryFingerprint(); connect(importJob, &QGpgME::ImportFromKeyserverJob::result, importJob, [this](GpgME::ImportResult, QString, GpgME::Error) { qCDebug(KLEOPATRA_LOG) << "import job done"; mStatusLabel->setText(i18n("Automatic import finished.")); }); importJob->start(keys); } else if (keys.size() > 1) { qCDebug(KLEOPATRA_LOG) << "Multiple keys found on server"; mStatusLabel->setText(i18n("Error multiple keys found on server.")); } else { qCDebug(KLEOPATRA_LOG) << "No key found"; mStatusLabel->setText(i18n("Key not found in directory service.")); } }); job->start(QStringList() << QString::fromStdString(fpr)); } void P15CardWidget::setCard(const P15Card *card) { mCardSerialNumber = card->serialNumber(); mVersionLabel->setText(i18nc("%1 is a smartcard manufacturer", "%1 PKCS#15 card", QString::fromStdString(card->manufacturer()))); mSerialNumber->setText(card->displaySerialNumber()); mSerialNumber->setToolTip(QString::fromStdString(card->serialNumber())); const auto sigInfo = card->keyInfo(card->signingKeyRef()); if (!sigInfo.grip.empty()) { const auto key = KeyCache::instance()->findSubkeyByKeyGrip(sigInfo.grip, GpgME::OpenPGP).parent(); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Failed to find key for grip:" << sigInfo.grip.c_str(); const auto pgpSigFpr = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()); if (!pgpSigFpr.empty()) { qCDebug(KLEOPATRA_LOG) << "Should be pgp key:" << pgpSigFpr.c_str(); searchPGPFpr(pgpSigFpr); } } else { mStatusLabel->setVisible(false); } } const bool cardHasOpenPGPKeys = (!card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() // || !card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty()); mOpenPGPKeysSection->setVisible(cardHasOpenPGPKeys); if (cardHasOpenPGPKeys) { mOpenPGPKeysWidget->update(card); } /* Check if additional keys could be available */ if (!Settings().autoLoadP15Certs()) { return; } for (const auto &info : card->keyInfos()) { const auto key = KeyCache::instance()->findSubkeyByKeyGrip(info.grip); if (key.isNull()) { auto cmd = new LearnCardKeysCommand(GpgME::CMS); cmd->setParentWidget(this); cmd->setShowsOutputWindow(false); qCDebug(KLEOPATRA_LOG) << "Did not find:" << info.grip.c_str() << "Starting gpgsm --learn."; cmd->start(); connect(cmd, &Command::finished, this, []() { qCDebug(KLEOPATRA_LOG) << "Learn command finished."; }); return; } } qCDebug(KLEOPATRA_LOG) << "All certificates from card cached - Not learning."; } #include "moc_p15cardwidget.cpp" diff --git a/src/view/pgpcardwidget.cpp b/src/view/pgpcardwidget.cpp index e168618da..cd32e178c 100644 --- a/src/view/pgpcardwidget.cpp +++ b/src/view/pgpcardwidget.cpp @@ -1,553 +1,553 @@ /* view/pgpcardwiget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "pgpcardwidget.h" #include "openpgpkeycardwidget.h" #include "kleopatra_debug.h" #include "commands/createcsrforcardkeycommand.h" #include "commands/createopenpgpkeyfromcardkeyscommand.h" #include "commands/openpgpgeneratecardkeycommand.h" #include "smartcard/algorithminfo.h" #include "smartcard/openpgpcard.h" #include "smartcard/readerstatus.h" #include "smartcard/utils.h" #include "dialogs/gencardkeydialog.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::SmartCard; namespace { class GenKeyThread : public QThread { Q_OBJECT public: explicit GenKeyThread(const GenCardKeyDialog::KeyParams ¶ms, const std::string &serial) : mSerial(serial) , mParams(params) { } GpgME::Error error() { return mErr; } std::string bkpFile() { return mBkpFile; } protected: void run() override { // the index of the curves in this list has to match the enum values // minus 1 of GpgGenCardKeyInteractor::Curve static const std::vector curves = { "curve25519", "curve448", "nistp256", "nistp384", "nistp521", "brainpoolP256r1", "brainpoolP384r1", "brainpoolP512r1", "secp256k1", // keep it, even if we don't support it in Kleopatra }; auto ei = std::make_unique(mSerial); if (mParams.algorithm.starts_with("rsa")) { ei->setAlgo(GpgME::GpgGenCardKeyInteractor::RSA); ei->setKeySize(QByteArray::fromStdString(mParams.algorithm.substr(3)).toInt()); } else { ei->setAlgo(GpgME::GpgGenCardKeyInteractor::ECC); const auto curveIt = std::find(curves.cbegin(), curves.cend(), mParams.algorithm); if (curveIt != curves.end()) { ei->setCurve(static_cast(curveIt - curves.cbegin() + 1)); } else { qCWarning(KLEOPATRA_LOG) << this << __func__ << "Invalid curve name:" << mParams.algorithm; mErr = GpgME::Error::fromCode(GPG_ERR_INV_VALUE); return; } } ei->setNameUtf8(mParams.name.toStdString()); ei->setEmailUtf8(mParams.email.toStdString()); ei->setDoBackup(mParams.backup); const auto ctx = std::shared_ptr(GpgME::Context::createForProtocol(GpgME::OpenPGP)); ctx->setFlag("extended-edit", "1"); // we want to be able to select all curves QGpgME::QByteArrayDataProvider dp; GpgME::Data data(&dp); mErr = ctx->cardEdit(GpgME::Key(), std::move(ei), data); mBkpFile = static_cast(ctx->lastCardEditInteractor())->backupFileName(); } private: GpgME::Error mErr; std::string mSerial; GenCardKeyDialog::KeyParams mParams; std::string mBkpFile; }; } // Namespace PGPCardWidget::PGPCardWidget(QWidget *parent) : QWidget(parent) , mSerialNumber(new QLabel(this)) , mCardHolderLabel(new QLabel(this)) , mVersionLabel(new QLabel(this)) , mUrlLabel(new QLabel(this)) , mCardIsEmpty(false) { // Set up the scroll area auto myLayout = new QVBoxLayout(this); myLayout->setContentsMargins(0, 0, 0, 0); auto area = new QScrollArea; area->setFrameShape(QFrame::NoFrame); area->setWidgetResizable(true); myLayout->addWidget(area); auto areaWidget = new QWidget; area->setWidget(areaWidget); auto areaVLay = new QVBoxLayout(areaWidget); auto cardInfoGrid = new QGridLayout; { int row = 0; // Version and Serialnumber cardInfoGrid->addWidget(mVersionLabel, row, 0, 1, 2); mVersionLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; cardInfoGrid->addWidget(new QLabel(i18n("Serial number:")), row, 0); cardInfoGrid->addWidget(mSerialNumber, row, 1); mSerialNumber->setTextInteractionFlags(Qt::TextBrowserInteraction); row++; // Cardholder Row cardInfoGrid->addWidget(new QLabel(i18nc("The owner of a smartcard. GnuPG refers to this as cardholder.", "Cardholder:")), row, 0); cardInfoGrid->addWidget(mCardHolderLabel, row, 1); mCardHolderLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeNameRequested); } row++; // URL Row cardInfoGrid->addWidget(new QLabel(i18nc("The URL under which a public key that " "corresponds to a smartcard can be downloaded", "Pubkey URL:")), row, 0); cardInfoGrid->addWidget(mUrlLabel, row, 1); mUrlLabel->setTextInteractionFlags(Qt::TextBrowserInteraction); { auto button = new QPushButton; button->setIcon(QIcon::fromTheme(QStringLiteral("cell_edit"))); button->setAccessibleName(i18nc("@action:button", "Edit")); button->setToolTip(i18n("Change")); cardInfoGrid->addWidget(button, row, 2); connect(button, &QPushButton::clicked, this, &PGPCardWidget::changeUrlRequested); } cardInfoGrid->setColumnStretch(cardInfoGrid->columnCount(), 1); } areaVLay->addLayout(cardInfoGrid); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); // The keys areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Keys:")))); mKeysWidget = new OpenPGPKeyCardWidget{this}; areaVLay->addWidget(mKeysWidget); connect(mKeysWidget, &OpenPGPKeyCardWidget::createCSRRequested, this, &PGPCardWidget::createCSR); connect(mKeysWidget, &OpenPGPKeyCardWidget::generateKeyRequested, this, &PGPCardWidget::generateKey); areaVLay->addWidget(new KSeparator(Qt::Horizontal)); areaVLay->addWidget(new QLabel(QStringLiteral("%1").arg(i18n("Actions:")))); auto actionLayout = new QHBoxLayout; { auto generateButton = new QPushButton(i18n("Generate New Keys")); generateButton->setToolTip(i18n("Create a new primary key and generate subkeys on the card.")); actionLayout->addWidget(generateButton); connect(generateButton, &QPushButton::clicked, this, &PGPCardWidget::genkeyRequested); } { auto pinButton = new QPushButton(i18n("Change PIN")); pinButton->setToolTip(i18n("Change the PIN required for using the keys on the smartcard.")); actionLayout->addWidget(pinButton); connect(pinButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::pinKeyRef()); }); } { auto unblockButton = new QPushButton(i18n("Unblock Card")); unblockButton->setToolTip(i18n("Unblock the smartcard and set a new PIN.")); actionLayout->addWidget(unblockButton); connect(unblockButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::resetCodeKeyRef()); }); } { auto pukButton = new QPushButton(i18n("Change Admin PIN")); pukButton->setToolTip(i18n("Change the PIN required for administrative operations.")); actionLayout->addWidget(pukButton); connect(pukButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::adminPinKeyRef()); }); } { auto resetCodeButton = new QPushButton(i18n("Change Reset Code")); resetCodeButton->setToolTip(i18n("Change the PIN required to unblock the smartcard and set a new PIN.")); actionLayout->addWidget(resetCodeButton); connect(resetCodeButton, &QPushButton::clicked, this, [this]() { doChangePin(OpenPGPCard::resetCodeKeyRef(), ChangePinCommand::ResetMode); }); } if (CreateOpenPGPKeyFromCardKeysCommand::isSupported()) { mKeyForCardKeysButton = new QPushButton(this); mKeyForCardKeysButton->setText(i18n("Create OpenPGP Key")); mKeyForCardKeysButton->setToolTip(i18n("Create an OpenPGP key for the keys stored on the card.")); actionLayout->addWidget(mKeyForCardKeysButton); connect(mKeyForCardKeysButton, &QPushButton::clicked, this, &PGPCardWidget::createKeyFromCardKeys); } actionLayout->addStretch(-1); areaVLay->addLayout(actionLayout); areaVLay->addStretch(1); } void PGPCardWidget::setCard(const OpenPGPCard *card) { const QString version = card->displayAppVersion(); mIs21 = card->appVersion() >= 0x0201; const QString manufacturer = QString::fromStdString(card->manufacturer()); - const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1String("unknown"); + const bool manufacturerIsUnknown = manufacturer.isEmpty() || manufacturer == QLatin1StringView("unknown"); mVersionLabel->setText( manufacturerIsUnknown ? i18nc("Placeholder is a version number", "Unknown OpenPGP v%1 card", version) : i18nc("First placeholder is manufacturer, second placeholder is a version number", "%1 OpenPGP v%2 card", manufacturer, version)); mSerialNumber->setText(card->displaySerialNumber()); mRealSerial = card->serialNumber(); const auto holder = card->cardHolder(); const auto url = QString::fromStdString(card->pubkeyUrl()); mCardHolderLabel->setText(holder.isEmpty() ? i18n("not set") : holder); mUrl = url; mUrlLabel->setText(url.isEmpty() ? i18n("not set") : QStringLiteral("
    %1").arg(url.toHtmlEscaped())); mUrlLabel->setOpenExternalLinks(true); mKeysWidget->update(card); mCardIsEmpty = card->keyFingerprint(OpenPGPCard::pgpSigKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpEncKeyRef()).empty() && card->keyFingerprint(OpenPGPCard::pgpAuthKeyRef()).empty(); if (mKeyForCardKeysButton) { mKeyForCardKeysButton->setEnabled(card->hasSigningKey() // && card->hasEncryptionKey() // && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->signingKeyRef()).algorithm) && DeVSCompliance::algorithmIsCompliant(card->keyInfo(card->encryptionKeyRef()).algorithm)); } } void PGPCardWidget::doChangePin(const std::string &keyRef, ChangePinCommand::ChangePinMode mode) { auto cmd = new ChangePinCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &ChangePinCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->setKeyRef(keyRef); cmd->setMode(mode); cmd->start(); } void PGPCardWidget::doGenKey(GenCardKeyDialog *dlg) { const GpgME::Error err = ReaderStatus::switchCardAndApp(mRealSerial, OpenPGPCard::AppName); if (err) { return; } const auto params = dlg->getKeyParams(); auto progress = new QProgressDialog(this, Qt::CustomizeWindowHint | Qt::WindowTitleHint | Qt::Dialog); progress->setAutoClose(true); progress->setMinimumDuration(0); progress->setMaximum(0); progress->setMinimum(0); progress->setModal(true); progress->setCancelButton(nullptr); progress->setWindowTitle(i18nc("@title:window", "Generating Keys")); progress->setLabel(new QLabel(i18n("This may take several minutes..."))); auto workerThread = new GenKeyThread(params, mRealSerial); connect(workerThread, &QThread::finished, this, [this, workerThread, progress] { progress->accept(); progress->deleteLater(); genKeyDone(workerThread->error(), workerThread->bkpFile()); delete workerThread; }); workerThread->start(); progress->exec(); } void PGPCardWidget::genKeyDone(const GpgME::Error &err, const std::string &backup) { if (err) { KMessageBox::error(this, i18nc("@info", "Failed to generate new key: %1", Formatting::errorAsString(err))); return; } if (err.isCanceled()) { return; } if (!backup.empty()) { const auto bkpFile = QString::fromStdString(backup); QFileInfo fi(bkpFile); const auto target = QFileDialog::getSaveFileName(this, i18n("Save backup of encryption key"), fi.fileName(), QStringLiteral("%1 (*.gpg)").arg(i18n("Backup Key"))); if (!target.isEmpty() && !QFile::copy(bkpFile, target)) { KMessageBox::error(this, i18nc("@info", "Failed to move backup. The backup key is still stored under: %1", bkpFile)); } else if (!target.isEmpty()) { QFile::remove(bkpFile); } } KMessageBox::information(this, i18nc("@info", "Successfully generated a new key for this card."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } void PGPCardWidget::genkeyRequested() { const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } if (!mCardIsEmpty) { auto ret = KMessageBox::warningContinueCancel(this, i18n("The existing keys on this card will be deleted " "and replaced by new keys.") + QStringLiteral("

    ") + i18n("It will no longer be possible to decrypt past communication " "encrypted for the existing key."), i18n("Secret Key Deletion"), KStandardGuiItem::guiItem(KStandardGuiItem::Delete), KStandardGuiItem::cancel(), QString(), KMessageBox::Notify | KMessageBox::Dangerous); if (ret != KMessageBox::Continue) { return; } } auto dlg = new GenCardKeyDialog(GenCardKeyDialog::AllKeyAttributes, this); const auto allowedAlgos = getAllowedAlgorithms(pgpCard->supportedAlgorithms()); if (allowedAlgos.empty()) { KMessageBox::error(this, i18nc("@info", "You cannot generate keys on this smart card because it doesn't support any of the compliant algorithms.")); return; } dlg->setSupportedAlgorithms(allowedAlgos, getPreferredAlgorithm(allowedAlgos)); connect(dlg, &QDialog::accepted, this, [this, dlg]() { doGenKey(dlg); dlg->deleteLater(); }); dlg->setModal(true); dlg->show(); } void PGPCardWidget::changeNameRequested() { QString text = mCardHolderLabel->text(); while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change cardholder"), i18n("New name:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.contains(QLatin1Char('<'))) { KMessageBox::error(this, i18nc("@info", "The \"<\" character may not be used.")); continue; } - if (text.contains(QLatin1String(" "))) { + if (text.contains(QLatin1StringView(" "))) { KMessageBox::error(this, i18nc("@info", "Double spaces are not allowed")); continue; } if (text.size() > 38) { KMessageBox::error(this, i18nc("@info", "The size of the name may not exceed 38 characters.")); } break; } auto parts = text.split(QLatin1Char(' ')); const auto lastName = parts.takeLast(); const QString formatted = lastName + QStringLiteral("<<") + parts.join(QLatin1Char('<')); const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR DISP-NAME ") + formatted.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeNameResult(err); }); } void PGPCardWidget::changeNameResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "Name change failed: %1", Formatting::errorAsString(err))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "Name successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::changeUrlRequested() { QString text = mUrl; while (true) { bool ok = false; text = QInputDialog::getText(this, i18n("Change the URL where the pubkey can be found"), i18n("New pubkey URL:"), QLineEdit::Normal, text, &ok, Qt::WindowFlags(), Qt::ImhLatinOnly); if (!ok) { return; } // Some additional restrictions imposed by gnupg if (text.size() > 254) { KMessageBox::error(this, i18nc("@info", "The size of the URL may not exceed 254 characters.")); } break; } const auto pgpCard = ReaderStatus::instance()->getCard(mRealSerial); if (!pgpCard) { KMessageBox::error(this, i18n("Failed to find the OpenPGP card with the serial number: %1", QString::fromStdString(mRealSerial))); return; } const QByteArray command = QByteArrayLiteral("SCD SETATTR PUBKEY-URL ") + text.toUtf8(); ReaderStatus::mutableInstance()->startSimpleTransaction(pgpCard, command, this, [this](const GpgME::Error &err) { changeUrlResult(err); }); } void PGPCardWidget::changeUrlResult(const GpgME::Error &err) { if (err) { KMessageBox::error(this, i18nc("@info", "URL change failed: %1", Formatting::errorAsString(err))); return; } if (!err.isCanceled()) { KMessageBox::information(this, i18nc("@info", "URL successfully changed."), i18nc("@title", "Success")); ReaderStatus::mutableInstance()->updateStatus(); } } void PGPCardWidget::createKeyFromCardKeys() { auto cmd = new CreateOpenPGPKeyFromCardKeysCommand(mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateOpenPGPKeyFromCardKeysCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::createCSR(const std::string &keyref) { auto cmd = new CreateCSRForCardKeyCommand(keyref, mRealSerial, OpenPGPCard::AppName, this); this->setEnabled(false); connect(cmd, &CreateCSRForCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } void PGPCardWidget::generateKey(const std::string &keyref) { auto cmd = new OpenPGPGenerateCardKeyCommand(keyref, mRealSerial, this); this->setEnabled(false); connect(cmd, &OpenPGPGenerateCardKeyCommand::finished, this, [this]() { this->setEnabled(true); }); cmd->start(); } #include "pgpcardwidget.moc" #include "moc_pgpcardwidget.cpp" diff --git a/src/view/smartcardwidget.cpp b/src/view/smartcardwidget.cpp index c03536f49..11ab1b029 100644 --- a/src/view/smartcardwidget.cpp +++ b/src/view/smartcardwidget.cpp @@ -1,249 +1,249 @@ /* view/smartcardwidget.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-FileCopyrightText: 2020 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include "smartcardwidget.h" #include "smartcard/netkeycard.h" #include "smartcard/openpgpcard.h" #include "smartcard/p15card.h" #include "smartcard/pivcard.h" #include "smartcard/readerstatus.h" #include "smartcard/utils.h" #include "view/netkeywidget.h" #include "view/p15cardwidget.h" #include "view/pgpcardwidget.h" #include "view/pivcardwidget.h" #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::SmartCard; namespace { class PlaceHolderWidget : public QWidget { Q_OBJECT public: explicit PlaceHolderWidget(QWidget *parent = nullptr) : QWidget{parent} { auto lay = new QVBoxLayout; lay->addStretch(-1); const QStringList supported = QStringList() << i18nc("OpenPGP refers to a smartcard protocol", "OpenPGP v2.0 - v3.3") << i18nc("Gnuk is a cryptographic token for GnuPG", "Gnuk") << i18nc("NetKey refers to a smartcard protocol", "NetKey v3") << i18nc("PIV refers to a smartcard protocol", "PIV (requires GnuPG 2.3 or later)") << i18nc("CardOS is a smartcard operating system", "CardOS 5 (various apps)"); lay->addWidget(new QLabel(QStringLiteral("\t\t

    ") + i18n("Please insert a compatible smartcard.") + QStringLiteral("

    "), this)); lay->addSpacing(10); lay->addWidget(new QLabel(QStringLiteral("\t\t") + i18n("Kleopatra currently supports the following card types:") + QStringLiteral("
    • ") - + supported.join(QLatin1String("
    • ")) + QStringLiteral("
    "), + + supported.join(QLatin1StringView("
  • ")) + QStringLiteral("
  • "), this)); lay->addSpacing(10); { auto hbox = new QHBoxLayout; hbox->addStretch(1); mReloadButton = new QPushButton{i18n("Reload"), this}; hbox->addWidget(mReloadButton); hbox->addStretch(1); lay->addLayout(hbox); } lay->addStretch(-1); auto hLay = new QHBoxLayout(this); hLay->addStretch(-1); hLay->addLayout(lay); hLay->addStretch(-1); lay->addStretch(-1); connect(mReloadButton, &QPushButton::clicked, this, &PlaceHolderWidget::reload); } Q_SIGNALS: void reload(); private: QPushButton *mReloadButton = nullptr; }; } // namespace class SmartCardWidget::Private { friend class ::Kleo::SmartCardWidget; public: Private(SmartCardWidget *qq); void cardAddedOrChanged(const std::string &serialNumber, const std::string &appName); void cardRemoved(const std::string &serialNumber, const std::string &appName); private: template void cardAddedOrChanged(const std::string &serialNumber); private: SmartCardWidget *const q; QMap, QPointer> mCardWidgets; PlaceHolderWidget *mPlaceHolderWidget; QStackedWidget *mStack; QTabWidget *mTabWidget; }; SmartCardWidget::Private::Private(SmartCardWidget *qq) : q{qq} { auto vLay = new QVBoxLayout(q); vLay->addWidget(new QLabel(QStringLiteral("

    ") + i18n("Smartcard Management") + QStringLiteral("

    "))); mStack = new QStackedWidget; vLay->addWidget(mStack); mPlaceHolderWidget = new PlaceHolderWidget; mStack->addWidget(mPlaceHolderWidget); mTabWidget = new QTabWidget; mStack->addWidget(mTabWidget); mStack->setCurrentWidget(mPlaceHolderWidget); connect(mPlaceHolderWidget, &PlaceHolderWidget::reload, q, &SmartCardWidget::reload); connect(ReaderStatus::instance(), &ReaderStatus::cardAdded, q, [this](const std::string &serialNumber, const std::string &appName) { cardAddedOrChanged(serialNumber, appName); }); connect(ReaderStatus::instance(), &ReaderStatus::cardChanged, q, [this](const std::string &serialNumber, const std::string &appName) { cardAddedOrChanged(serialNumber, appName); }); connect(ReaderStatus::instance(), &ReaderStatus::cardRemoved, q, [this](const std::string &serialNumber, const std::string &appName) { cardRemoved(serialNumber, appName); }); } void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber, const std::string &appName) { if (appName == SmartCard::NetKeyCard::AppName) { cardAddedOrChanged(serialNumber); } else if (appName == SmartCard::OpenPGPCard::AppName) { cardAddedOrChanged(serialNumber); } else if (appName == SmartCard::PIVCard::AppName) { cardAddedOrChanged(serialNumber); } else if (appName == SmartCard::P15Card::AppName) { cardAddedOrChanged(serialNumber); } else { qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:" << "App" << appName.c_str() << "is not supported"; } } namespace { static QString getCardLabel(const std::shared_ptr &card) { if (!card->cardHolder().isEmpty()) { return i18nc("@title:tab smartcard application - name of card holder - serial number of smartcard", "%1 - %2 - %3", displayAppName(card->appName()), card->cardHolder(), card->displaySerialNumber()); } else { return i18nc("@title:tab smartcard application - serial number of smartcard", "%1 - %2", displayAppName(card->appName()), card->displaySerialNumber()); } } } template void SmartCardWidget::Private::cardAddedOrChanged(const std::string &serialNumber) { const auto card = ReaderStatus::instance()->getCard(serialNumber); if (!card) { qCWarning(KLEOPATRA_LOG) << "SmartCardWidget::Private::cardAddedOrChanged:" << "New or changed card" << serialNumber.c_str() << "with app" << C::AppName.c_str() << "not found"; return; } W *cardWidget = dynamic_cast(mCardWidgets.value({serialNumber, C::AppName}).data()); if (!cardWidget) { cardWidget = new W; mCardWidgets.insert({serialNumber, C::AppName}, cardWidget); mTabWidget->addTab(cardWidget, getCardLabel(card)); if (mCardWidgets.size() == 1) { mStack->setCurrentWidget(mTabWidget); } } cardWidget->setCard(card.get()); } void SmartCardWidget::Private::cardRemoved(const std::string &serialNumber, const std::string &appName) { QWidget *cardWidget = mCardWidgets.take({serialNumber, appName}); if (cardWidget) { const int index = mTabWidget->indexOf(cardWidget); if (index != -1) { mTabWidget->removeTab(index); } delete cardWidget; } if (mCardWidgets.empty()) { mStack->setCurrentWidget(mPlaceHolderWidget); } } SmartCardWidget::SmartCardWidget(QWidget *parent) : QWidget{parent} , d{std::make_unique(this)} { } Kleo::SmartCardWidget::~SmartCardWidget() = default; QWidget *getFirstEnabledFocusWidget(QWidget *parent) { for (auto w = parent->nextInFocusChain(); w != parent; w = w->nextInFocusChain()) { if (w->isEnabled() && (w->focusPolicy() != Qt::NoFocus)) { return w; } } return nullptr; } void SmartCardWidget::focusFirstChild(Qt::FocusReason reason) { if (d->mStack->currentWidget() == d->mPlaceHolderWidget) { if (auto w = getFirstEnabledFocusWidget(d->mPlaceHolderWidget)) { w->setFocus(reason); } } else if (auto cardWidget = d->mTabWidget->currentWidget()) { if (auto w = getFirstEnabledFocusWidget(cardWidget)) { w->setFocus(reason); } } } void SmartCardWidget::reload() { ReaderStatus::mutableInstance()->updateStatus(); } #include "smartcardwidget.moc" #include "moc_smartcardwidget.cpp" diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp index b5c9caaa9..092d15b4c 100644 --- a/src/view/tabwidget.cpp +++ b/src/view/tabwidget.cpp @@ -1,1114 +1,1114 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/tabwidget.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 "tabwidget.h" #include "keytreeview.h" #include "kleopatra_debug.h" #include "searchbar.h" #include #include #include #include #include #include #include #include // needed for GPGME_VERSION_NUMBER #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace GpgME; namespace { class Page : public Kleo::KeyTreeView { Q_OBJECT Page(const Page &other); public: Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy = nullptr, const QString &toolTip = QString(), QWidget *parent = nullptr, const KConfigGroup &group = KConfigGroup()); Page(const KConfigGroup &group, QWidget *parent = nullptr); ~Page() override; void setTemporary(bool temporary); bool isTemporary() const { return m_isTemporary; } void setHierarchicalView(bool hierarchical) override; void setStringFilter(const QString &filter) override; void setKeyFilter(const std::shared_ptr &filter) override; QString title() const { return m_title.isEmpty() && keyFilter() ? keyFilter()->name() : m_title; } void setTitle(const QString &title); QString toolTip() const { return m_toolTip.isEmpty() ? title() : m_toolTip; } // not used void setToolTip(const QString &tip); bool canBeClosed() const { return m_canBeClosed; } bool canBeRenamed() const { return m_canBeRenamed; } bool canChangeStringFilter() const { return m_canChangeStringFilter; } bool canChangeKeyFilter() const { return m_canChangeKeyFilter && !m_isTemporary; } bool canChangeHierarchical() const { return m_canChangeHierarchical; } void saveTo(KConfigGroup &group) const; Page *clone() const override { return new Page(*this); } void liftAllRestrictions() { m_canBeClosed = m_canBeRenamed = m_canChangeStringFilter = m_canChangeKeyFilter = m_canChangeHierarchical = true; } Q_SIGNALS: void titleChanged(const QString &title); private: void init(); private: QString m_title; QString m_toolTip; bool m_isTemporary : 1; bool m_canBeClosed : 1; bool m_canBeRenamed : 1; bool m_canChangeStringFilter : 1; bool m_canChangeKeyFilter : 1; bool m_canChangeHierarchical : 1; }; } // anon namespace Page::Page(const Page &other) : KeyTreeView(other) , m_title(other.m_title) , m_toolTip(other.m_toolTip) , m_isTemporary(other.m_isTemporary) , m_canBeClosed(other.m_canBeClosed) , m_canBeRenamed(other.m_canBeRenamed) , m_canChangeStringFilter(other.m_canChangeStringFilter) , m_canChangeKeyFilter(other.m_canChangeKeyFilter) , m_canChangeHierarchical(other.m_canChangeHierarchical) { init(); } Page::Page(const QString &title, const QString &id, const QString &text, AbstractKeyListSortFilterProxyModel *proxy, const QString &toolTip, QWidget *parent, const KConfigGroup &group) : KeyTreeView(text, KeyFilterManager::instance()->keyFilterByID(id), proxy, parent, group) , m_title(title) , m_toolTip(toolTip) , m_isTemporary(false) , m_canBeClosed(true) , m_canBeRenamed(true) , m_canChangeStringFilter(true) , m_canChangeKeyFilter(true) , m_canChangeHierarchical(true) { init(); } static const char TITLE_ENTRY[] = "title"; static const char STRING_FILTER_ENTRY[] = "string-filter"; static const char KEY_FILTER_ENTRY[] = "key-filter"; static const char HIERARCHICAL_VIEW_ENTRY[] = "hierarchical-view"; static const char COLUMN_SIZES[] = "column-sizes"; static const char SORT_COLUMN[] = "sort-column"; static const char SORT_DESCENDING[] = "sort-descending"; Page::Page(const KConfigGroup &group, QWidget *parent) : KeyTreeView(group.readEntry(STRING_FILTER_ENTRY), KeyFilterManager::instance()->keyFilterByID(group.readEntry(KEY_FILTER_ENTRY)), nullptr, parent, group) , m_title(group.readEntry(TITLE_ENTRY)) , m_toolTip() , m_isTemporary(false) , m_canBeClosed(!group.isImmutable()) , m_canBeRenamed(!group.isEntryImmutable(TITLE_ENTRY)) , m_canChangeStringFilter(!group.isEntryImmutable(STRING_FILTER_ENTRY)) , m_canChangeKeyFilter(!group.isEntryImmutable(KEY_FILTER_ENTRY)) , m_canChangeHierarchical(!group.isEntryImmutable(HIERARCHICAL_VIEW_ENTRY)) { init(); setHierarchicalView(group.readEntry(HIERARCHICAL_VIEW_ENTRY, true)); const QList settings = group.readEntry(COLUMN_SIZES, QList()); std::vector sizes; sizes.reserve(settings.size()); std::copy(settings.cbegin(), settings.cend(), std::back_inserter(sizes)); setColumnSizes(sizes); setSortColumn(group.readEntry(SORT_COLUMN, 0), group.readEntry(SORT_DESCENDING, true) ? Qt::DescendingOrder : Qt::AscendingOrder); } void Page::init() { #if GPGME_VERSION_NUMBER >= 0x011800 // 1.24.0 view()->setDragDropMode(QAbstractItemView::DragOnly); view()->setDragEnabled(true); #endif } Page::~Page() { } void Page::saveTo(KConfigGroup &group) const { group.writeEntry(TITLE_ENTRY, m_title); group.writeEntry(STRING_FILTER_ENTRY, stringFilter()); group.writeEntry(KEY_FILTER_ENTRY, keyFilter() ? keyFilter()->id() : QString()); group.writeEntry(HIERARCHICAL_VIEW_ENTRY, isHierarchicalView()); QList settings; const auto sizes = columnSizes(); settings.reserve(sizes.size()); std::copy(sizes.cbegin(), sizes.cend(), std::back_inserter(settings)); group.writeEntry(COLUMN_SIZES, settings); group.writeEntry(SORT_COLUMN, sortColumn()); group.writeEntry(SORT_DESCENDING, sortOrder() == Qt::DescendingOrder); } void Page::setStringFilter(const QString &filter) { if (!m_canChangeStringFilter) { return; } KeyTreeView::setStringFilter(filter); } void Page::setKeyFilter(const std::shared_ptr &filter) { if (!canChangeKeyFilter()) { return; } const QString oldTitle = title(); KeyTreeView::setKeyFilter(filter); const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } void Page::setTitle(const QString &t) { if (t == m_title) { return; } if (!m_canBeRenamed) { return; } const QString oldTitle = title(); m_title = t; const QString newTitle = title(); if (oldTitle != newTitle) { Q_EMIT titleChanged(newTitle); } } #if 0 // not used void Page::setToolTip(const QString &tip) { if (tip == m_toolTip) { return; } if (!m_canBeRenamed) { return; } const QString oldTip = toolTip(); m_toolTip = tip; const QString newTip = toolTip(); if (oldTip != newTip) { Q_EMIT titleChanged(title()); } } #endif void Page::setHierarchicalView(bool on) { if (!m_canChangeHierarchical) { return; } KeyTreeView::setHierarchicalView(on); } void Page::setTemporary(bool on) { if (on == m_isTemporary) { return; } m_isTemporary = on; if (on) { setKeyFilter(std::shared_ptr()); } } namespace { class Actions { public: constexpr static const char *Rename = "window_rename_tab"; constexpr static const char *Duplicate = "window_duplicate_tab"; constexpr static const char *Close = "window_close_tab"; constexpr static const char *MoveLeft = "window_move_tab_left"; constexpr static const char *MoveRight = "window_move_tab_right"; constexpr static const char *Hierarchical = "window_view_hierarchical"; constexpr static const char *ExpandAll = "window_expand_all"; constexpr static const char *CollapseAll = "window_collapse_all"; explicit Actions() { } void insert(const std::string &name, QAction *action) { actions.insert({name, action}); } auto get(const std::string &name) const { const auto it = actions.find(name); return (it != actions.end()) ? it->second : nullptr; } void setChecked(const std::string &name, bool checked) const { if (auto action = get(name)) { action->setChecked(checked); } } void setEnabled(const std::string &name, bool enabled) const { if (auto action = get(name)) { action->setEnabled(enabled); } } void setVisible(const std::string &name, bool visible) const { if (auto action = get(name)) { action->setVisible(visible); } } private: std::map actions; }; } // // // TabWidget // // class TabWidget::Private { friend class ::Kleo::TabWidget; TabWidget *const q; public: explicit Private(TabWidget *qq); ~Private() { } private: void slotContextMenu(const QPoint &p); void currentIndexChanged(int index); void slotPageTitleChanged(const QString &title); void slotPageKeyFilterChanged(const std::shared_ptr &filter); void slotPageStringFilterChanged(const QString &filter); void slotPageHierarchyChanged(bool on); #ifndef QT_NO_INPUTDIALOG void slotRenameCurrentTab() { renamePage(currentPage()); } #endif // QT_NO_INPUTDIALOG void slotNewTab(); void slotDuplicateCurrentTab() { duplicatePage(currentPage()); } void slotCloseCurrentTab() { closePage(currentPage()); } void slotMoveCurrentTabLeft() { movePageLeft(currentPage()); } void slotMoveCurrentTabRight() { movePageRight(currentPage()); } void slotToggleHierarchicalView(bool on) { toggleHierarchicalView(currentPage(), on); } void slotExpandAll() { expandAll(currentPage()); } void slotCollapseAll() { collapseAll(currentPage()); } #ifndef QT_NO_INPUTDIALOG void renamePage(Page *page); #endif void duplicatePage(Page *page); void closePage(Page *page); void movePageLeft(Page *page); void movePageRight(Page *page); void toggleHierarchicalView(Page *page, bool on); void expandAll(Page *page); void collapseAll(Page *page); void enableDisableCurrentPageActions(); void enableDisablePageActions(const Actions &actions, const Page *page); Page *currentPage() const { Q_ASSERT(!tabWidget->currentWidget() || qobject_cast(tabWidget->currentWidget())); return static_cast(tabWidget->currentWidget()); } Page *page(unsigned int idx) const { Q_ASSERT(!tabWidget->widget(idx) || qobject_cast(tabWidget->widget(idx))); return static_cast(tabWidget->widget(idx)); } Page *senderPage() const { QObject *const sender = q->sender(); Q_ASSERT(!sender || qobject_cast(sender)); return static_cast(sender); } bool isSenderCurrentPage() const { Page *const sp = senderPage(); return sp && sp == currentPage(); } QTreeView *addView(Page *page, Page *columnReference); private: AbstractKeyListModel *flatModel = nullptr; AbstractKeyListModel *hierarchicalModel = nullptr; QToolButton *newTabButton = nullptr; QToolButton *closeTabButton = nullptr; QTabWidget *tabWidget = nullptr; QAction *newAction = nullptr; Actions currentPageActions; Actions otherPageActions; bool actionsCreated = false; }; TabWidget::Private::Private(TabWidget *qq) : q{qq} { auto layout = new QVBoxLayout{q}; layout->setContentsMargins(0, 0, 0, 0); // create "New Tab" button before tab widget to ensure correct tab order newTabButton = new QToolButton{q}; tabWidget = new QTabWidget{q}; KDAB_SET_OBJECT_NAME(tabWidget); layout->addWidget(tabWidget); tabWidget->setMovable(true); tabWidget->tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); // create "Close Tab" button after tab widget to ensure correct tab order closeTabButton = new QToolButton{q}; connect(tabWidget, &QTabWidget::currentChanged, q, [this](int index) { currentIndexChanged(index); }); connect(tabWidget->tabBar(), &QWidget::customContextMenuRequested, q, [this](const QPoint &p) { slotContextMenu(p); }); } void TabWidget::Private::slotContextMenu(const QPoint &p) { const int tabUnderPos = tabWidget->tabBar()->tabAt(p); Page *const contextMenuPage = static_cast(tabWidget->widget(tabUnderPos)); const Page *const current = currentPage(); const auto actions = contextMenuPage == current ? currentPageActions : otherPageActions; enableDisablePageActions(actions, contextMenuPage); QMenu menu; if (auto action = actions.get(Actions::Rename)) { menu.addAction(action); } menu.addSeparator(); menu.addAction(newAction); if (auto action = actions.get(Actions::Duplicate)) { menu.addAction(action); } menu.addSeparator(); if (auto action = actions.get(Actions::MoveLeft)) { menu.addAction(action); } if (auto action = actions.get(Actions::MoveRight)) { menu.addAction(action); } menu.addSeparator(); if (auto action = actions.get(Actions::Close)) { menu.addAction(action); } const QAction *const action = menu.exec(tabWidget->tabBar()->mapToGlobal(p)); if (!action) { return; } if (contextMenuPage == current || action == newAction) { return; // performed through signal/slot connections... } #ifndef QT_NO_INPUTDIALOG if (action == otherPageActions.get(Actions::Rename)) { renamePage(contextMenuPage); } #endif // QT_NO_INPUTDIALOG else if (action == otherPageActions.get(Actions::Duplicate)) { duplicatePage(contextMenuPage); } else if (action == otherPageActions.get(Actions::Close)) { closePage(contextMenuPage); } else if (action == otherPageActions.get(Actions::MoveLeft)) { movePageLeft(contextMenuPage); } else if (action == otherPageActions.get(Actions::MoveRight)) { movePageRight(contextMenuPage); } } void TabWidget::Private::currentIndexChanged(int index) { const Page *const page = this->page(index); Q_EMIT q->currentViewChanged(page ? page->view() : nullptr); Q_EMIT q->keyFilterChanged(page ? page->keyFilter() : std::shared_ptr()); Q_EMIT q->stringFilterChanged(page ? page->stringFilter() : QString()); enableDisableCurrentPageActions(); } void TabWidget::Private::enableDisableCurrentPageActions() { const Page *const page = currentPage(); Q_EMIT q->enableChangeStringFilter(page && page->canChangeStringFilter()); Q_EMIT q->enableChangeKeyFilter(page && page->canChangeKeyFilter()); enableDisablePageActions(currentPageActions, page); } void TabWidget::Private::enableDisablePageActions(const Actions &actions, const Page *p) { actions.setEnabled(Actions::Rename, p && p->canBeRenamed()); actions.setEnabled(Actions::Duplicate, p); actions.setEnabled(Actions::Close, p && p->canBeClosed() && tabWidget->count() > 1); actions.setEnabled(Actions::MoveLeft, p && tabWidget->indexOf(const_cast(p)) != 0); actions.setEnabled(Actions::MoveRight, p && tabWidget->indexOf(const_cast(p)) != tabWidget->count() - 1); actions.setEnabled(Actions::Hierarchical, p && p->canChangeHierarchical()); actions.setChecked(Actions::Hierarchical, p && p->isHierarchicalView()); actions.setVisible(Actions::Hierarchical, Kleo::Settings{}.cmsEnabled()); actions.setEnabled(Actions::ExpandAll, p && p->isHierarchicalView()); actions.setEnabled(Actions::CollapseAll, p && p->isHierarchicalView()); } void TabWidget::Private::slotPageTitleChanged(const QString &) { if (Page *const page = senderPage()) { const int idx = tabWidget->indexOf(page); tabWidget->setTabText(idx, page->title()); tabWidget->setTabToolTip(idx, page->toolTip()); } } void TabWidget::Private::slotPageKeyFilterChanged(const std::shared_ptr &kf) { if (isSenderCurrentPage()) { Q_EMIT q->keyFilterChanged(kf); } } void TabWidget::Private::slotPageStringFilterChanged(const QString &filter) { if (isSenderCurrentPage()) { Q_EMIT q->stringFilterChanged(filter); } } void TabWidget::Private::slotPageHierarchyChanged(bool) { enableDisableCurrentPageActions(); } void TabWidget::Private::slotNewTab() { const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", tabWidget->count())); Page *page = new Page(QString(), QStringLiteral("all-certificates"), QString(), nullptr, QString(), nullptr, group); addView(page, currentPage()); tabWidget->setCurrentIndex(tabWidget->count() - 1); } void TabWidget::Private::renamePage(Page *page) { if (!page) { return; } bool ok; const QString text = QInputDialog::getText(q, i18n("Rename Tab"), i18n("New tab title:"), QLineEdit::Normal, page->title(), &ok); if (!ok) { return; } page->setTitle(text); } void TabWidget::Private::duplicatePage(Page *page) { if (!page) { return; } Page *const clone = page->clone(); Q_ASSERT(clone); clone->liftAllRestrictions(); addView(clone, page); } void TabWidget::Private::closePage(Page *page) { if (!page || !page->canBeClosed() || tabWidget->count() <= 1) { return; } Q_EMIT q->viewAboutToBeRemoved(page->view()); tabWidget->removeTab(tabWidget->indexOf(page)); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageLeft(Page *page) { if (!page) { return; } const int idx = tabWidget->indexOf(page); if (idx <= 0) { return; } tabWidget->tabBar()->moveTab(idx, idx - 1); enableDisableCurrentPageActions(); } void TabWidget::Private::movePageRight(Page *page) { if (!page) { return; } const int idx = tabWidget->indexOf(page); if (idx < 0 || idx >= tabWidget->count() - 1) { return; } tabWidget->tabBar()->moveTab(idx, idx + 1); enableDisableCurrentPageActions(); } void TabWidget::Private::toggleHierarchicalView(Page *page, bool on) { if (!page) { return; } page->setHierarchicalView(on); } void TabWidget::Private::expandAll(Page *page) { if (!page || !page->view()) { return; } page->view()->expandAll(); } void TabWidget::Private::collapseAll(Page *page) { if (!page || !page->view()) { return; } page->view()->collapseAll(); } TabWidget::TabWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f) , d(new Private(this)) { } TabWidget::~TabWidget() { saveViews(KSharedConfig::openConfig().data()); } void TabWidget::setFlatModel(AbstractKeyListModel *model) { if (model == d->flatModel) { return; } d->flatModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setFlatModel(model); } } AbstractKeyListModel *TabWidget::flatModel() const { return d->flatModel; } void TabWidget::setHierarchicalModel(AbstractKeyListModel *model) { if (model == d->hierarchicalModel) { return; } d->hierarchicalModel = model; for (unsigned int i = 0, end = count(); i != end; ++i) if (Page *const page = d->page(i)) { page->setHierarchicalModel(model); } } AbstractKeyListModel *TabWidget::hierarchicalModel() const { return d->hierarchicalModel; } QString TabWidget::stringFilter() const { return d->currentPage() ? d->currentPage()->stringFilter() : QString{}; } void TabWidget::setStringFilter(const QString &filter) { if (Page *const page = d->currentPage()) { page->setStringFilter(filter); } } void TabWidget::setKeyFilter(const std::shared_ptr &filter) { if (!filter) { qCDebug(KLEOPATRA_LOG) << "TabWidget::setKeyFilter() trial to set filter=NULL"; return; } if (Page *const page = d->currentPage()) { page->setKeyFilter(filter); } } std::vector TabWidget::views() const { std::vector result; const unsigned int N = count(); result.reserve(N); for (unsigned int i = 0; i != N; ++i) if (const Page *const p = d->page(i)) { result.push_back(p->view()); } return result; } QAbstractItemView *TabWidget::currentView() const { if (Page *const page = d->currentPage()) { return page->view(); } else { return nullptr; } } KeyListModelInterface *TabWidget::currentModel() const { const QAbstractItemView *const view = currentView(); if (!view) { return nullptr; } auto const proxy = qobject_cast(view->model()); if (!proxy) { return nullptr; } return dynamic_cast(proxy); } unsigned int TabWidget::count() const { return d->tabWidget->count(); } void TabWidget::setMultiSelection(bool on) { for (unsigned int i = 0, end = count(); i != end; ++i) if (const Page *const p = d->page(i)) if (QTreeView *const view = p->view()) { view->setSelectionMode(on ? QAbstractItemView::ExtendedSelection : QAbstractItemView::SingleSelection); } } void TabWidget::createActions(KActionCollection *coll) { if (!coll) { return; } const action_data actionDataNew = { "window_new_tab", i18n("New Tab"), i18n("Open a new tab"), "tab-new-background", this, [this](bool) { d->slotNewTab(); }, QStringLiteral("CTRL+SHIFT+N"), }; d->newAction = make_action_from_data(actionDataNew, coll); const std::vector actionData = { { Actions::Rename, i18n("Rename Tab..."), i18n("Rename this tab"), "edit-rename", this, [this](bool) { d->slotRenameCurrentTab(); }, QStringLiteral("CTRL+SHIFT+R"), RegularQAction, Disabled, }, { Actions::Duplicate, i18n("Duplicate Tab"), i18n("Duplicate this tab"), "tab-duplicate", this, [this](bool) { d->slotDuplicateCurrentTab(); }, QStringLiteral("CTRL+SHIFT+D"), }, { Actions::Close, i18n("Close Tab"), i18n("Close this tab"), "tab-close", this, [this](bool) { d->slotCloseCurrentTab(); }, QStringLiteral("CTRL+SHIFT+W"), RegularQAction, Disabled, }, // ### CTRL-W when available { Actions::MoveLeft, i18n("Move Tab Left"), i18n("Move this tab left"), nullptr, this, [this](bool) { d->slotMoveCurrentTabLeft(); }, QStringLiteral("CTRL+SHIFT+LEFT"), RegularQAction, Disabled, }, { Actions::MoveRight, i18n("Move Tab Right"), i18n("Move this tab right"), nullptr, this, [this](bool) { d->slotMoveCurrentTabRight(); }, QStringLiteral("CTRL+SHIFT+RIGHT"), RegularQAction, Disabled, }, { Actions::Hierarchical, i18n("Hierarchical Certificate List"), QString(), nullptr, this, [this](bool on) { d->slotToggleHierarchicalView(on); }, QString(), KFToggleAction, Disabled, }, { Actions::ExpandAll, i18n("Expand All"), QString(), nullptr, this, [this](bool) { d->slotExpandAll(); }, QStringLiteral("CTRL+."), RegularQAction, Disabled, }, { Actions::CollapseAll, i18n("Collapse All"), QString(), nullptr, this, [this](bool) { d->slotCollapseAll(); }, QStringLiteral("CTRL+,"), RegularQAction, Disabled, }, }; for (const auto &ad : actionData) { d->currentPageActions.insert(ad.name, make_action_from_data(ad, coll)); } for (const auto &ad : actionData) { // create actions for the context menu of the currently not active tabs, // but do not add those actions to the action collection auto action = new QAction(ad.text, coll); if (ad.icon) { - action->setIcon(QIcon::fromTheme(QLatin1String(ad.icon))); + action->setIcon(QIcon::fromTheme(QLatin1StringView(ad.icon))); } action->setEnabled(ad.actionState == Enabled); d->otherPageActions.insert(ad.name, action); } d->newTabButton->setDefaultAction(d->newAction); d->tabWidget->setCornerWidget(d->newTabButton, Qt::TopLeftCorner); if (auto action = d->currentPageActions.get(Actions::Close)) { d->closeTabButton->setDefaultAction(action); d->tabWidget->setCornerWidget(d->closeTabButton, Qt::TopRightCorner); } else { d->closeTabButton->setVisible(false); } d->actionsCreated = true; } QAbstractItemView *TabWidget::addView(const QString &title, const QString &id, const QString &text) { const KConfigGroup group = KSharedConfig::openConfig()->group(QString::asprintf("View #%u", d->tabWidget->count())); Page *page = new Page(title, id, text, nullptr, QString(), nullptr, group); return d->addView(page, d->currentPage()); } QAbstractItemView *TabWidget::addView(const KConfigGroup &group) { return d->addView(new Page(group), nullptr); } QAbstractItemView *TabWidget::addTemporaryView(const QString &title, AbstractKeyListSortFilterProxyModel *proxy, const QString &tabToolTip) { const KConfigGroup group = KSharedConfig::openConfig()->group(QStringLiteral("KeyTreeView_default")); Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip, nullptr, group); page->setTemporary(true); QAbstractItemView *v = d->addView(page, d->currentPage()); d->tabWidget->setCurrentIndex(d->tabWidget->count() - 1); return v; } QTreeView *TabWidget::Private::addView(Page *page, Page *columnReference) { if (!page) { return nullptr; } if (!actionsCreated) { auto coll = new KActionCollection(q); q->createActions(coll); } page->setFlatModel(flatModel); page->setHierarchicalModel(hierarchicalModel); connect(page, &Page::titleChanged, q, [this](const QString &text) { slotPageTitleChanged(text); }); connect(page, &Page::keyFilterChanged, q, [this](const std::shared_ptr &filter) { slotPageKeyFilterChanged(filter); }); connect(page, &Page::stringFilterChanged, q, [this](const QString &text) { slotPageStringFilterChanged(text); }); connect(page, &Page::hierarchicalChanged, q, [this](bool on) { slotPageHierarchyChanged(on); }); if (columnReference) { page->setColumnSizes(columnReference->columnSizes()); page->setSortColumn(columnReference->sortColumn(), columnReference->sortOrder()); } QAbstractItemView *const previous = q->currentView(); const int tabIndex = tabWidget->addTab(page, page->title()); setTabOrder(closeTabButton, page->view()); tabWidget->setTabToolTip(tabIndex, page->toolTip()); // work around a bug in QTabWidget (tested with 4.3.2) not emitting currentChanged() when the first widget is inserted QAbstractItemView *const current = q->currentView(); if (previous != current) { currentIndexChanged(tabWidget->currentIndex()); } enableDisableCurrentPageActions(); QTreeView *view = page->view(); Q_EMIT q->viewAdded(view); return view; } static QStringList extractViewGroups(const KConfig *config) { return config ? config->groupList().filter(QRegularExpression(QStringLiteral("^View #\\d+$"))) : QStringList(); } // work around deleteGroup() not deleting groups out of groupList(): static const bool KCONFIG_DELETEGROUP_BROKEN = true; void TabWidget::loadViews(const KConfig *config) { if (config) { QStringList groupList = extractViewGroups(config); groupList.sort(); for (const QString &group : std::as_const(groupList)) { const KConfigGroup kcg(config, group); if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) { addView(kcg); } } } if (!count()) { // add default view: addView(i18n("All Certificates"), QStringLiteral("all-certificates")); } } void TabWidget::saveViews(KConfig *config) const { if (!config) { return; } const auto extraView{extractViewGroups(config)}; for (const QString &group : extraView) { config->deleteGroup(group); } unsigned int vg = 0; for (unsigned int i = 0, end = count(); i != end; ++i) { if (const Page *const p = d->page(i)) { if (p->isTemporary()) { continue; } KConfigGroup group(config, QString::asprintf("View #%u", vg++)); p->saveTo(group); if (KCONFIG_DELETEGROUP_BROKEN) { group.writeEntry("magic", 0xFA1AFE1U); } } } } void TabWidget::connectSearchBar(SearchBar *sb) { connect(sb, &SearchBar::stringFilterChanged, this, &TabWidget::setStringFilter); connect(this, &TabWidget::stringFilterChanged, sb, &SearchBar::setStringFilter); connect(sb, &SearchBar::keyFilterChanged, this, &TabWidget::setKeyFilter); connect(this, &TabWidget::keyFilterChanged, sb, &SearchBar::setKeyFilter); connect(this, &TabWidget::enableChangeStringFilter, sb, &SearchBar::setChangeStringFilterEnabled); connect(this, &TabWidget::enableChangeKeyFilter, sb, &SearchBar::setChangeKeyFilterEnabled); } #include "moc_tabwidget.cpp" #include "tabwidget.moc" diff --git a/src/view/urllabel.cpp b/src/view/urllabel.cpp index 90974b903..bda3a2175 100644 --- a/src/view/urllabel.cpp +++ b/src/view/urllabel.cpp @@ -1,56 +1,56 @@ /* view/urllabel.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2021, 2022 g10 Code GmbH SPDX-FileContributor: Ingo Klöcker SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "urllabel.h" #include using namespace Kleo; UrlLabel::UrlLabel(QWidget *parent) : HtmlLabel{parent} { } UrlLabel::~UrlLabel() = default; void UrlLabel::setUrl(const QUrl &url, const QString &text) { // we prepend a zero-width-space character to work around a bug in QLabel::focusNextPrevChild(false) // which makes it impossible to leave the label with Shift+Tab if the text starts with a link - static const QString templateString{QLatin1String{"​%2"}}; + static const QString templateString{QLatin1StringView{"​%2"}}; if (url.isEmpty()) { HtmlLabel::setHtml({}); return; } setHtml(templateString.arg( // url.url(QUrl::FullyEncoded), // text.isEmpty() ? url.toDisplayString().toHtmlEscaped() : text.toHtmlEscaped())); } void UrlLabel::focusInEvent(QFocusEvent *event) { // immediately focus the URL when the label get focus QLabel::focusInEvent(event); if (!hasSelectedText()) { QMetaObject::invokeMethod( this, [this]() { focusNextPrevChild(true); }, Qt::QueuedConnection); } } #include "moc_urllabel.cpp" diff --git a/tests/kleo_test.h b/tests/kleo_test.h index 6e15a388e..5b70e8e9e 100644 --- a/tests/kleo_test.h +++ b/tests/kleo_test.h @@ -1,30 +1,30 @@ /* This file is part of Kleopatra's test suite. SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include #ifndef KLEO_TEST_GNUPGHOME #error KLEO_TEST_GNUPGHOME not defined! #endif // based on qtest_kde.h #define QTEST_KLEOMAIN(TestObject) \ int main(int argc, char *argv[]) \ { \ qputenv("GNUPGHOME", KLEO_TEST_GNUPGHOME); \ qputenv("LC_ALL", "C"); \ - qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1String("/.kde-unit-test"))); \ - KAboutData aboutData(QLatin1String("qttest"), i18n("qttest"), QLatin1String("version")); \ + qputenv("KDEHOME", QFile::encodeName(QDir::homePath() + QLatin1StringView("/.kde-unit-test"))); \ + KAboutData aboutData(QLatin1StringView("qttest"), i18n("qttest"), QLatin1String("version")); \ QApplication app(argc, argv); \ - app.setApplicationName(QLatin1String("qttest")); \ + app.setApplicationName(QLatin1StringView("qttest")); \ TestObject tc; \ return QTest::qExec(&tc, argc, argv); \ } diff --git a/tests/test_verify.cpp b/tests/test_verify.cpp index c41303c06..dd58435ea 100644 --- a/tests/test_verify.cpp +++ b/tests/test_verify.cpp @@ -1,207 +1,207 @@ /* This file is part of Kleopatra's test suite. SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "kleo_test.h" #include #include #include #include #include #include #include #include #include #include #include #include using namespace std::chrono_literals; // Replace this with a gpgme version check once GnuPG Bug #2092 // ( https://bugs.gnupg.org/gnupg/issue2092 ) is fixed. #define GPGME_MULTITHREADED_KEYLIST_BROKEN Q_DECLARE_METATYPE(GpgME::VerificationResult) class VerifyTest : public QObject { Q_OBJECT private: // Data shared with all tests QByteArray mSignature; QByteArray mSignedData; const QGpgME::Protocol *mBackend; QEventLoop mEventLoop; // Data for testParallelVerifyAndKeyListJobs() QList mParallelVerifyJobs; QList mParallelKeyListJobs; // Data for testMixedParallelJobs() QList mRunningJobs; int mJobsStarted; public Q_SLOTS: void slotParallelKeyListJobFinished() { mParallelKeyListJobs.removeAll(static_cast(sender())); // When all jobs are done, quit the event loop if (mParallelVerifyJobs.isEmpty() && mParallelKeyListJobs.isEmpty()) { mEventLoop.quit(); } } void slotParallelVerifyJobFinished(const GpgME::VerificationResult &result) { // Verify the result of the job is correct QVERIFY(mParallelVerifyJobs.contains(static_cast(sender()))); QCOMPARE(result.signature(0).validity(), GpgME::Signature::Full); mParallelVerifyJobs.removeAll(static_cast(sender())); // Start a key list job QGpgME::KeyListJob *job = mBackend->keyListJob(); mParallelKeyListJobs.append(job); connect(job, &QGpgME::Job::done, this, &VerifyTest::slotParallelKeyListJobFinished); QVERIFY(!job->start(QStringList())); } void someJobDone() { // Don't bother checking any results here mRunningJobs.removeAll(static_cast(sender())); } void startAnotherJob() { static int counter = 0; counter++; // Randomly kill a running job if (counter % 10 == 0 && !mRunningJobs.isEmpty()) { mRunningJobs.at(counter % mRunningJobs.size())->slotCancel(); } // Randomly either start a keylist or a verify job QGpgME::Job *job; if (counter % 2 == 0) { QGpgME::VerifyDetachedJob *vdj = mBackend->verifyDetachedJob(); QVERIFY(!vdj->start(mSignature, mSignedData)); job = vdj; } else { QGpgME::KeyListJob *klj = mBackend->keyListJob(); QVERIFY(!klj->start(QStringList())); job = klj; } mRunningJobs.append(job); connect(job, &QGpgME::Job::done, this, &VerifyTest::someJobDone); // Quit after 2500 jobs, that should be enough mJobsStarted++; if (mJobsStarted >= 2500) { QTimer::singleShot(1s, &mEventLoop, &QEventLoop::quit); } else { QTimer::singleShot(0, this, &VerifyTest::startAnotherJob); } } private Q_SLOTS: void initTestCase() { qRegisterMetaType(); - const QString sigFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data.sig"); - const QString dataFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data"); + const QString sigFileName = QLatin1StringView(KLEO_TEST_DATADIR) + QLatin1String("/test.data.sig"); + const QString dataFileName = QLatin1StringView(KLEO_TEST_DATADIR) + QLatin1String("/test.data"); QFile sigFile(sigFileName); QVERIFY(sigFile.open(QFile::ReadOnly)); QFile dataFile(dataFileName); QVERIFY(dataFile.open(QFile::ReadOnly)); mSignature = sigFile.readAll(); mSignedData = dataFile.readAll(); mBackend = QGpgME::openpgp(); } void testVerify() { QGpgME::VerifyDetachedJob *job = mBackend->verifyDetachedJob(); QSignalSpy spy(job, &QGpgME::VerifyDetachedJob::result); QVERIFY(spy.isValid()); GpgME::Error err = job->start(mSignature, mSignedData); QVERIFY(!err); QTest::qWait(1000); // ### we need to enter the event loop, can be done nicer though QCOMPARE(spy.count(), 1); auto result = spy.takeFirst().at(0).value(); QCOMPARE(result.numSignatures(), 1U); GpgME::Signature sig = result.signature(0); QCOMPARE(sig.summary() & GpgME::Signature::KeyMissing, 0); QCOMPARE((quint64)sig.creationTime(), Q_UINT64_C(1530524124)); QCOMPARE(sig.validity(), GpgME::Signature::Full); } /* Test that the decrypt verify job also works with signed only, not * encrypted PGP messages */ void testDecryptVerifyOpaqueSigned() { - const QString sigFileName = QLatin1String(KLEO_TEST_DATADIR) + QLatin1String("/test.data.signed-opaque.asc"); + const QString sigFileName = QLatin1StringView(KLEO_TEST_DATADIR) + QLatin1String("/test.data.signed-opaque.asc"); std::pair result; QByteArray plaintext; QFile sigFile(sigFileName); QVERIFY(sigFile.open(QFile::ReadOnly)); const QByteArray ciphertext = sigFile.readAll(); QGpgME::DecryptVerifyJob *job = mBackend->decryptVerifyJob(); result = job->exec(ciphertext, plaintext); QVERIFY(result.first.error().code()); QVERIFY(result.second.numSignatures()); GpgME::Signature sig = result.second.signature(0); QVERIFY(sig.validity() == GpgME::Signature::Validity::Full); QVERIFY(!sig.status().code()); - QVERIFY(QString::fromUtf8(plaintext).startsWith(QLatin1String("/* -*- mode: c++; c-basic-offset:4 -*-"))); + QVERIFY(QString::fromUtf8(plaintext).startsWith(QLatin1StringView("/* -*- mode: c++; c-basic-offset:4 -*-"))); } #ifndef GPGME_MULTITHREADED_KEYLIST_BROKEN // The following two tests are disabled because they trigger an // upstream bug in gpgme. See: https://bugs.gnupg.org/gnupg/issue2092 // Which has a testcase attached that does similar things using gpgme // directly and triggers various problems. void testParallelVerifyAndKeyListJobs() { // ### Increasing 10 to 500 makes the verify jobs fail! // ^ This should also be reevaluated if the underlying bug in gpgme // is fixed. for (int i = 0; i < 10; ++i) { QGpgME::VerifyDetachedJob *job = mBackend->verifyDetachedJob(); mParallelVerifyJobs.append(job); QVERIFY(!job->start(mSignature, mSignedData)); connect(job, SIGNAL(result(GpgME::VerificationResult)), this, SLOT(slotParallelVerifyJobFinished(GpgME::VerificationResult))); } mEventLoop.exec(); } void testMixedParallelJobs() { mJobsStarted = 0; QTimer::singleShot(0, this, SLOT(startAnotherJob())); mEventLoop.exec(); } #endif }; QTEST_KLEOMAIN(VerifyTest) #include "test_verify.moc"