Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
++ // Qt-Security score:significant
++
++ #include "qqmljsresourcefilemapper_p.h"
++
++ #include <QFileInfo>
++ #include <QDir>
++ #include <QXmlStreamReader>
++
++ QT_BEGIN_NAMESPACE
++
++ QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::allQmlJSFilter() {
++ return Filter {
++ QString(),
++ QStringList { QStringLiteral("qml"), QStringLiteral("js"), QStringLiteral("mjs") },
++ Directory | Recurse
++ };
++ }
++
++ QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::localFileFilter(const QString &file)
++ {
++ return Filter {
++ QDir::cleanPath(QFileInfo(file).absoluteFilePath()),
++ QStringList(),
++ File
++ };
++ }
++
++ QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceFileFilter(const QString &file)
++ {
++ return Filter {
++ file,
++ QStringList(),
++ Resource
++ };
++ }
++
++ QQmlJSResourceFileMapper::Filter QQmlJSResourceFileMapper::resourceQmlDirectoryFilter(
++ const QString &directory)
++ {
++ return Filter {
++ directory,
++ QStringList { QStringLiteral("qml") },
++ Directory | Resource
++ };
++ }
++
++ QQmlJSResourceFileMapper::QQmlJSResourceFileMapper(const QStringList &resourceFiles)
++ {
++ for (const QString &fileName: resourceFiles) {
++ QFile f(fileName);
++ if (!f.open(QIODevice::ReadOnly))
++ continue;
++ populateFromQrcFile(f);
++ }
++ }
++
++ bool QQmlJSResourceFileMapper::isEmpty() const
++ {
++ return qrcPathToFileSystemPath.isEmpty();
++ }
++
++ bool QQmlJSResourceFileMapper::isFile(QStringView resourcePath) const
++ {
++ for (const auto &entry : qrcPathToFileSystemPath) {
++ if (entry.resourcePath == resourcePath)
++ return true;
++ }
++ return false;
++ }
++
++ static bool hasSuffix(const QString &qrcPath, const QStringList &suffixes)
++ {
++ if (suffixes.isEmpty())
++ return true;
++ const QString suffix = QFileInfo(qrcPath).suffix();
++ return suffixes.contains(suffix);
++ }
++
++ template<typename HandleMatch>
++ void doFilter(const QList<QQmlJSResourceFileMapper::Entry> &qrcPathToFileSystemPath,
++ const QQmlJSResourceFileMapper::Filter &filter,
++ const HandleMatch &handler)
++ {
++ if (filter.flags & QQmlJSResourceFileMapper::Directory) {
++ const QString terminatedDirectory = filter.path.endsWith(u'/')
++ ? filter.path : (filter.path + u'/');
++
++ for (auto it = qrcPathToFileSystemPath.constBegin(),
++ end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) {
++
++ const QString candidate = (filter.flags & QQmlJSResourceFileMapper::Resource)
++ ? it->resourcePath
++ : it->filePath;
++
++ if (!filter.path.isEmpty() && !candidate.startsWith(terminatedDirectory))
++ continue;
++
++ if (!hasSuffix(candidate, filter.suffixes))
++ continue;
++
++ if ((filter.flags & QQmlJSResourceFileMapper::Recurse)
++ // Crude. But shall we really allow slashes in QRC file names?
++ || !candidate.mid(terminatedDirectory.size()).contains(u'/')) {
++ if (handler(*it))
++ return;
++ }
++ }
++ return;
++ }
++
++ if (!hasSuffix(filter.path, filter.suffixes))
++ return;
++
++ for (auto it = qrcPathToFileSystemPath.constBegin(),
++ end = qrcPathToFileSystemPath.constEnd(); it != end; ++it) {
++ if (filter.flags & QQmlJSResourceFileMapper::Resource) {
++ if (it->resourcePath == filter.path && handler(*it))
++ return;
++ } else if (it->filePath == filter.path && handler(*it)) {
++ return;
++ }
++ }
++ }
++
++ QList<QQmlJSResourceFileMapper::Entry> QQmlJSResourceFileMapper::filter(
++ const QQmlJSResourceFileMapper::Filter &filter) const
++ {
++ QList<Entry> result;
++ doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) {
++ result.append(entry);
++ return false;
++ });
++ return result;
++ }
++
++ QStringList QQmlJSResourceFileMapper::filePaths(
++ const QQmlJSResourceFileMapper::Filter &filter) const
++ {
++ QStringList result;
++ doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) {
++ result.append(entry.filePath);
++ return false;
++ });
++ return result;
++ }
++
++ QStringList QQmlJSResourceFileMapper::resourcePaths(
++ const QQmlJSResourceFileMapper::Filter &filter) const
++ {
++ QStringList result;
++ doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) {
++ result.append(entry.resourcePath);
++ return false;
++ });
++ return result;
++ }
++
++ QQmlJSResourceFileMapper::Entry QQmlJSResourceFileMapper::entry(
++ const QQmlJSResourceFileMapper::Filter &filter) const
++ {
++ Entry result;
++ doFilter(qrcPathToFileSystemPath, filter, [&](const Entry &entry) {
++ result = entry;
++ return true;
++ });
++ return result;
++ }
++
++ void QQmlJSResourceFileMapper::populateFromQrcFile(QFile &file)
++ {
++ enum State {
++ InitialState,
++ InRCC,
++ InResource,
++ InFile
++ };
++ State state = InitialState;
++
++ QDir qrcDir = QFileInfo(file).absoluteDir();
++
++ QString prefix;
++ QString currentFileName;
++ QXmlStreamAttributes currentFileAttributes;
++
++ QXmlStreamReader reader(&file);
++ while (!reader.atEnd()) {
++ switch (reader.readNext()) {
++ case QXmlStreamReader::StartElement:
++ if (reader.name() == QStringLiteral("RCC")) {
++ if (state != InitialState)
++ return;
++ state = InRCC;
++ continue;
++ } else if (reader.name() == QStringLiteral("qresource")) {
++ if (state != InRCC)
++ return;
++ state = InResource;
++ QXmlStreamAttributes attributes = reader.attributes();
++ if (attributes.hasAttribute(QStringLiteral("prefix")))
++ prefix = attributes.value(QStringLiteral("prefix")).toString();
++ if (!prefix.startsWith(QLatin1Char('/')))
++ prefix.prepend(QLatin1Char('/'));
++ if (!prefix.endsWith(QLatin1Char('/')))
++ prefix.append(QLatin1Char('/'));
++ continue;
++ } else if (reader.name() == QStringLiteral("file")) {
++ if (state != InResource)
++ return;
++ state = InFile;
++ currentFileAttributes = reader.attributes();
++ continue;
++ }
++ return;
++
++ case QXmlStreamReader::EndElement:
++ if (reader.name() == QStringLiteral("file")) {
++ if (state != InFile)
++ return;
++ state = InResource;
++ continue;
++ } else if (reader.name() == QStringLiteral("qresource")) {
++ if (state != InResource)
++ return;
++ state = InRCC;
++ continue;
++ } else if (reader.name() == QStringLiteral("RCC")) {
++ if (state != InRCC)
++ return;
++ state = InitialState;
++ continue;
++ }
++ return;
++
++ case QXmlStreamReader::Characters: {
++ if (reader.isWhitespace())
++ break;
++ if (state != InFile)
++ return;
++ currentFileName = reader.text().toString();
++ if (currentFileName.isEmpty())
++ continue;
++
++ const QString fsPath = QDir::cleanPath(qrcDir.absoluteFilePath(currentFileName));
++
++ if (currentFileAttributes.hasAttribute(QStringLiteral("alias")))
++ currentFileName = currentFileAttributes.value(QStringLiteral("alias")).toString();
++
++ currentFileName = QDir::cleanPath(currentFileName);
++ while (currentFileName.startsWith(QLatin1String("../")))
++ currentFileName.remove(0, 3);
++
++ const QString qrcPath = prefix + currentFileName;
++ if (QFile::exists(fsPath))
++ qrcPathToFileSystemPath.append({qrcPath, fsPath});
++ continue;
++ }
++
++ default: break;
++ }
++ }
++ }
++
++ QT_END_NAMESPACE