diff --git a/src/commands/decryptverifyclipboardcommand.cpp b/src/commands/decryptverifyclipboardcommand.cpp index f6b94f8fe..4d56f4396 100644 --- a/src/commands/decryptverifyclipboardcommand.cpp +++ b/src/commands/decryptverifyclipboardcommand.cpp @@ -1,174 +1,173 @@ /* -*- mode: c++; c-basic-offset:4 -*- commands/decryptverifyclipboardcommand.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 "decryptverifyclipboardcommand.h" #ifndef QT_NO_CLIPBOARD #include "command_p.h" #include #include #include -#include #include #include #include #include "kleopatra_debug.h" #include using namespace Kleo; using namespace Kleo::Commands; using namespace Kleo::Crypto; class DecryptVerifyClipboardCommand::Private : public Command::Private { friend class ::Kleo::Commands::DecryptVerifyClipboardCommand; DecryptVerifyClipboardCommand *q_func() const { return static_cast(q); } public: explicit Private(DecryptVerifyClipboardCommand *qq, KeyListController *c); ~Private(); void init(); private: void slotControllerDone() { finished(); } void slotControllerError(int, const QString &) { finished(); } private: std::shared_ptr shared_qq; std::shared_ptr input; DecryptVerifyEMailController controller; }; DecryptVerifyClipboardCommand::Private *DecryptVerifyClipboardCommand::d_func() { return static_cast(d.get()); } const DecryptVerifyClipboardCommand::Private *DecryptVerifyClipboardCommand::d_func() const { return static_cast(d.get()); } #define d d_func() #define q q_func() DecryptVerifyClipboardCommand::Private::Private(DecryptVerifyClipboardCommand *qq, KeyListController *c) : Command::Private(qq, c), shared_qq(qq, [](DecryptVerifyClipboardCommand*){}), input(), controller() { } DecryptVerifyClipboardCommand::Private::~Private() { qCDebug(KLEOPATRA_LOG); } DecryptVerifyClipboardCommand::DecryptVerifyClipboardCommand(KeyListController *c) : Command(new Private(this, c)) { d->init(); } DecryptVerifyClipboardCommand::DecryptVerifyClipboardCommand(QAbstractItemView *v, KeyListController *c) : Command(v, new Private(this, c)) { d->init(); } void DecryptVerifyClipboardCommand::Private::init() { controller.setExecutionContext(shared_qq); connect(&controller, SIGNAL(done()), q, SLOT(slotControllerDone())); connect(&controller, SIGNAL(error(int,QString)), q, SLOT(slotControllerError(int,QString))); } DecryptVerifyClipboardCommand::~DecryptVerifyClipboardCommand() { qCDebug(KLEOPATRA_LOG); } // static bool DecryptVerifyClipboardCommand::canDecryptVerifyCurrentClipboard() { try { return Input::createFromClipboard()->classification() & (Class::CipherText | Class::ClearsignedMessage | Class::OpaqueSignature); } catch (...) {} return false; } void DecryptVerifyClipboardCommand::doStart() { try { const std::shared_ptr input = Input::createFromClipboard(); const unsigned int classification = input->classification(); if (classification & (Class::ClearsignedMessage | Class::OpaqueSignature)) { d->controller.setOperation(Verify); d->controller.setVerificationMode(Opaque); } else if (classification & Class::CipherText) { d->controller.setOperation(DecryptVerify); } else { d->information(i18n("The clipboard does not appear to " "contain a signature or encrypted text."), i18n("Decrypt/Verify Clipboard Error")); d->finished(); return; } d->controller.setProtocol(findProtocol(classification)); d->controller.setInput(input); d->controller.setOutput(Output::createFromClipboard()); d->controller.start(); } catch (const std::exception &e) { d->information(i18n("An error occurred: %1", QString::fromLocal8Bit(e.what())), i18n("Decrypt/Verify Clipboard Error")); d->finished(); } } void DecryptVerifyClipboardCommand::doCancel() { qCDebug(KLEOPATRA_LOG); d->controller.cancel(); } #undef d #undef q #include "moc_decryptverifyclipboardcommand.cpp" #endif // QT_NO_CLIPBOARD diff --git a/src/commands/exportpaperkeycommand.cpp b/src/commands/exportpaperkeycommand.cpp index 1a4882d88..191c4ff85 100644 --- a/src/commands/exportpaperkeycommand.cpp +++ b/src/commands/exportpaperkeycommand.cpp @@ -1,125 +1,124 @@ /* -*- 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 #include "kleopatra_debug.h" #include "command_p.h" using namespace Kleo; using namespace Kleo::Commands; using namespace GpgME; ExportPaperKeyCommand::ExportPaperKeyCommand(QAbstractItemView *v, KeyListController *c) : GnuPGProcessCommand(v, c), mParent(v) { connect(&mPkProc, static_cast(&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()); return result; } bool ExportPaperKeyCommand::preStartHook(QWidget *parent) const { if (paperKeyInstallPath().isNull()) { KMessageBox::sorry(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()); } diff --git a/src/conf/configuredialog.cpp b/src/conf/configuredialog.cpp index ef4e7f140..910a64b42 100644 --- a/src/conf/configuredialog.cpp +++ b/src/conf/configuredialog.cpp @@ -1,73 +1,72 @@ /* configuredialog.cpp This file is part of kleopatra SPDX-FileCopyrightText: 2000 Espen Sand SPDX-FileCopyrightText: 2001-2002 Marc Mutz SPDX-FileCopyrightText: 2004, 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-only */ -#include #include "configuredialog.h" #include #include #include #include #include #include #if HAVE_KCMUTILS # include #else # include "kleopageconfigdialog.h" #endif ConfigureDialog::ConfigureDialog(QWidget *parent) #if HAVE_KCMUTILS : KCMultiDialog(parent) #else : KleoPageConfigDialog(parent) #endif { setFaceType(KPageDialog::List); setWindowTitle(i18nc("@title:window", "Configure")); addModule(QStringLiteral("kleopatra_config_dirserv")); addModule(QStringLiteral("kleopatra_config_appear")); addModule(QStringLiteral("kleopatra_config_cryptooperations")); addModule(QStringLiteral("kleopatra_config_smimevalidation")); addModule(QStringLiteral("kleopatra_config_gnupgsystem")); // We store the minimum size of the dialog on hide, because otherwise // the KCMultiDialog starts with the size of the first kcm, not // the largest one. This way at least after the first showing of // the largest kcm the size is kept. const KConfigGroup geometry(KSharedConfig::openConfig(), "Geometry"); const int width = geometry.readEntry("ConfigureDialogWidth", 0); const int height = geometry.readEntry("ConfigureDialogHeight", 0); if (width != 0 && height != 0) { setMinimumSize(width, height); } } void ConfigureDialog::hideEvent(QHideEvent *e) { const QSize minSize = minimumSizeHint(); KConfigGroup geometry(KSharedConfig::openConfig(), "Geometry"); geometry.writeEntry("ConfigureDialogWidth", minSize.width()); geometry.writeEntry("ConfigureDialogHeight", minSize.height()); #if HAVE_KCMUTILS KCMultiDialog::hideEvent(e); #else KleoPageConfigDialog::hideEvent(e); #endif } ConfigureDialog::~ConfigureDialog() { } diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index e2bd0c91d..a850592b1 100644 --- a/src/crypto/autodecryptverifyfilescontroller.cpp +++ b/src/crypto/autodecryptverifyfilescontroller.cpp @@ -1,524 +1,522 @@ /* -*- 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 #include "kleopatra_debug.h" #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); ~Private() { qCDebug(KLEOPATRA_LOG); delete m_workDir; } void slotDialogCanceled(); void schedule(); 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; }; QVector 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; DecryptVerifyFilesDialog *m_dialog = nullptr; QTemporaryDir *m_workDir = nullptr; }; AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq) { qRegisterMetaType(); } void AutoDecryptVerifyFilesController::Private::slotDialogCanceled() { qCDebug(KLEOPATRA_LOG); } 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 : qAsConst(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } 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); Q_FOREACH (const std::shared_ptr &i, m_runnableTasks) { q->connectTask(i); } coll->setTasks(m_runnableTasks); m_dialog = new DecryptVerifyFilesDialog(coll); m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles)); QTimer::singleShot(0, q, SLOT(schedule())); if (m_dialog->exec() == QDialog::Accepted && m_workDir) { // Without workdir there is nothing to move. const QDir workdir(m_workDir->path()); const QDir outDir(m_dialog->outputLocation()); bool overWriteAll = false; qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &fi: workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) { 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.baseName(); 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); if (!moveDir(inpath, ofi.absoluteFilePath())) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, ofi.absoluteFilePath())); } continue; } const auto outpath = outDir.absoluteFilePath(fi.fileName()); qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath; const QFileInfo ofi(outpath); if (ofi.exists()) { int sel = KMessageBox::No; if (!overWriteAll) { sel = KMessageBox::questionYesNoCancel(m_dialog, i18n("The file %1 already exists.\n" "Overwrite?", outpath), i18n("Overwrite Existing File?"), KStandardGuiItem::overwrite(), KGuiItem(i18n("Overwrite All")), KStandardGuiItem::cancel()); } if (sel == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath; continue; } if (sel == KMessageBox::No) { //Overwrite All overWriteAll = true; } if (!QFile::remove(outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete %1.", outpath)); continue; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, outpath)); } } } q->emitDoneOrError(); delete m_dialog; m_dialog = nullptr; } QVector AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) || mayBeOpaqueSignature(classification) || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QVector out; for (const auto &file : files) { CryptoFile cFile; cFile.fileName = file; cFile.baseName = file.left(file.length() - 4); 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; } std::vector< std::shared_ptr > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QVector 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 (signedDataFileName.isEmpty()) { signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with \"%1\"", fi.fileName()), fi.dir().dirName()); } 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); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); 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); t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); t->setProtocol(proto); tasks.push_back(t); } } if (!foundSig) { undetected << cFile.fileName; qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; } } else { // Any Message type so we have input and output. const auto input = Input::createFromFile(cFile.fileName); const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); const auto ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); if (FileOperationsPreferences().dontUseTmpDir()) { if (!m_workDir) { m_workDir = new QTemporaryDir(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX")); } if (!m_workDir->isValid()) { qCDebug(KLEOPATRA_LOG) << m_workDir->path() << "not a valid temporary directory."; delete m_workDir; m_workDir = new QTemporaryDir(); } } else if (!m_workDir) { m_workDir = new QTemporaryDir(); } qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory."; const auto wd = QDir(m_workDir->path()); const auto output = ad ? ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd) : /*else*/ Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); // 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); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); 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); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); 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); 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/decryptverifyemailcontroller.cpp b/src/crypto/decryptverifyemailcontroller.cpp index 50aa689af..804ddc859 100644 --- a/src/crypto/decryptverifyemailcontroller.cpp +++ b/src/crypto/decryptverifyemailcontroller.cpp @@ -1,480 +1,479 @@ /* -*- mode: c++; c-basic-offset:4 -*- decryptverifyemailcontroller.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 "decryptverifyemailcontroller.h" #include "kleopatra_debug.h" #include "emailoperationspreferences.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 Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace KMime::Types; namespace { class DecryptVerifyEMailWizard : public QWizard { Q_OBJECT public: explicit DecryptVerifyEMailWizard(QWidget *parent = nullptr, Qt::WindowFlags f = {}) : QWizard(parent, f), m_resultPage(this) { KDAB_SET_OBJECT_NAME(m_resultPage); m_resultPage.setSubTitle(i18n("Status and progress of the crypto operations is shown here.")); addPage(&m_resultPage); } void addTaskCollection(const std::shared_ptr &coll) { m_resultPage.addTaskCollection(coll); } public Q_SLOTS: void accept() override { EMailOperationsPreferences prefs; prefs.setDecryptVerifyPopupGeometry(geometry()); prefs.save(); QWizard::accept(); } private: NewResultPage m_resultPage; }; } class DecryptVerifyEMailController::Private { DecryptVerifyEMailController *const q; public: explicit Private(DecryptVerifyEMailController *qq); void slotWizardCanceled(); void schedule(); std::vector > buildTasks(); static DecryptVerifyEMailWizard *findOrCreateWizard(unsigned int id); void ensureWizardCreated(); void ensureWizardVisible(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); std::vector > m_inputs, m_signedDatas; std::vector > m_outputs; unsigned int m_sessionId; QPointer m_wizard; std::vector > m_results; std::vector > m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_silent; bool m_operationCompleted; DecryptVerifyOperation m_operation; Protocol m_protocol; VerificationMode m_verificationMode; std::vector m_informativeSenders; }; DecryptVerifyEMailController::Private::Private(DecryptVerifyEMailController *qq) : q(qq), m_sessionId(0), m_silent(false), m_operationCompleted(false), m_operation(DecryptVerify), m_protocol(UnknownProtocol), m_verificationMode(Detached) { qRegisterMetaType(); } void DecryptVerifyEMailController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); if (!m_operationCompleted) { reportError(gpg_error(GPG_ERR_CANCELED), i18n("User canceled")); } } void DecryptVerifyEMailController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->m_runningTask.get()) { d->m_completedTasks.push_back(d->m_runningTask); const std::shared_ptr &dvr = std::dynamic_pointer_cast(result); Q_ASSERT(dvr); d->m_results.push_back(dvr); d->m_runningTask.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyEMailController::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()); Q_FOREACH (const std::shared_ptr &i, m_results) { Q_EMIT q->verificationResult(i->verificationResult()); } // if there is a popup, wait for either the client cancel or the user closing the popup. // Otherwise (silent case), finish immediately m_operationCompleted = true; q->emitDoneOrError(); } } void DecryptVerifyEMailController::Private::ensureWizardCreated() { if (m_wizard) { return; } DecryptVerifyEMailWizard *w = findOrCreateWizard(m_sessionId); connect(w, SIGNAL(destroyed()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); m_wizard = w; } namespace { template void collectGarbage(C &c) { typename C::iterator it = c.begin(); while (it != c.end() /*sic!*/) if (it->second) { ++it; } else { c.erase(it++ /*sic!*/); } } } // static DecryptVerifyEMailWizard *DecryptVerifyEMailController::Private::findOrCreateWizard(unsigned int id) { static std::map > s_wizards; collectGarbage(s_wizards); qCDebug(KLEOPATRA_LOG) << "id = " << id; if (id != 0) { const std::map >::const_iterator it = s_wizards.find(id); if (it != s_wizards.end()) { Q_ASSERT(it->second && "This should have been garbage-collected"); return it->second; } } DecryptVerifyEMailWizard *w = new DecryptVerifyEMailWizard; w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify E-Mail")); w->setAttribute(Qt::WA_DeleteOnClose); const QRect preferredGeometry = EMailOperationsPreferences().decryptVerifyPopupGeometry(); if (preferredGeometry.isValid()) { w->setGeometry(preferredGeometry); } s_wizards[id] = w; return w; } std::vector< std::shared_ptr > DecryptVerifyEMailController::Private::buildTasks() { const uint numInputs = m_inputs.size(); const uint numMessages = m_signedDatas.size(); const uint numOutputs = m_outputs.size(); const uint numInformativeSenders = m_informativeSenders.size(); // these are duplicated from DecryptVerifyCommandEMailBase::Private::checkForErrors with slightly modified error codes/messages if (!numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("At least one input needs to be provided")); if (numInformativeSenders > 0 && numInformativeSenders != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Informative sender/signed data count mismatch")); if (numMessages) { if (numMessages != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Signature/signed data count mismatch")); else if (m_operation != Verify || m_verificationMode != Detached) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("Signed data can only be given for detached signature verification")); } if (numOutputs) { if (numOutputs != numInputs) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), //TODO use better error code if possible i18n("Input/Output count mismatch")); else if (numMessages) throw Kleo::Exception(makeGnuPGError(GPG_ERR_CONFLICT), i18n("Cannot use output and signed data simultaneously")); } kleo_assert(m_protocol != UnknownProtocol); const QGpgME::Protocol *const backend = (m_protocol == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); if (!backend) { throw Kleo::Exception(makeGnuPGError(GPG_ERR_UNSUPPORTED_PROTOCOL), i18n("No backend support for %1", Formatting::displayName(m_protocol))); } if (m_operation != Decrypt && !m_silent) { ensureWizardVisible(); } std::vector< std::shared_ptr > tasks; for (unsigned int i = 0; i < numInputs; ++i) { std::shared_ptr task; switch (m_operation) { case Decrypt: { std::shared_ptr t(new DecryptTask); t->setInput(m_inputs.at(i)); Q_ASSERT(numOutputs); t->setOutput(m_outputs.at(i)); t->setProtocol(m_protocol); task = t; } break; case Verify: { if (m_verificationMode == Detached) { std::shared_ptr t(new VerifyDetachedTask); t->setInput(m_inputs.at(i)); t->setSignedData(m_signedDatas.at(i)); if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } else { std::shared_ptr t(new VerifyOpaqueTask); t->setInput(m_inputs.at(i)); if (numOutputs) { t->setOutput(m_outputs.at(i)); } if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } } break; case DecryptVerify: { std::shared_ptr t(new DecryptVerifyTask); t->setInput(m_inputs.at(i)); Q_ASSERT(numOutputs); t->setOutput(m_outputs.at(i)); if (numInformativeSenders > 0) { t->setInformativeSender(m_informativeSenders.at(i)); } t->setProtocol(m_protocol); task = t; } } Q_ASSERT(task); tasks.push_back(task); } return tasks; } void DecryptVerifyEMailController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(m_wizard); } DecryptVerifyEMailController::DecryptVerifyEMailController(QObject *parent) : Controller(parent), d(new Private(this)) { } DecryptVerifyEMailController::DecryptVerifyEMailController(const std::shared_ptr &ctx, QObject *parent) : Controller(ctx, parent), d(new Private(this)) { } DecryptVerifyEMailController::~DecryptVerifyEMailController() { qCDebug(KLEOPATRA_LOG); } void DecryptVerifyEMailController::start() { d->m_runnableTasks = d->buildTasks(); const std::shared_ptr coll(new TaskCollection); std::vector > tsks; Q_FOREACH (const std::shared_ptr &i, d->m_runnableTasks) { connectTask(i); tsks.push_back(i); } coll->setTasks(tsks); d->ensureWizardCreated(); d->m_wizard->addTaskCollection(coll); d->ensureWizardVisible(); QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyEMailController::setInput(const std::shared_ptr &input) { d->m_inputs.resize(1, input); } void DecryptVerifyEMailController::setInputs(const std::vector > &inputs) { d->m_inputs = inputs; } void DecryptVerifyEMailController::setSignedData(const std::shared_ptr &data) { d->m_signedDatas.resize(1, data); } void DecryptVerifyEMailController::setSignedData(const std::vector > &data) { d->m_signedDatas = data; } void DecryptVerifyEMailController::setOutput(const std::shared_ptr &output) { d->m_outputs.resize(1, output); } void DecryptVerifyEMailController::setOutputs(const std::vector > &outputs) { d->m_outputs = outputs; } void DecryptVerifyEMailController::setInformativeSenders(const std::vector &senders) { d->m_informativeSenders = senders; } void DecryptVerifyEMailController::setWizardShown(bool shown) { d->m_silent = !shown; if (d->m_wizard) { d->m_wizard->setVisible(shown); } } void DecryptVerifyEMailController::setOperation(DecryptVerifyOperation operation) { d->m_operation = operation; } void DecryptVerifyEMailController::setVerificationMode(VerificationMode vm) { d->m_verificationMode = vm; } void DecryptVerifyEMailController::setProtocol(Protocol prot) { d->m_protocol = prot; } void DecryptVerifyEMailController::setSessionId(unsigned int id) { qCDebug(KLEOPATRA_LOG) << "id = " << id; d->m_sessionId = id; } void DecryptVerifyEMailController::cancel() { qCDebug(KLEOPATRA_LOG); try { if (d->m_wizard) { disconnect(d->m_wizard); d->m_wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void DecryptVerifyEMailController::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(); } } #include "decryptverifyemailcontroller.moc" #include "moc_decryptverifyemailcontroller.cpp" diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index 3ccc83233..bb6614c9e 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,445 +1,443 @@ /* -*- 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 #include "kleopatra_debug.h" #include #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 /* _needs_ the info */ ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS")) : /* else we don't care */ UnknownProtocol; const std::shared_ptr input = Input::createFromFile(fileName); const std::shared_ptr output = ad ? ad->createOutputFromUnpackCommand(proto, fileName, outDir) : /*else*/ 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::shared_ptr(new OverwritePolicy(m_wizard))); 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); } 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 : std::unary_function, bool> { 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< std::shared_ptr > &ads, const QString &filename) { const QFileInfo fi(outputFileName(filename)); QString extension = fi.completeSuffix(); if (extension == QLatin1String("out")) { // added by outputFileName() -> useless return std::shared_ptr(); } if (extension.endsWith(QLatin1String(".out"))) { // added by outputFileName() -> remove extension.chop(4); } for (;;) { const std::vector >::const_iterator 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< std::shared_ptr > archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); unsigned int counter = 0; for (const auto &fname: qAsConst(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< std::shared_ptr > 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().code(), 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); 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/gui/certificatelineedit.cpp b/src/crypto/gui/certificatelineedit.cpp index d4445c17c..9942c2972 100644 --- a/src/crypto/gui/certificatelineedit.cpp +++ b/src/crypto/gui/certificatelineedit.cpp @@ -1,250 +1,248 @@ /* 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-License-Identifier: GPL-2.0-or-later */ #include "certificatelineedit.h" -#include #include #include #include #include #include #include "kleopatra_debug.h" -#include "dialogs/certificateselectiondialog.h" #include "commands/detailscommand.h" #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Dialogs; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) static QStringList s_lookedUpKeys; namespace { class ProxyModel : public KeyListSortFilterProxyModel { Q_OBJECT public: ProxyModel(QObject *parent = nullptr) : KeyListSortFilterProxyModel(parent) { } QVariant data(const QModelIndex &index, int role) const override { if (!index.isValid()) { return QVariant(); } switch (role) { case Qt::DecorationRole: { const auto key = KeyListSortFilterProxyModel::data(index, Kleo::KeyListModelInterface::KeyRole).value(); Q_ASSERT(!key.isNull()); if (key.isNull()) { return QVariant(); } return Kleo::Formatting::iconForUid(key.userID(0)); } default: return KeyListSortFilterProxyModel::data(index, role); } } }; } // namespace CertificateLineEdit::CertificateLineEdit(AbstractKeyListModel *model, QWidget *parent, KeyFilter *filter) : QLineEdit(parent), mFilterModel(new KeyListSortFilterProxyModel(this)), mFilter(std::shared_ptr(filter)), mLineAction(new QAction(this)) { setPlaceholderText(i18n("Please enter a name or email address...")); setClearButtonEnabled(true); addAction(mLineAction, QLineEdit::LeadingPosition); auto *completer = new QCompleter(this); auto *completeFilterModel = new ProxyModel(completer); completeFilterModel->setKeyFilter(mFilter); completeFilterModel->setSourceModel(model); completer->setModel(completeFilterModel); completer->setCompletionColumn(KeyListModelInterface::Summary); completer->setFilterMode(Qt::MatchContains); completer->setCaseSensitivity(Qt::CaseInsensitive); setCompleter(completer); mFilterModel->setSourceModel(model); mFilterModel->setFilterKeyColumn(KeyListModelInterface::Summary); if (filter) { mFilterModel->setKeyFilter(mFilter); } connect(KeyCache::instance().get(), &Kleo::KeyCache::keyListingDone, this, &CertificateLineEdit::updateKey); connect(this, &QLineEdit::editingFinished, this, &CertificateLineEdit::updateKey); connect(this, &QLineEdit::textChanged, this, &CertificateLineEdit::editChanged); connect(mLineAction, &QAction::triggered, this, &CertificateLineEdit::dialogRequested); connect(this, &QLineEdit::editingFinished, this, &CertificateLineEdit::checkLocate); updateKey(); /* Take ownership of the model to prevent double deletion when the * filter models are deleted */ model->setParent(parent ? parent : this); } void CertificateLineEdit::editChanged() { updateKey(); if (!mEditStarted) { Q_EMIT editingStarted(); mEditStarted = true; } mEditFinished = false; } void CertificateLineEdit::checkLocate() { if (!key().isNull()) { // Already have a key return; } // Only check once per mailbox const auto mailText = text(); if (s_lookedUpKeys.contains(mailText)) { return; } s_lookedUpKeys << mailText; qCDebug(KLEOPATRA_LOG) << "Lookup job for" << mailText; QGpgME::KeyForMailboxJob *job = QGpgME::openpgp()->keyForMailboxJob(); job->start(mailText); } void CertificateLineEdit::updateKey() { const auto mailText = text(); auto newKey = Key(); if (mailText.isEmpty()) { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("resource-group-new"))); mLineAction->setToolTip(i18n("Open selection dialog.")); } else { mFilterModel->setFilterFixedString(mailText); if (mFilterModel->rowCount() > 1) { if (mEditFinished) { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("question")).pixmap(KIconLoader::SizeSmallMedium)); mLineAction->setToolTip(i18n("Multiple certificates")); } } else if (mFilterModel->rowCount() == 1) { newKey = mFilterModel->data(mFilterModel->index(0, 0), KeyListModelInterface::KeyRole).value(); mLineAction->setToolTip(Formatting::validity(newKey.userID(0)) + QStringLiteral("
Click here for details.")); /* FIXME: This needs to be solved by a multiple UID supporting model */ mLineAction->setIcon(Formatting::iconForUid(newKey.userID(0))); } else { mLineAction->setIcon(QIcon::fromTheme(QStringLiteral("emblem-error"))); mLineAction->setToolTip(i18n("No matching certificates found.
Click here to import a certificate.")); } } mKey = newKey; if (mKey.isNull()) { setToolTip(QString()); } else { setToolTip(Formatting::toolTip(newKey, Formatting::ToolTipOption::AllOptions)); } Q_EMIT keyChanged(); if (mailText.isEmpty()) { Q_EMIT wantsRemoval(this); } } Key CertificateLineEdit::key() const { if (isEnabled()) { return mKey; } else { return Key(); } } void CertificateLineEdit::dialogRequested() { if (!mKey.isNull()) { auto cmd = new Commands::DetailsCommand(mKey, nullptr); cmd->start(); return; } CertificateSelectionDialog *const dlg = new CertificateSelectionDialog(this); dlg->setKeyFilter(mFilter); if (dlg->exec()) { const std::vector keys = dlg->selectedCertificates(); if (!keys.size()) { return; } for (unsigned int i = 0; i < keys.size(); i++) { if (!i) { setKey(keys[i]); } else { Q_EMIT addRequested(keys[i]); } } } delete dlg; updateKey(); } void CertificateLineEdit::setKey(const Key &k) { QSignalBlocker blocky(this); qCDebug(KLEOPATRA_LOG) << "Setting Key. " << Formatting::summaryLine(k); setText(Formatting::summaryLine(k)); updateKey(); } bool CertificateLineEdit::isEmpty() const { return text().isEmpty(); } void CertificateLineEdit::setKeyFilter(const std::shared_ptr &filter) { mFilter = filter; mFilterModel->setKeyFilter(filter); } #include "certificatelineedit.moc" diff --git a/src/crypto/gui/decryptverifyfilesdialog.cpp b/src/crypto/gui/decryptverifyfilesdialog.cpp index 20a7105ab..c47028e69 100644 --- a/src/crypto/gui/decryptverifyfilesdialog.cpp +++ b/src/crypto/gui/decryptverifyfilesdialog.cpp @@ -1,245 +1,244 @@ /* crypto/gui/decryptverifyfilesdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 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 "decryptverifyfilesdialog.h" #include "kleopatra_debug.h" #include "crypto/taskcollection.h" #include "crypto/decryptverifytask.h" #include "crypto/gui/resultpage.h" #include "crypto/gui/resultlistwidget.h" #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; DecryptVerifyFilesDialog::DecryptVerifyFilesDialog(const std::shared_ptr &coll, QWidget *parent) : QDialog(parent), m_tasks(coll), m_buttonBox(new QDialogButtonBox) { readConfig(); auto vLay = new QVBoxLayout(this); auto labels = new QWidget; auto outputLayout = new QHBoxLayout; m_outputLocationFNR = new FileNameRequester; auto outLabel = new QLabel(i18n("&Output folder:")); outLabel->setBuddy(m_outputLocationFNR); outputLayout->addWidget(outLabel); outputLayout->addWidget(m_outputLocationFNR); m_outputLocationFNR->setFilter(QDir::Dirs); vLay->addLayout(outputLayout); m_progressLabelLayout = new QVBoxLayout(labels); vLay->addWidget(labels); m_progressBar = new QProgressBar; vLay->addWidget(m_progressBar); m_resultList = new ResultListWidget; vLay->addWidget(m_resultList); m_tasks = coll; Q_ASSERT(m_tasks); m_resultList->setTaskCollection(coll); connect(m_tasks.get(), &TaskCollection::progress, this, &DecryptVerifyFilesDialog::progress); connect(m_tasks.get(), &TaskCollection::done, this, &DecryptVerifyFilesDialog::allDone); connect(m_tasks.get(), &TaskCollection::result, this, &DecryptVerifyFilesDialog::result); connect(m_tasks.get(), &TaskCollection::started, this, &DecryptVerifyFilesDialog::started); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_buttonBox, &QDialogButtonBox::clicked, this, &DecryptVerifyFilesDialog::btnClicked); layout()->addWidget(m_buttonBox); bool hasOutputs = false; for (const auto &t: coll->tasks()) { if (!qobject_cast(t.get())) { hasOutputs = true; break; } } if (hasOutputs) { setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files")); m_saveButton = QDialogButtonBox::SaveAll; m_buttonBox->addButton(QDialogButtonBox::Discard); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &DecryptVerifyFilesDialog::checkAccept); } else { outLabel->setVisible(false); m_outputLocationFNR->setVisible(false); setWindowTitle(i18nc("@title:window", "Verify Files")); m_buttonBox->addButton(QDialogButtonBox::Close); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); } if (m_saveButton) { m_buttonBox->addButton(m_saveButton); m_buttonBox->button(m_saveButton)->setEnabled(false); } } DecryptVerifyFilesDialog::~DecryptVerifyFilesDialog() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void DecryptVerifyFilesDialog::allDone() { qCDebug(KLEOPATRA_LOG) << "All done"; Q_ASSERT(m_tasks); m_progressBar->setRange(0, 100); m_progressBar->setValue(100); for (const auto &i: m_progressLabelByTag.keys()) { if (!i.isEmpty()) { m_progressLabelByTag.value(i)->setText(i18n("%1: All operations completed.", i)); } else { m_progressLabelByTag.value(i)->setText(i18n("All operations completed.")); } } if (m_tasks->allTasksHaveErrors()) { return; } if (m_saveButton != QDialogButtonBox::NoButton) { m_buttonBox->button(m_saveButton)->setEnabled(true); } else { m_buttonBox->removeButton(m_buttonBox->button(QDialogButtonBox::Close)); m_buttonBox->addButton(QDialogButtonBox::Ok); } } void DecryptVerifyFilesDialog::started(const std::shared_ptr &task) { Q_ASSERT(task); const auto tag = task->tag(); auto label = labelForTag(tag); Q_ASSERT(label); if (tag.isEmpty()) { label->setText(i18nc("number, operation description", "Operation %1: %2", m_tasks->numberOfCompletedTasks() + 1, task->label())); } else { label->setText(i18nc("tag( \"OpenPGP\" or \"CMS\"), operation description", "%1: %2", tag, task->label())); } if (m_saveButton != QDialogButtonBox::NoButton) { m_buttonBox->button(m_saveButton)->setEnabled(false); } else if (m_buttonBox->button(QDialogButtonBox::Ok)) { m_buttonBox->removeButton(m_buttonBox->button(QDialogButtonBox::Ok)); m_buttonBox->addButton(QDialogButtonBox::Close); } } QLabel *DecryptVerifyFilesDialog::labelForTag(const QString &tag) { if (QLabel *const label = m_progressLabelByTag.value(tag)) { return label; } auto label = new QLabel; label->setTextFormat(Qt::RichText); label->setWordWrap(true); m_progressLabelLayout->addWidget(label); m_progressLabelByTag.insert(tag, label); return label; } void DecryptVerifyFilesDialog::progress(const QString &msg, int progress, int total) { Q_UNUSED(msg); Q_ASSERT(progress >= 0); Q_ASSERT(total >= 0); m_progressBar->setRange(0, total); m_progressBar->setValue(progress); } void DecryptVerifyFilesDialog::setOutputLocation(const QString &dir) { m_outputLocationFNR->setFileName(dir); } QString DecryptVerifyFilesDialog::outputLocation() const { return m_outputLocationFNR->fileName(); } void DecryptVerifyFilesDialog::btnClicked(QAbstractButton *btn) { if (m_buttonBox->buttonRole(btn) == QDialogButtonBox::DestructiveRole) { close(); } } void DecryptVerifyFilesDialog::checkAccept() { const auto outLoc = outputLocation(); if (outLoc.isEmpty()) { KMessageBox::information(this, i18n("Please select an output folder."), i18n("No output folder.")); return; } const QFileInfo fi(outLoc); if (fi.exists() && fi.isDir() && fi.isWritable()) { accept(); return; } if (!fi.exists()) { qCDebug(KLEOPATRA_LOG) << "Output dir does not exist. Trying to create."; const QDir dir(outLoc); if (!dir.mkdir(outLoc)) { KMessageBox::information(this, i18n("Please select a different output folder."), i18n("Failed to create output folder.")); return; } else { accept(); return; } } KMessageBox::information(this, i18n("Please select a different output folder."), i18n("Invalid output folder.")); } void DecryptVerifyFilesDialog::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "DecryptVerifyFilesDialog"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void DecryptVerifyFilesDialog::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "DecryptVerifyFilesDialog"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } diff --git a/src/crypto/gui/resultlistwidget.cpp b/src/crypto/gui/resultlistwidget.cpp index 6e019413f..8325113bd 100644 --- a/src/crypto/gui/resultlistwidget.cpp +++ b/src/crypto/gui/resultlistwidget.cpp @@ -1,213 +1,212 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/resultlistwidget.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 "resultlistwidget.h" #include "emailoperationspreferences.h" #include -#include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class ResultListWidget::Private { ResultListWidget *const q; public: explicit Private(ResultListWidget *qq); void result(const std::shared_ptr &result); void started(const std::shared_ptr &task); void allTasksDone(); void addResultWidget(ResultItemWidget *widget); void setupSingle(); void setupMulti(); void resizeIfStandalone(); std::vector< std::shared_ptr > m_collections; bool m_standaloneMode = false; int m_lastErrorItemIndex = 0; ScrollArea *m_scrollArea = nullptr; QPushButton *m_closeButton = nullptr; QVBoxLayout *m_layout = nullptr; QLabel *m_progressLabel = nullptr; }; ResultListWidget::Private::Private(ResultListWidget *qq) : q(qq), m_collections() { m_layout = new QVBoxLayout(q); m_layout->setContentsMargins(0, 0, 0, 0); m_layout->setSpacing(0); m_progressLabel = new QLabel; m_progressLabel->setWordWrap(true); m_layout->addWidget(m_progressLabel); m_progressLabel->setVisible(false); m_closeButton = new QPushButton; KGuiItem::assign(m_closeButton, KStandardGuiItem::close()); q->connect(m_closeButton, &QPushButton::clicked, q, &ResultListWidget::close); m_layout->addWidget(m_closeButton); m_closeButton->setVisible(false); } ResultListWidget::ResultListWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(parent, f), d(new Private(this)) { } ResultListWidget::~ResultListWidget() { if (!d->m_standaloneMode) { return; } EMailOperationsPreferences prefs; prefs.setDecryptVerifyPopupGeometry(geometry()); prefs.save(); } void ResultListWidget::Private::setupSingle() { m_layout->addStretch(); } void ResultListWidget::Private::resizeIfStandalone() { if (m_standaloneMode) { q->resize(q->size().expandedTo(q->sizeHint())); } } void ResultListWidget::Private::setupMulti() { if (m_scrollArea) { return; // already been here... } m_scrollArea = new ScrollArea; m_scrollArea->setFocusPolicy(Qt::NoFocus); Q_ASSERT(qobject_cast(m_scrollArea->widget()->layout())); static_cast(m_scrollArea->widget()->layout())->setContentsMargins(0, 0, 0, 0); static_cast(m_scrollArea->widget()->layout())->setSpacing(2); static_cast(m_scrollArea->widget()->layout())->addStretch(); m_layout->insertWidget(0, m_scrollArea); } void ResultListWidget::Private::addResultWidget(ResultItemWidget *widget) { Q_ASSERT(widget); Q_ASSERT(std::any_of(m_collections.cbegin(), m_collections.cend(), [](const std::shared_ptr &t) { return !t->isEmpty(); })); Q_ASSERT(m_scrollArea); Q_ASSERT(m_scrollArea->widget()); Q_ASSERT(qobject_cast(m_scrollArea->widget()->layout())); QBoxLayout &blay = *static_cast(m_scrollArea->widget()->layout()); blay.insertWidget(widget->hasErrorResult() ? m_lastErrorItemIndex++ : (blay.count() - 1), widget); widget->show(); resizeIfStandalone(); } void ResultListWidget::Private::allTasksDone() { if (!q->isComplete()) { return; } m_progressLabel->setVisible(false); resizeIfStandalone(); Q_EMIT q->completeChanged(); } void ResultListWidget::Private::result(const std::shared_ptr &result) { Q_ASSERT(result); Q_ASSERT(std::any_of(m_collections.cbegin(), m_collections.cend(), [](const std::shared_ptr &t) { return !t->isEmpty(); })); ResultItemWidget *wid = new ResultItemWidget(result); q->connect(wid, &ResultItemWidget::linkActivated, q, &ResultListWidget::linkActivated); q->connect(wid, &ResultItemWidget::closeButtonClicked, q, &ResultListWidget::close); addResultWidget(wid); } bool ResultListWidget::isComplete() const { return std::all_of(d->m_collections.cbegin(), d->m_collections.cend(), std::mem_fn(&TaskCollection::allTasksCompleted)); } unsigned int ResultListWidget::totalNumberOfTasks() const { return kdtools::accumulate_transform(d->m_collections.cbegin(), d->m_collections.cend(), std::mem_fn(&TaskCollection::size), 0U); } unsigned int ResultListWidget::numberOfCompletedTasks() const { return kdtools::accumulate_transform(d->m_collections.cbegin(), d->m_collections.cend(), std::mem_fn(&TaskCollection::numberOfCompletedTasks), 0U); } void ResultListWidget::setTaskCollection(const std::shared_ptr &coll) { //clear(); ### PENDING(marc) implement addTaskCollection(coll); } void ResultListWidget::addTaskCollection(const std::shared_ptr &coll) { Q_ASSERT(coll); Q_ASSERT(!coll->isEmpty()); d->m_collections.push_back(coll); connect(coll.get(), SIGNAL(result(std::shared_ptr)), this, SLOT(result(std::shared_ptr))); connect(coll.get(), SIGNAL(started(std::shared_ptr)), this, SLOT(started(std::shared_ptr))); connect(coll.get(), SIGNAL(done()), this, SLOT(allTasksDone())); d->setupMulti(); setStandaloneMode(d->m_standaloneMode); } void ResultListWidget::Private::started(const std::shared_ptr &task) { Q_ASSERT(task); Q_ASSERT(m_progressLabel); m_progressLabel->setText(i18nc("number, operation description", "Operation %1: %2", q->numberOfCompletedTasks() + 1, task->label())); resizeIfStandalone(); } void ResultListWidget::setStandaloneMode(bool standalone) { d->m_standaloneMode = standalone; if (totalNumberOfTasks() == 0) { return; } d->m_closeButton->setVisible(standalone); d->m_progressLabel->setVisible(standalone); } #include "moc_resultlistwidget.cpp" diff --git a/src/crypto/gui/signerresolvepage.cpp b/src/crypto/gui/signerresolvepage.cpp index 53905d87f..48254b8ca 100644 --- a/src/crypto/gui/signerresolvepage.cpp +++ b/src/crypto/gui/signerresolvepage.cpp @@ -1,667 +1,666 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signerresolvepage.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 "signerresolvepage.h" #include "signerresolvepage_p.h" #include "signingcertificateselectiondialog.h" #include #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::Crypto; using namespace Kleo::Crypto::Gui; namespace { static SignerResolvePage::Operation operationFromFlags(bool sign, bool encrypt) { if (!encrypt && sign) { return SignerResolvePage::SignOnly; } if (!sign && encrypt) { return SignerResolvePage::EncryptOnly; } return SignerResolvePage::SignAndEncrypt; } static QString formatLabel(Protocol p, const Key &key) { return i18nc("%1=protocol (S/Mime, OpenPGP), %2=certificate", "Sign using %1: %2", Formatting::displayName(p), !key.isNull() ? Formatting::formatForComboBox(key) : i18n("No certificate selected")); } static std::vector supportedProtocols() { std::vector protocols; protocols.push_back(OpenPGP); protocols.push_back(CMS); return protocols; } } AbstractSigningProtocolSelectionWidget::AbstractSigningProtocolSelectionWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f) { } ReadOnlyProtocolSelectionWidget::ReadOnlyProtocolSelectionWidget(QWidget *p, Qt::WindowFlags f) : AbstractSigningProtocolSelectionWidget(p, f) { QVBoxLayout *const layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); const auto supportedProtocolsLst = supportedProtocols(); for (const Protocol i: supportedProtocolsLst) { QLabel *const l = new QLabel; l->setText(formatLabel(i, Key())); layout->addWidget(l); m_labels[i] = l; } } void ReadOnlyProtocolSelectionWidget::setProtocolChecked(Protocol protocol, bool checked) { QLabel *const l = label(protocol); Q_ASSERT(l); l->setVisible(checked); } bool ReadOnlyProtocolSelectionWidget::isProtocolChecked(Protocol protocol) const { QLabel *const l = label(protocol); Q_ASSERT(l); return l->isVisible(); } std::vector ReadOnlyProtocolSelectionWidget::checkedProtocols() const { std::vector res; Q_FOREACH (const Protocol i, supportedProtocols()) //krazy:exclude=foreach if (isProtocolChecked(i)) { res.push_back(i); } return res; } SigningProtocolSelectionWidget::SigningProtocolSelectionWidget(QWidget *parent, Qt::WindowFlags f) : AbstractSigningProtocolSelectionWidget(parent, f) { m_buttonGroup = new QButtonGroup(this); connect(m_buttonGroup, static_cast(&QButtonGroup::buttonClicked), this, &SigningProtocolSelectionWidget::userSelectionChanged); QVBoxLayout *const layout = new QVBoxLayout(this); layout->setContentsMargins(0, 0, 0, 0); Q_FOREACH (const Protocol i, supportedProtocols()) { //krazy:exclude=foreach QCheckBox *const b = new QCheckBox; b->setText(formatLabel(i, Key())); m_buttons[i] = b; layout->addWidget(b); m_buttonGroup->addButton(b); } setExclusive(true); } void SigningProtocolSelectionWidget::setProtocolChecked(Protocol p, bool checked) { Q_ASSERT(p != UnknownProtocol); QCheckBox *const b = button(p); Q_ASSERT(b); b->setChecked(checked); } bool SigningProtocolSelectionWidget::isProtocolChecked(Protocol p) const { Q_ASSERT(p != UnknownProtocol); const QAbstractButton *const b = button(p); Q_ASSERT(b); return b->isChecked(); } std::vector SigningProtocolSelectionWidget::checkedProtocols() const { std::vector res; for (std::map::const_iterator it = m_buttons.begin(), end = m_buttons.end(); it != end; ++it) if (it->second->isChecked()) { res.push_back(it->first); } return res; } void SigningProtocolSelectionWidget::setExclusive(bool exclusive) { if (exclusive == isExclusive()) { return; } m_buttonGroup->setExclusive(exclusive); Q_EMIT userSelectionChanged(); } QCheckBox *SigningProtocolSelectionWidget::button(Protocol p) const { const std::map::const_iterator it = m_buttons.find(p); return it == m_buttons.end() ? nullptr : it->second; } QLabel *ReadOnlyProtocolSelectionWidget::label(Protocol p) const { const std::map::const_iterator it = m_labels.find(p); return it == m_labels.end() ? nullptr : it->second; } bool SigningProtocolSelectionWidget::isExclusive() const { return m_buttonGroup->exclusive(); } void SigningProtocolSelectionWidget::setCertificate(Protocol prot, const Key &key) { QAbstractButton *const b = button(prot); Q_ASSERT(b); b->setText(formatLabel(prot, key)); } void ReadOnlyProtocolSelectionWidget::setCertificate(Protocol prot, const Key &key) { QLabel *const l = label(prot); l->setText(formatLabel(prot, key)); } namespace { class ValidatorImpl : public SignerResolvePage::Validator { public: QString explanation() const override { return QString(); } bool isComplete() const override { return true; } QString customWindowTitle() const override { return QString(); } }; } class SignerResolvePage::Private { friend class ::Kleo::Crypto::Gui::SignerResolvePage; SignerResolvePage *const q; public: explicit Private(SignerResolvePage *qq); ~Private(); void setOperation(Operation operation); void operationButtonClicked(int operation); void selectCertificates(); void setCertificates(const QMap &certs); void updateModeSelectionWidgets(); void updateUi(); bool protocolSelected(Protocol p) const; bool protocolSelectionActuallyUserMutable() const; private: QButtonGroup *signEncryptGroup; QRadioButton *signAndEncryptRB; QRadioButton *encryptOnlyRB; QRadioButton *signOnlyRB; QGroupBox *signingCertificateBox; QLabel *signerLabelLabel; QLabel *signerLabel; QGroupBox *encryptBox; QCheckBox *textArmorCO; QPushButton *selectCertificatesButton; SigningProtocolSelectionWidget *signingProtocolSelectionWidget; ReadOnlyProtocolSelectionWidget *readOnlyProtocolSelectionWidget; std::vector presetProtocols; bool signingMutable; bool encryptionMutable; bool signingSelected; bool encryptionSelected; bool multipleProtocolsAllowed; bool protocolSelectionUserMutable; QMap certificates; std::shared_ptr validator; std::shared_ptr signingPreferences; }; bool SignerResolvePage::Private::protocolSelectionActuallyUserMutable() const { return (q->protocolSelectionUserMutable() || presetProtocols.empty()) && q->operation() == SignOnly; } SignerResolvePage::Private::Private(SignerResolvePage *qq) : q(qq) , presetProtocols() , signingMutable(true) , encryptionMutable(true) , signingSelected(false) , encryptionSelected(false) , multipleProtocolsAllowed(false) , protocolSelectionUserMutable(true) , validator(new ValidatorImpl) { QVBoxLayout *layout = new QVBoxLayout(q); signEncryptGroup = new QButtonGroup(q); q->connect(signEncryptGroup, SIGNAL(buttonClicked(int)), q, SLOT(operationButtonClicked(int))); signAndEncryptRB = new QRadioButton; signAndEncryptRB->setText(i18n("Sign and encrypt (OpenPGP only)")); signAndEncryptRB->setChecked(true); signEncryptGroup->addButton(signAndEncryptRB, SignAndEncrypt); layout->addWidget(signAndEncryptRB); encryptOnlyRB = new QRadioButton; encryptOnlyRB->setText(i18n("Encrypt only")); signEncryptGroup->addButton(encryptOnlyRB, EncryptOnly); layout->addWidget(encryptOnlyRB); signOnlyRB = new QRadioButton; signOnlyRB->setText(i18n("Sign only")); signEncryptGroup->addButton(signOnlyRB, SignOnly); layout->addWidget(signOnlyRB); encryptBox = new QGroupBox; encryptBox->setTitle(i18n("Encryption Options")); QBoxLayout *const encryptLayout = new QVBoxLayout(encryptBox); textArmorCO = new QCheckBox; textArmorCO->setText(i18n("Text output (ASCII armor)")); encryptLayout->addWidget(textArmorCO); layout->addWidget(encryptBox); signingCertificateBox = new QGroupBox; signingCertificateBox->setTitle(i18n("Signing Options")); QGridLayout *signerLayout = new QGridLayout(signingCertificateBox); signerLayout->setColumnStretch(1, 1); signerLabelLabel = new QLabel; signerLabelLabel->setText(i18n("Signer:")); signerLayout->addWidget(signerLabelLabel, 1, 0); signerLabel = new QLabel; signerLayout->addWidget(signerLabel, 1, 1); signerLabelLabel->setVisible(false); signerLabel->setVisible(false); signingProtocolSelectionWidget = new SigningProtocolSelectionWidget; connect(signingProtocolSelectionWidget, SIGNAL(userSelectionChanged()), q, SLOT(updateUi())); signerLayout->addWidget(signingProtocolSelectionWidget, 2, 0, 1, -1); readOnlyProtocolSelectionWidget = new ReadOnlyProtocolSelectionWidget; signerLayout->addWidget(readOnlyProtocolSelectionWidget, 3, 0, 1, -1); selectCertificatesButton = new QPushButton; selectCertificatesButton->setText(i18n("Change Signing Certificates...")); selectCertificatesButton->setSizePolicy(QSizePolicy::Preferred, QSizePolicy::Fixed); signerLayout->addWidget(selectCertificatesButton, 4, 0, 1, -1, Qt::AlignLeft); q->connect(selectCertificatesButton, SIGNAL(clicked()), q, SLOT(selectCertificates())); layout->addWidget(signingCertificateBox); layout->addStretch(); } void SignerResolvePage::setValidator(const std::shared_ptr &validator) { Q_ASSERT(validator); d->validator = validator; d->updateUi(); } std::shared_ptr SignerResolvePage::validator() const { return d->validator; } SignerResolvePage::Private::~Private() {} bool SignerResolvePage::Private::protocolSelected(Protocol p) const { Q_ASSERT(p != UnknownProtocol); return signingProtocolSelectionWidget->isProtocolChecked(p); } void SignerResolvePage::Private::setCertificates(const QMap &certs) { certificates = certs; Q_FOREACH (const Protocol i, certs.keys()) { //krazy:exclude=foreach const Key key = certs.value(i); readOnlyProtocolSelectionWidget->setCertificate(i, key); signingProtocolSelectionWidget->setCertificate(i, key); } updateUi(); } void SignerResolvePage::Private::updateUi() { const bool ismutable = protocolSelectionActuallyUserMutable(); readOnlyProtocolSelectionWidget->setVisible(!ismutable); signingProtocolSelectionWidget->setVisible(ismutable); q->setExplanation(validator->explanation()); Q_EMIT q->completeChanged(); const QString customTitle = validator->customWindowTitle(); if (!customTitle.isEmpty()) { Q_EMIT q->windowTitleChanged(customTitle); } selectCertificatesButton->setEnabled(signingProtocolSelectionWidget->checkedProtocols().size() > 0); } void SignerResolvePage::setProtocolSelectionUserMutable(bool ismutable) { if (d->protocolSelectionUserMutable == ismutable) { return; } d->protocolSelectionUserMutable = ismutable; d->updateModeSelectionWidgets(); } bool SignerResolvePage::protocolSelectionUserMutable() const { return d->protocolSelectionUserMutable; } void SignerResolvePage::setMultipleProtocolsAllowed(bool allowed) { if (d->multipleProtocolsAllowed == allowed) { return; } d->multipleProtocolsAllowed = allowed; d->updateModeSelectionWidgets(); } bool SignerResolvePage::multipleProtocolsAllowed() const { return d->multipleProtocolsAllowed; } void SignerResolvePage::Private::updateModeSelectionWidgets() { const bool bothMutable = signingMutable && encryptionMutable; const bool noSigningPossible = !signingSelected && !signingMutable; const bool noEncryptionPossible = !encryptionSelected && !encryptionMutable; signAndEncryptRB->setChecked(signingSelected && encryptionSelected); signOnlyRB->setChecked(signingSelected && !encryptionSelected); encryptOnlyRB->setChecked(encryptionSelected && !signingSelected); const bool canSignAndEncrypt = !noSigningPossible && !noEncryptionPossible && bothMutable && presetProtocols != std::vector(1, CMS); const bool canSignOnly = !encryptionSelected || encryptionMutable; const bool canEncryptOnly = !signingSelected || signingMutable; signAndEncryptRB->setEnabled(canSignAndEncrypt); signOnlyRB->setEnabled(canSignOnly); encryptOnlyRB->setEnabled(canEncryptOnly); const bool buttonsVisible = signingMutable || encryptionMutable; signOnlyRB->setVisible(buttonsVisible); encryptOnlyRB->setVisible(buttonsVisible); signAndEncryptRB->setVisible(buttonsVisible); signingProtocolSelectionWidget->setExclusive(!multipleProtocolsAllowed); signingCertificateBox->setVisible(!noSigningPossible); encryptBox->setVisible(!noEncryptionPossible); updateUi(); } void SignerResolvePage::Private::selectCertificates() { QPointer dlg = new SigningCertificateSelectionDialog(q); dlg->setAllowedProtocols(QVector::fromStdVector(signingProtocolSelectionWidget->checkedProtocols())); if (dlg->exec() == QDialog::Accepted && dlg) { const QMap certs = dlg->selectedCertificates(); setCertificates(certs); if (signingPreferences && dlg->rememberAsDefault()) { signingPreferences->setPreferredCertificate(OpenPGP, certs.value(OpenPGP)); signingPreferences->setPreferredCertificate(CMS, certs.value(CMS)); } } delete dlg; updateUi(); } void SignerResolvePage::Private::operationButtonClicked(int mode_) { const Operation op = static_cast(mode_); signingCertificateBox->setEnabled(op != EncryptOnly); encryptBox->setEnabled(op != SignOnly); if (op == SignAndEncrypt) { signingProtocolSelectionWidget->setProtocolChecked(CMS, false); readOnlyProtocolSelectionWidget->setProtocolChecked(CMS, false); signingProtocolSelectionWidget->setProtocolChecked(OpenPGP, true); readOnlyProtocolSelectionWidget->setProtocolChecked(OpenPGP, true); } updateUi(); } void SignerResolvePage::Private::setOperation(Operation op) { switch (op) { case SignOnly: signOnlyRB->click(); break; case EncryptOnly: encryptOnlyRB->click(); break; case SignAndEncrypt: signAndEncryptRB->click(); break; } } SignerResolvePage::Operation SignerResolvePage::operation() const { return operationFromFlags(signingSelected(), encryptionSelected()); } SignerResolvePage::SignerResolvePage(QWidget *parent, Qt::WindowFlags f) : WizardPage(parent, f), d(new Private(this)) { setTitle(i18n("Choose Operation to be Performed")); // setSubTitle( i18n( "TODO" ) ); setPresetProtocol(UnknownProtocol); d->setCertificates(QMap()); d->updateModeSelectionWidgets(); d->operationButtonClicked(EncryptOnly); } SignerResolvePage::~SignerResolvePage() {} void SignerResolvePage::setSignersAndCandidates(const std::vector &signers, const std::vector< std::vector > &keys) { kleo_assert(signers.empty() || signers.size() == keys.size()); switch (signers.size()) { case 0: d->signerLabelLabel->setVisible(false); d->signerLabel->setVisible(false); // TODO: use default identity? break; case 1: d->signerLabelLabel->setVisible(true); d->signerLabel->setVisible(true); // TODO: use default identity? d->signerLabel->setText(signers.front().prettyAddress()); break; default: // > 1 kleo_assert(!"Resolving multiple signers not implemented"); } d->updateUi(); } void SignerResolvePage::setPresetProtocol(Protocol protocol) { std::vector protocols; if (protocol != CMS) { protocols.push_back(OpenPGP); } if (protocol != OpenPGP) { protocols.push_back(CMS); } setPresetProtocols(protocols); d->updateUi(); } void SignerResolvePage::setPresetProtocols(const std::vector &protocols) { d->presetProtocols = protocols; Q_FOREACH (const Protocol i, supportedProtocols()) { //krazy:exclude=foreach const bool checked = std::find(protocols.begin(), protocols.end(), i) != protocols.end(); d->signingProtocolSelectionWidget->setProtocolChecked(i, checked); d->readOnlyProtocolSelectionWidget->setProtocolChecked(i, checked); } d->updateModeSelectionWidgets(); } std::vector SignerResolvePage::selectedProtocols() const { return d->signingProtocolSelectionWidget->checkedProtocols(); } std::vector SignerResolvePage::signingCertificates(Protocol protocol) const { std::vector result; if (protocol != CMS && d->signingProtocolSelectionWidget->isProtocolChecked(OpenPGP) && !d->certificates[OpenPGP].isNull()) { result.push_back(d->certificates[OpenPGP]); } if (protocol != OpenPGP && d->signingProtocolSelectionWidget->isProtocolChecked(CMS) && !d->certificates[CMS].isNull()) { result.push_back(d->certificates[CMS]); } return result; } std::vector SignerResolvePage::resolvedSigners() const { std::vector result = signingCertificates(CMS); const std::vector pgp = signingCertificates(OpenPGP); result.insert(result.end(), pgp.begin(), pgp.end()); return result; } bool SignerResolvePage::isComplete() const { Q_ASSERT(d->validator); return d->validator->isComplete(); } bool SignerResolvePage::encryptionSelected() const { return !d->signOnlyRB->isChecked(); } void SignerResolvePage::setEncryptionSelected(bool selected) { d->encryptionSelected = selected; d->updateModeSelectionWidgets(); d->setOperation(operationFromFlags(d->signingSelected, d->encryptionSelected)); } bool SignerResolvePage::signingSelected() const { return !d->encryptOnlyRB->isChecked(); } void SignerResolvePage::setSigningSelected(bool selected) { d->signingSelected = selected; d->updateModeSelectionWidgets(); d->setOperation(operationFromFlags(d->signingSelected, d->encryptionSelected)); } bool SignerResolvePage::isEncryptionUserMutable() const { return d->encryptionMutable; } bool SignerResolvePage::isSigningUserMutable() const { return d->signingMutable; } void SignerResolvePage::setEncryptionUserMutable(bool ismutable) { d->encryptionMutable = ismutable; d->updateModeSelectionWidgets(); } void SignerResolvePage::setSigningUserMutable(bool ismutable) { d->signingMutable = ismutable; d->updateModeSelectionWidgets(); } std::vector SignerResolvePage::selectedProtocolsWithoutSigningCertificate() const { std::vector res; Q_FOREACH (const Protocol i, selectedProtocols()) //krazy:exclude=foreach if (signingCertificates(i).empty()) { res.push_back(i); } return res; } bool SignerResolvePage::isAsciiArmorEnabled() const { return d->textArmorCO->isChecked(); } void SignerResolvePage::setAsciiArmorEnabled(bool enabled) { d->textArmorCO->setChecked(enabled); } void SignerResolvePage::setSigningPreferences(const std::shared_ptr &prefs) { d->signingPreferences = prefs; QMap map; map[OpenPGP] = prefs ? prefs->preferredCertificate(OpenPGP) : Key(); map[CMS] = prefs ? prefs->preferredCertificate(CMS) : Key(); d->setCertificates(map); } std::shared_ptr SignerResolvePage::signingPreferences() const { return d->signingPreferences; } void SignerResolvePage::onNext() { } #include "moc_signerresolvepage.cpp" #include "moc_signerresolvepage_p.cpp" diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index 9a35fdaae..7764684a3 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,704 +1,703 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencryptfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2017 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfilescontroller.h" #include "signencrypttask.h" #include "certificateresolver.h" #include "crypto/gui/signencryptfileswizard.h" #include "crypto/taskcollection.h" #include "fileoperationspreferences.h" #include "utils/input.h" #include "utils/output.h" #include "utils/kleo_assert.h" #include "utils/archivedefinition.h" #include "utils/path-helper.h" #include #include -#include #include #include "kleopatra_debug.h" #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; using namespace KMime::Types; class SignEncryptFilesController::Private { friend class ::Kleo::Crypto::SignEncryptFilesController; SignEncryptFilesController *const q; public: explicit Private(SignEncryptFilesController *qq); ~Private(); private: void slotWizardOperationPrepared(); void slotWizardCanceled(); private: void ensureWizardCreated(); void ensureWizardVisible(); void updateWizardMode(); void cancelAllTasks(); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void schedule(); std::shared_ptr takeRunnable(GpgME::Protocol proto); static void assertValidOperation(unsigned int); static QString titleForOperation(unsigned int op); private: std::vector< std::shared_ptr > runnable, completed; std::shared_ptr cms, openpgp; QPointer wizard; QStringList files; unsigned int operation; Protocol protocol; }; SignEncryptFilesController::Private::Private(SignEncryptFilesController *qq) : q(qq), runnable(), cms(), openpgp(), wizard(), files(), operation(SignAllowed | EncryptAllowed | ArchiveAllowed), protocol(UnknownProtocol) { } SignEncryptFilesController::Private::~Private() { qCDebug(KLEOPATRA_LOG); } QString SignEncryptFilesController::Private::titleForOperation(unsigned int op) { const bool signDisallowed = (op & SignMask) == SignDisallowed; const bool encryptDisallowed = (op & EncryptMask) == EncryptDisallowed; const bool archiveSelected = (op & ArchiveMask) == ArchiveForced; kleo_assert(!signDisallowed || !encryptDisallowed); if (!signDisallowed && encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Sign Files"); } else { return i18n("Sign Files"); } } if (signDisallowed && !encryptDisallowed) { if (archiveSelected) { return i18n("Archive and Encrypt Files"); } else { return i18n("Encrypt Files"); } } if (archiveSelected) { return i18n("Archive and Sign/Encrypt Files"); } else { return i18n("Sign/Encrypt Files"); } } SignEncryptFilesController::SignEncryptFilesController(QObject *p) : Controller(p), d(new Private(this)) { } SignEncryptFilesController::SignEncryptFilesController(const std::shared_ptr &ctx, QObject *p) : Controller(ctx, p), d(new Private(this)) { } SignEncryptFilesController::~SignEncryptFilesController() { qCDebug(KLEOPATRA_LOG); if (d->wizard && !d->wizard->isVisible()) { delete d->wizard; } //d->wizard->close(); ### ? } void SignEncryptFilesController::setProtocol(Protocol proto) { kleo_assert(d->protocol == UnknownProtocol || d->protocol == proto); d->protocol = proto; d->ensureWizardCreated(); } Protocol SignEncryptFilesController::protocol() const { return d->protocol; } // static void SignEncryptFilesController::Private::assertValidOperation(unsigned int op) { kleo_assert((op & SignMask) == SignDisallowed || (op & SignMask) == SignAllowed || (op & SignMask) == SignSelected); kleo_assert((op & EncryptMask) == EncryptDisallowed || (op & EncryptMask) == EncryptAllowed || (op & EncryptMask) == EncryptSelected); kleo_assert((op & ArchiveMask) == ArchiveDisallowed || (op & ArchiveMask) == ArchiveAllowed || (op & ArchiveMask) == ArchiveForced); kleo_assert((op & ~(SignMask | EncryptMask | ArchiveMask)) == 0); } void SignEncryptFilesController::setOperationMode(unsigned int mode) { Private::assertValidOperation(mode); d->operation = mode; d->updateWizardMode(); } void SignEncryptFilesController::Private::updateWizardMode() { if (!wizard) { return; } wizard->setWindowTitle(titleForOperation(operation)); const unsigned int signOp = (operation & SignMask); const unsigned int encrOp = (operation & EncryptMask); const unsigned int archOp = (operation & ArchiveMask); if (signOp == SignDisallowed) { wizard->setSigningUserMutable(false); wizard->setSigningPreset(false); } else { wizard->setSigningUserMutable(true); wizard->setSigningPreset(signOp == SignSelected); } if (encrOp == EncryptDisallowed) { wizard->setEncryptionPreset(false); wizard->setEncryptionUserMutable(false); } else { wizard->setEncryptionUserMutable(true); wizard->setEncryptionPreset(false); wizard->setEncryptionPreset(encrOp == EncryptSelected); } wizard->setArchiveForced(archOp == ArchiveForced); wizard->setArchiveMutable(archOp == ArchiveAllowed); } unsigned int SignEncryptFilesController::operationMode() const { return d->operation; } static const char *extension(bool pgp, bool sign, bool encrypt, bool ascii, bool detached) { unsigned int cls = pgp ? Class::OpenPGP : Class::CMS; if (encrypt) { cls |= Class::CipherText; } else if (sign) { cls |= detached ? Class::DetachedSignature : Class::OpaqueSignature; } cls |= ascii ? Class::Ascii : Class::Binary; const bool usePGPFileExt = FileOperationsPreferences().usePGPFileExt(); if (const char *const ext = outputFileExtension(cls, usePGPFileExt)) { return ext; } else { return "out"; } } static std::shared_ptr getDefaultAd() { std::vector > ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr ad = ads.front(); const FileOperationsPreferences prefs; Q_FOREACH (const std::shared_ptr toCheck, ads) { if (toCheck->id() == prefs.archiveCommand()) { ad = toCheck; break; } } return ad; } static QMap buildOutputNames(const QStringList &files, const bool archive) { QMap nameMap; // Build the default names for the wizard. QString baseNameCms; QString baseNamePgp; const QFileInfo firstFile(files.first()); if (archive) { QString baseName; baseName = QDir(heuristicBaseDirectory(files)).absoluteFilePath(files.size() > 1 ? i18nc("base name of an archive file, e.g. archive.zip or archive.tar.gz", "archive") : firstFile.baseName()); const auto ad = getDefaultAd(); baseNamePgp = baseName + QLatin1Char('.') + ad->extensions(GpgME::OpenPGP).first() + QLatin1Char('.'); baseNameCms = baseName + QLatin1Char('.') + ad->extensions(GpgME::CMS).first() + QLatin1Char('.'); } else { baseNameCms = baseNamePgp = files.first() + QLatin1Char('.'); } const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); nameMap.insert(SignEncryptFilesWizard::SignatureCMS, baseNameCms + QString::fromLatin1(extension(false, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::EncryptedCMS, baseNameCms + QString::fromLatin1(extension(false, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::CombinedPGP, baseNamePgp + QString::fromLatin1(extension(true, true, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::EncryptedPGP, baseNamePgp + QString::fromLatin1(extension(true, false, true, ascii, false))); nameMap.insert(SignEncryptFilesWizard::SignaturePGP, baseNamePgp + QString::fromLatin1(extension(true, true, false, ascii, true))); nameMap.insert(SignEncryptFilesWizard::Directory, heuristicBaseDirectory(files)); return nameMap; } static QMap buildOutputNamesForDir(const QString &file, const QMap &orig) { QMap ret; const QString dir = orig.value(SignEncryptFilesWizard::Directory); if (dir.isEmpty()) { return orig; } // Build the default names for the wizard. const QFileInfo fi(file); const QString baseName = dir + QLatin1Char('/') + fi.fileName() + QLatin1Char('.'); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); ret.insert(SignEncryptFilesWizard::SignatureCMS, baseName + QString::fromLatin1(extension(false, true, false, ascii, true))); ret.insert(SignEncryptFilesWizard::EncryptedCMS, baseName + QString::fromLatin1(extension(false, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::CombinedPGP, baseName + QString::fromLatin1(extension(true, true, true, ascii, false))); ret.insert(SignEncryptFilesWizard::EncryptedPGP, baseName + QString::fromLatin1(extension(true, false, true, ascii, false))); ret.insert(SignEncryptFilesWizard::SignaturePGP, baseName + QString::fromLatin1(extension(true, true, false, ascii, true))); return ret; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = files; bool archive = false; if (files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } for (const auto &file: files) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); d->wizard->setOutputNames(buildOutputNames(files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); reportError(gpg_error(GPG_ERR_CANCELED), i18n("User cancel")); } void SignEncryptFilesController::start() { d->ensureWizardVisible(); } static std::shared_ptr createSignEncryptTaskForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &recipients, const std::vector &signers, const QString &outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(true); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); task->setDetachedSignature(false); } else { task->setEncrypt(false); } task->setEncryptSymmetric(symmetric); const QString input = fi.absoluteFilePath(); task->setInputFileName(input); task->setInput(Input::createFromFile(input)); task->setOutputFileName(outputName); return task; } static std::shared_ptr createArchiveSignEncryptTaskForFiles(const QStringList &files, const std::shared_ptr &ad, bool pgp, bool ascii, const std::vector &recipients, const std::vector &signers, const QString& outputName, bool symmetric) { const std::shared_ptr task(new SignEncryptTask); task->setEncryptSymmetric(symmetric); Q_ASSERT(!signers.empty() || !recipients.empty() || symmetric); task->setAsciiArmor(ascii); if (!signers.empty()) { task->setSign(true); task->setSigners(signers); task->setDetachedSignature(false); } else { task->setSign(false); } if (!recipients.empty()) { task->setEncrypt(true); task->setRecipients(recipients); } else { task->setEncrypt(false); } kleo_assert(ad); const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); task->setInput(ad->createInputFromPackCommand(proto, files)); task->setOutputFileName(outputName); return task; } static std::vector< std::shared_ptr > createSignEncryptTasksForFileInfo(const QFileInfo &fi, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { // Symmetric encryption is only supported for PGP int outKind = 0; if ((!pgpRecipients.empty() || symmetric)&& !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { // There is no combined sign / encrypt in gpgsm so we create one sign task // and one encrypt task. Which leaves us with the age old dilemma, encrypt // then sign, or sign then encrypt. Ugly. if (!cmsSigners.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createSignEncryptTaskForFileInfo(fi, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } static std::vector< std::shared_ptr > createArchiveSignEncryptTasksForFiles(const QStringList &files, const std::shared_ptr &ad, bool ascii, const std::vector &pgpRecipients, const std::vector &pgpSigners, const std::vector &cmsRecipients, const std::vector &cmsSigners, const QMap &outputNames, bool symmetric) { std::vector< std::shared_ptr > result; const bool pgp = !pgpSigners.empty() || !pgpRecipients.empty(); const bool cms = !cmsSigners.empty() || !cmsRecipients.empty(); result.reserve(pgp + cms); if (pgp || symmetric) { int outKind = 0; if ((!pgpRecipients.empty() || symmetric) && !pgpSigners.empty()) { outKind = SignEncryptFilesWizard::CombinedPGP; } else if (!pgpRecipients.empty() || symmetric) { outKind = SignEncryptFilesWizard::EncryptedPGP; } else { outKind = SignEncryptFilesWizard::SignaturePGP; } result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, true, ascii, pgpRecipients, pgpSigners, outputNames[outKind], symmetric)); } if (cms) { if (!cmsSigners.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, std::vector(), cmsSigners, outputNames[SignEncryptFilesWizard::SignatureCMS], false)); } if (!cmsRecipients.empty()) { result.push_back(createArchiveSignEncryptTaskForFiles(files, ad, false, ascii, cmsRecipients, std::vector(), outputNames[SignEncryptFilesWizard::EncryptedCMS], false)); } } return result; } void SignEncryptFilesController::Private::slotWizardOperationPrepared() { try { kleo_assert(wizard); kleo_assert(!files.empty()); const bool archive = (wizard->outputNames().value(SignEncryptFilesWizard::Directory).isNull() && files.size() > 1) || ((operation & ArchiveMask) == ArchiveForced); const QVector recipients = wizard->resolvedRecipients(); const QVector signers = wizard->resolvedSigners(); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); QVector pgpRecipients, cmsRecipients, pgpSigners, cmsSigners; Q_FOREACH (const Key &k, recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients << k; } else { cmsRecipients << k; } } Q_FOREACH (const Key &k, signers) { if (k.protocol() == GpgME::OpenPGP) { pgpSigners << k; } else { cmsSigners << k; } } std::vector< std::shared_ptr > tasks; if (!archive) { tasks.reserve(files.size()); } if (archive) { tasks = createArchiveSignEncryptTasksForFiles(files, getDefaultAd(), ascii, pgpRecipients.toStdVector(), pgpSigners.toStdVector(), cmsRecipients.toStdVector(), cmsSigners.toStdVector(), wizard->outputNames(), wizard->encryptSymmetric()); } else { Q_FOREACH (const QString &file, files) { const std::vector< std::shared_ptr > created = createSignEncryptTasksForFileInfo(QFileInfo(file), ascii, pgpRecipients.toStdVector(), pgpSigners.toStdVector(), cmsRecipients.toStdVector(), cmsSigners.toStdVector(), buildOutputNamesForDir(file, wizard->outputNames()), wizard->encryptSymmetric()); tasks.insert(tasks.end(), created.begin(), created.end()); } } const std::shared_ptr overwritePolicy(new OverwritePolicy(wizard)); Q_FOREACH (const std::shared_ptr &i, tasks) { i->setOverwritePolicy(overwritePolicy); } kleo_assert(runnable.empty()); runnable.swap(tasks); for (const std::shared_ptr &task : qAsConst(runnable)) { q->connectTask(task); } std::shared_ptr coll(new TaskCollection); std::vector > tmp; std::copy(runnable.begin(), runnable.end(), std::back_inserter(tmp)); coll->setTasks(tmp); wizard->setTaskCollection(coll); QTimer::singleShot(0, q, SLOT(schedule())); } catch (const Kleo::Exception &e) { reportError(e.error().encodedError(), e.message()); } catch (const std::exception &e) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unexpected exception in SignEncryptFilesController::Private::slotWizardOperationPrepared: %1", QString::fromLocal8Bit(e.what()))); } catch (...) { reportError(gpg_error(GPG_ERR_UNEXPECTED), i18n("Caught unknown exception in SignEncryptFilesController::Private::slotWizardOperationPrepared")); } } void SignEncryptFilesController::Private::schedule() { if (!cms) if (const std::shared_ptr t = takeRunnable(CMS)) { t->start(); cms = t; } if (!openpgp) if (const std::shared_ptr t = takeRunnable(OpenPGP)) { t->start(); openpgp = t; } if (!cms && !openpgp) { kleo_assert(runnable.empty()); q->emitDoneOrError(); } } std::shared_ptr SignEncryptFilesController::Private::takeRunnable(GpgME::Protocol proto) { const auto it = std::find_if(runnable.begin(), runnable.end(), [proto](const std::shared_ptr &task) { return task->protocol() == proto; }); if (it == runnable.end()) { return std::shared_ptr(); } const std::shared_ptr result = *it; runnable.erase(it); return result; } void SignEncryptFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_UNUSED(result) Q_ASSERT(task); // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container if (task == d->cms.get()) { d->completed.push_back(d->cms); d->cms.reset(); } else if (task == d->openpgp.get()) { d->completed.push_back(d->openpgp); d->openpgp.reset(); } QTimer::singleShot(0, this, SLOT(schedule())); } void SignEncryptFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { if (d->wizard) { d->wizard->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void SignEncryptFilesController::Private::cancelAllTasks() { // we just kill all runnable tasks - this will not result in // signal emissions. runnable.clear(); // a cancel() will result in a call to if (cms) { cms->cancel(); } if (openpgp) { openpgp->cancel(); } } void SignEncryptFilesController::Private::ensureWizardCreated() { if (wizard) { return; } std::unique_ptr w(new SignEncryptFilesWizard); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(rejected()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); wizard = w.release(); updateWizardMode(); } void SignEncryptFilesController::Private::ensureWizardVisible() { ensureWizardCreated(); q->bringToForeground(wizard); } #include "moc_signencryptfilescontroller.cpp" diff --git a/src/crypto/task.cpp b/src/crypto/task.cpp index 165c541a7..1ab516537 100644 --- a/src/crypto/task.cpp +++ b/src/crypto/task.cpp @@ -1,236 +1,235 @@ /* -*- 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 "kleopatra_debug.h" #include #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(int code, const QString &details) : Task::Result(), m_code(code), m_details(details) {} QString overview() const override { return makeOverview(m_details); } QString details() const override { return QString(); } int errorCode() const override { return m_code; } QString errorString() const override { return m_details; } VisualCode code() const override { return NeutralError; } AuditLog auditLog() const override { return AuditLog(); } private: const int m_code; const QString m_details; }; } class Task::Private { friend class ::Kleo::Crypto::Task; Task *const q; public: explicit Private(Task *qq); private: QString m_progressLabel; 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_progressLabel(), 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(int code, const QString &details, const QString &label) { const std::shared_ptr t(new SimpleTask(label)); t->setResult(t->makeErrorResult(code, 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(); } QString Task::progressLabel() const { return d->m_progressLabel; } void Task::setProgress(const QString &label, int processed, int total) { d->m_progress = processed; d->m_totalProgress = total; d->m_progressLabel = label; Q_EMIT progress(label, processed, total, QPrivateSignal()); } void Task::start() { try { doStart(); } catch (const Kleo::Exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(int, e.error().encodedError()), Q_ARG(QString, e.message())); } catch (const GpgME::Exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(int, e.error().encodedError()), Q_ARG(QString, QString::fromLocal8Bit(e.what()))); } catch (const std::exception &e) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(int, makeGnuPGError(GPG_ERR_UNEXPECTED)), Q_ARG(QString, QString::fromLocal8Bit(e.what()))); } catch (...) { QMetaObject::invokeMethod(this, "emitError", Qt::QueuedConnection, Q_ARG(int, makeGnuPGError(GPG_ERR_UNEXPECTED)), Q_ARG(QString, i18n("Unknown exception in Task::start()"))); } Q_EMIT started(QPrivateSignal()); } void Task::emitError(int errCode, const QString &details) { emitResult(makeErrorResult(errCode, details)); } void Task::emitResult(const std::shared_ptr &r) { d->m_progress = d->m_totalProgress; Q_EMIT progress(progressLabel(), currentProgress(), totalProgress(), QPrivateSignal()); Q_EMIT result(r, QPrivateSignal()); } std::shared_ptr Task::makeErrorResult(int errCode, const QString &details) { return std::shared_ptr(new ErrorResult(errCode, details)); } class Task::Result::Private { public: Private() {} }; Task::Result::Result() : d(new Private()) {} Task::Result::~Result() {} bool Task::Result::hasError() const { return errorCode() != 0; } static QString image(const char *img) { // ### escape? return KIconLoader::global()->iconPath(QLatin1String(img), KIconLoader::Small); } QString Task::Result::makeOverview(const QString &msg) { return QLatin1String("") + 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" diff --git a/src/dialogs/exportdialog.cpp b/src/dialogs/exportdialog.cpp index aa5769937..ad82f1e74 100644 --- a/src/dialogs/exportdialog.cpp +++ b/src/dialogs/exportdialog.cpp @@ -1,228 +1,227 @@ /* 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 #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_EXPORT_FLAGS #endif 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(">")); 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'); 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'", QString::fromLatin1(err.asString()))); } 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); #ifdef GPGME_HAS_EXPORT_FLAGS job->setExportFlags(flags); #endif job->start(QStringList() << QLatin1String(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); #ifdef GPGME_HAS_EXPORT_FLAGS job->setExportFlags(flags); #endif job->start(QStringList() << QLatin1String(key.primaryFingerprint())); } GpgME::Key ExportWidget::key() const { return d->key; } ExportDialog::ExportDialog(QWidget *parent) : QDialog(parent), mWidget(new ExportWidget(this)) { KConfigGroup dialog(KSharedConfig::openConfig(), "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::openConfig(), "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(); } diff --git a/src/dialogs/selftestdialog.cpp b/src/dialogs/selftestdialog.cpp index 9d8cc7391..d8915a7a9 100644 --- a/src/dialogs/selftestdialog.cpp +++ b/src/dialogs/selftestdialog.cpp @@ -1,360 +1,359 @@ /* -*- mode: c++; c-basic-offset:4 -*- dialogs/selftestdialog.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 "selftestdialog.h" #include "ui_selftestdialog.h" #include #include #include #include #include #include #include #include "kleopatra_debug.h" -#include using namespace Kleo; using namespace Kleo::Dialogs; namespace { class Model : public QAbstractTableModel { Q_OBJECT public: explicit Model(QObject *parent = nullptr) : QAbstractTableModel(parent), m_tests() { } enum Column { TestName, TestResult, NumColumns }; const std::shared_ptr &fromModelIndex(const QModelIndex &idx) const { const unsigned int row = idx.row(); if (row < m_tests.size()) { return m_tests[row]; } static const std::shared_ptr null; return null; } int rowCount(const QModelIndex &idx) const override { return idx.isValid() ? 0 : m_tests.size(); } int columnCount(const QModelIndex &) const override { return NumColumns; } QVariant data(const QModelIndex &idx, int role) const override { const unsigned int row = idx.row(); if (idx.isValid() && row < m_tests.size()) switch (role) { case Qt::DisplayRole: case Qt::ToolTipRole: switch (idx.column()) { case TestName: return m_tests[row]->name(); case TestResult: return m_tests[row]->skipped() ? i18n("Skipped") : m_tests[row]->passed() ? i18n("Passed") : /* else */ m_tests[row]->shortError(); } break; case Qt::BackgroundRole: KColorScheme scheme(qApp->palette().currentColorGroup()); return (m_tests[row]->skipped() ? scheme.background(KColorScheme::NeutralBackground) : m_tests[row]->passed() ? scheme.background(KColorScheme::PositiveBackground) : scheme.background(KColorScheme::NegativeBackground)).color(); } return QVariant(); } QVariant headerData(int section, Qt::Orientation o, int role) const override { if (o == Qt::Horizontal && section >= 0 && section < NumColumns && role == Qt::DisplayRole) switch (section) { case TestName: return i18n("Test Name"); case TestResult: return i18n("Result"); } return QVariant(); } void clear() { if (m_tests.empty()) { return; } beginRemoveRows(QModelIndex(), 0, m_tests.size() - 1); m_tests.clear(); endRemoveRows(); } void append(const std::vector> &tests) { if (tests.empty()) { return; } beginInsertRows(QModelIndex(), m_tests.size(), m_tests.size() + tests.size()); m_tests.insert(m_tests.end(), tests.begin(), tests.end()); endInsertRows(); } void reloadData() { if (!m_tests.empty()) { Q_EMIT dataChanged(index(0, 0), index(m_tests.size() - 1, NumColumns - 1)); } } const std::shared_ptr &at(unsigned int idx) const { return m_tests.at(idx); } private: std::vector> m_tests; }; class Proxy : public QSortFilterProxyModel { Q_OBJECT public: explicit Proxy(QObject *parent = nullptr) : QSortFilterProxyModel(parent), m_showAll(true) { setDynamicSortFilter(true); } bool showAll() const { return m_showAll; } Q_SIGNALS: void showAllChanged(bool); public Q_SLOTS: void setShowAll(bool on) { if (on == m_showAll) { return; } m_showAll = on; invalidateFilter(); Q_EMIT showAllChanged(on); } private: bool filterAcceptsRow(int src_row, const QModelIndex &src_parent) const override { if (m_showAll) { return true; } if (const Model *const model = qobject_cast(sourceModel())) { if (!src_parent.isValid() && src_row >= 0 && src_row < model->rowCount(src_parent)) { if (const std::shared_ptr &t = model->at(src_row)) { return !t->passed(); } else { qCWarning(KLEOPATRA_LOG) << "NULL test??"; } } else { if (src_parent.isValid()) { qCWarning(KLEOPATRA_LOG) << "view asks for subitems!"; } else { qCWarning(KLEOPATRA_LOG) << "index " << src_row << " is out of range [" << 0 << "," << model->rowCount(src_parent) << "]"; } } } else { qCWarning(KLEOPATRA_LOG) << "expected a ::Model, got "; if (!sourceModel()) { qCWarning(KLEOPATRA_LOG) << "a null pointer"; } else { qCWarning(KLEOPATRA_LOG) << sourceModel()->metaObject()->className(); } } return false; } private: bool m_showAll; }; } class SelfTestDialog::Private { friend class ::Kleo::Dialogs::SelfTestDialog; SelfTestDialog *const q; public: explicit Private(SelfTestDialog *qq) : q(qq), model(q), proxy(q), ui(q) { proxy.setSourceModel(&model); ui.resultsTV->setModel(&proxy); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); connect(ui.resultsTV->selectionModel(), SIGNAL(selectionChanged(QItemSelection,QItemSelection)), q, SLOT(slotSelectionChanged())); connect(ui.showAllCB, &QAbstractButton::toggled, &proxy, &Proxy::setShowAll); } private: void slotSelectionChanged() { const int row = selectedRowIndex(); if (row < 0) { ui.detailsLB->setText(i18n("(select test first)")); ui.detailsGB->hide(); ui.proposedCorrectiveActionGB->hide(); } else { const std::shared_ptr &t = model.at(row); ui.detailsLB->setText(t->longError()); ui.detailsGB->setVisible(!t->passed()); const QString action = t->proposedFix(); ui.proposedCorrectiveActionGB->setVisible(!t->passed() && !action.isEmpty()); ui.proposedCorrectiveActionLB->setText(action); ui.doItPB->setVisible(!t->passed() && t->canFixAutomatically()); } } void slotDoItClicked() { if (const std::shared_ptr st = model.fromModelIndex(selectedRow())) if (st->fix()) { model.reloadData(); } } private: void updateColumnSizes() { ui.resultsTV->header()->resizeSections(QHeaderView::ResizeToContents); } private: QModelIndex selectedRow() const { const QItemSelectionModel *const ism = ui.resultsTV->selectionModel(); if (!ism) { return QModelIndex(); } const QModelIndexList mil = ism->selectedRows(); return mil.empty() ? QModelIndex() : proxy.mapToSource(mil.front()); } int selectedRowIndex() const { return selectedRow().row(); } private: Model model; Proxy proxy; struct UI : public Ui_SelfTestDialog { QPushButton *rerunPB; explicit UI(SelfTestDialog *qq) : Ui_SelfTestDialog(), rerunPB(new QPushButton(i18n("Rerun Tests"))) { setupUi(qq); buttonBox->addButton(rerunPB, QDialogButtonBox::ActionRole); buttonBox->button(QDialogButtonBox::Ok)->setText(i18n("Continue")); connect(rerunPB, &QAbstractButton::clicked, qq, &SelfTestDialog::updateRequested); } } ui; }; SelfTestDialog::SelfTestDialog(QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { setAutomaticMode(false); } SelfTestDialog::SelfTestDialog(const std::vector> &tests, QWidget *p, Qt::WindowFlags f) : QDialog(p, f), d(new Private(this)) { addSelfTests(tests); setAutomaticMode(false); } SelfTestDialog::~SelfTestDialog() {} void SelfTestDialog::clear() { d->model.clear(); } void SelfTestDialog::addSelfTest(const std::shared_ptr &test) { d->model.append(std::vector>(1, test)); d->updateColumnSizes(); } void SelfTestDialog::addSelfTests(const std::vector> &tests) { d->model.append(tests); d->updateColumnSizes(); } void SelfTestDialog::setRunAtStartUp(bool on) { d->ui.runAtStartUpCB->setChecked(on); } bool SelfTestDialog::runAtStartUp() const { return d->ui.runAtStartUpCB->isChecked(); } void SelfTestDialog::setAutomaticMode(bool automatic) { d->ui.buttonBox->button(QDialogButtonBox::Ok)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Cancel)->setVisible(automatic); d->ui.buttonBox->button(QDialogButtonBox::Close)->setVisible(!automatic); } #include "selftestdialog.moc" #include "moc_selftestdialog.cpp" diff --git a/src/smartcard/readerstatus.cpp b/src/smartcard/readerstatus.cpp index 1bc0e9290..8f13af059 100644 --- a/src/smartcard/readerstatus.cpp +++ b/src/smartcard/readerstatus.cpp @@ -1,1016 +1,1013 @@ /* -*- 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 "keypairinfo.h" #include #include #include #include #include #include #include -#include "kleopatra_debug.h" #include "openpgpcard.h" #include "netkeycard.h" #include "pivcard.h" #include #include #include #include #include #include #include -#include -#include #include #include #include #include #include #include #include "utils/kdtoolsglobal.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)) static const char *flags[] = { "NOCARD", "PRESENT", "ACTIVE", "USABLE", }; static_assert(sizeof flags / sizeof * flags == Card::_NumScdStates, ""); static const char *prettyFlags[] = { "NoCard", "CardPresent", "CardActive", "CardUsable", "CardError", }; static_assert(sizeof prettyFlags / sizeof * prettyFlags == Card::NumStates, ""); Q_DECLARE_METATYPE(GpgME::Error) namespace { static QDebug operator<<(QDebug s, const std::string &string) { return s << QString::fromStdString(string); } static QDebug operator<<(QDebug s, const GpgME::Error &err) { const bool oldSetting = s.autoInsertSpaces(); s.nospace() << err.asString() << " (code: " << err.code() << ", source: " << err.source() << ")"; s.setAutoInsertSpaces(oldSetting); return s.maybeSpace(); } static QDebug operator<<(QDebug s, const std::vector< std::pair > &v) { typedef std::pair 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); } 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; } } } template static std::unique_ptr gpgagent_transact(std::shared_ptr &gpgAgent, const char *command, std::unique_ptr transaction, Error &err) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << ")"; err = gpgAgent->assuanTransact(command, std::move(transaction)); if (err.code()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_transact(" << command << "): Error:" << err; if (err.code() >= GPG_ERR_ASS_GENERAL && err.code() <= GPG_ERR_ASS_UNKNOWN_INQUIRE) { qCDebug(KLEOPATRA_LOG) << "Assuan problem, killing context"; gpgAgent.reset(); } return std::unique_ptr(); } std::unique_ptr t = gpgAgent->takeLastAssuanTransaction(); return std::unique_ptr(dynamic_cast(t.release())); } static std::unique_ptr gpgagent_default_transact(std::shared_ptr &gpgAgent, const char *command, Error &err) { return gpgagent_transact(gpgAgent, command, std::unique_ptr(new DefaultAssuanTransaction), err); } static const std::string gpgagent_data(std::shared_ptr gpgAgent, const char *command, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, command, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): got" << QString::fromStdString(t->data()); return t->data(); } else { qCDebug(KLEOPATRA_LOG) << "gpgagent_data(" << command << "): t == NULL"; return std::string(); } } static const std::vector< std::pair > gpgagent_statuslines(std::shared_ptr gpgAgent, const char *what, Error &err) { const std::unique_ptr t = gpgagent_default_transact(gpgAgent, what, err); if (t.get()) { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): got" << t->statusLines(); return t->statusLines(); } else { qCDebug(KLEOPATRA_LOG) << "agent_getattr_status(" << what << "): t == NULL"; return std::vector >(); } } static const std::string gpgagent_status(const std::shared_ptr &gpgAgent, const char *what, Error &err) { const auto lines = gpgagent_statuslines (gpgAgent, what, err); // The status is only the last attribute // e.g. for SCD SERIALNO it would only be "SERIALNO" and for SCD GETATTR FOO // it would only be FOO const char *p = strrchr(what, ' '); const char *needle = (p + 1) ? (p + 1) : what; for (const auto &pair: lines) { if (pair.first == needle) { return pair.second; } } return std::string(); } static const std::string scd_getattr_status(std::shared_ptr &gpgAgent, const char *what, Error &err) { std::string cmd = "SCD GETATTR "; cmd += what; return gpgagent_status(gpgAgent, cmd.c_str(), err); } static std::vector getCardsAndApps(std::shared_ptr &gpgAgent, Error &err) { std::vector result; const std::string command = "SCD GETINFO all_active_apps"; const auto statusLines = gpgagent_statuslines(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); // 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); } } 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 = gpgagent_statuslines(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 = gpgagent_statuslines(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 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 const std::string get_manufacturer(std::shared_ptr &gpgAgent, Error &err) { // The result of SCD GETATTR MANUFACTURER is the manufacturer ID as unsigned number // optionally followed by the name of the manufacturer, e.g. // 6 Yubico // 65534 unmanaged S/N range const auto manufacturerIdAndName = scd_getattr_status(gpgAgent, "MANUFACTURER", err); if (err.code()) { if (err.code() == GPG_ERR_INV_NAME) { qCDebug(KLEOPATRA_LOG) << "get_manufacturer(): Querying for attribute MANUFACTURER not yet supported; needs GnuPG 2.2.21+"; } else { qCWarning(KLEOPATRA_LOG) << "Running SCD GETATTR MANUFACTURER failed:" << err; } return std::string(); } const auto startOfManufacturerName = manufacturerIdAndName.find(' '); if (startOfManufacturerName == std::string::npos) { // only ID without manufacturer name return "unknown"; } const auto manufacturerName = manufacturerIdAndName.substr(startOfManufacturerName + 1); return manufacturerName; } 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 (card->cardType() == "yubikey" && isOpenPGPCardSerialNumber(card->serialNumber()) && !displaySerialNumber.startsWith(QLatin1String("yk-"))) { // workaround for special Yubikey serial number being overwritten by OpenPGP card serial number (https://dev.gnupg.org/T5100) card->setDisplaySerialNumber( QLatin1String("yk-") + QString::fromStdString(card->serialNumber().substr(20, 8)).replace(leadingZeros, QString())); return; } 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 handle_openpgp_card(std::shared_ptr &ci, std::shared_ptr &gpg_agent) { Error err; auto pgpCard = new OpenPGPCard(*ci); pgpCard->setManufacturer(get_manufacturer(gpg_agent, err)); if (err.code()) { // fallback, e.g. if gpg does not yet support querying for the MANUFACTURER attribute pgpCard->setManufacturer(get_openpgp_card_manufacturer_from_serial_number(ci->serialNumber())); } const auto info = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err.code()) { ci->setStatus(Card::CardError); return; } pgpCard->setCardInfo(info); setDisplaySerialNumber(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 = gpgagent_statuslines(gpg_agent, command.c_str(), err); if (err) { qCWarning(KLEOPATRA_LOG) << "Running" << command << "failed:" << err; return; } for (const auto &pair: keyPairInfoLines) { if (pair.first == "KEYPAIRINFO") { const KeyPairInfo info = KeyPairInfo::fromStatusLine(pair.second); if (info.grip.empty()) { qCWarning(KLEOPATRA_LOG) << "Invalid KEYPAIRINFO status line" << QString::fromStdString(pair.second); continue; } pivCard->setKeyAlgorithm(keyRef, info.algorithm); } else { logUnexpectedStatusLine(pair, "readKeyPairInfoFromPIVCard()", command); } } } 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 = gpgagent_data(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 = gpgagent_statuslines(gpg_agent, "SCD LEARN --force", err); if (err) { ci->setStatus(Card::CardError); return; } pivCard->setCardInfo(info); setDisplaySerialNumber(pivCard, gpg_agent); for (const std::string &keyRef : PIVCard::supportedKeys()) { if (!pivCard->keyGrip(keyRef).empty()) { readKeyPairInfoFromPIVCard(keyRef, pivCard, gpg_agent); readCertificateFromPIVCard(keyRef, pivCard, gpg_agent); } } ci.reset(pivCard); } 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: ") + QString::fromUtf8(err.asString())); return; } if (ci->appVersion() != 3) { qCDebug(KLEOPATRA_LOG) << "not a NetKey v3 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: ") + QString::fromUtf8(err.asString())); 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); // check for keys to learn: const std::unique_ptr result = gpgagent_default_transact(gpg_agent, "SCD LEARN --keypairinfo", err); if (err.code() || !result.get()) { if (err) { ci->setErrorMsg(QString::fromLatin1(err.asString())); } else { ci->setErrorMsg(QStringLiteral("Invalid internal state. No result.")); } return; } const std::vector keyPairInfos = result->statusLine("KEYPAIRINFO"); if (keyPairInfos.empty()) { return; } nkCard->setKeyPairInfo(keyPairInfos); } 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()); // 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); } // 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); // 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); return ci; } else if (appName == OpenPGPCard::AppName) { qCDebug(KLEOPATRA_LOG) << "get_card_status: found OpenPGP card" << ci->serialNumber().c_str() << "end"; handle_openpgp_card(ci, gpg_agent); return ci; } 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); return ci; } else { qCDebug(KLEOPATRA_LOG) << "get_card_status: unhandled application:" << appName; return ci; } 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 = "SCD SERIALNO --all"; const std::string serialno = gpgagent_status(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, 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; const char *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 oneTransactionFinished(const GpgME::Error &err); public Q_SLOTS: 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); Q_FOREACH (const Transaction &t, ft) if (t.receiver && t.slot && *t.slot) { QMetaObject::invokeMethod(t.receiver, t.slot, Qt::DirectConnection, Q_ARG(GpgME::Error, err)); } } 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; 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)) { std::vector > newCards = update_cardinfo(gpgAgent); KDAB_SYNCHRONIZED(m_mutex) m_cardInfos = newCards; bool anyLC = false; std::string firstCardWithNullPin; bool anyError = false; 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); if (anyError) { gpgAgent.reset(); } } else { GpgME::Error err; switchCard(gpgAgent, cardApp.serialNumber, err); if (!err) { switchApp(gpgAgent, cardApp.serialNumber, cardApp.appName, err); } if (!err) { if (assuanTransaction) { (void)gpgagent_transact(gpgAgent, command.constData(), std::unique_ptr(assuanTransaction), err); } else { (void)gpgagent_default_transact(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"); watcher.whitelistFiles(QStringList(QStringLiteral("reader_*.status"))); watcher.addPath(Kleo::gnupgHomeDirectory()); watcher.setDelay(100); connect(this, &::ReaderStatusThread::cardAdded, q, &ReaderStatus::cardAdded); connect(this, &::ReaderStatusThread::cardChanged, q, &ReaderStatus::cardChanged); connect(this, &::ReaderStatusThread::cardRemoved, q, &ReaderStatus::cardRemoved); connect(this, &::ReaderStatusThread::firstCardWithNullPinChanged, q, &ReaderStatus::firstCardWithNullPinChanged); connect(this, &::ReaderStatusThread::anyCardCanLearnKeysChanged, q, &ReaderStatus::anyCardCanLearnKeysChanged); connect(&watcher, &FileSystemWatcher::triggered, this, &::ReaderStatusThread::ping); } ~Private() { 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; }; 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(); } // 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 char *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 char *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(); } 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(); } #include "readerstatus.moc" diff --git a/src/uiserver/assuanserverconnection.cpp b/src/uiserver/assuanserverconnection.cpp index 672a6f649..9f72b5797 100644 --- a/src/uiserver/assuanserverconnection.cpp +++ b/src/uiserver/assuanserverconnection.cpp @@ -1,1694 +1,1690 @@ /* -*- 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 "assuanserverconnection.h" #include "assuancommand.h" #include "sessiondata.h" #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 #include #include #include #include #ifdef __GLIBCXX__ # include // for is_sorted #endif #ifdef Q_OS_WIN32 # 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; #ifdef HAVE_ASSUAN2 static void my_assuan_release(assuan_context_t ctx) { if (ctx) { assuan_release(ctx); } } #endif // std::shared_ptr for assuan_context_t w/ deleter enforced to assuan_deinit_server: typedef std::shared_ptr::type> AssuanContextBase; struct AssuanContext : AssuanContextBase { AssuanContext() : AssuanContextBase() {} #ifndef HAVE_ASSUAN2 explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &assuan_deinit_server) {} #else explicit AssuanContext(assuan_context_t ctx) : AssuanContextBase(ctx, &my_assuan_release) {} #endif #ifndef HAVE_ASSUAN2 void reset(assuan_context_t ctx = 0) { AssuanContextBase::reset(ctx, &assuan_deinit_server); } #else void reset(assuan_context_t ctx = nullptr) { AssuanContextBase::reset(ctx, &my_assuan_release); } #endif }; 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 std::string &err_msg) { return assuan_process_done_msg(ctx, err, err_msg.c_str()); } 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; std::map::iterator 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< std::shared_ptr > &factories_, AssuanServerConnection *qq); ~Private(); Q_SIGNALS: void startKeyManager(); public Q_SLOTS: void slotReadActivity(int) { Q_ASSERT(ctx); #ifndef HAVE_ASSUAN2 if (const int err = assuan_process_next(ctx.get())) { #else int done = false; if (const int err = assuan_process_next(ctx.get(), &done) || done) { #endif //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_WIN32) 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: #ifndef HAVE_ASSUAN2 static void reset_handler(assuan_context_t ctx_) { #else static gpg_error_t reset_handler(assuan_context_t ctx_, char *) { #endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); conn.reset(); #ifdef HAVE_ASSUAN2 return 0; #endif } #ifndef HAVE_ASSUAN2 static int option_handler(assuan_context_t ctx_, const char *key, const char *value) { #else static gpg_error_t option_handler(assuan_context_t ctx_, const char *key, const char *value) { #endif 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 ); } #ifndef HAVE_ASSUAN2 static int session_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t session_handler(assuan_context_t ctx_, char *line) { #endif Q_ASSERT(assuan_get_pointer(ctx_)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx_)); const QString str = QString::fromUtf8(line); QRegExp rx(QLatin1String("(\\d+)(?:\\s+(.*))?")); if (!rx.exactMatch(str)) { 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 = rx.cap(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); } } if (!rx.cap(2).isEmpty()) { conn.sessionTitle = rx.cap(2); } qCDebug(KLEOPATRA_LOG) << "session_handler: " << "id=" << static_cast(conn.sessionId) << ", title=" << qPrintable(conn.sessionTitle); return assuan_process_done(ctx_, 0); } #ifndef HAVE_ASSUAN2 static int capabilities_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t capabilities_handler(assuan_context_t ctx_, char *line) { #endif 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)); } #ifndef HAVE_ASSUAN2 static int getinfo_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t getinfo_handler(assuan_context_t ctx_, char *line) { #endif 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())); } #ifndef HAVE_ASSUAN2 static int start_keymanager_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t start_keymanager_handler(assuan_context_t ctx_, char *line) { #endif 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); } #ifndef HAVE_ASSUAN2 static int start_confdialog_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t start_confdialog_handler(assuan_context_t ctx_, char *line) { #endif 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 #ifndef HAVE_ASSUAN2 static int IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { #else static gpg_error_t IO_handler(assuan_context_t ctx_, char *line_, T_memptr which) { #endif 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< typename Input_or_Output::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_WIN32) 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) { Output *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"); } } #ifndef HAVE_ASSUAN2 static int input_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t input_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::inputs); } #ifndef HAVE_ASSUAN2 static int output_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t output_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::outputs); } #ifndef HAVE_ASSUAN2 static int message_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t message_handler(assuan_context_t ctx, char *line) { #endif return IO_handler(ctx, line, &Private::messages); } #ifndef HAVE_ASSUAN2 static int file_handler(assuan_context_t ctx_, char *line) { #else static gpg_error_t file_handler(assuan_context_t ctx_, char *line) { #endif 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 #ifndef HAVE_ASSUAN2 static int recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { #else static gpg_error_t recipient_sender_handler(T_memptr mp, T_memptr2 info, assuan_context_t ctx, char *line, bool sender = false) { #endif 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); } } #ifndef HAVE_ASSUAN2 static int recipient_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t recipient_handler(assuan_context_t ctx, char *line) { #endif return recipient_sender_handler(&Private::recipients, &Private::informativeRecipients, ctx, line); } #ifndef HAVE_ASSUAN2 static int sender_handler(assuan_context_t ctx, char *line) { #else static gpg_error_t sender_handler(assuan_context_t ctx, char *line) { #endif return recipient_sender_handler(&Private::senders, &Private::informativeSenders, ctx, line, true); } QByteArray dumpOptions() const { QByteArray result; for (std::map::const_iterator 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 (std::map< QByteArray, std::shared_ptr >::const_iterator 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< std::shared_ptr > notifiers; std::vector< std::shared_ptr > factories; // sorted: _detail::ByName std::shared_ptr currentCommand; std::vector< std::shared_ptr > nohupedCommands; std::map options; std::vector senders, recipients; std::vector< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > outputs; std::vector files; std::map< QByteArray, std::shared_ptr > 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< std::shared_ptr > &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"); } #ifndef HAVE_ASSUAN2 assuan_context_t naked_ctx = 0; if (const gpg_error_t err = assuan_init_socket_server_ext(&naked_ctx, fd, INIT_SOCKET_FLAGS)) #else { 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)) #endif throw Exception(err, "assuan_init_socket_server_ext"); #ifndef HAVE_ASSUAN2 ctx.reset(naked_ctx); naked_ctx = 0; #endif // 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: #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "INPUT", input_handler, "")) #endif throw Exception(err, "register \"INPUT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "MESSAGE", message_handler, "")) #endif throw Exception(err, "register \"MESSAGE\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "OUTPUT", output_handler, "")) #endif throw Exception(err, "register \"OUTPUT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "FILE", file_handler, "")) #endif throw Exception(err, "register \"FILE\" handler"); // register user-defined commands: Q_FOREACH (std::shared_ptr fac, factories) #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler())) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), fac->name(), fac->_handler(), "")) #endif throw Exception(err, std::string("register \"") + fac->name() + "\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "GETINFO", getinfo_handler, "")) #endif throw Exception(err, "register \"GETINFO\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_KEYMANAGER", start_keymanager_handler, "")) #endif throw Exception(err, "register \"START_KEYMANAGER\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "START_CONFDIALOG", start_confdialog_handler, "")) #endif throw Exception(err, "register \"START_CONFDIALOG\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "RECIPIENT", recipient_handler, "")) #endif throw Exception(err, "register \"RECIPIENT\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SENDER", sender_handler, "")) #endif throw Exception(err, "register \"SENDER\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "SESSION", session_handler, "")) #endif throw Exception(err, "register \"SESSION\" handler"); #ifndef HAVE_ASSUAN2 if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler)) #else if (const gpg_error_t err = assuan_register_command(ctx.get(), "CAPABILITIES", capabilities_handler, "")) #endif 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< std::shared_ptr > &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: #if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) explicit InquiryHandler(const char *keyword_, QObject *p = nullptr) : QObject(p), # if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) buffer(0), buflen(0), # endif keyword(keyword_) { } # if defined(HAVE_ASSUAN2) || defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) # ifndef HAVE_ASSUAN2 static int handler(void *cb_data, int rc, unsigned char *buffer, size_t buflen) # else static gpg_error_t handler(void *cb_data, gpg_error_t rc, unsigned char *buffer, size_t buflen) # endif { Q_ASSERT(cb_data); InquiryHandler *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; } # else static int handler(void *cb_data, int rc) { Q_ASSERT(cb_data); InquiryHandler *this_ = static_cast(cb_data); Q_EMIT this_->signal(rc, QByteArray::fromRawData(reinterpret_cast(this_->buffer), this_->buflen), this_->keyword); std::free(this_->buffer); delete this_; return 0; } # endif private: #if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) friend class ::Kleo::AssuanCommand; unsigned char *buffer; size_t buflen; #endif const char *keyword; #endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) 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< std::shared_ptr > inputs, messages; std::vector< std::shared_ptr > 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 std::map::const_iterator 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< QByteArray, std::shared_ptr > &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 std::map< QByteArray, std::shared_ptr >::const_iterator it = sd->mementos.find(tag); if (it != sd->mementos.end()) { return it->second; } } const std::map< QByteArray, std::shared_ptr >::const_iterator 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< std::shared_ptr > &AssuanCommand::inputs() const { return d->inputs; } const std::vector< std::shared_ptr > &AssuanCommand::messages() const { return d->messages; } const std::vector< std::shared_ptr > &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); } #if defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) 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, # if !defined(HAVE_ASSUAN2) && !defined(HAVE_NEW_STYLE_ASSUAN_INQUIRE_EXT) &ih->buffer, &ih->buflen, # endif maxSize, InquiryHandler::handler, ih.get())) { return err; } ih.release(); return 0; #else return makeError(GPG_ERR_NOT_SUPPORTED); // libassuan too old #endif // defined(HAVE_ASSUAN2) || defined(HAVE_ASSUAN_INQUIRE_EXT) } 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) << err.asString() << ": called with NULL ctx."; return; } if (d->done) { qCDebug(KLEOPATRA_LOG) << err.asString() << ": 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; } #ifndef HAVE_ASSUAN2 int AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { #else gpg_error_t AssuanCommandFactory::_handle(assuan_context_t ctx, char *line, const char *commandName) { #endif Q_ASSERT(assuan_get_pointer(ctx)); AssuanServerConnection::Private &conn = *static_cast(assuan_get_pointer(ctx)); try { const std::vector< std::shared_ptr >::const_iterator 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 (std::map::const_iterator 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")) { return FileManager; } if (modeString == QLatin1String("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")) { return GpgME::OpenPGP; } if (protocolString == QLatin1String("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" diff --git a/src/utils/archivedefinition.cpp b/src/utils/archivedefinition.cpp index 933a5d04c..bd934b8ca 100644 --- a/src/utils/archivedefinition.cpp +++ b/src/utils/archivedefinition.cpp @@ -1,395 +1,393 @@ /* -*- 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 #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 INSTALLPATH_PLACEHOLDER("%I"); static const QLatin1String 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() {} 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])); 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__")) .replace(INSTALLPATH_PLACEHOLDER, QStringLiteral("__path_goes_here__")); l = KShell::splitArgs(cmdline, KShell::AbortOnMeta | KShell::TildeExpand, &errors); l = l.replaceInStrings(QStringLiteral("__files_go_here__"), FILE_PLACEHOLDER); if (l.indexOf(QRegExp(QLatin1String(".*__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]; 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() {} static QByteArray make_input(const QStringList &files, char sep) { QByteArray result; for (const QString &file : files) { result += QFile::encodeName(file); 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< std::shared_ptr > ArchiveDefinition::getArchiveDefinitions() { QStringList errors; return getArchiveDefinitions(errors); } // static std::vector< std::shared_ptr > ArchiveDefinition::getArchiveDefinitions(QStringList &errors) { std::vector< std::shared_ptr > 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/log.cpp b/src/utils/log.cpp index 2646c02d9..2d3e46316 100644 --- a/src/utils/log.cpp +++ b/src/utils/log.cpp @@ -1,152 +1,151 @@ /* -*- 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 -#include using namespace Kleo; class Log::Private { Log *const q; public: explicit Private(Log *qq) : q(qq), m_ioLoggingEnabled(false), m_logFile(nullptr) {} ~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"); 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/view/keytreeview.cpp b/src/view/keytreeview.cpp index b402c8d82..79f38a769 100644 --- a/src/view/keytreeview.cpp +++ b/src/view/keytreeview.cpp @@ -1,698 +1,697 @@ /* -*- mode: c++; c-basic-offset:4 -*- view/keytreeview.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 "keytreeview.h" #include #include #include #include #include "utils/headerview.h" #include "utils/remarks.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include -#include #include #include #if GPGMEPP_VERSION >= 0x10E00 // 1.14.0 # define GPGME_HAS_REMARKS #endif #define REMARK_COLUMN 13 using namespace Kleo; using namespace GpgME; Q_DECLARE_METATYPE(GpgME::Key) namespace { class TreeView : public QTreeView { public: explicit TreeView(QWidget *parent = nullptr) : QTreeView(parent) { header()->installEventFilter(this); } QSize minimumSizeHint() const override { const QSize min = QTreeView::minimumSizeHint(); return QSize(min.width(), min.height() + 5 * fontMetrics().height()); } protected: bool eventFilter(QObject *watched, QEvent *event) override { Q_UNUSED(watched); if (event->type() == QEvent::ContextMenu) { QContextMenuEvent *e = static_cast(event); if (!mHeaderPopup) { mHeaderPopup = new QMenu(this); mHeaderPopup->setTitle(i18n("View Columns")); for (int i = 0; i < model()->columnCount(); ++i) { QAction *tmp = mHeaderPopup->addAction(model()->headerData(i, Qt::Horizontal).toString()); tmp->setData(QVariant(i)); tmp->setCheckable(true); mColumnActions << tmp; } connect(mHeaderPopup, &QMenu::triggered, this, [this] (QAction *action) { const int col = action->data().toInt(); if (col == REMARK_COLUMN) { Remarks::enableRemarks(action->isChecked()); } if (action->isChecked()) { showColumn(col); } else { hideColumn(col); } KeyTreeView *tv = qobject_cast (parent()); if (tv) { tv->resizeColumns(); } }); } foreach (QAction *action, mColumnActions) { int column = action->data().toInt(); action->setChecked(!isColumnHidden(column)); } mHeaderPopup->popup(mapToGlobal(e->pos())); return true; } return false; } private: QMenu *mHeaderPopup = nullptr; QList mColumnActions; }; } // anon namespace KeyTreeView::KeyTreeView(QWidget *parent) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(nullptr), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(), m_keyFilter(), m_isHierarchical(true) { init(); } KeyTreeView::KeyTreeView(const KeyTreeView &other) : QWidget(nullptr), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(other.m_additionalProxy ? other.m_additionalProxy->clone() : nullptr), m_view(new TreeView(this)), m_flatModel(other.m_flatModel), m_hierarchicalModel(other.m_hierarchicalModel), m_stringFilter(other.m_stringFilter), m_keyFilter(other.m_keyFilter), m_group(other.m_group), m_isHierarchical(other.m_isHierarchical) { init(); setColumnSizes(other.columnSizes()); setSortColumn(other.sortColumn(), other.sortOrder()); } KeyTreeView::KeyTreeView(const QString &text, const std::shared_ptr &kf, AbstractKeyListSortFilterProxyModel *proxy, QWidget *parent, const KConfigGroup &group) : QWidget(parent), m_proxy(new KeyListSortFilterProxyModel(this)), m_additionalProxy(proxy), m_view(new TreeView(this)), m_flatModel(nullptr), m_hierarchicalModel(nullptr), m_stringFilter(text), m_keyFilter(kf), m_group(group), m_isHierarchical(true), m_onceResized(false) { init(); } void KeyTreeView::setColumnSizes(const std::vector &sizes) { if (sizes.empty()) { return; } Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (HeaderView *const hv = static_cast(m_view->header())) { hv->setSectionSizes(sizes); } } void KeyTreeView::setSortColumn(int sortColumn, Qt::SortOrder sortOrder) { Q_ASSERT(m_view); m_view->sortByColumn(sortColumn, sortOrder); } int KeyTreeView::sortColumn() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorSection(); } Qt::SortOrder KeyTreeView::sortOrder() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); return m_view->header()->sortIndicatorOrder(); } std::vector KeyTreeView::columnSizes() const { Q_ASSERT(m_view); Q_ASSERT(m_view->header()); Q_ASSERT(qobject_cast(m_view->header()) == static_cast(m_view->header())); if (HeaderView *const hv = static_cast(m_view->header())) { return hv->sectionSizes(); } else { return std::vector(); } } void KeyTreeView::init() { KDAB_SET_OBJECT_NAME(m_proxy); KDAB_SET_OBJECT_NAME(m_view); if (!m_group.isValid()) { m_group = KSharedConfig::openConfig()->group("KeyTreeView_default"); } else { // Reopen as non const KConfig *conf = m_group.config(); m_group = conf->group(m_group.name()); } if (m_additionalProxy && m_additionalProxy->objectName().isEmpty()) { KDAB_SET_OBJECT_NAME(m_additionalProxy); } QLayout *layout = new QVBoxLayout(this); KDAB_SET_OBJECT_NAME(layout); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(m_view); HeaderView *headerView = new HeaderView(Qt::Horizontal); KDAB_SET_OBJECT_NAME(headerView); headerView->installEventFilter(m_view); headerView->setSectionsMovable(true); m_view->setHeader(headerView); m_view->setSelectionBehavior(QAbstractItemView::SelectRows); m_view->setSelectionMode(QAbstractItemView::ExtendedSelection); //m_view->setAlternatingRowColors( true ); m_view->setAllColumnsShowFocus(true); m_view->setSortingEnabled(true); if (model()) { if (m_additionalProxy) { m_additionalProxy->setSourceModel(model()); } else { m_proxy->setSourceModel(model()); } } if (m_additionalProxy) { m_proxy->setSourceModel(m_additionalProxy); if (!m_additionalProxy->parent()) { m_additionalProxy->setParent(this); } } m_proxy->setFilterFixedString(m_stringFilter); m_proxy->setKeyFilter(m_keyFilter); m_proxy->setSortCaseSensitivity(Qt::CaseInsensitive); KeyRearrangeColumnsProxyModel *rearangingModel = new KeyRearrangeColumnsProxyModel(this); rearangingModel->setSourceModel(m_proxy); rearangingModel->setSourceColumns(QVector() << KeyListModelInterface::PrettyName << KeyListModelInterface::PrettyEMail << KeyListModelInterface::Validity << KeyListModelInterface::ValidFrom << KeyListModelInterface::ValidUntil << KeyListModelInterface::TechnicalDetails << KeyListModelInterface::KeyID << KeyListModelInterface::Fingerprint << KeyListModelInterface::OwnerTrust << KeyListModelInterface::Origin << KeyListModelInterface::LastUpdate << KeyListModelInterface::Issuer << KeyListModelInterface::SerialNumber #ifdef GPGME_HAS_REMARKS // If a column is added before this REMARK_COLUMN define has to be updated accordingly << KeyListModelInterface::Remarks #endif ); m_view->setModel(rearangingModel); /* Handle expansion state */ m_expandedKeys = m_group.readEntry("Expanded", QStringList()); connect(m_view, &QTreeView::expanded, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); const auto fpr = QString::fromLatin1(key.primaryFingerprint()); if (m_expandedKeys.contains(fpr)) { return; } m_expandedKeys << fpr; m_group.writeEntry("Expanded", m_expandedKeys); }); connect(m_view, &QTreeView::collapsed, this, [this] (const QModelIndex &index) { if (!index.isValid()) { return; } const auto &key = index.data(Kleo::KeyListModelInterface::KeyRole).value(); m_expandedKeys.removeAll(QString::fromLatin1(key.primaryFingerprint())); m_group.writeEntry("Expanded", m_expandedKeys); }); connect(KeyCache::instance().get(), &KeyCache::keysMayHaveChanged, this, [this] () { /* We use a single shot timer here to ensure that the keysMayHaveChanged * handlers are all handled before we restore the expand state so that * the model is already populated. */ QTimer::singleShot(0, [this] () { restoreExpandState(); setupRemarkKeys(); if (!m_onceResized) { m_onceResized = true; resizeColumns(); } }); }); resizeColumns(); restoreLayout(); } void KeyTreeView::restoreExpandState() { if (!KeyCache::instance()->initialized()) { qCWarning(KLEOPATRA_LOG) << "Restore expand state before keycache available. Aborting."; return; } for (const auto &fpr: qAsConst(m_expandedKeys)) { const KeyListModelInterface *km = dynamic_cast (m_view->model()); if (!km) { qCWarning(KLEOPATRA_LOG) << "invalid model"; return; } const auto key = KeyCache::instance()->findByFingerprint(fpr.toLatin1().constData()); if (key.isNull()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in cache"; m_expandedKeys.removeAll(fpr); return; } const auto idx = km->index(key); if (!idx.isValid()) { qCDebug(KLEOPATRA_LOG) << "Cannot find:" << fpr << "anymore in model"; m_expandedKeys.removeAll(fpr); return; } m_view->expand(idx); } } void KeyTreeView::setupRemarkKeys() { #ifdef GPGME_HAS_REMARKS const auto remarkKeys = Remarks::remarkKeys(); if (m_hierarchicalModel) { m_hierarchicalModel->setRemarkKeys(remarkKeys); } if (m_flatModel) { m_flatModel->setRemarkKeys(remarkKeys); } #endif } void KeyTreeView::saveLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility; QVariantList columnOrder; QVariantList columnWidths; const int headerCount = header->count(); columnVisibility.reserve(headerCount); columnWidths.reserve(headerCount); columnOrder.reserve(headerCount); for (int i = 0; i < headerCount; ++i) { columnVisibility << QVariant(!m_view->isColumnHidden(i)); columnWidths << QVariant(header->sectionSize(i)); columnOrder << QVariant(header->visualIndex(i)); } m_group.writeEntry("ColumnVisibility", columnVisibility); m_group.writeEntry("ColumnOrder", columnOrder); m_group.writeEntry("ColumnWidths", columnWidths); m_group.writeEntry("SortAscending", (int)header->sortIndicatorOrder()); if (header->isSortIndicatorShown()) { m_group.writeEntry("SortColumn", header->sortIndicatorSection()); } else { m_group.writeEntry("SortColumn", -1); } } void KeyTreeView::restoreLayout() { QHeaderView *header = m_view->header(); QVariantList columnVisibility = m_group.readEntry("ColumnVisibility", QVariantList()); QVariantList columnOrder = m_group.readEntry("ColumnOrder", QVariantList()); QVariantList columnWidths = m_group.readEntry("ColumnWidths", QVariantList()); if (columnVisibility.isEmpty()) { // if config is empty then use default settings // The numbers have to be in line with the order in // setsSourceColumns above m_view->hideColumn(5); for (int i = 7; i < m_view->model()->columnCount(); ++i) { m_view->hideColumn(i); } if (KeyCache::instance()->initialized()) { QTimer::singleShot(0, this, &KeyTreeView::resizeColumns); } } else { for (int i = 0; i < header->count(); ++i) { if (i >= columnOrder.size() || i >= columnWidths.size() || i >= columnVisibility.size()) { // An additional column that was not around last time we saved. // We default to hidden. m_view->hideColumn(i); continue; } bool visible = columnVisibility[i].toBool(); int width = columnWidths[i].toInt(); int order = columnOrder[i].toInt(); header->resizeSection(i, width ? width : 100); header->moveSection(header->visualIndex(i), order); if (i == REMARK_COLUMN) { Remarks::enableRemarks(visible); } if (!visible) { m_view->hideColumn(i); } } m_onceResized = true; } int sortOrder = m_group.readEntry("SortAscending", (int)Qt::AscendingOrder); int sortColumn = m_group.readEntry("SortColumn", -1); if (sortColumn >= 0) { m_view->sortByColumn(sortColumn, (Qt::SortOrder)sortOrder); } } KeyTreeView::~KeyTreeView() { saveLayout(); } static QAbstractProxyModel *find_last_proxy(QAbstractProxyModel *pm) { Q_ASSERT(pm); while (QAbstractProxyModel *const sm = qobject_cast(pm->sourceModel())) { pm = sm; } return pm; } void KeyTreeView::setFlatModel(AbstractKeyListModel *model) { if (model == m_flatModel) { return; } m_flatModel = model; if (!m_isHierarchical) // TODO: this fails when called after setHierarchicalView( false )... { find_last_proxy(m_proxy)->setSourceModel(model); } } void KeyTreeView::setHierarchicalModel(AbstractKeyListModel *model) { if (model == m_hierarchicalModel) { return; } m_hierarchicalModel = model; if (m_isHierarchical) { find_last_proxy(m_proxy)->setSourceModel(model); m_view->expandAll(); for (int column = 0; column < m_view->header()->count(); ++column) { m_view->header()->resizeSection(column, qMax(m_view->header()->sectionSize(column), m_view->header()->sectionSizeHint(column))); } } } void KeyTreeView::setStringFilter(const QString &filter) { if (filter == m_stringFilter) { return; } m_stringFilter = filter; m_proxy->setFilterFixedString(filter); Q_EMIT stringFilterChanged(filter); } void KeyTreeView::setKeyFilter(const std::shared_ptr &filter) { if (filter == m_keyFilter || (filter && m_keyFilter && filter->id() == m_keyFilter->id())) { return; } m_keyFilter = filter; m_proxy->setKeyFilter(filter); Q_EMIT keyFilterChanged(filter); } static QItemSelection itemSelectionFromKeys(const std::vector &keys, const KeyListSortFilterProxyModel &proxy) { QItemSelection result; for (const Key &key : keys) { const QModelIndex mi = proxy.index(key); if (mi.isValid()) { result.merge(QItemSelection(mi, mi), QItemSelectionModel::Select); } } return result; } void KeyTreeView::selectKeys(const std::vector &keys) { m_view->selectionModel()->select(itemSelectionFromKeys(keys, *m_proxy), QItemSelectionModel::ClearAndSelect | QItemSelectionModel::Rows); } std::vector KeyTreeView::selectedKeys() const { return m_proxy->keys(m_view->selectionModel()->selectedRows()); } void KeyTreeView::setHierarchicalView(bool on) { if (on == m_isHierarchical) { return; } if (on && !hierarchicalModel()) { qCWarning(KLEOPATRA_LOG) << "hierarchical view requested, but no hierarchical model set"; return; } if (!on && !flatModel()) { qCWarning(KLEOPATRA_LOG) << "flat view requested, but no flat model set"; return; } const std::vector selectedKeys = m_proxy->keys(m_view->selectionModel()->selectedRows()); const Key currentKey = m_proxy->key(m_view->currentIndex()); m_isHierarchical = on; find_last_proxy(m_proxy)->setSourceModel(model()); if (on) { m_view->expandAll(); } selectKeys(selectedKeys); if (!currentKey.isNull()) { const QModelIndex currentIndex = m_proxy->index(currentKey); if (currentIndex.isValid()) { m_view->selectionModel()->setCurrentIndex(m_proxy->index(currentKey), QItemSelectionModel::NoUpdate); m_view->scrollTo(currentIndex); } } Q_EMIT hierarchicalChanged(on); } void KeyTreeView::setKeys(const std::vector &keys) { std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); m_keys = sorted; if (m_flatModel) { m_flatModel->setKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->setKeys(sorted); } } void KeyTreeView::addKeysImpl(const std::vector &keys, bool select) { if (keys.empty()) { return; } if (m_keys.empty()) { setKeys(keys); return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector newKeys = _detail::union_by_fpr(sorted, m_keys); m_keys.swap(newKeys); if (m_flatModel) { m_flatModel->addKeys(sorted); } if (m_hierarchicalModel) { m_hierarchicalModel->addKeys(sorted); } if (select) { selectKeys(sorted); } } void KeyTreeView::addKeysSelected(const std::vector &keys) { addKeysImpl(keys, true); } void KeyTreeView::addKeysUnselected(const std::vector &keys) { addKeysImpl(keys, false); } void KeyTreeView::removeKeys(const std::vector &keys) { if (keys.empty()) { return; } std::vector sorted = keys; _detail::sort_by_fpr(sorted); _detail::remove_duplicates_by_fpr(sorted); std::vector newKeys; newKeys.reserve(m_keys.size()); std::set_difference(m_keys.begin(), m_keys.end(), sorted.begin(), sorted.end(), std::back_inserter(newKeys), _detail::ByFingerprint()); m_keys.swap(newKeys); if (m_flatModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_flatModel->removeKey(key); }); } if (m_hierarchicalModel) { std::for_each(sorted.cbegin(), sorted.cend(), [this](const Key &key) { m_hierarchicalModel->removeKey(key); }); } } static const struct { const char *signal; const char *slot; } connections[] = { { SIGNAL(stringFilterChanged(QString)), SLOT(setStringFilter(QString)) }, { SIGNAL(keyFilterChanged(std::shared_ptr)), SLOT(setKeyFilter(std::shared_ptr)) }, }; static const unsigned int numConnections = sizeof connections / sizeof * connections; void KeyTreeView::disconnectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) { disconnect(this, connections[i].signal, bar, connections[i].slot); disconnect(bar, connections[i].signal, this, connections[i].slot); } } bool KeyTreeView::connectSearchBar(const QObject *bar) { for (unsigned int i = 0; i < numConnections; ++i) if (!connect(this, connections[i].signal, bar, connections[i].slot) || !connect(bar, connections[i].signal, this, connections[i].slot)) { return false; } return true; } void KeyTreeView::resizeColumns() { m_view->setColumnWidth(KeyListModelInterface::PrettyName, 260); m_view->setColumnWidth(KeyListModelInterface::PrettyEMail, 260); for (int i = 2; i < m_view->model()->columnCount(); ++i) { m_view->resizeColumnToContents(i); } } diff --git a/src/view/tabwidget.cpp b/src/view/tabwidget.cpp index 5703f7a24..93dccb89a 100644 --- a/src/view/tabwidget.cpp +++ b/src/view/tabwidget.cpp @@ -1,955 +1,954 @@ /* -*- 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 #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 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(); 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() { } 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()); } } // // // 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(QAction *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); void setCornerAction(QAction *action, Qt::Corner corner); private: AbstractKeyListModel *flatModel; AbstractKeyListModel *hierarchicalModel; QTabWidget tabWidget; QVBoxLayout layout; enum { Rename, Duplicate, Close, MoveLeft, MoveRight, Hierarchical, ExpandAll, CollapseAll, NumPageActions }; QAction *newAction; QAction *currentPageActions[NumPageActions]; QAction *otherPageActions[NumPageActions]; bool actionsCreated; }; TabWidget::Private::Private(TabWidget *qq) : q(qq), flatModel(nullptr), hierarchicalModel(nullptr), tabWidget(q), layout(q), actionsCreated(false) { KDAB_SET_OBJECT_NAME(tabWidget); KDAB_SET_OBJECT_NAME(layout); layout.setContentsMargins(0, 0, 0, 0); layout.addWidget(&tabWidget); tabWidget.tabBar()->hide(); tabWidget.setMovable(true); tabWidget.tabBar()->setContextMenuPolicy(Qt::CustomContextMenu); connect(&tabWidget, SIGNAL(currentChanged(int)), q, SLOT(currentIndexChanged(int))); 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(); QAction **const actions = contextMenuPage == current ? currentPageActions : otherPageActions; enableDisablePageActions(actions, contextMenuPage); QMenu menu; menu.addAction(actions[Rename]); menu.addSeparator(); menu.addAction(newAction); menu.addAction(actions[Duplicate]); menu.addSeparator(); menu.addAction(actions[MoveLeft]); menu.addAction(actions[MoveRight]); menu.addSeparator(); menu.addAction(actions[Close]); const QAction *const action = menu.exec(tabWidget.tabBar()->mapToGlobal(p)); if (contextMenuPage == current || action == newAction) { return; // performed through signal/slot connections... } #ifndef QT_NO_INPUTDIALOG if (action == otherPageActions[Rename]) { renamePage(contextMenuPage); } #endif // QT_NO_INPUTDIALOG else if (action == otherPageActions[Duplicate]) { duplicatePage(contextMenuPage); } else if (action == otherPageActions[Close]) { closePage(contextMenuPage); } else if (action == otherPageActions[MoveLeft]) { movePageLeft(contextMenuPage); } else if (action == otherPageActions[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(QAction *actions[], const Page *p) { actions[Rename] ->setEnabled(p && p->canBeRenamed()); actions[Duplicate] ->setEnabled(p); actions[Close] ->setEnabled(p && p->canBeClosed() && tabWidget.count() > 1); actions[MoveLeft] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != 0); actions[MoveRight] ->setEnabled(p && tabWidget.indexOf(const_cast(p)) != tabWidget.count() - 1); actions[Hierarchical]->setEnabled(p && p->canChangeHierarchical()); actions[Hierarchical]->setChecked(p && p->isHierarchicalView()); actions[ExpandAll] ->setEnabled(p && p->isHierarchicalView()); actions[CollapseAll] ->setEnabled(p && p->isHierarchicalView()); if (tabWidget.count() < 2) { tabWidget.tabBar()->hide(); } else { tabWidget.tabBar()->show(); } } 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; } void TabWidget::Private::setCornerAction(QAction *action, Qt::Corner corner) { if (!action) { return; } QToolButton *b = new QToolButton; b->setDefaultAction(action); tabWidget.setCornerWidget(b, corner); } 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; } QAbstractProxyModel *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, SLOT(slotNewTab()), QStringLiteral("CTRL+SHIFT+N"), false, true }; d->newAction = make_action_from_data(actionDataNew, coll); struct action_data actionData[] = { { "window_rename_tab", i18n("Rename Tab..."), i18n("Rename this tab"), "edit-rename", this, SLOT(slotRenameCurrentTab()), QStringLiteral("CTRL+SHIFT+R"), false, false }, { "window_duplicate_tab", i18n("Duplicate Tab"), i18n("Duplicate this tab"), "tab-duplicate", this, SLOT(slotDuplicateCurrentTab()), QStringLiteral("CTRL+SHIFT+D"), false, true }, { "window_close_tab", i18n("Close Tab"), i18n("Close this tab"), "tab-close", this, SLOT(slotCloseCurrentTab()), QStringLiteral("CTRL+SHIFT+W"), false, false }, // ### CTRL-W when available { "window_move_tab_left", i18n("Move Tab Left"), i18n("Move this tab left"), nullptr, this, SLOT(slotMoveCurrentTabLeft()), QStringLiteral("CTRL+SHIFT+LEFT"), false, false }, { "window_move_tab_right", i18n("Move Tab Right"), i18n("Move this tab right"), nullptr, this, SLOT(slotMoveCurrentTabRight()), QStringLiteral("CTRL+SHIFT+RIGHT"), false, false }, { "window_view_hierarchical", i18n("Hierarchical Certificate List"), QString(), nullptr, this, SLOT(slotToggleHierarchicalView(bool)), QString(), true, false }, { "window_expand_all", i18n("Expand All"), QString(), nullptr, this, SLOT(slotExpandAll()), QStringLiteral("CTRL+."), false, false }, { "window_collapse_all", i18n("Collapse All"), QString(), nullptr, this, SLOT(slotCollapseAll()), QStringLiteral("CTRL+,"), false, false }, }; for (int i = 0; i < d->NumPageActions; ++i) { d->currentPageActions[i] = make_action_from_data(actionData[i], coll); } for (int i = 0; i < d->NumPageActions; ++i) { action_data ad = actionData[i]; Q_ASSERT(QString::fromLatin1(ad.name).startsWith(QLatin1String("window_"))); ad.name = ad.name + strlen("window_"); ad.tooltip.clear(); ad.receiver = nullptr; ad.shortcut.clear(); d->otherPageActions[i] = make_action_from_data(ad, coll); } d->setCornerAction(d->newAction, Qt::TopLeftCorner); d->setCornerAction(d->currentPageActions[d->Close], Qt::TopRightCorner); 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) { Page *const page = new Page(title, QString(), QString(), proxy, tabToolTip); 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) { KActionCollection *coll = new KActionCollection(q); q->createActions(coll); } page->setFlatModel(flatModel); page->setHierarchicalModel(hierarchicalModel); connect(page, SIGNAL(titleChanged(QString)), q, SLOT(slotPageTitleChanged(QString))); connect(page, SIGNAL(keyFilterChanged(std::shared_ptr)), q, SLOT(slotPageKeyFilterChanged(std::shared_ptr))); connect(page, SIGNAL(stringFilterChanged(QString)), q, SLOT(slotPageStringFilterChanged(QString))); connect(page, SIGNAL(hierarchicalChanged(bool)), q, SLOT(slotPageHierarchyChanged(bool))); 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()); 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 : qAsConst(groupList)) { const KConfigGroup kcg(config, group); if (!KCONFIG_DELETEGROUP_BROKEN || kcg.readEntry("magic", 0U) == 0xFA1AFE1U) { addView(kcg); } } } if (!count()) { // add default view: addView(QString(), QStringLiteral("all-certificates")); } } void TabWidget::saveViews(KConfig *config) const { if (!config) { return; } Q_FOREACH (const QString &group, extractViewGroups(config)) { 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); } } } } static void xconnect(const QObject *o1, const char *signal, const QObject *o2, const char *slot) { QObject::connect(o1, signal, o2, slot); QObject::connect(o2, signal, o1, slot); } void TabWidget::connectSearchBar(QObject *sb) { xconnect(sb, SIGNAL(stringFilterChanged(QString)), this, SLOT(setStringFilter(QString))); xconnect(sb, SIGNAL(keyFilterChanged(std::shared_ptr)), this, SLOT(setKeyFilter(std::shared_ptr))); connect(this, SIGNAL(enableChangeStringFilter(bool)), sb, SLOT(setChangeStringFilterEnabled(bool))); connect(this, SIGNAL(enableChangeKeyFilter(bool)), sb, SLOT(setChangeKeyFilterEnabled(bool))); } #include "moc_tabwidget.cpp" #include "tabwidget.moc"