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