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