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 "qfilesystementry_p.h" |
5 | |
6 | #include <QtCore/qdir.h> |
7 | #include <QtCore/qfile.h> |
8 | #include <QtCore/private/qfsfileengine_p.h> |
9 | #ifdef Q_OS_WIN |
10 | #include <QtCore/qstringbuilder.h> |
11 | #endif |
12 | |
13 | QT_BEGIN_NAMESPACE |
14 | |
15 | using namespace Qt::StringLiterals; |
16 | |
17 | #ifdef Q_OS_WIN |
18 | static bool isUncRoot(const QString &server) |
19 | { |
20 | QString localPath = QDir::toNativeSeparators(server); |
21 | if (!localPath.startsWith("\\\\"_L1 )) |
22 | return false; |
23 | |
24 | int idx = localPath.indexOf(u'\\', 2); |
25 | if (idx == -1 || idx + 1 == localPath.length()) |
26 | return true; |
27 | |
28 | return QStringView{localPath}.right(localPath.length() - idx - 1).trimmed().isEmpty(); |
29 | } |
30 | |
31 | static inline QString fixIfRelativeUncPath(const QString &path) |
32 | { |
33 | QString currentPath = QDir::currentPath(); |
34 | if (currentPath.startsWith("//"_L1 )) |
35 | return currentPath % QChar(u'/') % path; |
36 | return path; |
37 | } |
38 | #endif |
39 | |
40 | QFileSystemEntry::QFileSystemEntry() |
41 | : m_lastSeparator(-1), |
42 | m_firstDotInFileName(-1), |
43 | m_lastDotInFileName(-1) |
44 | { |
45 | } |
46 | |
47 | /*! |
48 | \internal |
49 | Use this constructor when the path is supplied by user code, as it may contain a mix |
50 | of '/' and the native separator. |
51 | */ |
52 | QFileSystemEntry::QFileSystemEntry(const QString &filePath) |
53 | : m_filePath(QDir::fromNativeSeparators(pathName: filePath)), |
54 | m_lastSeparator(-2), |
55 | m_firstDotInFileName(-2), |
56 | m_lastDotInFileName(0) |
57 | { |
58 | } |
59 | |
60 | /*! |
61 | \internal |
62 | Use this constructor when the path is guaranteed to be in internal format, i.e. all |
63 | directory separators are '/' and not the native separator. |
64 | */ |
65 | QFileSystemEntry::QFileSystemEntry(const QString &filePath, FromInternalPath /* dummy */) |
66 | : m_filePath(filePath), |
67 | m_lastSeparator(-2), |
68 | m_firstDotInFileName(-2), |
69 | m_lastDotInFileName(0) |
70 | { |
71 | } |
72 | |
73 | /*! |
74 | \internal |
75 | Use this constructor when the path comes from a native API |
76 | */ |
77 | QFileSystemEntry::QFileSystemEntry(const NativePath &nativeFilePath, FromNativePath /* dummy */) |
78 | : m_nativeFilePath(nativeFilePath), |
79 | m_lastSeparator(-2), |
80 | m_firstDotInFileName(-2), |
81 | m_lastDotInFileName(0) |
82 | { |
83 | } |
84 | |
85 | QFileSystemEntry::QFileSystemEntry(const QString &filePath, const NativePath &nativeFilePath) |
86 | : m_filePath(QDir::fromNativeSeparators(pathName: filePath)), |
87 | m_nativeFilePath(nativeFilePath), |
88 | m_lastSeparator(-2), |
89 | m_firstDotInFileName(-2), |
90 | m_lastDotInFileName(0) |
91 | { |
92 | } |
93 | |
94 | QString QFileSystemEntry::filePath() const |
95 | { |
96 | resolveFilePath(); |
97 | return m_filePath; |
98 | } |
99 | |
100 | QFileSystemEntry::NativePath QFileSystemEntry::nativeFilePath() const |
101 | { |
102 | resolveNativeFilePath(); |
103 | return m_nativeFilePath; |
104 | } |
105 | |
106 | void QFileSystemEntry::resolveFilePath() const |
107 | { |
108 | if (m_filePath.isEmpty() && !m_nativeFilePath.isEmpty()) { |
109 | #ifdef Q_OS_WIN |
110 | m_filePath = QDir::fromNativeSeparators(m_nativeFilePath); |
111 | #else |
112 | m_filePath = QDir::fromNativeSeparators(pathName: QFile::decodeName(localFileName: m_nativeFilePath)); |
113 | #endif |
114 | } |
115 | } |
116 | |
117 | void QFileSystemEntry::resolveNativeFilePath() const |
118 | { |
119 | if (!m_filePath.isEmpty() && m_nativeFilePath.isEmpty()) { |
120 | #ifdef Q_OS_WIN |
121 | QString filePath = m_filePath; |
122 | if (isRelative()) |
123 | filePath = fixIfRelativeUncPath(m_filePath); |
124 | m_nativeFilePath = QFSFileEnginePrivate::longFileName(QDir::toNativeSeparators(filePath)); |
125 | #else |
126 | m_nativeFilePath = QFile::encodeName(fileName: QDir::toNativeSeparators(pathName: m_filePath)); |
127 | #endif |
128 | } |
129 | } |
130 | |
131 | QString QFileSystemEntry::fileName() const |
132 | { |
133 | findLastSeparator(); |
134 | #if defined(Q_OS_WIN) |
135 | if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == u':') |
136 | return m_filePath.mid(2); |
137 | #endif |
138 | return m_filePath.mid(position: m_lastSeparator + 1); |
139 | } |
140 | |
141 | QString QFileSystemEntry::path() const |
142 | { |
143 | findLastSeparator(); |
144 | if (m_lastSeparator == -1) { |
145 | #if defined(Q_OS_WIN) |
146 | if (m_filePath.length() >= 2 && m_filePath.at(1) == u':') |
147 | return m_filePath.left(2); |
148 | #endif |
149 | return QString(u'.'); |
150 | } |
151 | if (m_lastSeparator == 0) |
152 | return QString(u'/'); |
153 | #if defined(Q_OS_WIN) |
154 | if (m_lastSeparator == 2 && m_filePath.at(1) == u':') |
155 | return m_filePath.left(m_lastSeparator + 1); |
156 | #endif |
157 | return m_filePath.left(n: m_lastSeparator); |
158 | } |
159 | |
160 | QString QFileSystemEntry::baseName() const |
161 | { |
162 | findFileNameSeparators(); |
163 | int length = -1; |
164 | if (m_firstDotInFileName >= 0) { |
165 | length = m_firstDotInFileName; |
166 | if (m_lastSeparator != -1) // avoid off by one |
167 | length--; |
168 | } |
169 | #if defined(Q_OS_WIN) |
170 | if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == u':') |
171 | return m_filePath.mid(2, length - 2); |
172 | #endif |
173 | return m_filePath.mid(position: m_lastSeparator + 1, n: length); |
174 | } |
175 | |
176 | QString QFileSystemEntry::completeBaseName() const |
177 | { |
178 | findFileNameSeparators(); |
179 | int length = -1; |
180 | if (m_firstDotInFileName >= 0) { |
181 | length = m_firstDotInFileName + m_lastDotInFileName; |
182 | if (m_lastSeparator != -1) // avoid off by one |
183 | length--; |
184 | } |
185 | #if defined(Q_OS_WIN) |
186 | if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == u':') |
187 | return m_filePath.mid(2, length - 2); |
188 | #endif |
189 | return m_filePath.mid(position: m_lastSeparator + 1, n: length); |
190 | } |
191 | |
192 | QString QFileSystemEntry::suffix() const |
193 | { |
194 | findFileNameSeparators(); |
195 | |
196 | if (m_lastDotInFileName == -1) |
197 | return QString(); |
198 | |
199 | return m_filePath.mid(position: qMax(a: (qint16)0, b: m_lastSeparator) + m_firstDotInFileName + m_lastDotInFileName + 1); |
200 | } |
201 | |
202 | QString QFileSystemEntry::completeSuffix() const |
203 | { |
204 | findFileNameSeparators(); |
205 | if (m_firstDotInFileName == -1) |
206 | return QString(); |
207 | |
208 | return m_filePath.mid(position: qMax(a: (qint16)0, b: m_lastSeparator) + m_firstDotInFileName + 1); |
209 | } |
210 | |
211 | #if defined(Q_OS_WIN) |
212 | bool QFileSystemEntry::isRelative() const |
213 | { |
214 | resolveFilePath(); |
215 | return (m_filePath.isEmpty() |
216 | || (m_filePath.at(0).unicode() != '/' |
217 | && !(m_filePath.length() >= 2 && m_filePath.at(1).unicode() == ':'))); |
218 | } |
219 | |
220 | bool QFileSystemEntry::isAbsolute() const |
221 | { |
222 | resolveFilePath(); |
223 | return ((m_filePath.length() >= 3 |
224 | && m_filePath.at(0).isLetter() |
225 | && m_filePath.at(1).unicode() == ':' |
226 | && m_filePath.at(2).unicode() == '/') |
227 | || (m_filePath.length() >= 2 |
228 | && m_filePath.at(0) == u'/' |
229 | && m_filePath.at(1) == u'/')); |
230 | } |
231 | #else |
232 | bool QFileSystemEntry::isRelative() const |
233 | { |
234 | return !isAbsolute(); |
235 | } |
236 | |
237 | bool QFileSystemEntry::isAbsolute() const |
238 | { |
239 | resolveFilePath(); |
240 | return (!m_filePath.isEmpty() && (m_filePath.at(i: 0).unicode() == '/')); |
241 | } |
242 | #endif |
243 | |
244 | #if defined(Q_OS_WIN) |
245 | bool QFileSystemEntry::isDriveRoot() const |
246 | { |
247 | resolveFilePath(); |
248 | return QFileSystemEntry::isDriveRootPath(m_filePath); |
249 | } |
250 | |
251 | bool QFileSystemEntry::isDriveRootPath(const QString &path) |
252 | { |
253 | return (path.length() == 3 |
254 | && path.at(0).isLetter() && path.at(1) == u':' |
255 | && path.at(2) == u'/'); |
256 | } |
257 | |
258 | QString QFileSystemEntry::removeUncOrLongPathPrefix(QString path) |
259 | { |
260 | constexpr qsizetype minPrefixSize = 4; |
261 | if (path.size() < minPrefixSize) |
262 | return path; |
263 | |
264 | auto data = path.data(); |
265 | const auto slash = path[0]; |
266 | if (slash != u'\\' && slash != u'/') |
267 | return path; |
268 | |
269 | // check for "//?/" or "/??/" |
270 | if (data[2] == u'?' && data[3] == slash && (data[1] == slash || data[1] == u'?')) { |
271 | path = path.sliced(minPrefixSize); |
272 | |
273 | // check for a possible "UNC/" prefix left-over |
274 | if (path.size() >= 4) { |
275 | data = path.data(); |
276 | if (data[0] == u'U' && data[1] == u'N' && data[2] == u'C' && data[3] == slash) { |
277 | data[2] = slash; |
278 | return path.sliced(2); |
279 | } |
280 | } |
281 | } |
282 | |
283 | return path; |
284 | } |
285 | #endif // Q_OS_WIN |
286 | |
287 | bool QFileSystemEntry::isRootPath(const QString &path) |
288 | { |
289 | if (path == "/"_L1 |
290 | #if defined(Q_OS_WIN) |
291 | || isDriveRootPath(path) |
292 | || isUncRoot(path) |
293 | #endif |
294 | ) |
295 | return true; |
296 | |
297 | return false; |
298 | } |
299 | |
300 | bool QFileSystemEntry::isRoot() const |
301 | { |
302 | resolveFilePath(); |
303 | return isRootPath(path: m_filePath); |
304 | } |
305 | |
306 | bool QFileSystemEntry::isEmpty() const |
307 | { |
308 | return m_filePath.isEmpty() && m_nativeFilePath.isEmpty(); |
309 | } |
310 | |
311 | // private methods |
312 | |
313 | void QFileSystemEntry::findLastSeparator() const |
314 | { |
315 | if (m_lastSeparator == -2) { |
316 | resolveFilePath(); |
317 | m_lastSeparator = m_filePath.lastIndexOf(c: u'/'); |
318 | } |
319 | } |
320 | |
321 | void QFileSystemEntry::findFileNameSeparators() const |
322 | { |
323 | if (m_firstDotInFileName == -2) { |
324 | resolveFilePath(); |
325 | int firstDotInFileName = -1; |
326 | int lastDotInFileName = -1; |
327 | int lastSeparator = m_lastSeparator; |
328 | |
329 | int stop; |
330 | if (lastSeparator < 0) { |
331 | lastSeparator = -1; |
332 | stop = 0; |
333 | } else { |
334 | stop = lastSeparator; |
335 | } |
336 | |
337 | int i = m_filePath.size() - 1; |
338 | for (; i >= stop; --i) { |
339 | if (m_filePath.at(i).unicode() == '.') { |
340 | firstDotInFileName = lastDotInFileName = i; |
341 | break; |
342 | } else if (m_filePath.at(i).unicode() == '/') { |
343 | lastSeparator = i; |
344 | break; |
345 | } |
346 | } |
347 | |
348 | if (lastSeparator != i) { |
349 | for (--i; i >= stop; --i) { |
350 | if (m_filePath.at(i).unicode() == '.') |
351 | firstDotInFileName = i; |
352 | else if (m_filePath.at(i).unicode() == '/') { |
353 | lastSeparator = i; |
354 | break; |
355 | } |
356 | } |
357 | } |
358 | m_lastSeparator = lastSeparator; |
359 | m_firstDotInFileName = firstDotInFileName == -1 ? -1 : firstDotInFileName - qMax(a: 0, b: lastSeparator); |
360 | if (lastDotInFileName == -1) |
361 | m_lastDotInFileName = -1; |
362 | else if (firstDotInFileName == lastDotInFileName) |
363 | m_lastDotInFileName = 0; |
364 | else |
365 | m_lastDotInFileName = lastDotInFileName - firstDotInFileName; |
366 | } |
367 | } |
368 | |
369 | bool QFileSystemEntry::isClean() const |
370 | { |
371 | resolveFilePath(); |
372 | int dots = 0; |
373 | bool dotok = true; // checking for ".." or "." starts to relative paths |
374 | bool slashok = true; |
375 | for (QString::const_iterator iter = m_filePath.constBegin(); iter != m_filePath.constEnd(); ++iter) { |
376 | if (*iter == u'/') { |
377 | if (dots == 1 || dots == 2) |
378 | return false; // path contains "./" or "../" |
379 | if (!slashok) |
380 | return false; // path contains "//" |
381 | dots = 0; |
382 | dotok = true; |
383 | slashok = false; |
384 | } else if (dotok) { |
385 | slashok = true; |
386 | if (*iter == u'.') { |
387 | dots++; |
388 | if (dots > 2) |
389 | dotok = false; |
390 | } else { |
391 | //path element contains a character other than '.', it's clean |
392 | dots = 0; |
393 | dotok = false; |
394 | } |
395 | } |
396 | } |
397 | return (dots != 1 && dots != 2); // clean if path doesn't end in . or .. |
398 | } |
399 | |
400 | QT_END_NAMESPACE |
401 | |