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"