diff --git a/src/crypto/decryptverifyfilescontroller.cpp b/src/crypto/decryptverifyfilescontroller.cpp index 1ee764efb..b18b416bc 100644 --- a/src/crypto/decryptverifyfilescontroller.cpp +++ b/src/crypto/decryptverifyfilescontroller.cpp @@ -1,445 +1,445 @@ /* -*- 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))); + std::vector > tasks = buildTasks(m_filesAfterPreparation, std::make_shared(m_wizard, OverwritePolicy::MultipleFiles)); 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) << this << __func__; q->cancel(); 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(), 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) << this << __func__; 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/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index f83c8a188..a3a8de02b 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,742 +1,742 @@ /* -*- 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 "kleopatra_debug.h" #if QGPGME_SUPPORTS_ARCHIVE_JOBS #include #endif #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(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() { const std::vector> ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr ad = ads.front(); const FileOperationsPreferences prefs; const QString archiveCmd = prefs.archiveCommand(); auto it = std::find_if(ads.cbegin(), ads.cend(), [&archiveCmd](const std::shared_ptr &toCheck) { return toCheck->id() == archiveCmd; }); if (it != ads.cend()) { ad = *it; } 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; } // strips all trailing slashes from the filename, but keeps filename "/" static QString stripTrailingSlashes(const QString &fileName) { if (fileName.size() < 2 || !fileName.endsWith(QLatin1Char('/'))) { return fileName; } auto tmp = QStringView{fileName}.chopped(1); while (tmp.size() > 1 && tmp.endsWith(QLatin1Char('/'))) { tmp.chop(1); } return tmp.toString(); } static QStringList stripTrailingSlashesForAll(const QStringList &fileNames) { QStringList result; result.reserve(fileNames.size()); std::transform(fileNames.begin(), fileNames.end(), std::back_inserter(result), &stripTrailingSlashes); return result; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = stripTrailingSlashesForAll(files); bool archive = false; if (d->files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } for (const auto &file: d->files) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); d->wizard->setSingleFile(!archive); d->wizard->setOutputNames(buildOutputNames(d->files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG) << this << __func__; q->cancel(); 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 bool archiveJobsCanBeUsed([[maybe_unused]] GpgME::Protocol protocol) { #if QGPGME_SUPPORTS_ARCHIVE_JOBS return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); #else return false; #endif } 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->setCreateArchive(true); 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); } const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); if (!archiveJobsCanBeUsed(proto)) { // use legacy archive creation kleo_assert(ad); 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 std::vector recipients = wizard->resolvedRecipients(); const std::vector signers = wizard->resolvedSigners(); const FileOperationsPreferences prefs; const bool ascii = prefs.addASCIIArmor(); std::vector pgpRecipients, cmsRecipients, pgpSigners, cmsSigners; for (const Key &k : recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients.push_back(k); } else { cmsRecipients.push_back(k); } } for (const Key &k : signers) { if (k.protocol() == GpgME::OpenPGP) { pgpSigners.push_back(k); } else { cmsSigners.push_back(k); } } std::vector< std::shared_ptr > tasks; if (!archive) { tasks.reserve(files.size()); } if (archive) { tasks = createArchiveSignEncryptTasksForFiles(files, getDefaultAd(), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, wizard->outputNames(), wizard->encryptSymmetric()); } else { for (const QString &file : std::as_const(files)) { const std::vector< std::shared_ptr > created = createSignEncryptTasksForFileInfo(QFileInfo(file), ascii, pgpRecipients, pgpSigners, cmsRecipients, cmsSigners, buildOutputNamesForDir(file, wizard->outputNames()), wizard->encryptSymmetric()); tasks.insert(tasks.end(), created.begin(), created.end()); } } - const std::shared_ptr overwritePolicy(new OverwritePolicy(wizard)); + const auto overwritePolicy = std::make_shared(wizard, tasks.size() > 1 ? OverwritePolicy::MultipleFiles : OverwritePolicy::Options{}); for (const std::shared_ptr &i : tasks) { i->setOverwritePolicy(overwritePolicy); } kleo_assert(runnable.empty()); runnable.swap(tasks); for (const auto &task : std::as_const(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) << this << __func__; 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/signencrypttask.cpp b/src/crypto/signencrypttask.cpp index fa6204e4d..1172f1803 100644 --- a/src/crypto/signencrypttask.cpp +++ b/src/crypto/signencrypttask.cpp @@ -1,836 +1,834 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/signencrypttask.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 "signencrypttask.h" #include #include #include #include #include #include #include #include #include #include #include #include #if QGPGME_SUPPORTS_ARCHIVE_JOBS #include #include #include #endif #include #include #include #include #include "kleopatra_debug.h" #include #include #include // for Qt::escape using namespace Kleo; using namespace Kleo::Crypto; using namespace GpgME; namespace { QString formatInputOutputLabel(const QString &input, const QString &output, bool outputDeleted) { return i18nc("Input file --> Output file (rarr is arrow", "%1 → %2", input.toHtmlEscaped(), outputDeleted ? QStringLiteral("%1").arg(output.toHtmlEscaped()) : output.toHtmlEscaped()); } class ErrorResult : public Task::Result { public: ErrorResult(bool sign, bool encrypt, const Error &err, const QString &errStr, const QString &input, const QString &output, const AuditLogEntry &auditLog) : Task::Result(), m_sign(sign), m_encrypt(encrypt), m_error(err), m_errString(errStr), m_inputLabel(input), m_outputLabel(output), m_auditLog(auditLog) {} QString overview() const override; QString details() const override; GpgME::Error error() const override { return m_error; } QString errorString() const override { return m_errString; } VisualCode code() const override { return NeutralError; } AuditLogEntry auditLog() const override { return m_auditLog; } private: const bool m_sign; const bool m_encrypt; const Error m_error; const QString m_errString; const QString m_inputLabel; const QString m_outputLabel; const AuditLogEntry m_auditLog; }; namespace { struct LabelAndError { QString label; QString errorString; }; } class SignEncryptFilesResult : public Task::Result { public: SignEncryptFilesResult(const SigningResult &sr, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_sresult(sr), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull()); } SignEncryptFilesResult(const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_eresult(er), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_eresult.isNull()); } SignEncryptFilesResult(const SigningResult &sr, const EncryptionResult &er, const LabelAndError &input, const LabelAndError &output, bool outputCreated, const AuditLogEntry &auditLog) : Task::Result(), m_sresult(sr), m_eresult(er), m_input{input}, m_output{output}, m_outputCreated(outputCreated), m_auditLog(auditLog) { qCDebug(KLEOPATRA_LOG) << "\ninputError :" << m_input.errorString << "\noutputError:" << m_output.errorString; Q_ASSERT(!m_sresult.isNull() || !m_eresult.isNull()); } QString overview() const override; QString details() const override; GpgME::Error error() const override; QString errorString() const override; VisualCode code() const override; AuditLogEntry auditLog() const override; private: const SigningResult m_sresult; const EncryptionResult m_eresult; const LabelAndError m_input; const LabelAndError m_output; const bool m_outputCreated; const AuditLogEntry m_auditLog; }; static QString makeSigningOverview(const Error &err) { if (err.isCanceled()) { return i18n("Signing canceled."); } if (err) { return i18n("Signing failed."); } return i18n("Signing succeeded."); } static QString makeResultOverview(const SigningResult &result) { return makeSigningOverview(result.error()); } static QString makeEncryptionOverview(const Error &err) { if (err.isCanceled()) { return i18n("Encryption canceled."); } if (err) { return i18n("Encryption failed."); } return i18n("Encryption succeeded."); } static QString makeResultOverview(const EncryptionResult &result) { return makeEncryptionOverview(result.error()); } static QString makeResultOverview(const SigningResult &sr, const EncryptionResult &er) { if (er.isNull() && sr.isNull()) { return QString(); } if (er.isNull()) { return makeResultOverview(sr); } if (sr.isNull()) { return makeResultOverview(er); } if (sr.error().isCanceled() || sr.error()) { return makeResultOverview(sr); } if (er.error().isCanceled() || er.error()) { return makeResultOverview(er); } return i18n("Signing and encryption succeeded."); } static QString escape(QString s) { s = s.toHtmlEscaped(); s.replace(QLatin1Char('\n'), QStringLiteral("
")); return s; } static QString makeResultDetails(const SigningResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return Formatting::errorAsString(err).toHtmlEscaped(); } return QString(); } static QString makeResultDetails(const EncryptionResult &result, const QString &inputError, const QString &outputError) { const Error err = result.error(); if (err.code() == GPG_ERR_EIO) { if (!inputError.isEmpty()) { return i18n("Input error: %1", escape(inputError)); } else if (!outputError.isEmpty()) { return i18n("Output error: %1", escape(outputError)); } } if (err || err.isCanceled()) { return Formatting::errorAsString(err).toHtmlEscaped(); } return i18n(" Encryption succeeded."); } } QString ErrorResult::overview() const { Q_ASSERT(m_error || m_error.isCanceled()); Q_ASSERT(m_sign || m_encrypt); const QString label = formatInputOutputLabel(m_inputLabel, m_outputLabel, true); const bool canceled = m_error.isCanceled(); if (m_sign && m_encrypt) { return canceled ? i18n("%1: Sign/encrypt canceled.", label) : i18n(" %1: Sign/encrypt failed.", label); } return i18nc("label: result. Example: foo -> foo.gpg: Encryption failed.", "%1: %2", label, m_sign ? makeSigningOverview(m_error) : makeEncryptionOverview(m_error)); } QString ErrorResult::details() const { return m_errString; } class SignEncryptTask::Private { friend class ::Kleo::Crypto::SignEncryptTask; SignEncryptTask *const q; public: explicit Private(SignEncryptTask *qq); private: QString inputLabel() const; QString outputLabel() const; void startSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createSignJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptJob(GpgME::Protocol proto); std::unique_ptr createEncryptJob(GpgME::Protocol proto); #if QGPGME_SUPPORTS_ARCHIVE_JOBS void startSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignArchiveJob(GpgME::Protocol proto); std::unique_ptr createSignEncryptArchiveJob(GpgME::Protocol proto); std::unique_ptr createEncryptArchiveJob(GpgME::Protocol proto); #endif std::shared_ptr makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog); private: void slotResult(const SigningResult &); void slotResult(const SigningResult &, const EncryptionResult &); void slotResult(const EncryptionResult &); void slotResult(const QGpgME::Job *, const SigningResult &, const EncryptionResult &); private: std::shared_ptr input; std::shared_ptr output; QStringList inputFileNames; QString outputFileName; std::vector signers; std::vector recipients; bool sign : 1; bool encrypt : 1; bool detached : 1; bool symmetric: 1; bool clearsign: 1; bool archive : 1; QPointer job; QString labelText; std::shared_ptr m_overwritePolicy; }; SignEncryptTask::Private::Private(SignEncryptTask *qq) : q{qq} , sign{true} , encrypt{true} , detached{false} , clearsign{false} , archive{false} - , m_overwritePolicy{new OverwritePolicy{nullptr}} + , m_overwritePolicy{new OverwritePolicy{OverwritePolicy::Ask}} { q->setAsciiArmor(true); } std::shared_ptr SignEncryptTask::Private::makeErrorResult(const Error &err, const QString &errStr, const AuditLogEntry &auditLog) { return std::shared_ptr(new ErrorResult(sign, encrypt, err, errStr, inputLabel(), outputLabel(), auditLog)); } SignEncryptTask::SignEncryptTask(QObject *p) : Task(p), d(new Private(this)) { } SignEncryptTask::~SignEncryptTask() {} void SignEncryptTask::setInputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->inputFileNames = QStringList(fileName); } void SignEncryptTask::setInputFileNames(const QStringList &fileNames) { kleo_assert(!d->job); kleo_assert(!fileNames.empty()); d->inputFileNames = fileNames; } void SignEncryptTask::setInput(const std::shared_ptr &input) { kleo_assert(!d->job); kleo_assert(input); d->input = input; } void SignEncryptTask::setOutput(const std::shared_ptr &output) { kleo_assert(!d->job); kleo_assert(output); d->output = output; } void SignEncryptTask::setOutputFileName(const QString &fileName) { kleo_assert(!d->job); kleo_assert(!fileName.isEmpty()); d->outputFileName = fileName; } void SignEncryptTask::setSigners(const std::vector &signers) { kleo_assert(!d->job); d->signers = signers; } void SignEncryptTask::setRecipients(const std::vector &recipients) { kleo_assert(!d->job); d->recipients = recipients; } void SignEncryptTask::setOverwritePolicy(const std::shared_ptr &policy) { kleo_assert(!d->job); d->m_overwritePolicy = policy; } void SignEncryptTask::setSign(bool sign) { kleo_assert(!d->job); d->sign = sign; } void SignEncryptTask::setEncrypt(bool encrypt) { kleo_assert(!d->job); d->encrypt = encrypt; } void SignEncryptTask::setDetachedSignature(bool detached) { kleo_assert(!d->job); d->detached = detached; } void SignEncryptTask::setEncryptSymmetric(bool symmetric) { kleo_assert(!d->job); d->symmetric = symmetric; } void SignEncryptTask::setClearsign(bool clearsign) { kleo_assert(!d->job); d->clearsign = clearsign; } void SignEncryptTask::setCreateArchive(bool archive) { kleo_assert(!d->job); d->archive = archive; } Protocol SignEncryptTask::protocol() const { if (d->sign && !d->signers.empty()) { return d->signers.front().protocol(); } if (d->encrypt || d->symmetric) { if (!d->recipients.empty()) { return d->recipients.front().protocol(); } else { return GpgME::OpenPGP; // symmetric OpenPGP encryption } } throw Kleo::Exception(gpg_error(GPG_ERR_INTERNAL), i18n("Cannot determine protocol for task")); } QString SignEncryptTask::label() const { if (!d->labelText.isEmpty()) { return d->labelText; } return d->inputLabel(); } QString SignEncryptTask::tag() const { return Formatting::displayName(protocol()); } unsigned long long SignEncryptTask::inputSize() const { return d->input ? d->input->size() : 0U; } #if QGPGME_SUPPORTS_ARCHIVE_JOBS static bool archiveJobsCanBeUsed(GpgME::Protocol protocol) { return (protocol == GpgME::OpenPGP) && QGpgME::SignEncryptArchiveJob::isSupported(); } #endif void SignEncryptTask::doStart() { kleo_assert(!d->job); if (d->sign) { kleo_assert(!d->signers.empty()); if (d->archive) { kleo_assert(!d->detached && !d->clearsign); } } if (!d->output) { d->output = Output::createFromFile(d->outputFileName, d->m_overwritePolicy); } - // release grab on policy, so that output can infer multiple outputs from use count > 1 - d->m_overwritePolicy.reset(); const auto proto = protocol(); #if QGPGME_SUPPORTS_ARCHIVE_JOBS if (d->archive && archiveJobsCanBeUsed(proto)) { d->startSignEncryptArchiveJob(proto); } else #endif { d->startSignEncryptJob(proto); } } QString SignEncryptTask::Private::inputLabel() const { if (input) { return input->label(); } if (!inputFileNames.empty()) { const auto firstFile = QFileInfo{inputFileNames.front()}.fileName(); return inputFileNames.size() == 1 ? firstFile : i18nc(", ...", "%1, ...", firstFile); } return {}; } QString SignEncryptTask::Private::outputLabel() const { return output ? output->label() : QFileInfo{outputFileName}.fileName(); } void SignEncryptTask::Private::startSignEncryptJob(GpgME::Protocol proto) { kleo_assert(input); if (encrypt || symmetric) { Context::EncryptionFlags flags = Context::AlwaysTrust; if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { std::unique_ptr job = createSignEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_SUPPORTS_SET_FILENAME if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } #endif job->start(signers, recipients, input->ioDevice(), output->ioDevice(), flags); this->job = job.release(); } else { std::unique_ptr job = createEncryptJob(proto); kleo_assert(job.get()); #if QGPGME_SUPPORTS_SET_FILENAME if (inputFileNames.size() == 1) { job->setFileName(inputFileNames.front()); } #endif job->start(recipients, input->ioDevice(), output->ioDevice(), flags); this->job = job.release(); } } else if (sign) { std::unique_ptr job = createSignJob(proto); kleo_assert(job.get()); kleo_assert(! (detached && clearsign)); job->start(signers, input->ioDevice(), output->ioDevice(), detached ? GpgME::Detached : clearsign ? GpgME::Clearsigned : GpgME::NormalSignatureMode); this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } void SignEncryptTask::cancel() { qCDebug(KLEOPATRA_LOG) << this << __func__; if (d->job) { d->job->slotCancel(); } } std::unique_ptr SignEncryptTask::Private::createSignJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(signJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(signJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(signJob.get(), SIGNAL(result(GpgME::SigningResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult))); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(signEncryptJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(signEncryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(signEncryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(signEncryptJob.get(), SIGNAL(result(GpgME::SigningResult,GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::SigningResult,GpgME::EncryptionResult))); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptJob(q->asciiArmor(), /*textmode=*/false)); kleo_assert(encryptJob.get()); #if QGPGME_JOB_HAS_NEW_PROGRESS_SIGNALS connect(encryptJob.get(), &QGpgME::Job::jobProgress, q, &SignEncryptTask::setProgress); #else connect(encryptJob.get(), &QGpgME::Job::progress, q, [this](const QString &, int processed, int total) { q->setProgress(processed, total); }); #endif connect(encryptJob.get(), SIGNAL(result(GpgME::EncryptionResult,QByteArray)), q, SLOT(slotResult(GpgME::EncryptionResult))); return encryptJob; } #if QGPGME_SUPPORTS_ARCHIVE_JOBS void SignEncryptTask::Private::startSignEncryptArchiveJob(GpgME::Protocol proto) { kleo_assert(!input); const auto baseDirectory = heuristicBaseDirectory(inputFileNames); if (baseDirectory.isEmpty()) { throw Kleo::Exception(GPG_ERR_CONFLICT, i18n("Cannot find common base directory for these files:\n%1", inputFileNames.join(QLatin1Char('\n')))); } qCDebug(KLEOPATRA_LOG) << "heuristicBaseDirectory(" << inputFileNames << ") ->" << baseDirectory; const auto tempPaths = makeRelativeTo(baseDirectory, inputFileNames); const auto relativePaths = std::vector{tempPaths.begin(), tempPaths.end()}; qCDebug(KLEOPATRA_LOG) << "relative paths:" << relativePaths; if (encrypt || symmetric) { Context::EncryptionFlags flags = Context::AlwaysTrust; if (symmetric) { flags = static_cast(flags | Context::Symmetric); qCDebug(KLEOPATRA_LOG) << "Adding symmetric flag"; } if (sign) { labelText = i18nc("@info", "Creating signed and encrypted archive ..."); std::unique_ptr job = createSignEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(signers, recipients, relativePaths, output->ioDevice(), flags); this->job = job.release(); } else { labelText = i18nc("@info", "Creating encrypted archive ..."); std::unique_ptr job = createEncryptArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(recipients, relativePaths, output->ioDevice(), flags); this->job = job.release(); } } else if (sign) { labelText = i18nc("@info", "Creating signed archive ..."); std::unique_ptr job = createSignArchiveJob(proto); kleo_assert(job.get()); job->setBaseDirectory(baseDirectory); job->start(signers, relativePaths, output->ioDevice()); this->job = job.release(); } else { kleo_assert(!"Either 'sign' or 'encrypt' or 'symmetric' must be set!"); } } std::unique_ptr SignEncryptTask::Private::createSignArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signJob(backend->signArchiveJob(q->asciiArmor())); auto job = signJob.get(); kleo_assert(job); connect(job, &QGpgME::SignArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult) { slotResult(job, signResult, EncryptionResult{}); }); return signJob; } std::unique_ptr SignEncryptTask::Private::createSignEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr signEncryptJob(backend->signEncryptArchiveJob(q->asciiArmor())); auto job = signEncryptJob.get(); kleo_assert(job); connect(job, &QGpgME::SignEncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::SignEncryptArchiveJob::result, q, [this, job](const GpgME::SigningResult &signResult, const GpgME::EncryptionResult &encryptResult) { slotResult(job, signResult, encryptResult); }); return signEncryptJob; } std::unique_ptr SignEncryptTask::Private::createEncryptArchiveJob(GpgME::Protocol proto) { const QGpgME::Protocol *const backend = (proto == GpgME::OpenPGP) ? QGpgME::openpgp() : QGpgME::smime(); kleo_assert(backend); std::unique_ptr encryptJob(backend->encryptArchiveJob(q->asciiArmor())); auto job = encryptJob.get(); kleo_assert(job); connect(job, &QGpgME::EncryptArchiveJob::dataProgress, q, &SignEncryptTask::setProgress); connect(job, &QGpgME::EncryptArchiveJob::result, q, [this, job](const GpgME::EncryptionResult &encryptResult) { slotResult(job, SigningResult{}, encryptResult); }); return encryptJob; } #endif void SignEncryptTask::Private::slotResult(const SigningResult &result) { slotResult(qobject_cast(q->sender()), result, EncryptionResult{}); } void SignEncryptTask::Private::slotResult(const SigningResult &sresult, const EncryptionResult &eresult) { slotResult(qobject_cast(q->sender()), sresult, eresult); } void SignEncryptTask::Private::slotResult(const EncryptionResult &result) { slotResult(qobject_cast(q->sender()), SigningResult{}, result); } void SignEncryptTask::Private::slotResult(const QGpgME::Job *job, const SigningResult &sresult, const EncryptionResult &eresult) { const AuditLogEntry auditLog = AuditLogEntry::fromJob(job); bool outputCreated = false; if (input && input->failed()) { output->cancel(); q->emitResult(makeErrorResult(Error::fromCode(GPG_ERR_EIO), i18n("Input error: %1", escape( input->errorString())), auditLog)); return; } else if (sresult.error().code() || eresult.error().code()) { output->cancel(); } else { try { kleo_assert(!sresult.isNull() || !eresult.isNull()); output->finalize(); outputCreated = true; if (input) { input->finalize(); } } catch (const GpgME::Exception &e) { q->emitResult(makeErrorResult(e.error(), QString::fromLocal8Bit(e.what()), auditLog)); return; } } const LabelAndError inputInfo{inputLabel(), input ? input->errorString() : QString{}}; const LabelAndError outputInfo{outputLabel(), output ? output->errorString() : QString{}}; q->emitResult(std::shared_ptr(new SignEncryptFilesResult(sresult, eresult, inputInfo, outputInfo, outputCreated, auditLog))); } QString SignEncryptFilesResult::overview() const { const QString files = formatInputOutputLabel(m_input.label, m_output.label, !m_outputCreated); return files + QLatin1String(": ") + makeOverview(makeResultOverview(m_sresult, m_eresult)); } QString SignEncryptFilesResult::details() const { return errorString(); } GpgME::Error SignEncryptFilesResult::error() const { if (m_sresult.error().code()) { return m_sresult.error(); } if (m_eresult.error().code()) { return m_eresult.error(); } return {}; } QString SignEncryptFilesResult::errorString() const { const bool sign = !m_sresult.isNull(); const bool encrypt = !m_eresult.isNull(); kleo_assert(sign || encrypt); if (sign && encrypt) { return m_sresult.error().code() ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) : m_eresult.error().code() ? makeResultDetails(m_eresult, m_input.errorString, m_output.errorString) : QString(); } return sign ? makeResultDetails(m_sresult, m_input.errorString, m_output.errorString) : /*else*/ makeResultDetails(m_eresult, m_input.errorString, m_output.errorString); } Task::Result::VisualCode SignEncryptFilesResult::code() const { if (m_sresult.error().isCanceled() || m_eresult.error().isCanceled()) { return Warning; } return (m_sresult.error().code() || m_eresult.error().code()) ? NeutralError : NeutralSuccess; } AuditLogEntry SignEncryptFilesResult::auditLog() const { return m_auditLog; } #include "moc_signencrypttask.cpp" diff --git a/src/utils/output.cpp b/src/utils/output.cpp index 1241d5517..6443bb443 100644 --- a/src/utils/output.cpp +++ b/src/utils/output.cpp @@ -1,814 +1,817 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/output.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 "output.h" #include "input_p.h" #include "detail_p.h" #include "kleo_assert.h" #include "kdpipeiodevice.h" #include "log.h" #include "cached.h" #include "overwritedialog.h" #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #ifdef Q_OS_WIN # include #endif #include using namespace Kleo; using namespace Kleo::_detail; static const int PROCESS_MAX_RUNTIME_TIMEOUT = -1; // no timeout static const int PROCESS_TERMINATE_TIMEOUT = 5 * 1000; // 5s class OverwritePolicy::Private { public: - Private(QWidget *p, OverwritePolicy::Policy pol) : policy(pol), widget(p) {} + Private(QWidget *p, OverwritePolicy::Options options_, OverwritePolicy::Policy pol) + : policy(pol) + , parentWidget(p) + , options{options_} + { + } + OverwritePolicy::Policy policy; - QWidget *widget; + QWidget *parentWidget; + OverwritePolicy::Options options; }; -OverwritePolicy::OverwritePolicy(QWidget *parent, Policy initialPolicy) : d(new Private(parent, initialPolicy)) +OverwritePolicy::OverwritePolicy(Policy initialPolicy) + : d{new Private{nullptr, {}, initialPolicy}} +{ +} + +OverwritePolicy::OverwritePolicy(QWidget *parent, OverwritePolicy::Options options) + : d{new Private{parent, options, Ask}} { } -OverwritePolicy::~OverwritePolicy() {} +OverwritePolicy::~OverwritePolicy() = default; OverwritePolicy::Policy OverwritePolicy::policy() const { return d->policy; } void OverwritePolicy::setPolicy(Policy policy) { d->policy = policy; } -QWidget *OverwritePolicy::parentWidget() const -{ - return d->widget; -} - namespace { class TemporaryFile : public QTemporaryFile { public: using QTemporaryFile::QTemporaryFile; void close() override { if (isOpen()) { m_oldFileName = fileName(); } QTemporaryFile::close(); } bool openNonInheritable() { if (!QTemporaryFile::open()) { return false; } #if defined(Q_OS_WIN) //QTemporaryFile (tested with 4.3.3) creates the file handle as inheritable. //The handle is then inherited by gpgsm, which prevents deletion of the temp file //in FileOutput::doFinalize() //There are no inheritable handles under wince return SetHandleInformation((HANDLE)_get_osfhandle(handle()), HANDLE_FLAG_INHERIT, 0); #endif return true; } QString oldFileName() const { return m_oldFileName; } private: QString m_oldFileName; }; template struct inhibit_close : T_IODevice { explicit inhibit_close() : T_IODevice() {} template explicit inhibit_close(T1 &t1) : T_IODevice(t1) {} /* reimp */ void close() override {} void reallyClose() { T_IODevice::close(); } }; template struct redirect_close : T_IODevice { explicit redirect_close() : T_IODevice(), m_closed(false) {} template explicit redirect_close(T1 &t1) : T_IODevice(t1), m_closed(false) {} /* reimp */ void close() override { this->closeWriteChannel(); m_closed = true; } bool isClosed() const { return m_closed; } private: bool m_closed; }; class FileOutput; class OutputInput : public InputImplBase { public: OutputInput(const std::shared_ptr &output); unsigned int classification() const override { return 0; } void outputFinalized() { if (!m_ioDevice->open(QIODevice::ReadOnly)) { qCCritical(KLEOPATRA_LOG) << "Failed to open file for reading"; } } std::shared_ptr ioDevice() const override { return m_ioDevice; } unsigned long long size() const override { return 0; } private: std::shared_ptr m_output; mutable std::shared_ptr m_ioDevice = nullptr; }; class OutputImplBase : public Output { public: OutputImplBase() : Output(), m_defaultLabel(), m_customLabel(), m_errorString(), m_isFinalized(false), m_isFinalizing(false), m_cancelPending(false), m_canceled(false), m_binaryOpt(false) { } QString label() const override { return m_customLabel.isEmpty() ? m_defaultLabel : m_customLabel; } void setLabel(const QString &label) override { m_customLabel = label; } void setDefaultLabel(const QString &l) { m_defaultLabel = l; } void setBinaryOpt(bool value) override { m_binaryOpt = value; } bool binaryOpt() const override { return m_binaryOpt; } QString errorString() const override { if (m_errorString.dirty()) { m_errorString = doErrorString(); } return m_errorString; } bool isFinalized() const override { return m_isFinalized; } void finalize() override { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalized || m_isFinalizing) { return; } m_isFinalizing = true; try { doFinalize(); } catch (...) { m_isFinalizing = false; throw; } m_isFinalizing = false; m_isFinalized = true; if (m_cancelPending) { cancel(); } } void cancel() override { qCDebug(KLEOPATRA_LOG) << this; if (m_isFinalizing) { m_cancelPending = true; } else if (!m_canceled) { m_isFinalizing = true; try { doCancel(); } catch (...) {} m_isFinalizing = false; m_isFinalized = false; m_canceled = true; } } private: virtual QString doErrorString() const { if (std::shared_ptr io = ioDevice()) { return io->errorString(); } else { return i18n("No output device"); } } virtual void doFinalize() = 0; virtual void doCancel() = 0; private: QString m_defaultLabel; QString m_customLabel; mutable cached m_errorString; bool m_isFinalized : 1; bool m_isFinalizing : 1; bool m_cancelPending : 1; bool m_canceled : 1; bool m_binaryOpt : 1; }; class PipeOutput : public OutputImplBase { public: explicit PipeOutput(assuan_fd_t fd); std::shared_ptr ioDevice() const override { return m_io; } void doFinalize() override { m_io->reallyClose(); } void doCancel() override { doFinalize(); } private: std::shared_ptr< inhibit_close > m_io; }; class ProcessStdInOutput : public OutputImplBase { public: explicit ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd); std::shared_ptr ioDevice() const override { return m_proc; } void doFinalize() override { /* Make sure the data is written in the output here. If this is not done the output will be written in small chunks trough the eventloop causing an unnecessary delay before the process has even a chance to work and finish. This delay is mainly noticeable on Windows where it can take ~30 seconds to write out a 10MB file in the 512 byte chunks gpgme serves. */ qCDebug(KLEOPATRA_LOG) << "Waiting for " << m_proc->bytesToWrite() << " Bytes to be written"; while (m_proc->waitForBytesWritten(PROCESS_MAX_RUNTIME_TIMEOUT)); if (!m_proc->isClosed()) { m_proc->close(); } m_proc->waitForFinished(PROCESS_MAX_RUNTIME_TIMEOUT); } bool failed() const override { if (!m_proc) { return false; } return !(m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0); } void doCancel() override { m_proc->terminate(); QTimer::singleShot(PROCESS_TERMINATE_TIMEOUT, m_proc.get(), &QProcess::kill); } QString label() const override; private: QString doErrorString() const override; private: const QString m_command; const QStringList m_arguments; const std::shared_ptr< redirect_close > m_proc; }; class FileOutput : public OutputImplBase { public: explicit FileOutput(const QString &fileName, const std::shared_ptr &policy); ~FileOutput() override { qCDebug(KLEOPATRA_LOG) << this; } QString label() const override { return QFileInfo(m_fileName).fileName(); } std::shared_ptr ioDevice() const override { return m_tmpFile; } void doFinalize() override; void doCancel() override { qCDebug(KLEOPATRA_LOG) << this; } QString fileName() const override { return m_fileName; } void attachInput(const std::shared_ptr &input) { m_attachedInput = std::weak_ptr(input); } -private: - // returns the file name to write to or an empty string if overwriting was declined - QString obtainOverwritePermission(const QString &fileName); - private: QString m_fileName; std::shared_ptr< TemporaryFile > m_tmpFile; const std::shared_ptr m_policy; std::weak_ptr m_attachedInput; }; #ifndef QT_NO_CLIPBOARD class ClipboardOutput : public OutputImplBase { public: explicit ClipboardOutput(QClipboard::Mode mode); QString label() const override; std::shared_ptr ioDevice() const override { return m_buffer; } void doFinalize() override; void doCancel() override {} private: QString doErrorString() const override { return QString(); } private: const QClipboard::Mode m_mode; std::shared_ptr m_buffer; }; #endif // QT_NO_CLIPBOARD class ByteArrayOutput: public OutputImplBase { public: explicit ByteArrayOutput(QByteArray *data): m_buffer(std::shared_ptr(new QBuffer(data))) { if (!m_buffer->open(QIODevice::WriteOnly)) throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not open bytearray for writing?!")); } QString label() const override { return m_label; } void setLabel(const QString &label) override { m_label = label; } std::shared_ptr ioDevice() const override { return m_buffer; } void doFinalize() override { m_buffer->close(); } void doCancel() override { m_buffer->close(); } private: QString doErrorString() const override { return QString(); } private: QString m_label; std::shared_ptr m_buffer; }; } std::shared_ptr Output::createFromPipeDevice(assuan_fd_t fd, const QString &label) { std::shared_ptr po(new PipeOutput(fd)); po->setDefaultLabel(label); return po; } PipeOutput::PipeOutput(assuan_fd_t fd) : OutputImplBase(), m_io(new inhibit_close) { errno = 0; if (!m_io->open(fd, QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not open FD %1 for writing", assuanFD2int(fd))); } std::shared_ptr Output::createFromFile(const QString &fileName, bool forceOverwrite) { - return createFromFile(fileName, std::shared_ptr(new OverwritePolicy(nullptr, forceOverwrite ? OverwritePolicy::Overwrite : OverwritePolicy::Skip))); + return createFromFile(fileName, std::make_shared(forceOverwrite ? OverwritePolicy::Overwrite : OverwritePolicy::Skip)); } std::shared_ptr Output::createFromFile(const QString &fileName, const std::shared_ptr &policy) { std::shared_ptr fo(new FileOutput(fileName, policy)); qCDebug(KLEOPATRA_LOG) << fo.get(); return fo; } FileOutput::FileOutput(const QString &fileName, const std::shared_ptr &policy) : OutputImplBase(), m_fileName(fileName), m_tmpFile(new TemporaryFile(fileName)), m_policy(policy) { Q_ASSERT(m_policy); errno = 0; if (!m_tmpFile->openNonInheritable()) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not create temporary file for output \"%1\"", fileName)); } static QString suggestFileName(const QString &fileName) { const QFileInfo fileInfo{fileName}; const QString path = fileInfo.absolutePath(); const QString newFileName = KFileUtils::suggestName(QUrl::fromLocalFile(path), fileInfo.fileName()); return path + QLatin1Char{'/'} + newFileName; } -QString FileOutput::obtainOverwritePermission(const QString &fileName) +QString OverwritePolicy::obtainOverwritePermission(const QString &fileName) { - switch (m_policy->policy()) { + switch (d->policy) { case OverwritePolicy::Ask: break; case OverwritePolicy::Overwrite: return fileName; case OverwritePolicy::Rename: { return suggestFileName(fileName); } case OverwritePolicy::Skip: return {}; case OverwritePolicy::Cancel: qCDebug(KLEOPATRA_LOG) << __func__ << "Error: Called although user canceled operation"; return {}; } - qCDebug(KLEOPATRA_LOG) << __func__ << "m_policy.use_count():" << m_policy.use_count(); OverwriteDialog::Options options = OverwriteDialog::AllowRename; - if (m_policy.use_count() > 1) { + if (d->options & MultipleFiles) { options |= OverwriteDialog::MultipleItems | OverwriteDialog::AllowSkip; } - OverwriteDialog dialog{m_policy->parentWidget(), + OverwriteDialog dialog{d->parentWidget, i18nc("@title:window", "File Already Exists"), fileName, options}; const auto result = static_cast(dialog.exec()); qCDebug(KLEOPATRA_LOG) << __func__ << "result:" << static_cast(result); switch (result) { case OverwriteDialog::Cancel: - m_policy->setPolicy(OverwritePolicy::Cancel); + d->policy = OverwritePolicy::Cancel; return {}; case OverwriteDialog::AutoSkip: - m_policy->setPolicy(OverwritePolicy::Skip); + d->policy = OverwritePolicy::Skip; [[fallthrough]]; case OverwriteDialog::Skip: return {}; case OverwriteDialog::OverwriteAll: - m_policy->setPolicy(OverwritePolicy::Overwrite); + d->policy = OverwritePolicy::Overwrite; [[fallthrough]]; case OverwriteDialog::Overwrite: return fileName; case OverwriteDialog::Rename: return dialog.newFileName(); case OverwriteDialog::AutoRename: { - m_policy->setPolicy(OverwritePolicy::Rename); + d->policy = OverwritePolicy::Rename; return suggestFileName(fileName); } default: qCDebug(KLEOPATRA_LOG) << __func__ << "unexpected result:" << result; }; return {}; } void FileOutput::doFinalize() { qCDebug(KLEOPATRA_LOG) << this; struct Remover { QString file; ~Remover() { if (QFile::exists(file)) { QFile::remove(file); } } } remover; kleo_assert(m_tmpFile); if (m_tmpFile->isOpen()) { m_tmpFile->close(); } QString tmpFileName = m_tmpFile->oldFileName(); remover.file = tmpFileName; m_tmpFile->setAutoRemove(false); QPointer guard = m_tmpFile.get(); m_tmpFile.reset(); // really close the file - needed on Windows for renaming :/ kleo_assert(!guard); // if this triggers, we need to audit for holder of std::shared_ptrs. const QFileInfo fi(tmpFileName); if (!fi.exists()) { /* QT Bug 83365 since qt 5.13 causes the filename of temporary files * in UNC path name directories (unmounted samba shares) to start with * UNC/ instead of the // that Qt can actually handle for things like * rename and remove. So if we can't find our temporary file we try * to workaround that bug. */ qCDebug(KLEOPATRA_LOG) << "failure to find " << tmpFileName; if (tmpFileName.startsWith(QLatin1String("UNC"))) { tmpFileName.replace(0, strlen("UNC"), QLatin1Char('/')); remover.file = tmpFileName; } const QFileInfo fi2(tmpFileName); if (!fi2.exists()) { throw Exception(gpg_error(GPG_ERR_EIO), QStringLiteral("Could not find temporary file \"%1\".").arg(tmpFileName)); } } qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; { - const auto newFileName = obtainOverwritePermission(m_fileName); + const auto newFileName = m_policy->obtainOverwritePermission(m_fileName); if (newFileName.isEmpty()) { throw Exception(gpg_error(GPG_ERR_CANCELED), i18n("Overwriting declined")); } if (newFileName == m_fileName) { qCDebug(KLEOPATRA_LOG) << this << "going to remove file for overwriting" << m_fileName; if (!QFile::remove(m_fileName)) { throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), xi18nc("@info", "Could not remove file %1 for overwriting.", m_fileName)); } qCDebug(KLEOPATRA_LOG) << this << "removing file to overwrite succeeded"; } else { m_fileName = newFileName; } } qCDebug(KLEOPATRA_LOG) << this << "renaming" << tmpFileName << "->" << m_fileName; if (QFile::rename(tmpFileName, m_fileName)) { qCDebug(KLEOPATRA_LOG) << this << "succeeded"; if (!m_attachedInput.expired()) { m_attachedInput.lock()->outputFinalized(); } return; } qCDebug(KLEOPATRA_LOG) << this << "failed"; throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n(R"(Could not rename file "%1" to "%2")", tmpFileName, m_fileName)); } std::shared_ptr Output::createFromProcessStdIn(const QString &command) { return std::shared_ptr(new ProcessStdInOutput(command, QStringList(), QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args) { return std::shared_ptr(new ProcessStdInOutput(command, args, QDir::current())); } std::shared_ptr Output::createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &wd) { return std::shared_ptr(new ProcessStdInOutput(command, args, wd)); } ProcessStdInOutput::ProcessStdInOutput(const QString &cmd, const QStringList &args, const QDir &wd) : OutputImplBase(), m_command(cmd), m_arguments(args), m_proc(new redirect_close) { qCDebug(KLEOPATRA_LOG) << "cd" << wd.absolutePath() << '\n' << cmd << args; if (cmd.isEmpty()) throw Exception(gpg_error(GPG_ERR_INV_ARG), i18n("Command not specified")); m_proc->setWorkingDirectory(wd.absolutePath()); m_proc->start(cmd, args); m_proc->setReadChannel(QProcess::StandardError); if (!m_proc->waitForStarted()) throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not start %1 process: %2", cmd, m_proc->errorString())); } QString ProcessStdInOutput::label() const { if (!m_proc) { return OutputImplBase::label(); } // output max. 3 arguments const QString cmdline = (QStringList(m_command) + m_arguments.mid(0, 3)).join(QLatin1Char(' ')); if (m_arguments.size() > 3) { return i18nc("e.g. \"Input to tar xf - file1 ...\"", "Input to %1 ...", cmdline); } else { return i18nc("e.g. \"Input to tar xf - file\"", "Input to %1", cmdline); } } QString ProcessStdInOutput::doErrorString() const { kleo_assert(m_proc); if (m_proc->exitStatus() == QProcess::NormalExit && m_proc->exitCode() == 0) { return QString(); } if (m_proc->error() == QProcess::UnknownError) return i18n("Error while running %1: %2", m_command, QString::fromLocal8Bit(m_proc->readAllStandardError().trimmed().constData())); else { return i18n("Failed to execute %1: %2", m_command, m_proc->errorString()); } } #ifndef QT_NO_CLIPBOARD std::shared_ptr Output::createFromClipboard() { return std::shared_ptr(new ClipboardOutput(QClipboard::Clipboard)); } ClipboardOutput::ClipboardOutput(QClipboard::Mode mode) : OutputImplBase(), m_mode(mode), m_buffer(new QBuffer) { errno = 0; if (!m_buffer->open(QIODevice::WriteOnly)) throw Exception(errno ? gpg_error_from_errno(errno) : gpg_error(GPG_ERR_EIO), i18n("Could not write to clipboard")); } QString ClipboardOutput::label() const { switch (m_mode) { case QClipboard::Clipboard: return i18n("Clipboard"); case QClipboard::FindBuffer: return i18n("Find buffer"); case QClipboard::Selection: return i18n("Selection"); } return QString(); } void ClipboardOutput::doFinalize() { if (m_buffer->isOpen()) { m_buffer->close(); } if (QClipboard *const cb = QApplication::clipboard()) { cb->setText(QString::fromUtf8(m_buffer->data())); } else throw Exception(gpg_error(GPG_ERR_EIO), i18n("Could not find clipboard")); } #endif // QT_NO_CLIPBOARD Output::~Output() {} OutputInput::OutputInput(const std::shared_ptr &output) : m_output(output) , m_ioDevice(new QFile(output->fileName())) { } std::shared_ptr Input::createFromOutput(const std::shared_ptr &output) { if (auto fo = std::dynamic_pointer_cast(output)) { auto input = std::shared_ptr(new OutputInput(fo)); fo->attachInput(input); return input; } else { return {}; } } std::shared_ptr Output::createFromByteArray(QByteArray *data, const QString &label) { auto ret = std::shared_ptr(new ByteArrayOutput(data)); ret->setLabel(label); return ret; } diff --git a/src/utils/output.h b/src/utils/output.h index 8feced6f0..cb6810e58 100644 --- a/src/utils/output.h +++ b/src/utils/output.h @@ -1,83 +1,90 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/output.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include // for assuan_fd_t #include #include #include #include class QIODevice; class QDir; class QWidget; namespace Kleo { class OverwritePolicy { public: enum Policy { Ask, Overwrite, Rename, Skip, Cancel, }; - explicit OverwritePolicy(QWidget *parent, Policy initialPolicy = Ask); + enum Options { + MultipleFiles = 1, + }; + + explicit OverwritePolicy(Policy initialPolicy); + /// creates an interactive policy, i.e. with initial policy set to Ask + OverwritePolicy(QWidget *parent, Options options); ~OverwritePolicy(); Policy policy() const; void setPolicy(Policy); - QWidget *parentWidget() const; + /// returns the file name to write to or an empty string if overwriting was declined + QString obtainOverwritePermission(const QString &fileName); private: class Private; kdtools::pimpl_ptr d; }; class Output { public: virtual ~Output(); virtual void setLabel(const QString &label) = 0; virtual QString label() const = 0; virtual std::shared_ptr ioDevice() const = 0; virtual QString errorString() const = 0; virtual bool isFinalized() const = 0; virtual void finalize() = 0; virtual void cancel() = 0; virtual bool binaryOpt() const = 0; virtual void setBinaryOpt(bool value) = 0; /** Whether or not the output failed. */ virtual bool failed() const { return false; } virtual QString fileName() const { return {}; } static std::shared_ptr createFromFile(const QString &fileName, const std::shared_ptr &); static std::shared_ptr createFromFile(const QString &fileName, bool forceOverwrite); static std::shared_ptr createFromPipeDevice(assuan_fd_t fd, const QString &label); static std::shared_ptr createFromProcessStdIn(const QString &command); static std::shared_ptr createFromProcessStdIn(const QString &command, const QStringList &args); static std::shared_ptr createFromProcessStdIn(const QString &command, const QStringList &args, const QDir &workingDirectory); #ifndef QT_NO_CLIPBOARD static std::shared_ptr createFromClipboard(); #endif static std::shared_ptr createFromByteArray(QByteArray *data, const QString &label); }; }