diff --git a/src/conf/cryptooperationsconfigwidget.cpp b/src/conf/cryptooperationsconfigwidget.cpp index f54a5d6ea..6ef8a1dc8 100644 --- a/src/conf/cryptooperationsconfigwidget.cpp +++ b/src/conf/cryptooperationsconfigwidget.cpp @@ -1,373 +1,381 @@ /* cryptooperationsconfigwidget.cpp This file is part of kleopatra, the KDE key manager Copyright (c) 2010 Klarälvdalens Datakonsult AB 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Libkleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Libkleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "cryptooperationsconfigwidget.h" #include "kleopatra_debug.h" #include "emailoperationspreferences.h" #include "fileoperationspreferences.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Config; CryptoOperationsConfigWidget::CryptoOperationsConfigWidget(QWidget *p, Qt::WindowFlags f) : QWidget(p, f) { setupGui(); } static void resetDefaults() { auto config = QGpgME::cryptoConfig(); if (!config) { qCWarning(KLEOPATRA_LOG) << "Failed to obtain config"; return; } for (const auto &compName: config->componentList()) { auto comp = config->component(compName); if (!comp) { qCWarning(KLEOPATRA_LOG) << "Failed to find component:" << comp; return; } for (const auto &grpName: comp->groupList()) { auto grp = comp->group(grpName); if (!grp) { qCWarning(KLEOPATRA_LOG) << "Failed to find group:" << grp << "in component:" << compName; return; } for (const auto &entryName: grp->entryList()) { auto entry = grp->entry(entryName); if (!entry) { qCWarning(KLEOPATRA_LOG) << "Failed to find entry:" << entry << "in group:"<< grp << "in component:" << compName; return; } entry->resetToDefault(); } } } config->sync(true); return; } void CryptoOperationsConfigWidget::applyProfile(const QString &profile) { qCDebug(KLEOPATRA_LOG) << "Applying profile " << profile; if (profile == i18n("default")) { if (KMessageBox::warningYesNo( this, i18n("This means that every configuration option of the GnuPG System will be reset to its default."), i18n("Apply profile"), KStandardGuiItem::apply(), KStandardGuiItem::no()) != KMessageBox::Yes) { return; } resetDefaults(); return; } QDir datadir(QString::fromLocal8Bit(GpgME::dirInfo("datadir")) + QStringLiteral("/../doc/gnupg/examples")); const auto path = datadir.filePath(profile + QStringLiteral(".prf")); auto gpgconf = new QProcess; const auto ei = GpgME::engineInfo(GpgME::GpgConfEngine); Q_ASSERT (ei.fileName()); gpgconf->setProgram(QFile::decodeName(ei.fileName())); gpgconf->setProcessChannelMode(QProcess::MergedChannels); gpgconf->setArguments(QStringList() << QStringLiteral("--apply-profile") << path.toLocal8Bit()); connect(gpgconf, static_cast(&QProcess::finished), this, [this, gpgconf, profile] () { if (gpgconf->exitStatus() != QProcess::NormalExit) { KMessageBox::error(this, QStringLiteral("
%1
").arg(QString::fromLocal8Bit(gpgconf->readAll()))); delete gpgconf; return; } delete gpgconf; KMessageBox::information(this, i18nc("%1 is the name of the profile", "The configuration profile \"%1\" was applied.", profile), i18n("GnuPG Profile - Kleopatra")); auto config = QGpgME::cryptoConfig(); if (config) { config->clear(); } }); gpgconf->start(); } // Get a list of available profile files and add a configuration // group if there are any. void CryptoOperationsConfigWidget::setupProfileGui(QBoxLayout *layout) { qCDebug(KLEOPATRA_LOG) << "Engine version "; if (GpgME::engineInfo(GpgME::GpgEngine).engineVersion() < "2.1.20" || !layout) { // Profile support is new in 2.1.20 qCDebug(KLEOPATRA_LOG) << "Engine version false"; return; } QDir datadir(QString::fromLocal8Bit(GpgME::dirInfo("datadir")) + QStringLiteral("/../doc/gnupg/examples")); if (!datadir.exists()) { qCDebug(KLEOPATRA_LOG) << "Failed to find gnupg's example profile directory" << datadir.path(); return; } const auto profiles = datadir.entryInfoList(QStringList() << QStringLiteral("*.prf"), QDir::Readable | QDir::Files, QDir::Name); if (profiles.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "Failed to find any profiles in: " << datadir.path(); return; } auto genGrp = new QGroupBox(i18nc("@title", "General Operations")); auto profLayout = new QHBoxLayout; genGrp->setLayout(profLayout); layout->addWidget(genGrp); auto profLabel = new QLabel(i18n("GnuPG Profile:")); profLabel->setToolTip(i18n("A profile consists of various settings that can apply to multiple components of the GnuPG system.")); auto combo = new QComboBox; profLabel->setBuddy(combo); // We don't translate "default" here because the other profile names are // also not translated as they are taken directly from file. combo->addItem(i18n("default")); for (const auto &profile: profiles) { combo->addItem(profile.baseName()); } auto btn = new QPushButton(i18n("Apply")); profLayout->addWidget(profLabel); profLayout->addWidget(combo); profLayout->addWidget(btn); profLayout->addStretch(1); connect(btn, &QPushButton::clicked, this, [this, combo] () { applyProfile(combo->currentText()); }); } void CryptoOperationsConfigWidget::setupGui() { QVBoxLayout *baseLay = new QVBoxLayout(this); baseLay->setMargin(0); QGroupBox *mailGrp = new QGroupBox(i18n("EMail Operations")); QVBoxLayout *mailGrpLayout = new QVBoxLayout; mQuickSignCB = new QCheckBox(i18n("Don't confirm signing certificate if there is only one valid certificate for the identity")); mQuickEncryptCB = new QCheckBox(i18n("Don't confirm encryption certificates if there is exactly one valid certificate for each recipient")); mailGrpLayout->addWidget(mQuickSignCB); mailGrpLayout->addWidget(mQuickEncryptCB); mailGrp->setLayout(mailGrpLayout); baseLay->addWidget(mailGrp); QGroupBox *fileGrp = new QGroupBox(i18n("File Operations")); QVBoxLayout *fileGrpLay = new QVBoxLayout; mPGPFileExtCB = new QCheckBox(i18n("Create OpenPGP encrypted files with \".pgp\" file extensions instead of \".gpg\"")); mASCIIArmorCB = new QCheckBox(i18n("Create signed or encrypted files as text files.")); mASCIIArmorCB->setToolTip(i18nc("@info", "Set this option to encode encrypted or signed files as base64 encoded text. " "So that they can be opened with an editor or sent in a mail body. " "This will increase file size by one third.")); mAutoDecryptVerifyCB = new QCheckBox(i18n("Automatically start operation based on input detection for decrypt/verify.")); + mTmpDirCB = new QCheckBox(i18n("Create temporary decrypted files in the folder of the encrypted file.")); + mTmpDirCB->setToolTip(i18nc("@info", "Set this option to avoid using the users temporary directory.")); + fileGrpLay->addWidget(mPGPFileExtCB); fileGrpLay->addWidget(mAutoDecryptVerifyCB); fileGrpLay->addWidget(mASCIIArmorCB); + fileGrpLay->addWidget(mTmpDirCB); QGridLayout *comboLay = new QGridLayout; QLabel *chkLabel = new QLabel(i18n("Checksum program to use when creating checksum files:")); comboLay->addWidget(chkLabel, 0, 0); mChecksumDefinitionCB = new QComboBox; comboLay->addWidget(mChecksumDefinitionCB, 0, 1); QLabel *archLabel = new QLabel(i18n("Archive command to use when archiving files:")); comboLay->addWidget(archLabel, 1, 0); mArchiveDefinitionCB = new QComboBox; comboLay->addWidget(mArchiveDefinitionCB, 1, 1); fileGrpLay->addLayout(comboLay); + fileGrp->setLayout(fileGrpLay); baseLay->addWidget(fileGrp); setupProfileGui(baseLay); baseLay->addStretch(1); if (!GpgME::hasFeature(0, GpgME::BinaryAndFineGrainedIdentify)) { /* Auto handling requires a working identify in GpgME. * so that classify in kleoaptra can correctly detect the input.*/ mAutoDecryptVerifyCB->setVisible(false); } connect(mQuickSignCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); connect(mQuickEncryptCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); connect(mChecksumDefinitionCB, static_cast(&QComboBox::currentIndexChanged), this, &CryptoOperationsConfigWidget::changed); connect(mArchiveDefinitionCB, static_cast(&QComboBox::currentIndexChanged), this, &CryptoOperationsConfigWidget::changed); connect(mPGPFileExtCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); connect(mAutoDecryptVerifyCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); connect(mASCIIArmorCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); + connect(mTmpDirCB, &QCheckBox::toggled, this, &CryptoOperationsConfigWidget::changed); } CryptoOperationsConfigWidget::~CryptoOperationsConfigWidget() {} void CryptoOperationsConfigWidget::defaults() { EMailOperationsPreferences emailPrefs; emailPrefs.setDefaults(); mQuickSignCB->setChecked(emailPrefs.quickSignEMail()); mQuickEncryptCB->setChecked(emailPrefs.quickEncryptEMail()); FileOperationsPreferences filePrefs; filePrefs.setDefaults(); mPGPFileExtCB->setChecked(filePrefs.usePGPFileExt()); mAutoDecryptVerifyCB->setChecked(filePrefs.autoDecryptVerify()); if (mChecksumDefinitionCB->count()) { mChecksumDefinitionCB->setCurrentIndex(0); } if (mArchiveDefinitionCB->count()) { mArchiveDefinitionCB->setCurrentIndex(0); } } Q_DECLARE_METATYPE(std::shared_ptr) void CryptoOperationsConfigWidget::load() { const EMailOperationsPreferences emailPrefs; mQuickSignCB ->setChecked(emailPrefs.quickSignEMail()); mQuickEncryptCB->setChecked(emailPrefs.quickEncryptEMail()); const FileOperationsPreferences filePrefs; mPGPFileExtCB->setChecked(filePrefs.usePGPFileExt()); mAutoDecryptVerifyCB->setChecked(filePrefs.autoDecryptVerify()); mASCIIArmorCB->setChecked(filePrefs.addASCIIArmor()); + mTmpDirCB->setChecked(filePrefs.dontUseTmpDir()); const std::vector< std::shared_ptr > cds = ChecksumDefinition::getChecksumDefinitions(); const std::shared_ptr default_cd = ChecksumDefinition::getDefaultChecksumDefinition(cds); mChecksumDefinitionCB->clear(); mArchiveDefinitionCB->clear(); for (const std::shared_ptr &cd : cds) { mChecksumDefinitionCB->addItem(cd->label(), qVariantFromValue(cd)); if (cd == default_cd) { mChecksumDefinitionCB->setCurrentIndex(mChecksumDefinitionCB->count() - 1); } } const QString ad_default_id = filePrefs.archiveCommand(); // This is a weird hack but because we are a KCM we can't link // against ArchiveDefinition which pulls in loads of other classes. // So we do the parsing which archive definitions exist here ourself. if (KSharedConfigPtr config = KSharedConfig::openConfig(QStringLiteral("libkleopatrarc"))) { const QStringList groups = config->groupList().filter(QRegularExpression(QStringLiteral("^Archive Definition #"))); for (const QString &group : groups) { const KConfigGroup cGroup(config, group); const QString id = cGroup.readEntryUntranslated(QStringLiteral("id")); const QString name = cGroup.readEntry("Name"); mArchiveDefinitionCB->addItem(name, QVariant(id)); if (id == ad_default_id) { mArchiveDefinitionCB->setCurrentIndex(mArchiveDefinitionCB->count() - 1); } } } } void CryptoOperationsConfigWidget::save() { EMailOperationsPreferences emailPrefs; emailPrefs.setQuickSignEMail(mQuickSignCB ->isChecked()); emailPrefs.setQuickEncryptEMail(mQuickEncryptCB->isChecked()); emailPrefs.save(); FileOperationsPreferences filePrefs; filePrefs.setUsePGPFileExt(mPGPFileExtCB->isChecked()); filePrefs.setAutoDecryptVerify(mAutoDecryptVerifyCB->isChecked()); filePrefs.setAddASCIIArmor(mASCIIArmorCB->isChecked()); + filePrefs.setDontUseTmpDir(mTmpDirCB->isChecked()); const int idx = mChecksumDefinitionCB->currentIndex(); if (idx >= 0) { const std::shared_ptr cd = qvariant_cast< std::shared_ptr >(mChecksumDefinitionCB->itemData(idx)); ChecksumDefinition::setDefaultChecksumDefinition(cd); } const int aidx = mArchiveDefinitionCB->currentIndex(); if (aidx >= 0) { const QString id = mArchiveDefinitionCB->itemData(aidx).toString(); filePrefs.setArchiveCommand(id); } filePrefs.save(); } diff --git a/src/conf/cryptooperationsconfigwidget.h b/src/conf/cryptooperationsconfigwidget.h index 69b96d894..ef5d35dbd 100644 --- a/src/conf/cryptooperationsconfigwidget.h +++ b/src/conf/cryptooperationsconfigwidget.h @@ -1,80 +1,81 @@ /* cryptooperationsconfigwidget.h This file is part of kleopatra, the KDE key manager Copyright (c) 2010 Klarälvdalens Datakonsult AB Libkleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Libkleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #ifndef __KLEOPATRA_CONFIG_CRYPTOOPERATIONSCONFIGWIDGET_H__ #define __KLEOPATRA_CONFIG_CRYPTOOPERATIONSCONFIGWIDGET_H__ #include #include class QCheckBox; class QComboBox; class QBoxLayout; namespace Kleo { namespace Config { class CryptoOperationsConfigWidget : public QWidget { Q_OBJECT public: explicit CryptoOperationsConfigWidget(QWidget *parent = nullptr, Qt::WindowFlags f = 0); ~CryptoOperationsConfigWidget(); public Q_SLOTS: void load(); void save(); void defaults(); Q_SIGNALS: void changed(); private: void setupGui(); void setupProfileGui(QBoxLayout *layout); void applyProfile(const QString &profile); QCheckBox *mQuickEncryptCB, *mQuickSignCB, *mPGPFileExtCB, *mAutoDecryptVerifyCB, - *mASCIIArmorCB; + *mASCIIArmorCB, + *mTmpDirCB; QComboBox *mChecksumDefinitionCB, *mArchiveDefinitionCB; }; } } #endif // __KLEOPATRA_CONFIG_CRYPTOOPERATIONSCONFIGWIDGET_H__ diff --git a/src/crypto/autodecryptverifyfilescontroller.cpp b/src/crypto/autodecryptverifyfilescontroller.cpp index 098592966..3e4010291 100644 --- a/src/crypto/autodecryptverifyfilescontroller.cpp +++ b/src/crypto/autodecryptverifyfilescontroller.cpp @@ -1,530 +1,549 @@ /* -*- mode: c++; c-basic-offset:4 -*- autodecryptverifyfilescontroller.cpp This file is part of Kleopatra, the KDE keymanager Copyright (c) 2008 Klarälvdalens Datakonsult AB 2016 by Bundesamt für Sicherheit in der Informationstechnik Software engineering by Intevation GmbH Kleopatra is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. Kleopatra is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA In addition, as a special exception, the copyright holders give permission to link the code of this program with any edition of the Qt library by Trolltech AS, Norway (or with modified versions of Qt that use the same license as Qt), and distribute linked combinations including the two. You must obey the GNU General Public License in all respects for all of the code used other than Qt. If you modify this file, you may extend this exception to your version of the file, but you are not obligated to do so. If you do not wish to do so, delete this exception statement from your version. */ #include #include "autodecryptverifyfilescontroller.h" +#include "fileoperationspreferences.h" + #include #include #include #include #include "commands/decryptverifyfilescommand.h" #include #include #include #include #include #include #include #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include #include #include using namespace GpgME; using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; class AutoDecryptVerifyFilesController::Private { AutoDecryptVerifyFilesController *const q; public: explicit Private(AutoDecryptVerifyFilesController *qq); - ~Private() { qCDebug(KLEOPATRA_LOG); } + ~Private() { + qCDebug(KLEOPATRA_LOG); + delete m_workDir; + } void slotDialogCanceled(); void schedule(); void exec(); std::vector > buildTasks(const QStringList &, QStringList &); struct CryptoFile { QString baseName; QString fileName; GpgME::Protocol protocol = GpgME::UnknownProtocol; int classification = 0; std::shared_ptr output; }; QVector classifyAndSortFiles(const QStringList &files); void reportError(int err, const QString &details) { q->setLastError(err, details); q->emitDoneOrError(); } void cancelAllTasks(); QStringList m_passedFiles, m_filesAfterPreparation; std::vector > m_results; std::vector > m_runnableTasks, m_completedTasks; std::shared_ptr m_runningTask; bool m_errorDetected; DecryptVerifyOperation m_operation; DecryptVerifyFilesDialog *m_dialog; - QTemporaryDir m_workDir; + QTemporaryDir *m_workDir; }; AutoDecryptVerifyFilesController::Private::Private(AutoDecryptVerifyFilesController *qq) : q(qq), m_errorDetected(false), m_operation(DecryptVerify), - m_dialog(nullptr) + m_dialog(nullptr), + m_workDir(nullptr) { qRegisterMetaType(); } void AutoDecryptVerifyFilesController::Private::slotDialogCanceled() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::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 std::shared_ptr &i : qAsConst(m_results)) { Q_EMIT q->verificationResult(i->verificationResult()); } } } void AutoDecryptVerifyFilesController::Private::exec() { Q_ASSERT(!m_dialog); QStringList undetected; std::vector > tasks = buildTasks(m_passedFiles, undetected); if (!undetected.isEmpty()) { // Since GpgME 1.7.0 Classification is supposed to be reliable // so we really can't do anything with this data. reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to find encrypted or signed data in one or more files." "You can manually select what to do with the files now." "If they contain signed or encrypted data please report a bug (see Help->Report Bug).")); auto cmd = new Commands::DecryptVerifyFilesCommand(undetected, nullptr, true); cmd->start(); } if (tasks.empty()) { q->emitDoneOrError(); return; } Q_ASSERT(m_runnableTasks.empty()); m_runnableTasks.swap(tasks); std::shared_ptr coll(new TaskCollection); Q_FOREACH (const std::shared_ptr &i, m_runnableTasks) { q->connectTask(i); } coll->setTasks(m_runnableTasks); m_dialog = new DecryptVerifyFilesDialog(coll); m_dialog->setOutputLocation(heuristicBaseDirectory(m_passedFiles)); QTimer::singleShot(0, q, SLOT(schedule())); if (m_dialog->exec() == QDialog::Accepted) { - const QDir workdir(m_workDir.path()); + Q_ASSERT(m_workDir); + const QDir workdir(m_workDir->path()); const QDir outDir(m_dialog->outputLocation()); bool overWriteAll = false; qCDebug(KLEOPATRA_LOG) << workdir.entryList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot); for (const QFileInfo &fi: workdir.entryInfoList(QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot)) { const auto inpath = fi.absoluteFilePath(); if (fi.isDir()) { // A directory. Assume that the input was an archive // and avoid directory merges by trying to find a non // existing directory. auto candidate = fi.baseName(); if (candidate.startsWith(QLatin1Char('-'))) { // Bug in GpgTar Extracts stdout passed archives to a dir named - candidate = QFileInfo(m_passedFiles.first()).baseName(); } QString suffix; QFileInfo ofi; int i = 0; do { ofi = QFileInfo(outDir.absoluteFilePath(candidate + suffix)); if (!ofi.exists()) { break; } suffix = QStringLiteral("_%1").arg(++i); } while (i < 1000); if (!moveDir(inpath, ofi.absoluteFilePath())) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, ofi.absoluteFilePath())); } continue; } const auto outpath = outDir.absoluteFilePath(fi.fileName()); qCDebug(KLEOPATRA_LOG) << "Moving " << inpath << " to " << outpath; const QFileInfo ofi(outpath); if (ofi.exists()) { int sel = KMessageBox::No; if (!overWriteAll) { sel = KMessageBox::questionYesNoCancel(m_dialog, i18n("The file %1 already exists.\n" "Overwrite?", outpath), i18n("Overwrite Existing File?"), KStandardGuiItem::overwrite(), KGuiItem(i18n("Overwrite All")), KStandardGuiItem::cancel()); } if (sel == KMessageBox::Cancel) { qCDebug(KLEOPATRA_LOG) << "Overwriting canceled for: " << outpath; continue; } if (sel == KMessageBox::No) { //Overwrite All overWriteAll = true; } if (!QFile::remove(outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to delete %1.", outpath)); continue; } } if (!QFile::rename(inpath, outpath)) { reportError(makeGnuPGError(GPG_ERR_GENERAL), xi18n("Failed to move %1 to %2.", inpath, outpath)); } } } q->emitDoneOrError(); delete m_dialog; m_dialog = nullptr; return; } QVector AutoDecryptVerifyFilesController::Private::classifyAndSortFiles(const QStringList &files) { const auto isSignature = [](int classification) -> bool { return mayBeDetachedSignature(classification) || mayBeOpaqueSignature(classification) || (classification & Class::TypeMask) == Class::ClearsignedMessage; }; QVector out; for (const auto &file : files) { CryptoFile cFile; cFile.fileName = file; cFile.baseName = file.left(file.length() - 4); cFile.classification = classify(file); cFile.protocol = findProtocol(cFile.classification); auto it = std::find_if(out.begin(), out.end(), [&cFile](const CryptoFile &other) { return other.protocol == cFile.protocol && other.baseName == cFile.baseName; }); if (it != out.end()) { // If we found a file with the same basename, make sure that encrypted // file is before the signature file, so that we first decrypt and then // verify if (isSignature(cFile.classification) && isCipherText(it->classification)) { out.insert(it + 1, cFile); } else if (isCipherText(cFile.classification) && isSignature(it->classification)) { out.insert(it, cFile); } else { // both are signatures or both are encrypted files, in which // case order does not matter out.insert(it, cFile); } } else { out.push_back(cFile); } } return out; } std::vector< std::shared_ptr > AutoDecryptVerifyFilesController::Private::buildTasks(const QStringList &fileNames, QStringList &undetected) { // sort files so that we make sure we first decrypt and then verify QVector cryptoFiles = classifyAndSortFiles(fileNames); std::vector > tasks; for (auto it = cryptoFiles.begin(), end = cryptoFiles.end(); it != end; ++it) { auto &cFile = (*it); QFileInfo fi(cFile.fileName); qCDebug(KLEOPATRA_LOG) << "classified" << cFile.fileName << "as" << printableClassification(cFile.classification); if (!fi.isReadable()) { reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("Cannot open %1 for reading.", cFile.fileName)); continue; } if (mayBeAnyCertStoreType(cFile.classification)) { // Trying to verify a certificate. Possible because extensions are often similar // for PGP Keys. reportError(makeGnuPGError(GPG_ERR_ASS_NO_INPUT), xi18n("The file %1 contains certificates and can't be decrypted or verified.", cFile.fileName)); qCDebug(KLEOPATRA_LOG) << "reported error"; continue; } // We can't reliably detect CMS detached signatures, so we will try to do // our best to use the current file as a detached signature and fallback to // opaque signature otherwise. if (cFile.protocol == GpgME::CMS && mayBeDetachedSignature(cFile.classification)) { // First, see if previous task was a decryption task for the same file // and "pipe" it's output into our input std::shared_ptr input; bool prepend = false; if (it != cryptoFiles.begin()) { const auto prev = it - 1; if (prev->protocol == cFile.protocol && prev->baseName == cFile.baseName) { input = Input::createFromOutput(prev->output); prepend = true; } } if (!input) { if (QFile::exists(cFile.baseName)) { input = Input::createFromFile(cFile.baseName); } } if (input) { qCDebug(KLEOPATRA_LOG) << "Detached CMS verify: " << cFile.fileName; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(input); t->setProtocol(cFile.protocol); if (prepend) { // Put the verify task BEFORE the decrypt task in the tasks queue, // because the tasks are executed in reverse order! tasks.insert(tasks.end() - 1, t); } else { tasks.push_back(t); } continue; } else { // No signed data, maybe not a detached signature } } if (isDetachedSignature(cFile.classification)) { // Detached signature, try to find data or ask the user. QString signedDataFileName = cFile.baseName; if (signedDataFileName.isEmpty()) { signedDataFileName = QFileDialog::getOpenFileName(nullptr, xi18n("Select the file to verify with \"%1\"", fi.fileName()), fi.dir().dirName()); } if (signedDataFileName.isEmpty()) { qCDebug(KLEOPATRA_LOG) << "No signed data selected. Verify abortet."; } else { qCDebug(KLEOPATRA_LOG) << "Detached verify: " << cFile.fileName << " Data: " << signedDataFileName; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(cFile.fileName)); t->setSignedData(Input::createFromFile(signedDataFileName)); t->setProtocol(cFile.protocol); tasks.push_back(t); } continue; } if (!mayBeAnyMessageType(cFile.classification)) { // Not a Message? Maybe there is a signature for this file? const auto signatures = findSignatures(cFile.fileName); bool foundSig = false; if (!signatures.empty()) { for (const QString &sig : signatures) { const auto classification = classify(sig); qCDebug(KLEOPATRA_LOG) << "Guessing: " << sig << " is a signature for: " << cFile.fileName << "Classification: " << classification; const auto proto = findProtocol(classification); if (proto == GpgME::UnknownProtocol) { qCDebug(KLEOPATRA_LOG) << "Could not determine protocol. Skipping guess."; continue; } foundSig = true; std::shared_ptr t(new VerifyDetachedTask); t->setInput(Input::createFromFile(sig)); t->setSignedData(Input::createFromFile(cFile.fileName)); t->setProtocol(proto); tasks.push_back(t); } } if (!foundSig) { undetected << cFile.fileName; qCDebug(KLEOPATRA_LOG) << "Failed detection for: " << cFile.fileName << " adding to undetected."; } } else { // Any Message type so we have input and output. const auto input = Input::createFromFile(cFile.fileName); const auto archiveDefinitions = ArchiveDefinition::getArchiveDefinitions(); const auto ad = q->pick_archive_definition(cFile.protocol, archiveDefinitions, cFile.fileName); - const auto wd = QDir(m_workDir.path()); + if (FileOperationsPreferences().dontUseTmpDir()) { + m_workDir = new QTemporaryDir(heuristicBaseDirectory(fileNames) + "/kleopatra-XXXXXX"); + if (!m_workDir->isValid()) { + qCDebug(KLEOPATRA_LOG) << m_workDir->path() << "not a valid temporary directory."; + delete m_workDir; + m_workDir = new QTemporaryDir(); + } + } else { + m_workDir = new QTemporaryDir(); + } + qCDebug(KLEOPATRA_LOG) << "Using:" << m_workDir->path() << "as temporary directory."; + + const auto wd = QDir(m_workDir->path()); const auto output = ad ? ad->createOutputFromUnpackCommand(cFile.protocol, cFile.fileName, wd) : /*else*/ Output::createFromFile(wd.absoluteFilePath(outputFileName(fi.fileName())), false); // If this might be opaque CMS signature, then try that. We already handled // detached CMS signature above const auto isCMSOpaqueSignature = cFile.protocol == GpgME::CMS && mayBeOpaqueSignature(cFile.classification); if (isOpaqueSignature(cFile.classification) || isCMSOpaqueSignature) { qCDebug(KLEOPATRA_LOG) << "creating a VerifyOpaqueTask"; std::shared_ptr t(new VerifyOpaqueTask); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); tasks.push_back(t); } else { // Any message. That is not an opaque signature needs to be // decrypted. Verify we always do because we can't know if // an encrypted message is also signed. qCDebug(KLEOPATRA_LOG) << "creating a DecryptVerifyTask"; std::shared_ptr t(new DecryptVerifyTask); t->setInput(input); t->setOutput(output); t->setProtocol(cFile.protocol); cFile.output = output; tasks.push_back(t); } } } return tasks; } void AutoDecryptVerifyFilesController::setFiles(const QStringList &files) { d->m_passedFiles = files; } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(QObject *parent) : DecryptVerifyFilesController(parent), d(new Private(this)) { } AutoDecryptVerifyFilesController::AutoDecryptVerifyFilesController(const std::shared_ptr &ctx, QObject *parent) : DecryptVerifyFilesController(ctx, parent), d(new Private(this)) { } AutoDecryptVerifyFilesController::~AutoDecryptVerifyFilesController() { qCDebug(KLEOPATRA_LOG); } void AutoDecryptVerifyFilesController::start() { d->exec(); } void AutoDecryptVerifyFilesController::setOperation(DecryptVerifyOperation op) { d->m_operation = op; } DecryptVerifyOperation AutoDecryptVerifyFilesController::operation() const { return d->m_operation; } void AutoDecryptVerifyFilesController::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 AutoDecryptVerifyFilesController::cancel() { qCDebug(KLEOPATRA_LOG); try { d->m_errorDetected = true; if (d->m_dialog) { d->m_dialog->close(); } d->cancelAllTasks(); } catch (const std::exception &e) { qCDebug(KLEOPATRA_LOG) << "Caught exception: " << e.what(); } } void AutoDecryptVerifyFilesController::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())); } #include "moc_autodecryptverifyfilescontroller.cpp" diff --git a/src/kcfg/fileoperationspreferences.kcfg b/src/kcfg/fileoperationspreferences.kcfg index b32c8ed3f..cd008ffcb 100644 --- a/src/kcfg/fileoperationspreferences.kcfg +++ b/src/kcfg/fileoperationspreferences.kcfg @@ -1,30 +1,35 @@ Set this to make kleopatra default to pgp file extensions for OpenPGP files. false With this option set Kleopatra no longer asks you what you want to do with input files but instead automatically starts the operations it detects as applicable to the input. true When encrypting multiple files or a folder Kleopatra creates an encrypted archive with this command. tar Set this option to encode encrypted or signed files as base64 encoded text. So that they can be opened with an editor or sent in a mail body. This will increase file size by one third. false + + + Set this option to avoid using the users temporary directory. + false +