QtPass 2.0.0
Developer documentation
Loading...
Searching...
No Matches
pathvalidator.cpp
Go to the documentation of this file.
1// SPDX-FileCopyrightText: 2014 Anne Jan Brouwer
2// SPDX-License-Identifier: GPL-3.0-or-later
8#include "pathvalidator.h"
9#include <QDir>
10#include <QFileInfo>
11#include <QStringList>
12
13auto PathValidator::isPathInStore(const QString &storeRoot,
14 const QString &candidate) -> bool {
15 return storeRelativePath(storeRoot, candidate).has_value();
16}
17
18auto PathValidator::storeRelativePath(const QString &storeRoot,
19 const QString &candidate)
20 -> std::optional<QString> {
21 if (storeRoot.isEmpty() || candidate.isEmpty()) {
22 return std::nullopt;
23 }
24 const QString rootCanon = QDir(storeRoot).canonicalPath();
25 if (rootCanon.isEmpty()) {
26 return std::nullopt;
27 }
28
29 QString resolved;
30 const QFileInfo fi(QDir::cleanPath(QFileInfo(candidate).absoluteFilePath()));
31 if (fi.exists()) {
32 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 QDir parent = fi.dir();
38 QStringList tail;
39 tail.prepend(fi.fileName());
40 while (!parent.exists()) {
41 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 if (!parent.cdUp()) {
46 return std::nullopt;
47 }
48 }
49 const QString parentCanon = parent.canonicalPath();
50 if (parentCanon.isEmpty()) {
51 return std::nullopt;
52 }
53 resolved = QDir::cleanPath(parentCanon + QLatin1Char('/') +
54 tail.join(QLatin1Char('/')));
55 }
56
57 if (resolved.isEmpty()) {
58 return std::nullopt;
59 }
60 if (resolved == rootCanon) {
61 return QString();
62 }
63 if (!resolved.startsWith(rootCanon + QLatin1Char('/'))) {
64 return std::nullopt;
65 }
66 return resolved.mid(rootCanon.size() + 1);
67}
68
69auto PathValidator::isTargetSpelled(const QString &storeRoot,
70 const QString &path) -> bool {
71 if (storeRoot.isEmpty() || !QDir::isAbsolutePath(path)) {
72 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 const QString root = QDir::cleanPath(storeRoot);
80 const QString canonical = QDir(root).canonicalPath();
81 if (canonical.isEmpty() || canonical.compare(root, cs) == 0) {
82 return false;
83 }
84 const auto atOrUnder = [cs](const QString &p, const QString &dir) {
85 const QString prefix =
86 dir.endsWith(QLatin1Char('/')) ? dir : dir + QLatin1Char('/');
87 return p.compare(dir, cs) == 0 || p.startsWith(prefix, cs);
88 };
89 const QString cleaned = QDir::cleanPath(path);
90 return !atOrUnder(cleaned, root) && atOrUnder(cleaned, canonical);
91}
static auto storeRelativePath(const QString &storeRoot, const QString &candidate) -> std::optional< QString >
Where candidate lies in the store, judged as isPathInStore() judges it.
static auto isPathInStore(const QString &storeRoot, const QString &candidate) -> bool
Check that a candidate path resolves inside the password store root, after following symlinks and res...
static auto isTargetSpelled(const QString &storeRoot, const QString &path) -> bool
Whether path is spelled through the target of a store root that is itself a link, rather than from th...
PathValidator: is a path inside the password store, and how is it spelled relative to the store root.