File indexing completed on 2024-06-16 04:55:56

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     decryptverifyfilescontroller.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "decryptverifyfilescontroller.h"
0013 
0014 #include <crypto/decryptverifytask.h>
0015 #include <crypto/gui/decryptverifyfileswizard.h>
0016 #include <crypto/gui/decryptverifyoperationwidget.h>
0017 #include <crypto/taskcollection.h>
0018 
0019 #include <Libkleo/GnuPG>
0020 #include <utils/archivedefinition.h>
0021 #include <utils/input.h>
0022 #include <utils/kleo_assert.h>
0023 #include <utils/output.h>
0024 #include <utils/path-helper.h>
0025 
0026 #include <Libkleo/Classify>
0027 
0028 #include "kleopatra_debug.h"
0029 #include <KLocalizedString>
0030 
0031 #include <QDir>
0032 #include <QFile>
0033 #include <QFileInfo>
0034 #include <QPointer>
0035 #include <QTimer>
0036 
0037 using namespace GpgME;
0038 using namespace Kleo;
0039 using namespace Kleo::Crypto;
0040 using namespace Kleo::Crypto::Gui;
0041 
0042 class DecryptVerifyFilesController::Private
0043 {
0044     DecryptVerifyFilesController *const q;
0045 
0046 public:
0047     static std::shared_ptr<AbstractDecryptVerifyTask> taskFromOperationWidget(const DecryptVerifyOperationWidget *w,
0048                                                                               const QString &fileName,
0049                                                                               const QDir &outDir,
0050                                                                               const std::shared_ptr<OverwritePolicy> &overwritePolicy);
0051 
0052     explicit Private(DecryptVerifyFilesController *qq);
0053 
0054     void slotWizardOperationPrepared();
0055     void slotWizardCanceled();
0056     void schedule();
0057 
0058     void prepareWizardFromPassedFiles();
0059     std::vector<std::shared_ptr<Task>> buildTasks(const QStringList &, const std::shared_ptr<OverwritePolicy> &);
0060 
0061     void ensureWizardCreated();
0062     void ensureWizardVisible();
0063     void reportError(int err, const QString &details)
0064     {
0065         q->setLastError(err, details);
0066         q->emitDoneOrError();
0067     }
0068     void cancelAllTasks();
0069 
0070     QStringList m_passedFiles, m_filesAfterPreparation;
0071     QPointer<DecryptVerifyFilesWizard> m_wizard;
0072     std::vector<std::shared_ptr<const DecryptVerifyResult>> m_results;
0073     std::vector<std::shared_ptr<Task>> m_runnableTasks, m_completedTasks;
0074     std::shared_ptr<Task> m_runningTask;
0075     bool m_errorDetected;
0076     DecryptVerifyOperation m_operation;
0077 };
0078 
0079 // static
0080 std::shared_ptr<AbstractDecryptVerifyTask>
0081 DecryptVerifyFilesController::Private::taskFromOperationWidget(const DecryptVerifyOperationWidget *w,
0082                                                                const QString &fileName,
0083                                                                const QDir &outDir,
0084                                                                const std::shared_ptr<OverwritePolicy> &overwritePolicy)
0085 {
0086     kleo_assert(w);
0087 
0088     std::shared_ptr<AbstractDecryptVerifyTask> task;
0089 
0090     switch (w->mode()) {
0091     case DecryptVerifyOperationWidget::VerifyDetachedWithSignature: {
0092         std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
0093         t->setInput(Input::createFromFile(fileName));
0094         t->setSignedData(Input::createFromFile(w->signedDataFileName()));
0095         task = t;
0096 
0097         kleo_assert(fileName == w->inputFileName());
0098     } break;
0099     case DecryptVerifyOperationWidget::VerifyDetachedWithSignedData: {
0100         std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
0101         t->setInput(Input::createFromFile(w->inputFileName()));
0102         t->setSignedData(Input::createFromFile(fileName));
0103         task = t;
0104 
0105         kleo_assert(fileName == w->signedDataFileName());
0106     } break;
0107     case DecryptVerifyOperationWidget::DecryptVerifyOpaque: {
0108         const unsigned int classification = classify(fileName);
0109         qCDebug(KLEOPATRA_LOG) << "classified" << fileName << "as" << printableClassification(classification);
0110 
0111         const std::shared_ptr<ArchiveDefinition> ad = w->selectedArchiveDefinition();
0112 
0113         const Protocol proto = isOpenPGP(classification) ? OpenPGP
0114             : isCMS(classification)                      ? CMS
0115             : ad ? throw Exception(gpg_error(GPG_ERR_CONFLICT), i18n("Cannot determine whether input data is OpenPGP or CMS"))
0116                  : UnknownProtocol;
0117 
0118         const std::shared_ptr<Input> input = Input::createFromFile(fileName);
0119         const std::shared_ptr<Output> output = ad
0120             ? ad->createOutputFromUnpackCommand(proto, fileName, outDir)
0121             : Output::createFromFile(outDir.absoluteFilePath(outputFileName(QFileInfo(fileName).fileName())), overwritePolicy);
0122 
0123         if (mayBeCipherText(classification)) {
0124             qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask";
0125             std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask);
0126             t->setInput(input);
0127             t->setOutput(output);
0128             task = t;
0129         } else {
0130             qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask";
0131             std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask);
0132             t->setInput(input);
0133             t->setOutput(output);
0134             task = t;
0135         }
0136 
0137         kleo_assert(fileName == w->inputFileName());
0138     } break;
0139     }
0140 
0141     task->autodetectProtocolFromInput();
0142     return task;
0143 }
0144 
0145 DecryptVerifyFilesController::Private::Private(DecryptVerifyFilesController *qq)
0146     : q(qq)
0147     , m_errorDetected(false)
0148     , m_operation(DecryptVerify)
0149 {
0150     qRegisterMetaType<VerificationResult>();
0151 }
0152 
0153 void DecryptVerifyFilesController::Private::slotWizardOperationPrepared()
0154 {
0155     ensureWizardCreated();
0156     std::vector<std::shared_ptr<Task>> tasks = buildTasks(m_filesAfterPreparation, std::make_shared<OverwritePolicy>(m_wizard, OverwritePolicy::MultipleFiles));
0157     if (tasks.empty()) {
0158         reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), i18n("No usable inputs found"));
0159     }
0160     kleo_assert(m_runnableTasks.empty());
0161     m_runnableTasks.swap(tasks);
0162 
0163     std::shared_ptr<TaskCollection> coll(new TaskCollection);
0164     for (const auto &i : m_runnableTasks) {
0165         q->connectTask(i);
0166     }
0167     coll->setTasks(m_runnableTasks);
0168     m_wizard->setTaskCollection(coll);
0169 
0170     QTimer::singleShot(0, q, SLOT(schedule()));
0171 }
0172 
0173 void DecryptVerifyFilesController::Private::slotWizardCanceled()
0174 {
0175     qCDebug(KLEOPATRA_LOG) << this << __func__;
0176     q->cancel();
0177     q->emitDoneOrError();
0178 }
0179 
0180 void DecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
0181 {
0182     Q_ASSERT(task);
0183     Q_UNUSED(task)
0184 
0185     // We could just delete the tasks here, but we can't use
0186     // Qt::QueuedConnection here (we need sender()) and other slots
0187     // might not yet have executed. Therefore, we push completed tasks
0188     // into a burial container
0189 
0190     d->m_completedTasks.push_back(d->m_runningTask);
0191     d->m_runningTask.reset();
0192 
0193     if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) {
0194         d->m_results.push_back(dvr);
0195     }
0196 
0197     QTimer::singleShot(0, this, SLOT(schedule()));
0198 }
0199 
0200 void DecryptVerifyFilesController::Private::schedule()
0201 {
0202     if (!m_runningTask && !m_runnableTasks.empty()) {
0203         const std::shared_ptr<Task> t = m_runnableTasks.back();
0204         m_runnableTasks.pop_back();
0205         t->start();
0206         m_runningTask = t;
0207     }
0208     if (!m_runningTask) {
0209         kleo_assert(m_runnableTasks.empty());
0210         for (const auto &i : m_results) {
0211             Q_EMIT q->verificationResult(i->verificationResult());
0212         }
0213         q->emitDoneOrError();
0214     }
0215 }
0216 
0217 void DecryptVerifyFilesController::Private::ensureWizardCreated()
0218 {
0219     if (m_wizard) {
0220         return;
0221     }
0222 
0223     std::unique_ptr<DecryptVerifyFilesWizard> w(new DecryptVerifyFilesWizard);
0224     w->setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files"));
0225     w->setAttribute(Qt::WA_DeleteOnClose);
0226 
0227     connect(w.get(), SIGNAL(operationPrepared()), q, SLOT(slotWizardOperationPrepared()), Qt::QueuedConnection);
0228     connect(w.get(), SIGNAL(canceled()), q, SLOT(slotWizardCanceled()), Qt::QueuedConnection);
0229     m_wizard = w.release();
0230 }
0231 
0232 namespace
0233 {
0234 struct FindExtension {
0235     const QString ext;
0236     const Protocol proto;
0237     FindExtension(const QString &ext, Protocol proto)
0238         : ext(ext)
0239         , proto(proto)
0240     {
0241     }
0242     bool operator()(const std::shared_ptr<ArchiveDefinition> &ad) const
0243     {
0244         qCDebug(KLEOPATRA_LOG) << "   considering" << (ad ? ad->label() : QStringLiteral("<null>")) << "for" << ext;
0245         bool result;
0246         if (proto == UnknownProtocol) {
0247             result = ad && (ad->extensions(OpenPGP).contains(ext, Qt::CaseInsensitive) || ad->extensions(CMS).contains(ext, Qt::CaseInsensitive));
0248         } else {
0249             result = ad && ad->extensions(proto).contains(ext, Qt::CaseInsensitive);
0250         }
0251         qCDebug(KLEOPATRA_LOG) << (result ? "   -> matches" : "   -> doesn't match");
0252         return result;
0253     }
0254 };
0255 }
0256 
0257 std::shared_ptr<ArchiveDefinition> DecryptVerifyFilesController::pick_archive_definition(GpgME::Protocol proto,
0258                                                                                          const std::vector<std::shared_ptr<ArchiveDefinition>> &ads,
0259                                                                                          const QString &filename)
0260 {
0261     const QFileInfo fi(outputFileName(filename));
0262     QString extension = fi.completeSuffix();
0263 
0264     if (extension == QLatin1StringView("out")) { // added by outputFileName() -> useless
0265         return std::shared_ptr<ArchiveDefinition>();
0266     }
0267 
0268     if (extension.endsWith(QLatin1StringView(".out"))) { // added by outputFileName() -> remove
0269         extension.chop(4);
0270     }
0271 
0272     for (;;) {
0273         const auto it = std::find_if(ads.begin(), ads.end(), FindExtension(extension, proto));
0274         if (it != ads.end()) {
0275             return *it;
0276         }
0277         const int idx = extension.indexOf(QLatin1Char('.'));
0278         if (idx < 0) {
0279             return std::shared_ptr<ArchiveDefinition>();
0280         }
0281         extension = extension.mid(idx + 1);
0282     }
0283 }
0284 
0285 void DecryptVerifyFilesController::Private::prepareWizardFromPassedFiles()
0286 {
0287     ensureWizardCreated();
0288     const std::vector<std::shared_ptr<ArchiveDefinition>> archiveDefinitions = ArchiveDefinition::getArchiveDefinitions();
0289 
0290     unsigned int counter = 0;
0291     for (const auto &fname : std::as_const(m_passedFiles)) {
0292         kleo_assert(!fname.isEmpty());
0293 
0294         const unsigned int classification = classify(fname);
0295         const Protocol proto = findProtocol(classification);
0296 
0297         if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification) || mayBeDetachedSignature(classification)) {
0298             DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++);
0299             kleo_assert(op != nullptr);
0300 
0301             op->setArchiveDefinitions(archiveDefinitions);
0302 
0303             const QString signedDataFileName = findSignedData(fname);
0304 
0305             // this breaks opaque signatures whose source files still
0306             // happen to exist in the same directory. Until we have
0307             // content-based classification, this is the most unlikely
0308             // case, so that's the case we break. ### FIXME remove when content-classify is done
0309             if (mayBeDetachedSignature(classification) && !signedDataFileName.isEmpty()) {
0310                 op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature);
0311             }
0312             // ### end FIXME
0313             else if (mayBeOpaqueSignature(classification) || mayBeCipherText(classification)) {
0314                 op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname));
0315             } else {
0316                 op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignature);
0317             }
0318 
0319             op->setInputFileName(fname);
0320             op->setSignedDataFileName(signedDataFileName);
0321 
0322             m_filesAfterPreparation << fname;
0323 
0324         } else {
0325             // probably the signed data file was selected:
0326             const QStringList signatures = findSignatures(fname);
0327 
0328             if (signatures.empty()) {
0329                 // We are assuming this is a detached signature file, but
0330                 // there were no signature files for it. Let's guess it's encrypted after all.
0331                 // ### FIXME once we have a proper heuristic for this, this should move into
0332                 // classify() and/or classifyContent()
0333                 DecryptVerifyOperationWidget *const op = m_wizard->operationWidget(counter++);
0334                 kleo_assert(op != nullptr);
0335                 op->setArchiveDefinitions(archiveDefinitions);
0336                 op->setMode(DecryptVerifyOperationWidget::DecryptVerifyOpaque, q->pick_archive_definition(proto, archiveDefinitions, fname));
0337                 op->setInputFileName(fname);
0338                 m_filesAfterPreparation << fname;
0339             } else {
0340                 for (const auto &s : signatures) {
0341                     DecryptVerifyOperationWidget *op = m_wizard->operationWidget(counter++);
0342                     kleo_assert(op != nullptr);
0343 
0344                     op->setArchiveDefinitions(archiveDefinitions);
0345                     op->setMode(DecryptVerifyOperationWidget::VerifyDetachedWithSignedData);
0346                     op->setInputFileName(s);
0347                     op->setSignedDataFileName(fname);
0348 
0349                     m_filesAfterPreparation << fname;
0350                 }
0351             }
0352         }
0353     }
0354 
0355     m_wizard->setOutputDirectory(heuristicBaseDirectory(m_passedFiles));
0356     return;
0357 }
0358 
0359 std::vector<std::shared_ptr<Task>> DecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames,
0360                                                                                      const std::shared_ptr<OverwritePolicy> &overwritePolicy)
0361 {
0362     const bool useOutDir = m_wizard->useOutputDirectory();
0363     const QFileInfo outDirInfo(m_wizard->outputDirectory());
0364 
0365     kleo_assert(!useOutDir || outDirInfo.isDir());
0366 
0367     const QDir outDir(outDirInfo.absoluteFilePath());
0368     kleo_assert(!useOutDir || outDir.exists());
0369 
0370     std::vector<std::shared_ptr<Task>> tasks;
0371     for (int i = 0, end = fileNames.size(); i != end; ++i)
0372         try {
0373             const QDir fileDir = QFileInfo(fileNames[i]).absoluteDir();
0374             kleo_assert(fileDir.exists());
0375             tasks.push_back(
0376                 taskFromOperationWidget(m_wizard->operationWidget(static_cast<unsigned int>(i)), fileNames[i], useOutDir ? outDir : fileDir, overwritePolicy));
0377         } catch (const GpgME::Exception &e) {
0378             tasks.push_back(Task::makeErrorTask(e.error(), QString::fromLocal8Bit(e.what()), fileNames[i]));
0379         }
0380 
0381     return tasks;
0382 }
0383 
0384 void DecryptVerifyFilesController::setFiles(const QStringList &files)
0385 {
0386     d->m_passedFiles = files;
0387 }
0388 
0389 void DecryptVerifyFilesController::Private::ensureWizardVisible()
0390 {
0391     ensureWizardCreated();
0392     q->bringToForeground(m_wizard);
0393 }
0394 
0395 DecryptVerifyFilesController::DecryptVerifyFilesController(QObject *parent)
0396     : Controller(parent)
0397     , d(new Private(this))
0398 {
0399 }
0400 
0401 DecryptVerifyFilesController::DecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent)
0402     : Controller(ctx, parent)
0403     , d(new Private(this))
0404 {
0405 }
0406 
0407 DecryptVerifyFilesController::~DecryptVerifyFilesController()
0408 {
0409     qCDebug(KLEOPATRA_LOG);
0410 }
0411 
0412 void DecryptVerifyFilesController::start()
0413 {
0414     d->prepareWizardFromPassedFiles();
0415     d->ensureWizardVisible();
0416 }
0417 
0418 void DecryptVerifyFilesController::setOperation(DecryptVerifyOperation op)
0419 {
0420     d->m_operation = op;
0421 }
0422 
0423 DecryptVerifyOperation DecryptVerifyFilesController::operation() const
0424 {
0425     return d->m_operation;
0426 }
0427 
0428 void DecryptVerifyFilesController::Private::cancelAllTasks()
0429 {
0430     // we just kill all runnable tasks - this will not result in
0431     // signal emissions.
0432     m_runnableTasks.clear();
0433 
0434     // a cancel() will result in a call to
0435     if (m_runningTask) {
0436         m_runningTask->cancel();
0437     }
0438 }
0439 
0440 void DecryptVerifyFilesController::cancel()
0441 {
0442     qCDebug(KLEOPATRA_LOG) << this << __func__;
0443     try {
0444         d->m_errorDetected = true;
0445         if (d->m_wizard) {
0446             d->m_wizard->close();
0447         }
0448         d->cancelAllTasks();
0449     } catch (const std::exception &e) {
0450         qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
0451     }
0452 }
0453 
0454 #include "moc_decryptverifyfilescontroller.cpp"