Line data Source code
1 : // SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2 : // SPDX-License-Identifier: GPL-3.0-or-later
3 : /**
4 : * @file
5 : * @brief Implements PathValidator; storeRelativePath() does the canonical
6 : * comparison the other checks build on.
7 : */
8 : #include "pathvalidator.h"
9 : #include <QDir>
10 : #include <QFileInfo>
11 : #include <QStringList>
12 :
13 72 : auto PathValidator::isPathInStore(const QString &storeRoot,
14 : const QString &candidate) -> bool {
15 72 : return storeRelativePath(storeRoot, candidate).has_value();
16 : }
17 :
18 126 : auto PathValidator::storeRelativePath(const QString &storeRoot,
19 : const QString &candidate)
20 : -> std::optional<QString> {
21 126 : if (storeRoot.isEmpty() || candidate.isEmpty()) {
22 3 : return std::nullopt;
23 : }
24 123 : const QString rootCanon = QDir(storeRoot).canonicalPath();
25 123 : if (rootCanon.isEmpty()) {
26 1 : return std::nullopt;
27 : }
28 :
29 122 : QString resolved;
30 244 : const QFileInfo fi(QDir::cleanPath(QFileInfo(candidate).absoluteFilePath()));
31 122 : if (fi.exists()) {
32 134 : resolved = fi.canonicalFilePath();
33 : } else {
34 : // Walk up to the nearest existing ancestor so we can canonicalise it
35 : // (resolves any symlinks on the way to the store root), then re-append
36 : // the leaf components below that ancestor.
37 55 : QDir parent = fi.dir();
38 55 : QStringList tail;
39 55 : tail.prepend(fi.fileName());
40 63 : while (!parent.exists()) {
41 8 : tail.prepend(parent.dirName());
42 : // The filesystem root always exists, so the loop terminates there
43 : // before cdUp() can fail; this guard is defensive against a
44 : // pathological QDir that can neither exist nor ascend.
45 8 : if (!parent.cdUp()) {
46 0 : return std::nullopt;
47 : }
48 : }
49 55 : const QString parentCanon = parent.canonicalPath();
50 55 : if (parentCanon.isEmpty()) {
51 0 : return std::nullopt;
52 : }
53 110 : resolved = QDir::cleanPath(parentCanon + QLatin1Char('/') +
54 110 : tail.join(QLatin1Char('/')));
55 55 : }
56 :
57 122 : if (resolved.isEmpty()) {
58 0 : return std::nullopt;
59 : }
60 122 : if (resolved == rootCanon) {
61 31 : return QString();
62 : }
63 182 : if (!resolved.startsWith(rootCanon + QLatin1Char('/'))) {
64 22 : return std::nullopt;
65 : }
66 138 : return resolved.mid(rootCanon.size() + 1);
67 122 : }
68 :
69 300 : auto PathValidator::isTargetSpelled(const QString &storeRoot,
70 : const QString &path) -> bool {
71 600 : if (storeRoot.isEmpty() || !QDir::isAbsolutePath(path)) {
72 2 : return false;
73 : }
74 : #ifdef Q_OS_WIN
75 : constexpr auto cs = Qt::CaseInsensitive;
76 : #else
77 : constexpr auto cs = Qt::CaseSensitive;
78 : #endif
79 298 : const QString root = QDir::cleanPath(storeRoot);
80 298 : const QString canonical = QDir(root).canonicalPath();
81 298 : if (canonical.isEmpty() || canonical.compare(root, cs) == 0) {
82 291 : return false;
83 : }
84 12 : const auto atOrUnder = [cs](const QString &p, const QString &dir) {
85 : const QString prefix =
86 12 : dir.endsWith(QLatin1Char('/')) ? dir : dir + QLatin1Char('/');
87 24 : return p.compare(dir, cs) == 0 || p.startsWith(prefix, cs);
88 : };
89 7 : const QString cleaned = QDir::cleanPath(path);
90 7 : return !atOrUnder(cleaned, root) && atOrUnder(cleaned, canonical);
91 : }
|