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
13QT_BEGIN_NAMESPACE
14
15using namespace Qt::StringLiterals;
16
17// Assigned to m_lastSeparator and m_firstDotInFileName to indicate resolveFilePath()
18// hasn't been called yet
19constexpr int Uninitialized = -2;
20
21#ifdef Q_OS_WIN
22static 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
35static 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
44QFileSystemEntry::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 */
56QFileSystemEntry::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 */
69QFileSystemEntry::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 */
81QFileSystemEntry::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
89QFileSystemEntry::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
98QString QFileSystemEntry::filePath() const
99{
100 resolveFilePath();
101 return m_filePath;
102}
103
104QFileSystemEntry::NativePath QFileSystemEntry::nativeFilePath() const
105{
106 resolveNativeFilePath();
107 return m_nativeFilePath;
108}
109
110void 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
121void 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
135QString 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
145QString 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
164QString 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
180QString 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
196QString 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
206QString 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)
216bool 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
224bool 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
236bool QFileSystemEntry::isRelative() const
237{
238 return !isAbsolute();
239}
240
241bool 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)
249bool QFileSystemEntry::isDriveRoot() const
250{
251 resolveFilePath();
252 return QFileSystemEntry::isDriveRootPath(m_filePath);
253}
254
255bool 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
262QString 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
291bool 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
304bool QFileSystemEntry::isRoot() const
305{
306 resolveFilePath();
307 return isRootPath(path: m_filePath);
308}
309
310bool QFileSystemEntry::isEmpty() const
311{
312 return m_filePath.isEmpty() && m_nativeFilePath.isEmpty();
313}
314
315// private methods
316
317void QFileSystemEntry::findLastSeparator() const
318{
319 if (m_lastSeparator == Uninitialized) {
320 resolveFilePath();
321 m_lastSeparator = m_filePath.lastIndexOf(c: u'/');
322 }
323}
324
325void 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
373bool 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
404QT_END_NAMESPACE
405

source code of qtbase/src/corelib/io/qfilesystementry.cpp