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
49 QT_BEGIN_NAMESPACE
50
51 #ifdef Q_OS_WIN
isUncRoot(const QString & server)52 static 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
fixIfRelativeUncPath(const QString & path)65 static 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
QFileSystemEntry()74 QFileSystemEntry::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 */
QFileSystemEntry(const QString & filePath)86 QFileSystemEntry::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 */
QFileSystemEntry(const QString & filePath,FromInternalPath)99 QFileSystemEntry::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 */
QFileSystemEntry(const NativePath & nativeFilePath,FromNativePath)111 QFileSystemEntry::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
QFileSystemEntry(const QString & filePath,const NativePath & nativeFilePath)119 QFileSystemEntry::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
filePath() const128 QString QFileSystemEntry::filePath() const
129 {
130 resolveFilePath();
131 return m_filePath;
132 }
133
nativeFilePath() const134 QFileSystemEntry::NativePath QFileSystemEntry::nativeFilePath() const
135 {
136 resolveNativeFilePath();
137 return m_nativeFilePath;
138 }
139
resolveFilePath() const140 void 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
resolveNativeFilePath() const157 void 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
fileName() const183 QString 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
path() const193 QString 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
baseName() const212 QString 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
completeBaseName() const228 QString 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
suffix() const244 QString 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
completeSuffix() const254 QString 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)
isRelative() const264 bool 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
isAbsolute() const272 bool 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
isRelative() const284 bool QFileSystemEntry::isRelative() const
285 {
286 return !isAbsolute();
287 }
288
isAbsolute() const289 bool 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)
isDriveRoot() const297 bool QFileSystemEntry::isDriveRoot() const
298 {
299 resolveFilePath();
300 return QFileSystemEntry::isDriveRootPath(m_filePath);
301 }
302
isDriveRootPath(const QString & path)303 bool 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
isRootPath(const QString & path)315 bool 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
isRoot() const328 bool QFileSystemEntry::isRoot() const
329 {
330 resolveFilePath();
331 return isRootPath(m_filePath);
332 }
333
334 // private methods
335
findLastSeparator() const336 void QFileSystemEntry::findLastSeparator() const
337 {
338 if (m_lastSeparator == -2) {
339 resolveFilePath();
340 m_lastSeparator = m_filePath.lastIndexOf(QLatin1Char('/'));
341 }
342 }
343
findFileNameSeparators() const344 void 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
isClean() const392 bool 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
423 QT_END_NAMESPACE
424