diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index b8789c4cc..8c5645e9c 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,443 +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 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); + q->emitDoneOrError(); } void DecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr &result) { Q_ASSERT(task); Q_UNUSED(task) // We could just delete the tasks here, but we can't use // Qt::QueuedConnection here (we need sender()) and other slots // might not yet have executed. Therefore, we push completed tasks // into a burial container d->m_completedTasks.push_back(d->m_runningTask); d->m_runningTask.reset(); if (const std::shared_ptr &dvr = std::dynamic_pointer_cast(result)) { d->m_results.push_back(dvr); } QTimer::singleShot(0, this, SLOT(schedule())); } void DecryptVerifyFilesController::Private::schedule() { if (!m_runningTask && !m_runnableTasks.empty()) { const std::shared_ptr t = m_runnableTasks.back(); m_runnableTasks.pop_back(); t->start(); m_runningTask = t; } if (!m_runningTask) { kleo_assert(m_runnableTasks.empty()); for (const auto &i: m_results) { Q_EMIT q->verificationResult(i->verificationResult()); } q->emitDoneOrError(); } } void DecryptVerifyFilesController::Private::ensureWizardCreated() { if (m_wizard) { return; } std::unique_ptr w(new DecryptVerifyFilesWizard); w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files")); w->setAttribute(Qt::WA_DeleteOnClose); connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection); connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection); m_wizard = w.release(); } namespace { struct FindExtension { const QString ext; const Protocol proto; FindExtension(const QString &ext, Protocol proto) : ext(ext), proto(proto) {} bool operator()(const std::shared_ptr &ad) const { qCDebug(KLEOPATRA_LOG) << " considering" << (ad ? ad->label() : QStringLiteral("")) << "for" << ext; bool result; if (proto == UnknownProtocol) { result = ad && (ad->extensions(OpenPGP).contains(ext, Qt::CaseInsensitive) || ad->extensions(CMS).contains(ext, Qt::CaseInsensitive)); } else { result = ad && ad->extensions(proto).contains(ext, Qt::CaseInsensitive); } qCDebug(KLEOPATRA_LOG) << (result ? " -> matches" : " -> doesn't match"); return result; } }; } std::shared_ptr DecryptVerifyFilesController::pick_archive_definition(GpgME::Protocol proto, const std::vector< 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 auto it = std::find_if(ads.begin(), ads.end(), FindExtension(extension, proto)); if (it != ads.end()) { return *it; } const int idx = extension.indexOf(QLatin1Char('.')); if (idx < 0) { return std::shared_ptr(); } extension = extension.mid(idx + 1); } } void DecryptVerifyFilesController::Private::prepareWizardFromPassedFiles() { ensureWizardCreated(); const std::vector< std::shared_ptr > archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); unsigned int counter = 0; for (const auto &fname: std::as_const(m_passedFiles)) { kleo_assert(!fname.isEmpty()); const unsigned int classification = classify(fname); const Protocol proto = findProtocol(classification); if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification) || mayBeDetachedSignature(classification)) { DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); const QString signedDataFileName = findSignedData(fname); // this breaks opaque signatures whose source files still // happen to exist in the same directory. Until we have // content-based classification, this is the most unlikely // case, so that's the case we break. ### FIXME remove when content-classify is done if (mayBeDetachedSignature(classification) && !signedDataFileName.isEmpty()) { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } // ### end FIXME else if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification)) { op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); } else { op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature); } op->setInputFileName(fname); op->setSignedDataFileName(signedDataFileName); m_filesAfterPreparation << fname; } else { // probably the signed data file was selected: const QStringList signatures = findSignatures(fname); if (signatures.empty()) { // We are assuming this is a detached signature file, but // there were no signature files for it. Let's guess it's encrypted after all. // ### FIXME once we have a proper heuristic for this, this should move into // classify() and/or classifyContent() DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname)); op->setInputFileName(fname); m_filesAfterPreparation << fname; } else { for (const auto &s: signatures) { DecryptVerifyOperationWidget *op = m_wizard->operationWidget(counter++); kleo_assert(op != nullptr); op->setArchiveDefinitions(archiveDefinitions); op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignedData); op->setInputFileName(s); op->setSignedDataFileName(fname); m_filesAfterPreparation << fname; } } } } m_wizard->setOutputDirectory(heuristicBaseDirectory(m_passedFiles)); return; } std::vector< 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"