File indexing completed on 2024-06-23 05:14:18

0001 /* -*- mode: c++; c-basic-offset:4 -*-
0002     utils/path-helper.cpp
0003 
0004     This file is part of Kleopatra, the KDE keymanager
0005     SPDX-FileCopyrightText: 2009 Klarälvdalens Datakonsult AB
0006 
0007     SPDX-License-Identifier: GPL-2.0-or-later
0008 */
0009 
0010 #include <config-kleopatra.h>
0011 
0012 #include "path-helper.h"
0013 
0014 #include <Libkleo/Stl_Util>
0015 
0016 #include <Libkleo/KleoException>
0017 
0018 #include "kleopatra_debug.h"
0019 #include <KLocalizedString>
0020 
0021 #include <QDir>
0022 #include <QFileInfo>
0023 #include <QStandardPaths>
0024 #include <QStorageInfo>
0025 #include <QString>
0026 #ifdef Q_OS_WIN
0027 #include <QTemporaryFile>
0028 #endif
0029 
0030 #include <algorithm>
0031 
0032 using namespace Kleo;
0033 
0034 static QString commonPrefix(const QString &s1, const QString &s2)
0035 {
0036     return QString(s1.data(), std::mismatch(s1.data(), s1.data() + std::min(s1.size(), s2.size()), s2.data()).first - s1.data());
0037 }
0038 
0039 static QString longestCommonPrefix(const QStringList &sl)
0040 {
0041     if (sl.empty()) {
0042         return QString();
0043     }
0044     QString result = sl.front();
0045     for (const QString &s : sl) {
0046         result = commonPrefix(s, result);
0047     }
0048     return result;
0049 }
0050 
0051 QString Kleo::heuristicBaseDirectory(const QStringList &fileNames)
0052 {
0053     QStringList dirs;
0054     for (const QString &fileName : fileNames) {
0055         dirs.push_back(QFileInfo(fileName).path() + QLatin1Char('/'));
0056     }
0057     qCDebug(KLEOPATRA_LOG) << "dirs" << dirs;
0058     const QString candidate = longestCommonPrefix(dirs);
0059     /* Special case handling for Outlook and KMail attachment temporary path.
0060      * This is otherwise something like:
0061      * c:\users\username\AppData\Local\Microsoft\Windows\INetCache\
0062      *      Content.Outlook\ADSDFG9\foo.txt
0063      *
0064      * For KMail it is usually /tmp/messageviewer/foo
0065      *
0066      * Both are paths that are unlikely to be the target path to save the
0067      * decrypted attachment.
0068      *
0069      * This is very common when encrypted attachments are opened
0070      * within Outlook or KMail.
0071      */
0072     if (candidate.contains(QStringLiteral("Content.Outlook")) //
0073         || candidate.contains(QStringLiteral("messageviewer"))) {
0074         return QStandardPaths::writableLocation(QStandardPaths::DocumentsLocation);
0075     }
0076     const int idx = candidate.lastIndexOf(QLatin1Char('/'));
0077     return candidate.left(idx);
0078 }
0079 
0080 QStringList Kleo::makeRelativeTo(const QString &base, const QStringList &fileNames)
0081 {
0082     if (base.isEmpty()) {
0083         return fileNames;
0084     } else {
0085         return makeRelativeTo(QDir(base), fileNames);
0086     }
0087 }
0088 
0089 QStringList Kleo::makeRelativeTo(const QDir &baseDir, const QStringList &fileNames)
0090 {
0091     QStringList rv;
0092     rv.reserve(fileNames.size());
0093     std::transform(fileNames.cbegin(), fileNames.cend(), std::back_inserter(rv), [&baseDir](const QString &file) {
0094         return baseDir.relativeFilePath(file);
0095     });
0096     return rv;
0097 }
0098 
0099 QString Kleo::stripSuffix(const QString &fileName)
0100 {
0101     const QFileInfo fi(fileName);
0102     return fi.dir().filePath(fi.completeBaseName());
0103 }
0104 
0105 bool Kleo::isWritable(const QFileInfo &fi)
0106 {
0107 #ifdef Q_OS_WIN
0108     if (fi.isDir()) {
0109         QTemporaryFile dummy{fi.absoluteFilePath() + QLatin1StringView{"/tempXXXXXX"}};
0110         const auto fileCreated = dummy.open();
0111         if (!fileCreated) {
0112             qCDebug(KLEOPATRA_LOG) << "Failed to create test file in folder" << fi.absoluteFilePath();
0113         }
0114         return fileCreated;
0115     }
0116 #endif
0117     return fi.isWritable();
0118 }
0119 
0120 #ifdef Q_OS_WIN
0121 void Kleo::recursivelyRemovePath(const QString &path)
0122 {
0123     const QFileInfo fi(path);
0124     if (fi.isDir()) {
0125         QDir dir(path);
0126         const auto dirs{dir.entryList(QDir::AllEntries | QDir::NoDotAndDotDot | QDir::Hidden)};
0127         for (const QString &fname : dirs) {
0128             recursivelyRemovePath(dir.filePath(fname));
0129         }
0130         const QString dirName = fi.fileName();
0131         dir.cdUp();
0132         if (!dir.rmdir(dirName)) {
0133             throw Exception(GPG_ERR_EPERM, i18n("Cannot remove directory %1", path));
0134         }
0135     } else {
0136         QFile file(path);
0137         if (!file.remove()) {
0138             throw Exception(GPG_ERR_EPERM, i18n("Cannot remove file %1: %2", path, file.errorString()));
0139         }
0140     }
0141 }
0142 
0143 bool Kleo::recursivelyCopy(const QString &src, const QString &dest)
0144 {
0145     QDir srcDir(src);
0146 
0147     if (!srcDir.exists()) {
0148         return false;
0149     }
0150 
0151     QDir destDir(dest);
0152     if (!destDir.exists() && !destDir.mkdir(dest)) {
0153         return false;
0154     }
0155 
0156     for (const auto &file : srcDir.entryList(QDir::Files | QDir::Hidden)) {
0157         const QString srcName = src + QLatin1Char('/') + file;
0158         const QString destName = dest + QLatin1Char('/') + file;
0159         if (!QFile::copy(srcName, destName)) {
0160             return false;
0161         }
0162     }
0163 
0164     for (const auto &dir : srcDir.entryList(QDir::AllDirs | QDir::NoDotAndDotDot | QDir::Hidden)) {
0165         const QString srcName = src + QLatin1Char('/') + dir;
0166         const QString destName = dest + QLatin1Char('/') + dir;
0167         if (!recursivelyCopy(srcName, destName)) {
0168             return false;
0169         }
0170     }
0171 
0172     return true;
0173 }
0174 
0175 bool Kleo::moveDir(const QString &src, const QString &dest)
0176 {
0177     // Need an existing path to query the device
0178     const QString parentDest = QFileInfo(dest).dir().absolutePath();
0179     const auto srcDevice = QStorageInfo(src).device();
0180     if (!srcDevice.isEmpty() //
0181         && srcDevice == QStorageInfo(parentDest).device() //
0182         && QFile::rename(src, dest)) {
0183         qCDebug(KLEOPATRA_LOG) << "Renamed" << src << "to" << dest;
0184         return true;
0185     }
0186     // first copy
0187     if (!recursivelyCopy(src, dest)) {
0188         return false;
0189     }
0190     // Then delete original
0191     recursivelyRemovePath(src);
0192 
0193     return true;
0194 }
0195 #endif