1// Copyright (C) 2018 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3
4#include "qqmlpreviewfileengine.h"
5#include "qqmlpreviewservice.h"
6
7#include <QtCore/qlibraryinfo.h>
8#include <QtCore/qthread.h>
9#include <QtCore/qwaitcondition.h>
10
11#include <cstring>
12
13QT_BEGIN_NAMESPACE
14
15static bool isRelative(const QString &path)
16{
17 if (path.isEmpty())
18 return true;
19 if (path.at(i: 0) == '/')
20 return false;
21 if (path.at(i: 0) == ':' && path.size() >= 2 && path.at(i: 1) == '/')
22 return false;
23#ifdef Q_OS_WIN
24 if (path.length() >= 2 && path.at(1) == ':')
25 return false;
26#endif
27 return true;
28}
29
30static QString absolutePath(const QString &path)
31{
32 return QDir::cleanPath(path: isRelative(path) ? (QDir::currentPath() + '/' + path) : path);
33}
34
35bool isRootPath(const QString &path)
36{
37 return QFileSystemEntry::isRootPath(path);
38}
39
40class QQmlPreviewFileEngineIterator : public QAbstractFileEngineIterator
41{
42public:
43 QQmlPreviewFileEngineIterator(const QString &path, QDirListing::IteratorFlags filters,
44 const QStringList &filterNames, const QStringList &m_entries);
45 ~QQmlPreviewFileEngineIterator();
46
47 bool advance() override;
48 QString currentFileName() const override;
49
50private:
51 const QStringList m_entries;
52 int m_index;
53};
54
55QQmlPreviewFileEngineIterator::QQmlPreviewFileEngineIterator(const QString &path,
56 QDirListing::IteratorFlags filters,
57 const QStringList &filterNames,
58 const QStringList &entries)
59 : QAbstractFileEngineIterator(path, filters, filterNames), m_entries(entries), m_index(0)
60{
61}
62
63QQmlPreviewFileEngineIterator::~QQmlPreviewFileEngineIterator()
64{
65}
66
67bool QQmlPreviewFileEngineIterator::advance()
68{
69 if (m_index >= m_entries.size())
70 return false;
71
72 ++m_index;
73 return true;
74}
75
76QString QQmlPreviewFileEngineIterator::currentFileName() const
77{
78 if (m_index == 0 || m_index > m_entries.size())
79 return QString();
80 return m_entries.at(i: m_index - 1);
81}
82
83QQmlPreviewFileEngine::QQmlPreviewFileEngine(const QString &file, const QString &absolute,
84 QQmlPreviewFileLoader *loader) :
85 m_name(file), m_absolute(absolute), m_loader(loader)
86{
87 load();
88}
89
90void QQmlPreviewFileEngine::setFileName(const QString &file)
91{
92 m_name = file;
93 m_absolute = absolutePath(path: file);
94 m_fallback.reset();
95 m_contents.close();
96 m_contents.setData(QByteArray());
97 m_entries.clear();
98 load();
99}
100
101bool QQmlPreviewFileEngine::open(QIODevice::OpenMode flags,
102 std::optional<QFile::Permissions> permissions)
103{
104 switch (m_result) {
105 case QQmlPreviewFileLoader::File:
106 return m_contents.open(openMode: flags);
107 case QQmlPreviewFileLoader::Directory:
108 return false;
109 case QQmlPreviewFileLoader::Fallback:
110 return m_fallback->open(openMode: flags, permissions);
111 default:
112 Q_UNREACHABLE_RETURN(false);
113 }
114}
115
116bool QQmlPreviewFileEngine::close()
117{
118 switch (m_result) {
119 case QQmlPreviewFileLoader::Fallback:
120 return m_fallback->close();
121 case QQmlPreviewFileLoader::File:
122 m_contents.close();
123 return true;
124 case QQmlPreviewFileLoader::Directory:
125 return false;
126 default:
127 Q_UNREACHABLE_RETURN(false);
128 }
129}
130
131qint64 QQmlPreviewFileEngine::size() const
132{
133 return m_fallback ? m_fallback->size() : m_contents.size();
134}
135
136qint64 QQmlPreviewFileEngine::pos() const
137{
138 return m_fallback ? m_fallback->pos() : m_contents.pos();
139}
140
141bool QQmlPreviewFileEngine::seek(qint64 newPos)
142{
143 return m_fallback? m_fallback->seek(pos: newPos) : m_contents.seek(off: newPos);
144}
145
146qint64 QQmlPreviewFileEngine::read(char *data, qint64 maxlen)
147{
148 return m_fallback ? m_fallback->read(data, maxlen) : m_contents.read(data, maxlen);
149}
150
151QAbstractFileEngine::FileFlags QQmlPreviewFileEngine::fileFlags(
152 QAbstractFileEngine::FileFlags type) const
153{
154 if (m_fallback)
155 return m_fallback->fileFlags(type);
156
157 QAbstractFileEngine::FileFlags ret;
158
159 if (type & PermsMask) {
160 ret |= QAbstractFileEngine::FileFlags(
161 ReadOwnerPerm | ReadUserPerm | ReadGroupPerm | ReadOtherPerm);
162 }
163
164 if (type & TypesMask) {
165 if (m_result == QQmlPreviewFileLoader::Directory)
166 ret |= DirectoryType;
167 else
168 ret |= FileType;
169 }
170
171 if (type & FlagsMask) {
172 ret |= ExistsFlag;
173 if (isRootPath(path: m_name))
174 ret |= RootFlag;
175 }
176
177 return ret;
178}
179
180QString QQmlPreviewFileEngine::fileName(QAbstractFileEngine::FileName file) const
181{
182 if (m_fallback)
183 return m_fallback->fileName(file);
184
185 if (file == BaseName) {
186 int slashPos = m_name.lastIndexOf(c: '/');
187 if (slashPos == -1)
188 return m_name;
189 return m_name.mid(position: slashPos + 1);
190 } else if (file == PathName || file == AbsolutePathName) {
191 const QString path = (file == AbsolutePathName) ? m_absolute : m_name;
192 const int slashPos = path.lastIndexOf(c: '/');
193 if (slashPos == -1)
194 return QString();
195 else if (slashPos == 0)
196 return "/";
197 return path.left(n: slashPos);
198 } else if (file == CanonicalName || file == CanonicalPathName) {
199 if (file == CanonicalPathName) {
200 const int slashPos = m_absolute.lastIndexOf(c: '/');
201 if (slashPos != -1)
202 return m_absolute.left(n: slashPos);
203 }
204 return m_absolute;
205 }
206 return m_name;
207}
208
209uint QQmlPreviewFileEngine::ownerId(QAbstractFileEngine::FileOwner owner) const
210{
211 return m_fallback ? m_fallback->ownerId(owner) : static_cast<uint>(-2);
212}
213
214QAbstractFileEngine::IteratorUniquePtr QQmlPreviewFileEngine::beginEntryList(
215 const QString &path, QDirListing::IteratorFlags filters, const QStringList &filterNames)
216{
217 return m_fallback ? m_fallback->beginEntryList(path, filters, filterNames)
218 : std::make_unique<QQmlPreviewFileEngineIterator>(
219 args: path, args&: filters, args: filterNames, args&: m_entries);
220}
221
222QAbstractFileEngine::IteratorUniquePtr QQmlPreviewFileEngine::endEntryList()
223{
224 return m_fallback ? m_fallback->endEntryList() : nullptr;
225}
226
227bool QQmlPreviewFileEngine::flush()
228{
229 return m_fallback ? m_fallback->flush() : true;
230}
231
232bool QQmlPreviewFileEngine::syncToDisk()
233{
234 return m_fallback ? m_fallback->syncToDisk() : false;
235}
236
237bool QQmlPreviewFileEngine::isSequential() const
238{
239 return m_fallback ? m_fallback->isSequential() : m_contents.isSequential();
240}
241
242bool QQmlPreviewFileEngine::remove()
243{
244 return m_fallback ? m_fallback->remove() : false;
245}
246
247bool QQmlPreviewFileEngine::copy(const QString &newName)
248{
249 return m_fallback ? m_fallback->copy(newName) : false;
250}
251
252bool QQmlPreviewFileEngine::rename(const QString &newName)
253{
254 return m_fallback ? m_fallback->rename(newName) : false;
255}
256
257bool QQmlPreviewFileEngine::renameOverwrite(const QString &newName)
258{
259 return m_fallback ? m_fallback->renameOverwrite(newName) : false;
260}
261
262bool QQmlPreviewFileEngine::link(const QString &newName)
263{
264 return m_fallback ? m_fallback->link(newName) : false;
265}
266
267bool QQmlPreviewFileEngine::mkdir(const QString &dirName, bool createParentDirectories,
268 std::optional<QFile::Permissions> permissions) const
269{
270 return m_fallback ? m_fallback->mkdir(dirName, createParentDirectories, permissions) : false;
271}
272
273bool QQmlPreviewFileEngine::rmdir(const QString &dirName, bool recurseParentDirectories) const
274{
275 return m_fallback ? m_fallback->rmdir(dirName, recurseParentDirectories) : false;
276}
277
278bool QQmlPreviewFileEngine::setSize(qint64 size)
279{
280 switch (m_result) {
281 case QQmlPreviewFileLoader::Fallback:
282 return m_fallback->setSize(size);
283 case QQmlPreviewFileLoader::File:
284 if (size < 0 || size > std::numeric_limits<int>::max())
285 return false;
286 m_contents.buffer().resize(size: static_cast<int>(size));
287 return true;
288 case QQmlPreviewFileLoader::Directory:
289 return false;
290 default:
291 Q_UNREACHABLE_RETURN(false);
292 }
293}
294
295bool QQmlPreviewFileEngine::caseSensitive() const
296{
297 return m_fallback ? m_fallback->caseSensitive() : true;
298}
299
300bool QQmlPreviewFileEngine::isRelativePath() const
301{
302 return m_fallback ? m_fallback->isRelativePath() : isRelative(path: m_name);
303}
304
305QStringList QQmlPreviewFileEngine::entryList(QDir::Filters filters,
306 const QStringList &filterNames) const
307{
308 return m_fallback ? m_fallback->entryList(filters, filterNames)
309 : QAbstractFileEngine::entryList(filters, filterNames);
310}
311
312bool QQmlPreviewFileEngine::setPermissions(uint perms)
313{
314 return m_fallback ? m_fallback->setPermissions(perms) : false;
315}
316
317QByteArray QQmlPreviewFileEngine::id() const
318{
319 return m_fallback ? m_fallback->id() : QByteArray();
320}
321
322QString QQmlPreviewFileEngine::owner(FileOwner owner) const
323{
324 return m_fallback ? m_fallback->owner(owner) : QString();
325}
326
327QDateTime QQmlPreviewFileEngine::fileTime(QFile::FileTime time) const
328{
329 // Files we replace are always newer than the ones we had before. This makes the QML engine
330 // actually recompile them, rather than pick them from the cache.
331 return m_fallback ? m_fallback->fileTime(time) : QDateTime::currentDateTime();
332}
333
334int QQmlPreviewFileEngine::handle() const
335{
336 return m_fallback ? m_fallback->handle() : -1;
337}
338
339qint64 QQmlPreviewFileEngine::readLine(char *data, qint64 maxlen)
340{
341 return m_fallback ? m_fallback->readLine(data, maxlen) : m_contents.readLine(data, maxlen);
342}
343
344qint64 QQmlPreviewFileEngine::write(const char *data, qint64 len)
345{
346 return m_fallback ? m_fallback->write(data, len) : m_contents.write(data, len);
347}
348
349bool QQmlPreviewFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
350{
351 return m_fallback ? m_fallback->extension(extension, option, output) : false;
352}
353
354bool QQmlPreviewFileEngine::supportsExtension(Extension extension) const
355{
356 return m_fallback ? m_fallback->supportsExtension(extension) : false;
357}
358
359void QQmlPreviewFileEngine::load() const
360{
361 // We can get here from different threads on different instances of QQmlPreviewFileEngine.
362 // However, there is only one loader per QQmlPreviewFileEngineHandler and it is not thread-safe.
363 // Its content mutex doesn't help us here because we explicitly wait on it in load(), which
364 // causes it to be released. Therefore, lock the load mutex first.
365 // This doesn't cause any deadlocks because the only thread that wakes the loader on the content
366 // mutex never calls load(). It's the QML debug server thread that handles the debug protocol.
367 QMutexLocker loadLocker(m_loader->loadMutex());
368
369 m_result = m_loader->load(file: m_absolute);
370 switch (m_result) {
371 case QQmlPreviewFileLoader::File:
372 m_contents.setData(m_loader->contents());
373 break;
374 case QQmlPreviewFileLoader::Directory:
375 m_entries = m_loader->entries();
376 break;
377 case QQmlPreviewFileLoader::Fallback:
378 m_fallback = QAbstractFileEngine::create(fileName: m_name);
379 break;
380 case QQmlPreviewFileLoader::Unknown:
381 Q_UNREACHABLE();
382 break;
383 }
384}
385
386QQmlPreviewFileEngineHandler::QQmlPreviewFileEngineHandler(QQmlPreviewFileLoader *loader)
387 : m_loader(loader)
388{
389}
390
391std::unique_ptr<QAbstractFileEngine> QQmlPreviewFileEngineHandler::create(
392 const QString &fileName) const
393{
394 using namespace Qt::StringLiterals;
395 static QList<QLatin1StringView> prohibitedSuffixes {
396 // Don't load compiled QML/JS over the network
397 ".qmlc"_L1, ".jsc"_L1, ".mjsc"_L1,
398
399 // Don't load plugins over the network
400 ".dll"_L1, ".so"_L1, ".dylib"_L1
401 };
402
403 for (QLatin1StringView suffix : prohibitedSuffixes) {
404 if (fileName.endsWith(s: suffix))
405 return nullptr;
406 }
407
408 if (isRootPath(path: fileName))
409 return nullptr;
410
411 QString relative = fileName;
412 while (relative.endsWith(c: '/'))
413 relative.chop(n: 1);
414
415 if (relative.isEmpty() || relative == ":")
416 return nullptr;
417
418 const QString absolute = relative.startsWith(c: ':') ? relative : absolutePath(path: relative);
419
420 if (m_loader->isBlacklisted(file: absolute))
421 return {};
422
423 return std::make_unique<QQmlPreviewFileEngine>(args&: relative, args: absolute, args: m_loader.data());
424}
425
426QT_END_NAMESPACE
427

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

source code of qtdeclarative/src/plugins/qmltooling/qmldbg_preview/qqmlpreviewfileengine.cpp