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 "qfilesystemengine_p.h"
41#include <QtCore/qdir.h>
42#include <QtCore/qset.h>
43#include <QtCore/qstringbuilder.h>
44#include <QtCore/private/qabstractfileengine_p.h>
45#ifdef QT_BUILD_CORE_LIB
46#include <QtCore/private/qresource_p.h>
47#endif
48#include <QtCore/private/qduplicatetracker_p.h>
49
50QT_BEGIN_NAMESPACE
51
52/*!
53 \internal
54
55 Returns the canonicalized form of \a path (i.e., with all symlinks
56 resolved, and all redundant path elements removed.
57*/
58QString QFileSystemEngine::slowCanonicalized(const QString &path)
59{
60 if (path.isEmpty())
61 return path;
62
63 QFileInfo fi;
64 const QChar slash(QLatin1Char('/'));
65 QString tmpPath = path;
66 int separatorPos = 0;
67 QSet<QString> nonSymlinks;
68 QDuplicateTracker<QString> known;
69
70 (void)known.hasSeen(path);
71 do {
72#ifdef Q_OS_WIN
73 if (separatorPos == 0) {
74 if (tmpPath.size() >= 2 && tmpPath.at(0) == slash && tmpPath.at(1) == slash) {
75 // UNC, skip past the first two elements
76 separatorPos = tmpPath.indexOf(slash, 2);
77 } else if (tmpPath.size() >= 3 && tmpPath.at(1) == QLatin1Char(':') && tmpPath.at(2) == slash) {
78 // volume root, skip since it can not be a symlink
79 separatorPos = 2;
80 }
81 }
82 if (separatorPos != -1)
83#endif
84 separatorPos = tmpPath.indexOf(slash, separatorPos + 1);
85 QString prefix = separatorPos == -1 ? tmpPath : tmpPath.left(separatorPos);
86 if (!nonSymlinks.contains(prefix)) {
87 fi.setFile(prefix);
88 if (fi.isSymLink()) {
89 QString target = fi.symLinkTarget();
90 if (separatorPos != -1) {
91 if (fi.isDir() && !target.endsWith(slash))
92 target.append(slash);
93 target.append(QStringView{tmpPath}.mid(separatorPos));
94 }
95 tmpPath = QDir::cleanPath(target);
96 separatorPos = 0;
97
98 if (known.hasSeen(tmpPath))
99 return QString();
100 } else {
101 nonSymlinks.insert(prefix);
102 }
103 }
104 } while (separatorPos != -1);
105
106 return QDir::cleanPath(tmpPath);
107}
108
109static inline bool _q_checkEntry(QFileSystemEntry &entry, QFileSystemMetaData &data, bool resolvingEntry)
110{
111 if (resolvingEntry) {
112 if (!QFileSystemEngine::fillMetaData(entry, data, QFileSystemMetaData::ExistsAttribute)
113 || !data.exists()) {
114 data.clear();
115 return false;
116 }
117 }
118
119 return true;
120}
121
122static inline bool _q_checkEntry(QAbstractFileEngine *&engine, bool resolvingEntry)
123{
124 if (resolvingEntry) {
125 if (!(engine->fileFlags(QAbstractFileEngine::FlagsMask) & QAbstractFileEngine::ExistsFlag)) {
126 delete engine;
127 engine = nullptr;
128 return false;
129 }
130 }
131
132 return true;
133}
134
135static bool _q_resolveEntryAndCreateLegacyEngine_recursive(QFileSystemEntry &entry, QFileSystemMetaData &data,
136 QAbstractFileEngine *&engine, bool resolvingEntry = false)
137{
138 QString const &filePath = entry.filePath();
139 if ((engine = qt_custom_file_engine_handler_create(filePath)))
140 return _q_checkEntry(engine, resolvingEntry);
141
142#if defined(QT_BUILD_CORE_LIB)
143 for (int prefixSeparator = 0; prefixSeparator < filePath.size(); ++prefixSeparator) {
144 QChar const ch = filePath[prefixSeparator];
145 if (ch == QLatin1Char('/'))
146 break;
147
148 if (ch == QLatin1Char(':')) {
149 if (prefixSeparator == 0) {
150 engine = new QResourceFileEngine(filePath);
151 return _q_checkEntry(engine, resolvingEntry);
152 }
153
154 if (prefixSeparator == 1)
155 break;
156
157 const QStringList &paths = QDir::searchPaths(filePath.left(prefixSeparator));
158 for (int i = 0; i < paths.count(); i++) {
159 entry = QFileSystemEntry(QDir::cleanPath(paths.at(i) % QLatin1Char('/') % QStringView{filePath}.mid(prefixSeparator + 1)));
160 // Recurse!
161 if (_q_resolveEntryAndCreateLegacyEngine_recursive(entry, data, engine, true))
162 return true;
163 }
164
165 // entry may have been clobbered at this point.
166 return false;
167 }
168
169 // There's no need to fully validate the prefix here. Consulting the
170 // unicode tables could be expensive and validation is already
171 // performed in QDir::setSearchPaths.
172 //
173 // if (!ch.isLetterOrNumber())
174 // break;
175 }
176#endif // defined(QT_BUILD_CORE_LIB)
177
178 return _q_checkEntry(entry, data, resolvingEntry);
179}
180
181/*!
182 \internal
183
184 Resolves the \a entry (see QDir::searchPaths) and returns an engine for
185 it, but never a QFSFileEngine.
186
187 Returns a file engine that can be used to access the entry. Returns 0 if
188 QFileSystemEngine API should be used to query and interact with the file
189 system object.
190*/
191QAbstractFileEngine *QFileSystemEngine::resolveEntryAndCreateLegacyEngine(
192 QFileSystemEntry &entry, QFileSystemMetaData &data) {
193 QFileSystemEntry copy = entry;
194 QAbstractFileEngine *engine = nullptr;
195
196 if (_q_resolveEntryAndCreateLegacyEngine_recursive(copy, data, engine))
197 // Reset entry to resolved copy.
198 entry = copy;
199 else
200 data.clear();
201
202 return engine;
203}
204
205//static
206QString QFileSystemEngine::resolveUserName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
207{
208#if defined(Q_OS_WIN)
209 Q_UNUSED(metaData);
210 return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerUser);
211#else //(Q_OS_UNIX)
212 if (!metaData.hasFlags(QFileSystemMetaData::UserId))
213 QFileSystemEngine::fillMetaData(entry, metaData, QFileSystemMetaData::UserId);
214 if (!metaData.exists())
215 return QString();
216 return resolveUserName(metaData.userId());
217#endif
218}
219
220//static
221QString QFileSystemEngine::resolveGroupName(const QFileSystemEntry &entry, QFileSystemMetaData &metaData)
222{
223#if defined(Q_OS_WIN)
224 Q_UNUSED(metaData);
225 return QFileSystemEngine::owner(entry, QAbstractFileEngine::OwnerGroup);
226#else //(Q_OS_UNIX)
227 if (!metaData.hasFlags(QFileSystemMetaData::GroupId))
228 QFileSystemEngine::fillMetaData(entry, metaData, QFileSystemMetaData::GroupId);
229 if (!metaData.exists())
230 return QString();
231 return resolveGroupName(metaData.groupId());
232#endif
233}
234
235QT_END_NAMESPACE
236