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#ifdef Q_OS_WIN
18static 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
31static 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
40QFileSystemEntry::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 */
52QFileSystemEntry::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 */
65QFileSystemEntry::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 */
77QFileSystemEntry::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
85QFileSystemEntry::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
94QString QFileSystemEntry::filePath() const
95{
96 resolveFilePath();
97 return m_filePath;
98}
99
100QFileSystemEntry::NativePath QFileSystemEntry::nativeFilePath() const
101{
102 resolveNativeFilePath();
103 return m_nativeFilePath;
104}
105
106void 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
117void 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
131QString 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
141QString 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
160QString 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
176QString 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
192QString 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
202QString 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)
212bool 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
220bool 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
232bool QFileSystemEntry::isRelative() const
233{
234 return !isAbsolute();
235}
236
237bool 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)
245bool QFileSystemEntry::isDriveRoot() const
246{
247 resolveFilePath();
248 return QFileSystemEntry::isDriveRootPath(m_filePath);
249}
250
251bool 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
258QString 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
287bool 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
300bool QFileSystemEntry::isRoot() const
301{
302 resolveFilePath();
303 return isRootPath(path: m_filePath);
304}
305
306bool QFileSystemEntry::isEmpty() const
307{
308 return m_filePath.isEmpty() && m_nativeFilePath.isEmpty();
309}
310
311// private methods
312
313void QFileSystemEntry::findLastSeparator() const
314{
315 if (m_lastSeparator == -2) {
316 resolveFilePath();
317 m_lastSeparator = m_filePath.lastIndexOf(c: u'/');
318 }
319}
320
321void 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
369bool 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
400QT_END_NAMESPACE
401

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