diff --git a/src/crypto/gui/signencryptfileswizard.cpp b/src/crypto/gui/signencryptfileswizard.cpp index 8ee52b1c7..e7ad5b135 100644 --- a/src/crypto/gui/signencryptfileswizard.cpp +++ b/src/crypto/gui/signencryptfileswizard.cpp @@ -1,587 +1,600 @@ /* crypto/gui/signencryptfileswizard.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "signencryptfileswizard.h" #include "signencryptwidget.h" #include "newresultpage.h" #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto::Gui; enum Page { SigEncPageId, ResultPageId, NumPages }; class FileNameRequesterWithIcon : public QWidget { Q_OBJECT public: explicit FileNameRequesterWithIcon(QDir::Filters filter, QWidget *parent = nullptr) : QWidget(parent) { auto layout = new QHBoxLayout{this}; layout->setContentsMargins(0, 0, 0, 0); mIconLabel = new QLabel{this}; mRequester = new FileNameRequester{filter, this}; mRequester->setExistingOnly(false); layout->addWidget(mIconLabel); layout->addWidget(mRequester); setFocusPolicy(mRequester->focusPolicy()); setFocusProxy(mRequester); connect(mRequester, &FileNameRequester::fileNameChanged, this, &FileNameRequesterWithIcon::fileNameChanged); } void setIcon(const QIcon &icon) { mIconLabel->setPixmap(icon.pixmap(32, 32)); } void setFileName(const QString &name) { mRequester->setFileName(name); } QString fileName() const { return mRequester->fileName(); } Q_SIGNALS: void fileNameChanged(const QString &filename); protected: bool event(QEvent *e) override { if (e->type() == QEvent::ToolTipChange) { mRequester->setToolTip(toolTip()); } return QWidget::event(e); } private: QLabel *mIconLabel; FileNameRequester *mRequester; }; class SigEncPage: public QWizardPage { Q_OBJECT public: explicit SigEncPage(QWidget *parent = nullptr) : QWizardPage(parent), mParent((SignEncryptFilesWizard *) parent), mWidget(new SignEncryptWidget), mOutLayout(new QVBoxLayout), mOutputLabel{nullptr}, mArchive(false), - mUseOutputDir(false) + mUseOutputDir(false), + mSingleFile{true} { setTitle(i18nc("@title", "Sign / Encrypt Files")); auto vLay = new QVBoxLayout(this); vLay->setContentsMargins(0, 0, 0, 0); mWidget->setSignAsText(i18nc("@option:check on SignEncryptPage", "&Sign as:")); mWidget->setEncryptForMeText(i18nc("@option:check on SignEncryptPage", "Encrypt for &me:")); mWidget->setEncryptForOthersText(i18nc("@option:check on SignEncryptPage", "Encrypt for &others:")); mWidget->setEncryptWithPasswordText(i18nc("@option:check on SignEncryptPage", "Encrypt with &password. Anyone you share the password with can read the data.")); vLay->addWidget(mWidget); connect(mWidget, &SignEncryptWidget::operationChanged, this, &SigEncPage::updateCommitButton); connect(mWidget, &SignEncryptWidget::keysChanged, this, &SigEncPage::updateFileWidgets); auto outputGrp = new QGroupBox(i18nc("@title:group", "Output")); outputGrp->setLayout(mOutLayout); mPlaceholderWidget = new QLabel(i18n("Please select an action.")); mOutLayout->addWidget(mPlaceholderWidget); mOutputLabel = new QLabel(i18nc("@label on SignEncryptPage", "Output &files/folder:")); mOutLayout->addWidget(mOutputLabel); createRequesters(mOutLayout); mUseOutputDirChk = new QCheckBox(i18nc("@option:check on SignEncryptPage", "Encrypt / Sign &each file separately.")); mUseOutputDirChk->setToolTip(i18nc("@info:tooltip", "Keep each file separate instead of creating an archive for all.")); mOutLayout->addWidget(mUseOutputDirChk); connect (mUseOutputDirChk, &QCheckBox::toggled, this, [this] (bool state) { mUseOutputDir = state; - mArchive = !mUseOutputDir; + mArchive = !mUseOutputDir && !mSingleFile; updateFileWidgets(); }); vLay->addWidget(outputGrp); setMinimumHeight(300); } void setEncryptionPreset(bool value) { mWidget->setEncryptionChecked(value); } void setSigningPreset(bool value) { mWidget->setSigningChecked(value); } bool isComplete() const override { return !mWidget->currentOp().isNull(); } int nextId() const override { return ResultPageId; } void initializePage() override { setCommitPage(true); updateCommitButton(mWidget->currentOp()); } void setArchiveForced(bool archive) { mArchive = archive; setArchiveMutable(!archive); } void setArchiveMutable(bool archive) { mUseOutputDirChk->setVisible(archive); if (archive) { const KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); mUseOutputDirChk->setChecked(archCfg.readEntry("LastUseOutputDir", false)); } else { mUseOutputDirChk->setChecked(false); } } + void setSingleFile(bool singleFile) + { + mSingleFile = singleFile; + mArchive = !mUseOutputDir && !mSingleFile; + } + bool validatePage() override { bool sign = !mWidget->signKey().isNull(); bool encrypt = !mWidget->selfKey().isNull() || !mWidget->recipients().empty(); if (!mWidget->validate()) { return false; } mWidget->saveOwnKeys(); if (mUseOutputDirChk->isVisible()) { KConfigGroup archCfg(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); archCfg.writeEntry("LastUseOutputDir", mUseOutputDir); } if (sign && !encrypt && mArchive) { return KMessageBox::warningContinueCancel(this, xi18nc("@info", "Archiving in combination with sign-only currently requires what are known as opaque signatures - " "unlike detached ones, these embed the content in the signature." "This format is rather unusual. You might want to archive the files separately, " "and then sign the archive as one file with Kleopatra." "Future versions of Kleopatra are expected to also support detached signatures in this case."), i18nc("@title:window", "Unusual Signature Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("signencryptfileswizard-archive+sign-only-warning")) == KMessageBox::Continue; } else if (sign && !encrypt) { return true; } if (!mWidget->selfKey().isNull() || mWidget->encryptSymmetric()) { return true; } const auto recipientKeys = recipients(); const bool hasSecret = std::any_of(std::begin(recipientKeys), std::end(recipientKeys), [](const auto &k) { return k.hasSecret(); }); if (!hasSecret) { if (KMessageBox::warningContinueCancel(this, xi18nc("@info", "None of the recipients you are encrypting to seems to be your own." "This means that you will not be able to decrypt the data anymore, once encrypted." "Do you want to continue, or cancel to change the recipient selection?"), i18nc("@title:window", "Encrypt-To-Self Warning"), KStandardGuiItem::cont(), KStandardGuiItem::cancel(), QStringLiteral("warn-encrypt-to-non-self"), KMessageBox::Notify | KMessageBox::Dangerous) == KMessageBox::Cancel) { return false; } } return true; } std::vector recipients() const { return mWidget->recipients(); } /* In the future we might find a usecase for multiple * signers */ std::vector signers() const { const Key k = mWidget->signKey(); if (!k.isNull()) { return {k}; } return {}; } private: void createRequesters(QBoxLayout *lay) { static const QMap icons = { { SignEncryptFilesWizard::SignatureCMS, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::SignaturePGP, QStringLiteral("document-sign") }, { SignEncryptFilesWizard::CombinedPGP, QStringLiteral("document-edit-sign-encrypt") }, { SignEncryptFilesWizard::EncryptedPGP, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::EncryptedCMS, QStringLiteral("document-encrypt") }, { SignEncryptFilesWizard::Directory, QStringLiteral("folder") } }; static const QMap toolTips = { { SignEncryptFilesWizard::SignatureCMS, i18n("The S/MIME signature.") }, { SignEncryptFilesWizard::SignaturePGP, i18n("The signature.") }, { SignEncryptFilesWizard::CombinedPGP, i18n("The signed and encrypted file.") }, { SignEncryptFilesWizard::EncryptedPGP, i18n("The encrypted file.") }, { SignEncryptFilesWizard::EncryptedCMS, i18n("The S/MIME encrypted file.") }, { SignEncryptFilesWizard::Directory, i18n("Output directory.") } }; if (!mRequesters.empty()) { return; } for (auto kind : icons.keys()) { auto requesterWithIcon = new FileNameRequesterWithIcon{ kind == SignEncryptFilesWizard::Directory ? QDir::Dirs : QDir::Files, this}; requesterWithIcon->setIcon(QIcon::fromTheme(icons[kind])); requesterWithIcon->setToolTip(toolTips[kind]); lay->addWidget(requesterWithIcon); connect(requesterWithIcon, &FileNameRequesterWithIcon::fileNameChanged, this, [this, kind](const QString &newName) { mOutNames[kind] = newName; }); mRequesters.insert(kind, requesterWithIcon); } } public: void setOutputNames(const QMap &names) { Q_ASSERT(mOutNames.isEmpty()); for (auto it = std::begin(names); it != std::end(names); ++it) { mRequesters.value(it.key())->setFileName(it.value()); } mOutNames = names; updateFileWidgets(); } QMap outputNames() const { if (!mUseOutputDir) { auto ret = mOutNames; ret.remove(SignEncryptFilesWizard::Directory); return ret; } return mOutNames; } bool encryptSymmetric() const { return mWidget->encryptSymmetric(); } private Q_SLOTS: void updateCommitButton(const QString &label) { if (mParent->currentPage() != this) { return; } auto btn = mParent->button(QWizard::CommitButton); if (!label.isEmpty()) { mParent->setButtonText(QWizard::CommitButton, label); if (Kleo::gpgComplianceP("de-vs")) { bool de_vs = mWidget->isDeVsAndValid(); btn->setIcon(QIcon::fromTheme(de_vs ? QStringLiteral("security-high") : QStringLiteral("security-medium"))); btn->setStyleSheet(QStringLiteral("background-color: ") + (de_vs ? KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::PositiveBackground).color().name() : KColorScheme(QPalette::Active, KColorScheme::View).background(KColorScheme::NegativeBackground).color().name())); mParent->setLabelText(de_vs ? i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication possible.", Formatting::deVsString()) : i18nc("%1 is a placeholder for the name of a compliance mode. E.g. NATO RESTRICTED compliant or VS-NfD compliant", "%1 communication not possible.", Formatting::deVsString())); } } else { mParent->setButtonText(QWizard::CommitButton, i18n("Next")); btn->setIcon(QIcon()); btn->setStyleSheet(QString()); } Q_EMIT completeChanged(); } void updateFileWidgets() { if (mRequesters.isEmpty()) { return; } const std::vector recipients = mWidget->recipients(); const Key sigKey = mWidget->signKey(); const bool pgp = mWidget->encryptSymmetric() || std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::OpenPGP; }); const bool cms = std::any_of(std::cbegin(recipients), std::cend(recipients), [](const auto &k) { return k.protocol() == Protocol::CMS; }); mOutLayout->setEnabled(false); if (cms || pgp || !sigKey.isNull()) { mPlaceholderWidget->setVisible(false); mOutputLabel->setVisible(true); mRequesters[SignEncryptFilesWizard::SignatureCMS]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::CMS); mRequesters[SignEncryptFilesWizard::EncryptedCMS]->setVisible(!mUseOutputDir && cms); mRequesters[SignEncryptFilesWizard::CombinedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::EncryptedPGP]->setVisible(!mUseOutputDir && sigKey.protocol() != Protocol::OpenPGP && pgp); mRequesters[SignEncryptFilesWizard::SignaturePGP]->setVisible(!mUseOutputDir && sigKey.protocol() == Protocol::OpenPGP && !pgp); mRequesters[SignEncryptFilesWizard::Directory]->setVisible(mUseOutputDir); auto firstNotHidden = std::find_if(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { return !w->isHidden(); }); mOutputLabel->setBuddy(*firstNotHidden); } else { mPlaceholderWidget->setVisible(true); mOutputLabel->setVisible(false); std::for_each(std::cbegin(mRequesters), std::cend(mRequesters), [](auto w) { w->setVisible(false); }); mOutputLabel->setBuddy(nullptr); } mOutLayout->setEnabled(true); } private: SignEncryptFilesWizard *mParent; SignEncryptWidget *mWidget; QMap mOutNames; QMap mRequesters; QVBoxLayout *mOutLayout; QWidget *mPlaceholderWidget; QCheckBox *mUseOutputDirChk; QLabel *mOutputLabel; bool mArchive; bool mUseOutputDir; + bool mSingleFile; }; class ResultPage : public NewResultPage { Q_OBJECT public: explicit ResultPage(QWidget *parent = nullptr) : NewResultPage(parent), mParent((SignEncryptFilesWizard *) parent) { setTitle(i18nc("@title", "Results")); setSubTitle(i18nc("@title", "Status and progress of the crypto operations is shown here.")); } void initializePage() override { mParent->setLabelText(QString()); } private: SignEncryptFilesWizard *mParent; }; SignEncryptFilesWizard::SignEncryptFilesWizard(QWidget *parent, Qt::WindowFlags f) : QWizard(parent, f) { readConfig(); bool de_vs = Kleo::gpgComplianceP("de-vs"); #ifdef Q_OS_WIN // Enforce modern style to avoid vista style ugliness. setWizardStyle(QWizard::ModernStyle); #endif mSigEncPage = new SigEncPage(this); mResultPage = new ResultPage(this); connect(this, &QWizard::currentIdChanged, this, &SignEncryptFilesWizard::slotCurrentIdChanged); setPage(SigEncPageId, mSigEncPage); setPage(ResultPageId, mResultPage); setOptions(QWizard::IndependentPages | (de_vs ? QWizard::HaveCustomButton1 : QWizard::WizardOption(0)) | QWizard::NoBackButtonOnLastPage | QWizard::NoBackButtonOnStartPage); if (de_vs) { /* We use a custom button to display a label next to the buttons. */ auto btn = button(QWizard::CustomButton1); /* We style the button so that it looks and acts like a label. */ btn->setStyleSheet(QStringLiteral("border: none")); btn->setFocusPolicy(Qt::NoFocus); } } void SignEncryptFilesWizard::setLabelText(const QString &label) { button(QWizard::CommitButton)->setToolTip(label); setButtonText(QWizard::CustomButton1, label); } void SignEncryptFilesWizard::slotCurrentIdChanged(int id) { if (id == ResultPageId) { Q_EMIT operationPrepared(); } } SignEncryptFilesWizard::~SignEncryptFilesWizard() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void SignEncryptFilesWizard::setSigningPreset(bool preset) { mSigEncPage->setSigningPreset(preset); } void SignEncryptFilesWizard::setSigningUserMutable(bool mut) { if (mut == mSigningUserMutable) { return; } mSigningUserMutable = mut; } void SignEncryptFilesWizard::setEncryptionPreset(bool preset) { mSigEncPage->setEncryptionPreset(preset); } void SignEncryptFilesWizard::setEncryptionUserMutable(bool mut) { if (mut == mEncryptionUserMutable) { return; } mEncryptionUserMutable = mut; } void SignEncryptFilesWizard::setArchiveForced(bool archive) { mSigEncPage->setArchiveForced(archive); } void SignEncryptFilesWizard::setArchiveMutable(bool archive) { mSigEncPage->setArchiveMutable(archive); } +void SignEncryptFilesWizard::setSingleFile(bool singleFile) +{ + mSigEncPage->setSingleFile(singleFile); +} + std::vector SignEncryptFilesWizard::resolvedRecipients() const { return mSigEncPage->recipients(); } std::vector SignEncryptFilesWizard::resolvedSigners() const { return mSigEncPage->signers(); } void SignEncryptFilesWizard::setTaskCollection(const std::shared_ptr &coll) { mResultPage->setTaskCollection(coll); } void SignEncryptFilesWizard::setOutputNames(const QMap &map) const { mSigEncPage->setOutputNames(map); } QMap SignEncryptFilesWizard::outputNames() const { return mSigEncPage->outputNames(); } bool SignEncryptFilesWizard::encryptSymmetric() const { return mSigEncPage->encryptSymmetric(); } void SignEncryptFilesWizard::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::restoreWindowSize(windowHandle(), cfgGroup); // NOTICE: QWindow::setGeometry() does NOT impact the backing QWidget geometry even if the platform // window was created -> QTBUG-40584. We therefore copy the size here. // TODO: remove once this was resolved in QWidget QPA resize(windowHandle()->size()); } void SignEncryptFilesWizard::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openConfig(), "SignEncryptFilesWizard"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } #include "signencryptfileswizard.moc" diff --git a/src/crypto/gui/signencryptfileswizard.h b/src/crypto/gui/signencryptfileswizard.h index a0fcbce68..33af2e0aa 100644 --- a/src/crypto/gui/signencryptfileswizard.h +++ b/src/crypto/gui/signencryptfileswizard.h @@ -1,102 +1,104 @@ /* -*- mode: c++; c-basic-offset:4 -*- crypto/gui/signencryptfileswizard.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-FileCopyrightText: 2016 Bundesamt für Sicherheit in der Informationstechnik SPDX-FileContributor: Intevation GmbH SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include #include #include #include #include #include namespace GpgME { class Key; } namespace Kleo { namespace Crypto { class TaskCollection; } } class ResultPage; class SigEncPage; namespace Kleo { class SignEncryptFilesWizard : public QWizard { Q_OBJECT public: enum KindNames { SignatureCMS, SignaturePGP, CombinedPGP, EncryptedPGP, EncryptedCMS, Directory }; explicit SignEncryptFilesWizard(QWidget *parent = nullptr, Qt::WindowFlags f = {}); ~SignEncryptFilesWizard(); // Inputs void setSigningPreset(bool preset); void setSigningUserMutable(bool mut); void setEncryptionPreset(bool preset); void setEncryptionUserMutable(bool mut); void setArchiveForced(bool archive); void setArchiveMutable(bool archive); + void setSingleFile(bool singleFile); + void setOutputNames(const QMap &nameMap) const; QMap outputNames() const; void setTaskCollection(const std::shared_ptr &coll); // Outputs std::vector resolvedRecipients() const; std::vector resolvedSigners() const; bool encryptSymmetric() const; void setLabelText(const QString &label); protected: void readConfig(); void writeConfig(); Q_SIGNALS: void operationPrepared(); private Q_SLOTS: void slotCurrentIdChanged(int); private: SigEncPage *mSigEncPage = nullptr; ResultPage *mResultPage = nullptr; bool mSigningUserMutable = true; bool mEncryptionUserMutable = true; }; } diff --git a/src/crypto/signencryptfilescontroller.cpp b/src/crypto/signencryptfilescontroller.cpp index 4663001a7..1a789a350 100644 --- a/src/crypto/signencryptfilescontroller.cpp +++ b/src/crypto/signencryptfilescontroller.cpp @@ -1,702 +1,703 @@ /* -*- 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" #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() { std::vector > ads = ArchiveDefinition::getArchiveDefinitions(); Q_ASSERT(!ads.empty()); std::shared_ptr ad = ads.front(); const FileOperationsPreferences prefs; Q_FOREACH (const std::shared_ptr toCheck, ads) { if (toCheck->id() == prefs.archiveCommand()) { ad = toCheck; break; } } 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; } void SignEncryptFilesController::setFiles(const QStringList &files) { kleo_assert(!files.empty()); d->files = files; bool archive = false; if (files.size() > 1) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveAllowed); archive = true; } for (const auto &file: files) { if (QFileInfo(file).isDir()) { setOperationMode((operationMode() & ~ArchiveMask) | ArchiveForced); archive = true; break; } } d->ensureWizardCreated(); + d->wizard->setSingleFile(!archive); d->wizard->setOutputNames(buildOutputNames(files, archive)); } void SignEncryptFilesController::Private::slotWizardCanceled() { qCDebug(KLEOPATRA_LOG); 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 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->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); } kleo_assert(ad); const Protocol proto = pgp ? OpenPGP : CMS; task->setInputFileNames(files); 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; Q_FOREACH (const Key &k, recipients) { if (k.protocol() == GpgME::OpenPGP) { pgpRecipients.push_back(k); } else { cmsRecipients.push_back(k); } } Q_FOREACH (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 { Q_FOREACH (const QString &file, 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)); Q_FOREACH (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); 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"