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

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     autodecryptverifyfilescontroller.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2008 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik
0008     SPDX-FileContributor: Intevation GmbH
0009 
0010     SPDX-License-Identifier: GPL-2.0-or-later
0011 */
0012 
0013 #include <config-kleopatra.h>
0014 
0015 #include "autodecryptverifyfilescontroller.h"
0016 
0017 #include "fileoperationspreferences.h"
0018 
0019 #include <crypto/decryptverifytask.h>
0020 #include <crypto/gui/decryptverifyfilesdialog.h>
0021 #include <crypto/gui/decryptverifyoperationwidget.h>
0022 #include <crypto/taskcollection.h>
0023 
0024 #include "commands/decryptverifyfilescommand.h"
0025 
0026 #include <utils/archivedefinition.h>
0027 #include <utils/input.h>
0028 #include <utils/kleo_assert.h>
0029 #include <utils/output.h>
0030 #include <utils/overwritedialog.h>
0031 #include <utils/path-helper.h>
0032 
0033 #include <Libkleo/Algorithm>
0034 #include <Libkleo/Classify>
0035 #include <Libkleo/GnuPG>
0036 
0037 #ifndef Q_OS_WIN
0038 #include <KIO/CopyJob>
0039 #include <KIO/Global>
0040 #endif
0041 #include "kleopatra_debug.h"
0042 #include <KLocalizedString>
0043 #include <KMessageBox>
0044 
0045 #include <QGpgME/DecryptVerifyArchiveJob>
0046 
0047 #include <QDir>
0048 #include <QFile>
0049 #include <QFileDialog>
0050 #include <QFileInfo>
0051 #include <QTemporaryDir>
0052 #include <QTimer>
0053 
0054 #include <gpgme++/decryptionresult.h>
0055 
0056 using namespace GpgME;
0057 using namespace Kleo;
0058 using namespace Kleo::Crypto;
0059 using namespace Kleo::Crypto::Gui;
0060 
0061 class AutoDecryptVerifyFilesController::Private
0062 {
0063     AutoDecryptVerifyFilesController *const q;
0064 
0065 public:
0066     explicit Private(AutoDecryptVerifyFilesController *qq);
0067 
0068     void schedule();
0069 
0070     QString getEmbeddedFileName(const QString &fileName) const;
0071     void exec();
0072     std::vector<std::shared_ptr<Task>> buildTasks(const QStringList &, QStringList &);
0073 
0074     struct CryptoFile {
0075         QString baseName;
0076         QString fileName;
0077         GpgME::Protocol protocol = GpgME::UnknownProtocol;
0078         int classification = 0;
0079         std::shared_ptr<Output> output;
0080     };
0081     QList<CryptoFile> classifyAndSortFiles(const QStringList &files);
0082 
0083     void reportError(int err, const QString &details)
0084     {
0085         q->setLastError(err, details);
0086         q->emitDoneOrError();
0087     }
0088     void cancelAllTasks();
0089 
0090     QStringList m_passedFiles, m_filesAfterPreparation;
0091     std::vector<std::shared_ptr<const DecryptVerifyResult>> m_results;
0092     std::vector<std::shared_ptr<Task>> m_runnableTasks, m_completedTasks;
0093     std::shared_ptr<Task> m_runningTask;
0094     bool m_errorDetected = false;
0095     DecryptVerifyOperation m_operation = DecryptVerify;
0096     QPointer<DecryptVerifyFilesDialog> m_dialog;
0097     std::unique_ptr<QTemporaryDir> m_workDir;
0098 };
0099 
0100 AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq)
0101     : q(qq)
0102 {
0103     qRegisterMetaType<VerificationResult>();
0104 }
0105 
0106 void AutoDecryptVerifyFilesController::Private::schedule()
0107 {
0108     if (!m_runningTask && !m_runnableTasks.empty()) {
0109         const std::shared_ptr<Task> t = m_runnableTasks.back();
0110         m_runnableTasks.pop_back();
0111         t->start();
0112         m_runningTask = t;
0113     }
0114     if (!m_runningTask) {
0115         kleo_assert(m_runnableTasks.empty());
0116         for (const std::shared_ptr<const DecryptVerifyResult> &i : std::as_const(m_results)) {
0117             Q_EMIT q->verificationResult(i->verificationResult());
0118         }
0119     }
0120 }
0121 
0122 QString AutoDecryptVerifyFilesController::Private::getEmbeddedFileName(const QString &fileName) const
0123 {
0124     auto it = std::find_if(m_results.cbegin(), m_results.cend(), [fileName](const auto &r) {
0125         return r->fileName() == fileName;
0126     });
0127     if (it != m_results.cend()) {
0128         const auto embeddedFilePath = QString::fromUtf8((*it)->decryptionResult().fileName());
0129         if (embeddedFilePath.contains(QLatin1Char{'\\'})) {
0130             // ignore embedded file names containing '\'
0131             return {};
0132         }
0133         // strip the path from the embedded file name
0134         return QFileInfo{embeddedFilePath}.fileName();
0135     } else {
0136         return {};
0137     }
0138 }
0139 
0140 void AutoDecryptVerifyFilesController::Private::exec()
0141 {
0142     Q_ASSERT(!m_dialog);
0143 
0144     QStringList undetected;
0145     std::vector<std::shared_ptr<Task>> tasks = buildTasks(m_passedFiles, undetected);
0146 
0147     if (!undetected.isEmpty()) {
0148         // Since GpgME 1.7.0 Classification is supposed to be reliable
0149         // so we really can't do anything with this data.
0150         reportError(makeGnuPGError(GPG_ERR_GENERAL),
0151                     xi18n("Failed to find encrypted or signed data in one or more files.<nl/>"
0152                           "You can manually select what to do with the files now.<nl/>"
0153                           "If they contain signed or encrypted data please report a bug (see Help->Report Bug)."));
0154         auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true);
0155         cmd->start();
0156     }
0157     if (tasks.empty()) {
0158         q->emitDoneOrError();
0159         return;
0160     }
0161     Q_ASSERT(m_runnableTasks.empty());
0162     m_runnableTasks.swap(tasks);
0163 
0164     std::shared_ptr<TaskCollection> coll(new TaskCollection);
0165     for (const std::shared_ptr<Task> &i : std::as_const(m_runnableTasks)) {
0166         q->connectTask(i);
0167     }
0168     coll->setTasks(m_runnableTasks);
0169     DecryptVerifyFilesDialog dialog{coll};
0170     m_dialog = &dialog;
0171     m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles));
0172     q->applyWindowID(m_dialog);
0173 
0174     QTimer::singleShot(0, q, SLOT(schedule()));
0175     const auto result = m_dialog->exec();
0176     if (result == QDialog::Rejected) {
0177         q->cancel();
0178     } else if (result == QDialog::Accepted && m_workDir) {
0179         // Without workdir there is nothing to move.
0180         const QDir workdir(m_workDir->path());
0181         const QDir outDir(m_dialog->outputLocation());
0182         qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0183         const auto filesAndFoldersToMove = workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot);
0184         const auto fileCount = Kleo::count_if(filesAndFoldersToMove, [](const auto &fi) {
0185             return !fi.isDir();
0186         });
0187         OverwritePolicy overwritePolicy{m_dialog, fileCount > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}};
0188         for (const QFileInfo &fi : filesAndFoldersToMove) {
0189             const auto inpath = fi.absoluteFilePath();
0190 
0191             if (fi.isDir()) {
0192                 // A directory. Assume that the input was an archive
0193                 // and avoid directory merges by trying to find a non
0194                 // existing directory.
0195                 auto candidate = fi.fileName();
0196                 if (candidate.startsWith(QLatin1Char('-'))) {
0197                     // Bug in GpgTar Extracts stdout passed archives to a dir named -
0198                     candidate = QFileInfo(m_passedFiles.first()).baseName();
0199                 }
0200 
0201                 QString suffix;
0202                 QFileInfo ofi;
0203                 int i = 0;
0204                 do {
0205                     ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix));
0206                     if (!ofi.exists()) {
0207                         break;
0208                     }
0209                     suffix = QStringLiteral("_%1").arg(++i);
0210                 } while (i < 1000);
0211 
0212                 const auto destPath = ofi.absoluteFilePath();
0213 #ifndef Q_OS_WIN
0214                 auto job = KIO::moveAs(QUrl::fromLocalFile(inpath), QUrl::fromLocalFile(destPath));
0215                 qCDebug(KLEOPATRA_LOG) << "Moving" << job->srcUrls().front().toLocalFile() << "to" << job->destUrl().toLocalFile();
0216                 if (!job->exec()) {
0217                     if (job->error() == KIO::ERR_USER_CANCELED) {
0218                         break;
0219                     }
0220                     reportError(makeGnuPGError(GPG_ERR_GENERAL),
0221                                 xi18nc("@info",
0222                                        "<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>"
0223                                        "<para><message>%3</message></para>",
0224                                        inpath,
0225                                        destPath,
0226                                        job->errorString()));
0227                 }
0228 #else
0229                 // On Windows, KIO::move does not work for folders when crossing partition boundaries
0230                 if (!moveDir(inpath, destPath)) {
0231                     reportError(makeGnuPGError(GPG_ERR_GENERAL),
0232                                 xi18nc("@info", "<para>Failed to move <filename>%1</filename> to <filename>%2</filename>.</para>", inpath, destPath));
0233                 }
0234 #endif
0235                 continue;
0236             }
0237 
0238             const auto embeddedFileName = getEmbeddedFileName(inpath);
0239             QString outFileName = fi.fileName();
0240             if (!embeddedFileName.isEmpty() && embeddedFileName != fi.fileName()) {
0241                 // we switch "Yes" and "No" because Yes is default, but saving with embedded file name could be dangerous
0242                 const auto answer = KMessageBox::questionTwoActionsCancel(
0243                     m_dialog,
0244                     xi18n("Shall the file be saved with the original file name <filename>%1</filename>?", embeddedFileName),
0245                     i18nc("@title:window", "Use Original File Name?"),
0246                     KGuiItem(xi18n("No, Save As <filename>%1</filename>", fi.fileName())),
0247                     KGuiItem(xi18n("Yes, Save As <filename>%1</filename>", embeddedFileName)));
0248                 if (answer == KMessageBox::Cancel) {
0249                     qCDebug(KLEOPATRA_LOG) << "Saving canceled for:" << inpath;
0250                     continue;
0251                 } else if (answer == KMessageBox::ButtonCode::SecondaryAction) {
0252                     outFileName = embeddedFileName;
0253                 }
0254             }
0255             auto outpath = outDir.absoluteFilePath(outFileName);
0256             qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath;
0257             const QFileInfo ofi(outpath);
0258             if (ofi.exists()) {
0259                 const auto newPath = overwritePolicy.obtainOverwritePermission(outpath);
0260                 if (newPath.isEmpty()) {
0261                     if (overwritePolicy.policy() == OverwritePolicy::Cancel) {
0262                         qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath;
0263                         break;
0264                     }
0265                     // else Skip
0266                     continue;
0267                 } else if (newPath == outpath) {
0268                     // overwrite existing file
0269                     if (!QFile::remove(outpath)) {
0270                         reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete <filename>%1</filename>.", outpath));
0271                         continue;
0272                     }
0273                 } else {
0274                     // use new name for file
0275                     outpath = newPath;
0276                 }
0277             }
0278             if (!QFile::rename(inpath, outpath)) {
0279                 reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move <filename>%1</filename> to <filename>%2</filename>.", inpath, outpath));
0280             }
0281         }
0282     }
0283     q->emitDoneOrError();
0284 }
0285 
0286 QList<AutoDecryptVerifyFilesController::Private::CryptoFile> AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files)
0287 {
0288     const auto isSignature = [](int classification) -> bool {
0289         return mayBeDetachedSignature(classification) //
0290             || mayBeOpaqueSignature(classification) //
0291             || (classification & Class::TypeMask) == Class::ClearsignedMessage;
0292     };
0293 
0294     QList<CryptoFile> out;
0295     for (const auto &file : files) {
0296         CryptoFile cFile;
0297         cFile.fileName = file;
0298         cFile.baseName = stripSuffix(file);
0299         cFile.classification = classify(file);
0300         cFile.protocol = findProtocol(cFile.classification);
0301 
0302         auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) {
0303             return other.protocol == cFile.protocol && other.baseName == cFile.baseName;
0304         });
0305         if (it != out.end()) {
0306             // If we found a file with the same basename, make sure that encrypted
0307             // file is before the signature file, so that we first decrypt and then
0308             // verify
0309             if (isSignature(cFile.classification) && isCipherText(it->classification)) {
0310                 out.insert(it + 1, cFile);
0311             } else if (isCipherText(cFile.classification) && isSignature(it->classification)) {
0312                 out.insert(it, cFile);
0313             } else {
0314                 // both are signatures or both are encrypted files, in which
0315                 // case order does not matter
0316                 out.insert(it, cFile);
0317             }
0318         } else {
0319             out.push_back(cFile);
0320         }
0321     }
0322 
0323     return out;
0324 }
0325 
0326 static bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol)
0327 {
0328     return (protocol == GpgME::OpenPGP) && QGpgME::DecryptVerifyArchiveJob::isSupported();
0329 }
0330 
0331 std::vector<std::shared_ptr<Task>> AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected)
0332 {
0333     // sort files so that we make sure we first decrypt and then verify
0334     QList<CryptoFile> cryptoFiles = classifyAndSortFiles(fileNames);
0335 
0336     std::vector<std::shared_ptr<Task>> tasks;
0337     for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) {
0338         auto &cFile = (*it);
0339         QFileInfo fi(cFile.fileName);
0340         qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification);
0341 
0342         if (!fi.isReadable()) {
0343             reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open <filename>%1</filename> for reading.", cFile.fileName));
0344             continue;
0345         }
0346 
0347         if (mayBeAnyCertStoreType(cFile.classification)) {
0348             // Trying to verify a certificate. Possible because extensions are often similar
0349             // for PGP Keys.
0350             reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT),
0351                         xi18n("The file <filename>%1</filename> contains certificates and can't be decrypted or verified.", cFile.fileName));
0352             qCDebug(KLEOPATRA_LOG) << "reported error";
0353             continue;
0354         }
0355 
0356         // We can't reliably detect CMS detached signatures, so we will try to do
0357         // our best to use the current file as a detached signature and fallback to
0358         // opaque signature otherwise.
0359         if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) {
0360             // First, see if previous task was a decryption task for the same file
0361             // and "pipe" it's output into our input
0362             std::shared_ptr<Input> input;
0363             bool prepend = false;
0364             if (it != cryptoFiles.begin()) {
0365                 const auto prev = it - 1;
0366                 if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) {
0367                     input = Input::createFromOutput(prev->output);
0368                     prepend = true;
0369                 }
0370             }
0371 
0372             if (!input) {
0373                 if (QFile::exists(cFile.baseName)) {
0374                     input = Input::createFromFile(cFile.baseName);
0375                 }
0376             }
0377 
0378             if (input) {
0379                 qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName;
0380                 std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
0381                 t->setInput(Input::createFromFile(cFile.fileName));
0382                 t->setSignedData(input);
0383                 t->setProtocol(cFile.protocol);
0384                 if (prepend) {
0385                     // Put the verify task BEFORE the decrypt task in the tasks queue,
0386                     // because the tasks are executed in reverse order!
0387                     tasks.insert(tasks.end() - 1, t);
0388                 } else {
0389                     tasks.push_back(t);
0390                 }
0391                 continue;
0392             } else {
0393                 // No signed data, maybe not a detached signature
0394             }
0395         }
0396 
0397         if (isDetachedSignature(cFile.classification)) {
0398             // Detached signature, try to find data or ask the user.
0399             QString signedDataFileName = cFile.baseName;
0400             if (!QFile::exists(signedDataFileName)) {
0401                 signedDataFileName = QFileDialog::getOpenFileName(nullptr,
0402                                                                   xi18n("Select the file to verify with the signature <filename>%1</filename>", fi.fileName()),
0403                                                                   fi.path());
0404             }
0405             if (signedDataFileName.isEmpty()) {
0406                 qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify aborted.";
0407             } else {
0408                 qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName;
0409                 std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
0410 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0411                 if (cFile.protocol == GpgME::OpenPGP) {
0412                     t->setSignatureFile(cFile.fileName);
0413                     t->setSignedFile(signedDataFileName);
0414                 } else {
0415                     t->setInput(Input::createFromFile(cFile.fileName));
0416                     t->setSignedData(Input::createFromFile(signedDataFileName));
0417                 }
0418 #else
0419                 t->setInput(Input::createFromFile(cFile.fileName));
0420                 t->setSignedData(Input::createFromFile(signedDataFileName));
0421 #endif
0422                 t->setProtocol(cFile.protocol);
0423                 tasks.push_back(t);
0424             }
0425             continue;
0426         }
0427 
0428         if (!mayBeAnyMessageType(cFile.classification)) {
0429             // Not a Message? Maybe there is a signature for this file?
0430             const auto signatures = findSignatures(cFile.fileName);
0431             bool foundSig = false;
0432             if (!signatures.empty()) {
0433                 for (const QString &sig : signatures) {
0434                     const auto classification = classify(sig);
0435                     qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification;
0436                     const auto proto = findProtocol(classification);
0437                     if (proto == GpgME::UnknownProtocol) {
0438                         qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess.";
0439                         continue;
0440                     }
0441                     foundSig = true;
0442                     std::shared_ptr<VerifyDetachedTask> t(new VerifyDetachedTask);
0443 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0444                     if (cFile.protocol == GpgME::OpenPGP) {
0445                         t->setSignatureFile(sig);
0446                         t->setSignedFile(cFile.fileName);
0447                     } else {
0448                         t->setInput(Input::createFromFile(sig));
0449                         t->setSignedData(Input::createFromFile(cFile.fileName));
0450                     }
0451 #else
0452                     t->setInput(Input::createFromFile(sig));
0453                     t->setSignedData(Input::createFromFile(cFile.fileName));
0454 #endif
0455                     t->setProtocol(proto);
0456                     tasks.push_back(t);
0457                 }
0458             }
0459             if (!foundSig) {
0460                 undetected << cFile.fileName;
0461                 qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected.";
0462             }
0463         } else {
0464             const FileOperationsPreferences fileOpSettings;
0465             // Any Message type so we have input and output.
0466             std::shared_ptr<Input> input;
0467 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0468             if (cFile.protocol != GpgME::OpenPGP) {
0469                 input = Input::createFromFile(cFile.fileName);
0470             }
0471 #else
0472             input = Input::createFromFile(cFile.fileName);
0473 #endif
0474 
0475             std::shared_ptr<ArchiveDefinition> ad;
0476             if (fileOpSettings.autoExtractArchives()) {
0477                 const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions();
0478                 ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName);
0479             }
0480 
0481             if (fileOpSettings.dontUseTmpDir()) {
0482                 if (!m_workDir) {
0483                     m_workDir = std::make_unique<QTemporaryDir>(heuristicBaseDirectory(fileNames) + QStringLiteral("/kleopatra-XXXXXX"));
0484                 }
0485                 if (!m_workDir->isValid()) {
0486                     qCDebug(KLEOPATRA_LOG) << heuristicBaseDirectory(fileNames) << "not a valid temporary directory.";
0487                     m_workDir.reset();
0488                 }
0489             }
0490             if (!m_workDir) {
0491                 m_workDir = std::make_unique<QTemporaryDir>();
0492             }
0493             qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory.";
0494 
0495             const auto wd = QDir(m_workDir->path());
0496 
0497             std::shared_ptr<Output> output;
0498             QString outputFilePath;
0499             if (ad) {
0500                 if ((ad->id() == QLatin1StringView{"tar"}) && archiveJobsCanBeUsed(cFile.protocol)) {
0501                     // we don't need an output
0502                 } else {
0503                     output = ad->createOutputFromUnpackCommand(cFile.protocol, ad->stripExtension(cFile.protocol, cFile.baseName), wd);
0504                 }
0505             } else {
0506                 outputFilePath = wd.absoluteFilePath(outputFileName(fi.fileName()));
0507 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0508                 if (cFile.protocol != GpgME::OpenPGP) {
0509                     output = Output::createFromFile(outputFilePath, false);
0510                 }
0511 #else
0512                 output = Output::createFromFile(outputFilePath, false);
0513 #endif
0514             }
0515 
0516             // If this might be opaque CMS signature, then try that. We already handled
0517             // detached CMS signature above
0518             const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification);
0519 
0520             if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) {
0521                 qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask";
0522                 std::shared_ptr<VerifyOpaqueTask> t(new VerifyOpaqueTask);
0523                 if (input) {
0524                     t->setInput(input);
0525                 }
0526                 if (output) {
0527                     t->setOutput(output);
0528                 }
0529                 t->setProtocol(cFile.protocol);
0530                 if (ad) {
0531                     t->setExtractArchive(true);
0532                     t->setInputFile(cFile.fileName);
0533                     if (output) {
0534                         t->setOutputDirectory(m_workDir->path());
0535                     } else {
0536                         // make gpgtar extract to a subfolder of the work directory based on the input file name
0537                         const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
0538                         t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
0539                     }
0540 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0541                 } else if (cFile.protocol == GpgME::OpenPGP) {
0542                     t->setInputFile(cFile.fileName);
0543                     t->setOutputFile(outputFilePath);
0544 #endif
0545                 }
0546                 tasks.push_back(t);
0547             } else {
0548                 // Any message. That is not an opaque signature needs to be
0549                 // decrypted. Verify we always do because we can't know if
0550                 // an encrypted message is also signed.
0551                 qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask";
0552                 std::shared_ptr<DecryptVerifyTask> t(new DecryptVerifyTask);
0553                 if (input) {
0554                     t->setInput(input);
0555                 }
0556                 if (output) {
0557                     t->setOutput(output);
0558                 }
0559                 t->setProtocol(cFile.protocol);
0560                 if (ad) {
0561                     t->setExtractArchive(true);
0562                     t->setInputFile(cFile.fileName);
0563                     if (output) {
0564                         t->setOutputDirectory(m_workDir->path());
0565                     } else {
0566                         // make gpgtar extract to a subfolder of the work directory based on the input file name
0567                         const auto baseFileName = QFileInfo{ad->stripExtension(cFile.protocol, cFile.baseName)}.fileName();
0568                         t->setOutputDirectory(QDir{m_workDir->path()}.filePath(baseFileName));
0569                     }
0570 #if QGPGME_FILE_JOBS_SUPPORT_DIRECT_FILE_IO
0571                 } else if (cFile.protocol == GpgME::OpenPGP) {
0572                     t->setInputFile(cFile.fileName);
0573                     t->setOutputFile(outputFilePath);
0574 #endif
0575                 }
0576                 cFile.output = output;
0577                 tasks.push_back(t);
0578             }
0579         }
0580     }
0581 
0582     return tasks;
0583 }
0584 
0585 void AutoDecryptVerifyFilesController::setFiles(const QStringList &files)
0586 {
0587     d->m_passedFiles = files;
0588 }
0589 
0590 AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent)
0591     : DecryptVerifyFilesController(parent)
0592     , d(new Private(this))
0593 {
0594 }
0595 
0596 AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr<const ExecutionContext> &ctx, QObject *parent)
0597     : DecryptVerifyFilesController(ctx, parent)
0598     , d(new Private(this))
0599 {
0600 }
0601 
0602 AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController()
0603 {
0604     qCDebug(KLEOPATRA_LOG);
0605 }
0606 
0607 void AutoDecryptVerifyFilesController::start()
0608 {
0609     d->exec();
0610 }
0611 
0612 void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op)
0613 {
0614     d->m_operation = op;
0615 }
0616 
0617 DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const
0618 {
0619     return d->m_operation;
0620 }
0621 
0622 void AutoDecryptVerifyFilesController::Private::cancelAllTasks()
0623 {
0624     // we just kill all runnable tasks - this will not result in
0625     // signal emissions.
0626     m_runnableTasks.clear();
0627 
0628     // a cancel() will result in a call to
0629     if (m_runningTask) {
0630         m_runningTask->cancel();
0631     }
0632 }
0633 
0634 void AutoDecryptVerifyFilesController::cancel()
0635 {
0636     qCDebug(KLEOPATRA_LOG) << this << __func__;
0637     try {
0638         d->m_errorDetected = true;
0639         if (d->m_dialog) {
0640             d->m_dialog->close();
0641         }
0642         d->cancelAllTasks();
0643     } catch (const std::exception &e) {
0644         qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what();
0645     }
0646 }
0647 
0648 void AutoDecryptVerifyFilesController::doTaskDone(const Task *task, const std::shared_ptr<const Task::Result> &result)
0649 {
0650     Q_ASSERT(task);
0651     Q_UNUSED(task)
0652 
0653     // We could just delete the tasks here, but we can't use
0654     // Qt::QueuedConnection here (we need sender()) and other slots
0655     // might not yet have executed. Therefore, we push completed tasks
0656     // into a burial container
0657 
0658     d->m_completedTasks.push_back(d->m_runningTask);
0659     d->m_runningTask.reset();
0660 
0661     if (const std::shared_ptr<const DecryptVerifyResult> &dvr = std::dynamic_pointer_cast<const DecryptVerifyResult>(result)) {
0662         d->m_results.push_back(dvr);
0663     }
0664 
0665     QTimer::singleShot(0, this, SLOT(schedule()));
0666 }
0667 #include "moc_autodecryptverifyfilescontroller.cpp"