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 localPath.rightRef(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#ifdef Q_OS_WINRT
171 while (m_nativeFilePath.startsWith(QLatin1Char('\\')))
172 m_nativeFilePath.remove(0,1);
173 if (m_nativeFilePath.isEmpty())
174 m_nativeFilePath.append(QLatin1Char('.'));
175 // WinRT/MSVC2015 allows a maximum of 256 characters for a filepath
176 // unless //?/ is prepended which extends the rule to have a maximum
177 // of 256 characters in the filename plus the preprending path
178 m_nativeFilePath.prepend("\\\\?\\");
179#endif
180 }
181}
182
183QString QFileSystemEntry::fileName() const
184{
185 findLastSeparator();
186#if defined(Q_OS_WIN)
187 if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':'))
188 return m_filePath.mid(2);
189#endif
190 return m_filePath.mid(m_lastSeparator + 1);
191}
192
193QString QFileSystemEntry::path() const
194{
195 findLastSeparator();
196 if (m_lastSeparator == -1) {
197#if defined(Q_OS_WIN)
198 if (m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':'))
199 return m_filePath.left(2);
200#endif
201 return QString(QLatin1Char('.'));
202 }
203 if (m_lastSeparator == 0)
204 return QString(QLatin1Char('/'));
205#if defined(Q_OS_WIN)
206 if (m_lastSeparator == 2 && m_filePath.at(1) == QLatin1Char(':'))
207 return m_filePath.left(m_lastSeparator + 1);
208#endif
209 return m_filePath.left(m_lastSeparator);
210}
211
212QString QFileSystemEntry::baseName() const
213{
214 findFileNameSeparators();
215 int length = -1;
216 if (m_firstDotInFileName >= 0) {
217 length = m_firstDotInFileName;
218 if (m_lastSeparator != -1) // avoid off by one
219 length--;
220 }
221#if defined(Q_OS_WIN)
222 if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':'))
223 return m_filePath.mid(2, length - 2);
224#endif
225 return m_filePath.mid(m_lastSeparator + 1, length);
226}
227
228QString QFileSystemEntry::completeBaseName() const
229{
230 findFileNameSeparators();
231 int length = -1;
232 if (m_firstDotInFileName >= 0) {
233 length = m_firstDotInFileName + m_lastDotInFileName;
234 if (m_lastSeparator != -1) // avoid off by one
235 length--;
236 }
237#if defined(Q_OS_WIN)
238 if (m_lastSeparator == -1 && m_filePath.length() >= 2 && m_filePath.at(1) == QLatin1Char(':'))
239 return m_filePath.mid(2, length - 2);
240#endif
241 return m_filePath.mid(m_lastSeparator + 1, length);
242}
243
244QString QFileSystemEntry::suffix() const
245{
246 findFileNameSeparators();
247
248 if (m_lastDotInFileName == -1)
249 return QString();
250
251 return m_filePath.mid(qMax((qint16)0, m_lastSeparator) + m_firstDotInFileName + m_lastDotInFileName + 1);
252}
253
254QString QFileSystemEntry::completeSuffix() const
255{
256 findFileNameSeparators();
257 if (m_firstDotInFileName == -1)
258 return QString();
259
260 return m_filePath.mid(qMax((qint16)0, m_lastSeparator) + m_firstDotInFileName + 1);
261}
262
263#if defined(Q_OS_WIN)
264bool QFileSystemEntry::isRelative() const
265{
266 resolveFilePath();
267 return (m_filePath.isEmpty()
268 || (m_filePath.at(0).unicode() != '/'
269 && !(m_filePath.length() >= 2 && m_filePath.at(1).unicode() == ':')));
270}
271
272bool QFileSystemEntry::isAbsolute() const
273{
274 resolveFilePath();
275 return ((m_filePath.length() >= 3
276 && m_filePath.at(0).isLetter()
277 && m_filePath.at(1).unicode() == ':'
278 && m_filePath.at(2).unicode() == '/')
279 || (m_filePath.length() >= 2
280 && m_filePath.at(0) == QLatin1Char('/')
281 && m_filePath.at(1) == QLatin1Char('/')));
282}
283#else
284bool QFileSystemEntry::isRelative() const
285{
286 return !isAbsolute();
287}
288
289bool QFileSystemEntry::isAbsolute() const
290{
291 resolveFilePath();
292 return (!m_filePath.isEmpty() && (m_filePath.at(0).unicode() == '/'));
293}
294#endif
295
296#if defined(Q_OS_WIN)
297bool QFileSystemEntry::isDriveRoot() const
298{
299 resolveFilePath();
300 return QFileSystemEntry::isDriveRootPath(m_filePath);
301}
302
303bool QFileSystemEntry::isDriveRootPath(const QString &path)
304{
305#ifndef Q_OS_WINRT
306 return (path.length() == 3
307 && path.at(0).isLetter() && path.at(1) == QLatin1Char(':')
308 && path.at(2) == QLatin1Char('/'));
309#else // !Q_OS_WINRT
310 return path == QDir::rootPath();
311#endif // !Q_OS_WINRT
312}
313#endif // Q_OS_WIN
314
315bool QFileSystemEntry::isRootPath(const QString &path)
316{
317 if (path == QLatin1String("/")
318#if defined(Q_OS_WIN)
319 || isDriveRootPath(path)
320 || isUncRoot(path)
321#endif
322 )
323 return true;
324
325 return false;
326}
327
328bool QFileSystemEntry::isRoot() const
329{
330 resolveFilePath();
331 return isRootPath(m_filePath);
332}
333
334// private methods
335
336void QFileSystemEntry::findLastSeparator() const
337{
338 if (m_lastSeparator == -2) {
339 resolveFilePath();
340 m_lastSeparator = m_filePath.lastIndexOf(QLatin1Char('/'));
341 }
342}
343
344void QFileSystemEntry::findFileNameSeparators() const
345{
346 if (m_firstDotInFileName == -2) {
347 resolveFilePath();
348 int firstDotInFileName = -1;
349 int lastDotInFileName = -1;
350 int lastSeparator = m_lastSeparator;
351
352 int stop;
353 if (lastSeparator < 0) {
354 lastSeparator = -1;
355 stop = 0;
356 } else {
357 stop = lastSeparator;
358 }
359
360 int i = m_filePath.size() - 1;
361 for (; i >= stop; --i) {
362 if (m_filePath.at(i).unicode() == '.') {
363 firstDotInFileName = lastDotInFileName = i;
364 break;
365 } else if (m_filePath.at(i).unicode() == '/') {
366 lastSeparator = i;
367 break;
368 }
369 }
370
371 if (lastSeparator != i) {
372 for (--i; i >= stop; --i) {
373 if (m_filePath.at(i).unicode() == '.')
374 firstDotInFileName = i;
375 else if (m_filePath.at(i).unicode() == '/') {
376 lastSeparator = i;
377 break;
378 }
379 }
380 }
381 m_lastSeparator = lastSeparator;
382 m_firstDotInFileName = firstDotInFileName == -1 ? -1 : firstDotInFileName - qMax(0, lastSeparator);
383 if (lastDotInFileName == -1)
384 m_lastDotInFileName = -1;
385 else if (firstDotInFileName == lastDotInFileName)
386 m_lastDotInFileName = 0;
387 else
388 m_lastDotInFileName = lastDotInFileName - firstDotInFileName;
389 }
390}
391
392bool QFileSystemEntry::isClean() const
393{
394 resolveFilePath();
395 int dots = 0;
396 bool dotok = true; // checking for ".." or "." starts to relative paths
397 bool slashok = true;
398 for (QString::const_iterator iter = m_filePath.constBegin(); iter != m_filePath.constEnd(); ++iter) {
399 if (*iter == QLatin1Char('/')) {
400 if (dots == 1 || dots == 2)
401 return false; // path contains "./" or "../"
402 if (!slashok)
403 return false; // path contains "//"
404 dots = 0;
405 dotok = true;
406 slashok = false;
407 } else if (dotok) {
408 slashok = true;
409 if (*iter == QLatin1Char('.')) {
410 dots++;
411 if (dots > 2)
412 dotok = false;
413 } else {
414 //path element contains a character other than '.', it's clean
415 dots = 0;
416 dotok = false;
417 }
418 }
419 }
420 return (dots != 1 && dots != 2); // clean if path doesn't end in . or ..
421}
422
423QT_END_NAMESPACE
424