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