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