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(std::unique_ptr<QAbstractFileEngine> &engine, bool resolvingEntry) |
87 | { |
88 | if (resolvingEntry) { |
89 | if (!(engine->fileFlags(type: QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) { |
90 | engine.reset(); |
91 | return false; |
92 | } |
93 | } |
94 | |
95 | return true; |
96 | } |
97 | |
98 | static bool _q_createLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data, |
99 | std::unique_ptr<QAbstractFileEngine> &engine, |
100 | 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 = std::make_unique<QResourceFileEngine>(args: 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_createLegacyEngine_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 | Q_CORE_EXPORT bool qt_isCaseSensitive(const QFileSystemEntry &entry, QFileSystemMetaData &data) |
147 | { |
148 | // called from QtGui (QFileSystemModel, QFileInfoGatherer) |
149 | return QFileSystemEngine::isCaseSensitive(entry, data); |
150 | } |
151 | |
152 | /*! |
153 | \internal |
154 | |
155 | Resolves the \a entry (see QDir::searchPaths) and returns an engine for |
156 | it, but never a QFSFileEngine. |
157 | |
158 | Returns a file engine that can be used to access the entry. Returns 0 if |
159 | QFileSystemEngine API should be used to query and interact with the file |
160 | system object. |
161 | */ |
162 | std::unique_ptr<QAbstractFileEngine> |
163 | QFileSystemEngine::createLegacyEngine(QFileSystemEntry &entry, QFileSystemMetaData &data) |
164 | { |
165 | QFileSystemEntry copy = entry; |
166 | std::unique_ptr<QAbstractFileEngine> engine; |
167 | |
168 | if (_q_createLegacyEngine_recursive(entry&: copy, data, engine)) |
169 | // Reset entry to resolved copy. |
170 | entry = copy; |
171 | else |
172 | data.clear(); |
173 | |
174 | return engine; |
175 | } |
176 | |
177 | //static |
178 | QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) |
179 | { |
180 | #if defined(Q_OS_WIN) |
181 | Q_UNUSED(metaData); |
182 | return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerUser); |
183 | #else //(Q_OS_UNIX) |
184 | if (!metaData.hasFlags(flags: QFileSystemMetaData::UserId)) |
185 | QFileSystemEngine::fillMetaData(entry, data&: metaData, what: QFileSystemMetaData::UserId); |
186 | if (!metaData.exists()) |
187 | return QString(); |
188 | return resolveUserName(userId: metaData.userId()); |
189 | #endif |
190 | } |
191 | |
192 | //static |
193 | QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData) |
194 | { |
195 | #if defined(Q_OS_WIN) |
196 | Q_UNUSED(metaData); |
197 | return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerGroup); |
198 | #else //(Q_OS_UNIX) |
199 | if (!metaData.hasFlags(flags: QFileSystemMetaData::GroupId)) |
200 | QFileSystemEngine::fillMetaData(entry, data&: metaData, what: QFileSystemMetaData::GroupId); |
201 | if (!metaData.exists()) |
202 | return QString(); |
203 | return resolveGroupName(groupId: metaData.groupId()); |
204 | #endif |
205 | } |
206 | |
207 | //static |
208 | QFileSystemEntry QFileSystemEngine::getJunctionTarget(const QFileSystemEntry &link, |
209 | QFileSystemMetaData &data) |
210 | { |
211 | #if defined(Q_OS_WIN) |
212 | return junctionTarget(link, data); |
213 | #else |
214 | Q_UNUSED(link); |
215 | Q_UNUSED(data); |
216 | return {}; |
217 | #endif |
218 | } |
219 | |
220 | QT_END_NAMESPACE |
221 | |