1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
3 | |
4 | #include "qfilesystemengine_p.h" |
5 | #include <QtCore/qdir.h> |
6 | #include <QtCore/qset.h> |
7 | #include <QtCore/qstringbuilder.h> |
8 | #include <QtCore/private/qabstractfileengine_p.h> |
9 | #ifdef QT_BUILD_CORE_LIB |
10 | #include <QtCore/private/qresource_p.h> |
11 | #endif |
12 | #include <QtCore/private/qduplicatetracker_p.h> |
13 | |
14 | QT_BEGIN_NAMESPACE |
15 | |
16 | /*! |
17 | \internal |
18 | |
19 | Returns the canonicalized form of \a path (i.e., with all symlinks |
20 | resolved, and all redundant path elements removed. |
21 | */ |
22 | QString QFileSystemEngine::slowCanonicalized(const QString &path) |
23 | { |
24 | if (path.isEmpty()) |
25 | return path; |
26 | |
27 | QFileInfo fi; |
28 | const QChar slash(u'/'); |
29 | QString tmpPath = path; |
30 | qsizetype separatorPos = 0; |
31 | QSet<QString> nonSymlinks; |
32 | QDuplicateTracker<QString> known; |
33 | |
34 | (void)known.hasSeen(s: path); |
35 | do { |
36 | #ifdef Q_OS_WIN |
37 | if (separatorPos == 0) { |
38 | if (tmpPath.size() >= 2 && tmpPath.at(0) == slash && tmpPath.at(1) == slash) { |
39 | // UNC, skip past the first two elements |
40 | separatorPos = tmpPath.indexOf(slash, 2); |
41 | } else if (tmpPath.size() >= 3 && tmpPath.at(1) == u':' && tmpPath.at(2) == slash) { |
42 | // volume root, skip since it can not be a symlink |
43 | separatorPos = 2; |
44 | } |
45 | } |
46 | if (separatorPos != -1) |
47 | #endif |
48 | separatorPos = tmpPath.indexOf(c: slash, from: separatorPos + 1); |
49 | QString prefix = separatorPos == -1 ? tmpPath : tmpPath.left(n: separatorPos); |
50 | if (!nonSymlinks.contains(value: prefix)) { |
51 | fi.setFile(prefix); |
52 | if (fi.isSymLink()) { |
53 | QString target = fi.symLinkTarget(); |
54 | if (separatorPos != -1) { |
55 | if (fi.isDir() && !target.endsWith(c: slash)) |
56 | target.append(c: slash); |
57 | target.append(v: QStringView{tmpPath}.mid(pos: separatorPos)); |
58 | } |
59 | tmpPath = QDir::cleanPath(path: target); |
60 | separatorPos = 0; |
61 | |
62 | if (known.hasSeen(s: tmpPath)) |
63 | return QString(); |
64 | } else { |
65 | nonSymlinks.insert(value: prefix); |
66 | } |
67 | } |
68 | } while (separatorPos != -1); |
69 | |
70 | return QDir::cleanPath(path: tmpPath); |
71 | } |
72 | |
73 | static inline bool _q_checkEntry(QFileSystemEntry &entry, QFileSystemMetaData &data, bool resolvingEntry) |
74 | { |
75 | if (resolvingEntry) { |
76 | if (!QFileSystemEngine::fillMetaData(entry, data, what: QFileSystemMetaData::ExistsAttribute) |
77 | || !data.exists()) { |
78 | data.clear(); |
79 | return false; |
80 | } |
81 | } |
82 | |
83 | return true; |
84 | } |
85 | |
86 | static inline bool _q_checkEntry(QAbstractFileEngine *&engine, bool resolvingEntry) |
87 | { |
88 | if (resolvingEntry) { |
89 | if (!(engine->fileFlags(type: QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) { |
90 | delete engine; |
91 | engine = nullptr; |
92 | return false; |
93 | } |
94 | } |
95 | |
96 | return true; |
97 | } |
98 | |
99 | static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data, |
100 | QAbstractFileEngine *&engine, bool resolvingEntry = false) |
101 | { |
102 | QString const &filePath = entry.filePath(); |
103 | if ((engine = qt_custom_file_engine_handler_create(path: filePath))) |
104 | return _q_checkEntry(engine, resolvingEntry); |
105 | |
106 | #if defined(QT_BUILD_CORE_LIB) |
107 | for (qsizetype prefixSeparator = 0; prefixSeparator < filePath.size(); ++prefixSeparator) { |
108 | QChar const ch = filePath[prefixSeparator]; |
109 | if (ch == u'/') |
110 | break; |
111 | |
112 | if (ch == u':') { |
113 | if (prefixSeparator == 0) { |
114 | engine = new QResourceFileEngine(filePath); |
115 | return _q_checkEntry(engine, resolvingEntry); |
116 | } |
117 | |
118 | if (prefixSeparator == 1) |
119 | break; |
120 | |
121 | const QStringList &paths = QDir::searchPaths(prefix: filePath.left(n: prefixSeparator)); |
122 | for (int i = 0; i < paths.size(); i++) { |
123 | entry = QFileSystemEntry(QDir::cleanPath( |
124 | path: paths.at(i) % u'/' % QStringView{filePath}.mid(pos: prefixSeparator + 1))); |
125 | // Recurse! |
126 | if (_q_resolveEntryAndCreateLegacyEngine_recursive(entry, data, engine, resolvingEntry: true)) |
127 | return true; |
128 | } |
129 | |
130 | // entry may have been clobbered at this point. |
131 | return false; |
132 | } |
133 | |
134 | // There's no need to fully validate the prefix here. Consulting the |
135 | // unicode tables could be expensive and validation is already |
136 | // performed in QDir::setSearchPaths. |
137 | // |
138 | // if (!ch.isLetterOrNumber()) |
139 | // break; |
140 | } |
141 | #endif // defined(QT_BUILD_CORE_LIB) |
142 | |
143 | return _q_checkEntry(entry, data, resolvingEntry); |
144 | } |
145 | |
146 | /*! |
147 | \internal |
148 | |
149 | Resolves the \a entry (see QDir::searchPaths) and returns an engine for |
150 | it, but never a QFSFileEngine. |
151 | |
152 | Returns a file engine that can be used to access the entry. Returns 0 if |
153 | QFileSystemEngine API should be used to query and interact with the file |
154 | system object. |
155 | */ |
156 | QAbstractFileEngine *QFileSystemEngine::resolveEntryAndCreateLegacyEngine( |
157 | QFileSystemEntry &entry, QFileSystemMetaData &data) { |
158 | QFileSystemEntry copy = entry; |
159 | QAbstractFileEngine *engine = nullptr; |
160 | |
161 | if (_q_resolveEntryAndCreateLegacyEngine_recursive(entry&: copy, data, engine)) |
162 | // Reset entry to resolved copy. |
163 | entry = copy; |
164 | else |
165 | data.clear(); |
166 | |
167 | return engine; |
168 | } |
169 | |
170 | //static |
171 | QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) |
172 | { |
173 | #if defined(Q_OS_WIN) |
174 | Q_UNUSED(metaData); |
175 | return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerUser); |
176 | #else //(Q_OS_UNIX) |
177 | if (!metaData.hasFlags(flags: QFileSystemMetaData::UserId)) |
178 | QFileSystemEngine::fillMetaData(entry, data&: metaData, what: QFileSystemMetaData::UserId); |
179 | if (!metaData.exists()) |
180 | return QString(); |
181 | return resolveUserName(userId: metaData.userId()); |
182 | #endif |
183 | } |
184 | |
185 | //static |
186 | QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) |
187 | { |
188 | #if defined(Q_OS_WIN) |
189 | Q_UNUSED(metaData); |
190 | return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerGroup); |
191 | #else //(Q_OS_UNIX) |
192 | if (!metaData.hasFlags(flags: QFileSystemMetaData::GroupId)) |
193 | QFileSystemEngine::fillMetaData(entry, data&: metaData, what: QFileSystemMetaData::GroupId); |
194 | if (!metaData.exists()) |
195 | return QString(); |
196 | return resolveGroupName(groupId: metaData.groupId()); |
197 | #endif |
198 | } |
199 | |
200 | //static |
201 | QFileSystemEntry QFileSystemEngine::getJunctionTarget(const QFileSystemEntry &link, |
202 | QFileSystemMetaData &data) |
203 | { |
204 | #if defined(Q_OS_WIN) |
205 | return junctionTarget(link, data); |
206 | #else |
207 | Q_UNUSED(link); |
208 | Q_UNUSED(data); |
209 | return {}; |
210 | #endif |
211 | } |
212 | |
213 | QT_END_NAMESPACE |
214 | |