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