diff --git a/src/crypto/gui/decryptverifyfilesdialog.cpp b/src/crypto/gui/decryptverifyfilesdialog.cpp index 52142da2e..fb2e31e3f 100644 --- a/src/crypto/gui/decryptverifyfilesdialog.cpp +++ b/src/crypto/gui/decryptverifyfilesdialog.cpp @@ -1,263 +1,263 @@ /* crypto/gui/decryptverifyfilesdialog.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2007 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 "decryptverifyfilesdialog.h" #include "kleopatra_debug.h" #include "crypto/decryptverifytask.h" #include "crypto/gui/resultlistwidget.h" #include "crypto/gui/resultpage.h" #include "crypto/taskcollection.h" #include "utils/path-helper.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Kleo; using namespace Kleo::Crypto; using namespace Kleo::Crypto::Gui; using namespace MimeTreeParser::Widgets; DecryptVerifyFilesDialog::DecryptVerifyFilesDialog(const std::shared_ptr &coll, QWidget *parent) : QDialog(parent) , m_tasks(coll) , m_buttonBox(new QDialogButtonBox) { readConfig(); auto vLay = new QVBoxLayout(this); auto labels = new QWidget; auto outputLayout = new QHBoxLayout; m_outputLocationFNR = new FileNameRequester; auto outLabel = new QLabel(i18n("&Output folder:")); outLabel->setBuddy(m_outputLocationFNR); outputLayout->addWidget(outLabel); outputLayout->addWidget(m_outputLocationFNR); m_outputLocationFNR->setFilter(QDir::Dirs); vLay->addLayout(outputLayout); m_progressLabelLayout = new QVBoxLayout(labels); vLay->addWidget(labels); m_progressBar = new QProgressBar; vLay->addWidget(m_progressBar); m_resultList = new ResultListWidget; connect(m_resultList, &ResultListWidget::showButtonClicked, this, &DecryptVerifyFilesDialog::showContent); vLay->addWidget(m_resultList); m_tasks = coll; Q_ASSERT(m_tasks); m_resultList->setTaskCollection(coll); connect(m_tasks.get(), &TaskCollection::progress, this, &DecryptVerifyFilesDialog::progress); connect(m_tasks.get(), &TaskCollection::done, this, &DecryptVerifyFilesDialog::allDone); connect(m_tasks.get(), &TaskCollection::started, this, &DecryptVerifyFilesDialog::started); connect(m_buttonBox, &QDialogButtonBox::rejected, this, &QDialog::reject); connect(m_buttonBox, &QDialogButtonBox::clicked, this, &DecryptVerifyFilesDialog::btnClicked); layout()->addWidget(m_buttonBox); bool hasOutputs = false; for (const auto &t : coll->tasks()) { if (!qobject_cast(t.get())) { hasOutputs = true; break; } } if (hasOutputs) { setWindowTitle(i18nc("@title:window", "Decrypt/Verify Files")); m_saveButton = QDialogButtonBox::SaveAll; m_buttonBox->addButton(QDialogButtonBox::Discard); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &DecryptVerifyFilesDialog::checkAccept); } else { outLabel->setVisible(false); m_outputLocationFNR->setVisible(false); setWindowTitle(i18nc("@title:window", "Verify Files")); m_buttonBox->addButton(QDialogButtonBox::Close); connect(m_buttonBox, &QDialogButtonBox::accepted, this, &QDialog::accept); } if (m_saveButton) { m_buttonBox->addButton(m_saveButton); m_buttonBox->button(m_saveButton)->setEnabled(false); } } DecryptVerifyFilesDialog::~DecryptVerifyFilesDialog() { qCDebug(KLEOPATRA_LOG); writeConfig(); } void DecryptVerifyFilesDialog::allDone() { qCDebug(KLEOPATRA_LOG) << "All done"; Q_ASSERT(m_tasks); m_progressBar->setRange(0, 100); m_progressBar->setValue(100); for (const auto &i : m_progressLabelByTag.keys()) { if (!i.isEmpty()) { m_progressLabelByTag.value(i)->setText(i18n("%1: All operations completed.", i)); } else { m_progressLabelByTag.value(i)->setText(i18n("All operations completed.")); } } if (m_tasks->allTasksHaveErrors()) { return; } if (m_saveButton != QDialogButtonBox::NoButton) { m_buttonBox->button(m_saveButton)->setEnabled(true); } else { m_buttonBox->removeButton(m_buttonBox->button(QDialogButtonBox::Close)); m_buttonBox->addButton(QDialogButtonBox::Ok); } } void DecryptVerifyFilesDialog::started(const std::shared_ptr &task) { Q_ASSERT(task); const auto tag = task->tag(); auto label = labelForTag(tag); Q_ASSERT(label); if (tag.isEmpty()) { label->setText(i18nc("number, operation description", "Operation %1: %2", m_tasks->numberOfCompletedTasks() + 1, task->label())); } else { label->setText(i18nc(R"(tag( "OpenPGP" or "CMS"), operation description)", "%1: %2", tag, task->label())); } if (m_saveButton != QDialogButtonBox::NoButton) { m_buttonBox->button(m_saveButton)->setEnabled(false); } else if (m_buttonBox->button(QDialogButtonBox::Ok)) { m_buttonBox->removeButton(m_buttonBox->button(QDialogButtonBox::Ok)); m_buttonBox->addButton(QDialogButtonBox::Close); } } QLabel *DecryptVerifyFilesDialog::labelForTag(const QString &tag) { if (QLabel *const label = m_progressLabelByTag.value(tag)) { return label; } auto label = new QLabel; label->setTextFormat(Qt::RichText); label->setWordWrap(true); m_progressLabelLayout->addWidget(label); m_progressLabelByTag.insert(tag, label); return label; } void DecryptVerifyFilesDialog::progress(int progress, int total) { Q_ASSERT(progress >= 0); Q_ASSERT(total >= 0); m_progressBar->setRange(0, total); m_progressBar->setValue(progress); } void DecryptVerifyFilesDialog::setOutputLocation(const QString &dir) { m_outputLocationFNR->setFileName(dir); } QString DecryptVerifyFilesDialog::outputLocation() const { return m_outputLocationFNR->fileName(); } void DecryptVerifyFilesDialog::btnClicked(QAbstractButton *btn) { if (m_buttonBox->buttonRole(btn) == QDialogButtonBox::DestructiveRole) { close(); } } void DecryptVerifyFilesDialog::checkAccept() { const auto outLoc = outputLocation(); if (outLoc.isEmpty()) { KMessageBox::information(this, i18n("Please select an output folder."), i18nc("@title:window", "No Output Folder")); return; } const QFileInfo fi(outLoc); if (!fi.exists()) { qCDebug(KLEOPATRA_LOG) << "Output dir does not exist. Trying to create."; const QDir dir(outLoc); if (!dir.mkdir(outLoc)) { KMessageBox::information( this, xi18nc("@info", "Failed to create output folder %1.Please select a different output folder.", outLoc), i18nc("@title:window", "Unusable Output Folder")); } else { accept(); } } else if (!fi.isDir()) { KMessageBox::information(this, i18n("Please select a different output folder."), i18nc("@title:window", "Invalid Output Folder")); - } else if (!fi.isWritable()) { + } else if (!Kleo::isWritable(fi)) { KMessageBox::information( this, xi18nc("@info", "Cannot write in the output folder %1.Please select a different output folder.", outLoc), i18nc("@title:window", "Unusable Output Folder")); } else { accept(); } } void DecryptVerifyFilesDialog::readConfig() { winId(); // ensure there's a window created // set default window size windowHandle()->resize(640, 480); // restore size from config file KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "DecryptVerifyFilesDialog"); 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 DecryptVerifyFilesDialog::writeConfig() { KConfigGroup cfgGroup(KSharedConfig::openStateConfig(), "DecryptVerifyFilesDialog"); KWindowConfig::saveWindowSize(windowHandle(), cfgGroup); cfgGroup.sync(); } void DecryptVerifyFilesDialog::showContent(const std::shared_ptr &result) { if (auto decryptVerifyResult = std::dynamic_pointer_cast(result)) { MessageViewerDialog dialog(decryptVerifyResult->fileName()); dialog.exec(); } } diff --git a/src/utils/path-helper.cpp b/src/utils/path-helper.cpp index 997e4e6f2..f8117279b 100644 --- a/src/utils/path-helper.cpp +++ b/src/utils/path-helper.cpp @@ -1,216 +1,195 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/path-helper.cpp This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #include #include "path-helper.h" #include #include #include "kleopatra_debug.h" #include #include #include #include #include #include - -#include - #ifdef Q_OS_WIN -extern Q_CORE_EXPORT int qt_ntfs_permission_lookup; +#include #endif +#include + using namespace Kleo; static QString commonPrefix(const QString &s1, const QString &s2) { return QString(s1.data(), std::mismatch(s1.data(), s1.data() + std::min(s1.size(), s2.size()), s2.data()).first - s1.data()); } static QString longestCommonPrefix(const QStringList &sl) { if (sl.empty()) { return QString(); } QString result = sl.front(); for (const QString &s : sl) { result = commonPrefix(s, result); } return result; } QString Kleo::heuristicBaseDirectory(const QStringList &fileNames) { QStringList dirs; for (const QString &fileName : fileNames) { dirs.push_back(QFileInfo(fileName).path() + QLatin1Char('/')); } qCDebug(KLEOPATRA_LOG) << "dirs" << dirs; const QString candidate = longestCommonPrefix(dirs); /* Special case handling for Outlook and KMail attachment temporary path. * This is otherwise something like: * c:\users\username\AppData\Local\Microsoft\Windows\INetCache\ * Content.Outlook\ADSDFG9\foo.txt * * For KMail it is usually /tmp/messageviewer/foo * * Both are paths that are unlikely to be the target path to save the * decrypted attachment. * * This is very common when encrypted attachments are opened * within Outlook or KMail. */ if (candidate.contains(QStringLiteral("Content.Outlook")) // || candidate.contains(QStringLiteral("messageviewer"))) { return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation); } const int idx = candidate.lastIndexOf(QLatin1Char('/')); return candidate.left(idx); } QStringList Kleo::makeRelativeTo(const QString &base, const QStringList &fileNames) { if (base.isEmpty()) { return fileNames; } else { return makeRelativeTo(QDir(base), fileNames); } } QStringList Kleo::makeRelativeTo(const QDir &baseDir, const QStringList &fileNames) { QStringList rv; rv.reserve(fileNames.size()); std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(rv), [&baseDir](const QString &file) { return baseDir.relativeFilePath(file); }); return rv; } QString Kleo::stripSuffix(const QString &fileName) { const QFileInfo fi(fileName); return fi.dir().filePath(fi.completeBaseName()); } -#ifdef Q_OS_WIN -namespace -{ -class NTFSPermissionsCheck -{ -public: - NTFSPermissionsCheck() - { - // enable the NTFS permissions check - qt_ntfs_permission_lookup++; - qCDebug(KLEOPATRA_LOG) << __func__ << "NTFS permissions check" << (qt_ntfs_permission_lookup ? "enabled" : "disabled"); - } - - ~NTFSPermissionsCheck() - { - // disable the NTFS permissions check - qt_ntfs_permission_lookup--; - qCDebug(KLEOPATRA_LOG) << __func__ << "NTFS permissions check" << (qt_ntfs_permission_lookup ? "enabled" : "disabled"); - } -}; -} -#endif - bool Kleo::isWritable(const QFileInfo &fi) { #ifdef Q_OS_WIN - NTFSPermissionsCheck withExpensiveNTFSPermissionsCheck; - const auto result = fi.isWritable(); - qCDebug(KLEOPATRA_LOG) << __func__ << fi.absoluteFilePath() << (result ? "is writable" : "is not writable"); - return result; -#else - return fi.isWritable(); + if (fi.isDir()) { + QTemporaryFile dummy{fi.absoluteFilePath() + QLatin1String{"/tempXXXXXX"}}; + const auto fileCreated = dummy.open(); + if (!fileCreated) { + qCDebug(KLEOPATRA_LOG) << "Failed to create test file in folder" << fi.absoluteFilePath(); + } + return fileCreated; + } #endif + return fi.isWritable(); } #ifdef Q_OS_WIN void Kleo::recursivelyRemovePath(const QString &path) { const QFileInfo fi(path); if (fi.isDir()) { QDir dir(path); const auto dirs{dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden)}; for (const QString &fname : dirs) { recursivelyRemovePath(dir.filePath(fname)); } const QString dirName = fi.fileName(); dir.cdUp(); if (!dir.rmdir(dirName)) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove directory %1", path)); } } else { QFile file(path); if (!file.remove()) { throw Exception(GPG_ERR_EPERM, i18n("Cannot remove file %1: %2", path, file.errorString())); } } } bool Kleo::recursivelyCopy(const QString &src, const QString &dest) { QDir srcDir(src); if (!srcDir.exists()) { return false; } QDir destDir(dest); if (!destDir.exists() && !destDir.mkdir(dest)) { return false; } for (const auto &file : srcDir.entryList(QDir::Files | QDir::Hidden)) { const QString srcName = src + QLatin1Char('/') + file; const QString destName = dest + QLatin1Char('/') + file; if (!QFile::copy(srcName, destName)) { return false; } } for (const auto &dir : srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden)) { const QString srcName = src + QLatin1Char('/') + dir; const QString destName = dest + QLatin1Char('/') + dir; if (!recursivelyCopy(srcName, destName)) { return false; } } return true; } bool Kleo::moveDir(const QString &src, const QString &dest) { // Need an existing path to query the device const QString parentDest = QFileInfo(dest).dir().absolutePath(); const auto srcDevice = QStorageInfo(src).device(); if (!srcDevice.isEmpty() // && srcDevice == QStorageInfo(parentDest).device() // && QFile::rename(src, dest)) { qCDebug(KLEOPATRA_LOG) << "Renamed" << src << "to" << dest; return true; } // first copy if (!recursivelyCopy(src, dest)) { return false; } // Then delete original recursivelyRemovePath(src); return true; } #endif diff --git a/src/utils/path-helper.h b/src/utils/path-helper.h index bebae977a..d64275c9d 100644 --- a/src/utils/path-helper.h +++ b/src/utils/path-helper.h @@ -1,41 +1,40 @@ /* -*- mode: c++; c-basic-offset:4 -*- utils/path-helper.h This file is part of Kleopatra, the KDE keymanager SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once #include class QString; #include class QDir; class QFileInfo; namespace Kleo { QString heuristicBaseDirectory(const QStringList &files); QStringList makeRelativeTo(const QDir &dir, const QStringList &files); QStringList makeRelativeTo(const QString &dir, const QStringList &files); QString stripSuffix(const QString &fileName); /** * Checks if the file/directory referenced by \p fi is writable. * - * On Windows, this enables the NTFS permissions check which is an expensive - * operation. Only use this in the main thread. + * On Windows, a temporary file is created to check if a directory is writable. * \sa QFileInfo::isWritable */ bool isWritable(const QFileInfo &fi); #ifdef Q_OS_WIN void recursivelyRemovePath(const QString &path); bool recursivelyCopy(const QString &src, const QString &dest); bool moveDir(const QString &src, const QString &dest); #endif }