Revision control

++ // Copyright (C) 2019 The Qt Company Ltd.
++ // Copyright (C) 2020 Intel Corporation.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
++ // Qt-Security score:critical reason:data-parser
++
++ #include "qresource.h"
++ #include "qresource_p.h"
++ #include "qresource_iterator_p.h"
++ #include "qset.h"
++ #include <private/qlocking_p.h>
++ #include "qdebug.h"
++ #include "qlocale.h"
++ #include "qglobal.h"
++ #include "qlist.h"
++ #include "qdatetime.h"
++ #include "qbytearray.h"
++ #include "qstringlist.h"
++ #include "qvarlengtharray.h"
++ #include "qendian.h"
++ #include <qshareddata.h>
++ #include <qplatformdefs.h>
++ #include <qendian.h>
++ #include "private/qabstractfileengine_p.h"
++ #include "private/qduplicatetracker_p.h"
++ #include "private/qnumeric_p.h"
++ #include "private/qsimd_p.h"
++ #include "private/qtools_p.h"
++ #include "private/qsystemerror_p.h"
++
++ #ifndef QT_NO_COMPRESS
++ # include <zconf.h>
++ # include <zlib.h>
++ #endif
++ #if QT_CONFIG(zstd)
++ # include <zstd.h>
++ #endif
++
++ #if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
++ # define QT_USE_MMAP
++ # include <sys/mman.h>
++ # ifdef Q_OS_LINUX
++ // since 5.7, so define in case we're being compiled with older kernel headers
++ # define MREMAP_DONTUNMAP 4
++ # elif defined(Q_OS_DARWIN)
++ # include <mach/mach.h>
++ # include <mach/vm_map.h>
++ # endif
++ #endif
++ #ifdef Q_OS_WIN
++ # include <qt_windows.h>
++ #endif
++
++ //#define DEBUG_RESOURCE_MATCH
++
++ QT_BEGIN_NAMESPACE
++
++ using namespace Qt::StringLiterals;
++
++ // Symbols used by code generated by RCC.
++ // They cause compilation errors if the RCC content couldn't
++ // be interpreted by this QtCore version.
++ #if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
++ # define RCC_FEATURE_SYMBOL(feature) \
++ extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \
++ const quint8 qt_resourceFeature ## feature = 0;
++ #else
++ # define RCC_FEATURE_SYMBOL(feature) \
++ Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
++ #endif
++
++ #ifndef QT_NO_COMPRESS
++ RCC_FEATURE_SYMBOL(Zlib)
++ #endif
++ #if QT_CONFIG(zstd)
++ RCC_FEATURE_SYMBOL(Zstd)
++ #endif
++
++ #undef RCC_FEATURE_SYMBOL
++
++ namespace {
++ class QStringSplitter
++ {
++ public:
++ explicit QStringSplitter(QStringView sv)
++ : m_data(sv.data()), m_len(sv.size())
++ {
++ }
++
++ inline bool hasNext()
++ {
++ while (m_pos < m_len && m_data[m_pos] == m_splitChar)
++ ++m_pos;
++ return m_pos < m_len;
++ }
++
++ inline QStringView next()
++ {
++ const qsizetype start = m_pos;
++ while (m_pos < m_len && m_data[m_pos] != m_splitChar)
++ ++m_pos;
++ return QStringView(m_data + start, m_pos - start);
++ }
++
++ const QChar *m_data;
++ qsizetype m_len;
++ qsizetype m_pos = 0;
++ QChar m_splitChar = u'/';
++ };
++
++ // resource glue
++ class QResourceRoot
++ {
++ public:
++ enum Flags {
++ // must match rcc.h
++ Compressed = 0x01,
++ Directory = 0x02,
++ CompressedZstd = 0x04
++ };
++
++ private:
++ const uchar *tree, *names, *payloads;
++ int version;
++ inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
++ uint hash(int node) const;
++ QString name(int node) const;
++ bool nameMatches(int node, QStringView other) const;
++ short flags(int node) const;
++ public:
++ mutable QAtomicInt ref;
++
++ inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
++ inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(version, t, n, d); }
++ virtual ~QResourceRoot() = default;
++ Q_DISABLE_COPY_MOVE(QResourceRoot)
++ int findNode(const QString &path, const QLocale &locale) const;
++ inline bool isContainer(int node) const { return flags(node) & Directory; }
++ QResource::Compression compressionAlgo(int node)
++ {
++ uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
++ if (compressionFlags == Compressed)
++ return QResource::ZlibCompression;
++ if (compressionFlags == CompressedZstd)
++ return QResource::ZstdCompression;
++ return QResource::NoCompression;
++ }
++ const uchar *data(int node, qint64 *size) const;
++ qint64 lastModified(int node) const;
++ QStringList children(int node) const;
++ virtual QStringView mappingRoot() const { return {}; }
++ bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
++ inline bool operator==(const QResourceRoot &other) const
++ { return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
++ inline bool operator!=(const QResourceRoot &other) const
++ { return !operator==(other); }
++ enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
++ virtual ResourceRootType type() const { return Resource_Builtin; }
++
++ protected:
++ inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
++ tree = t;
++ names = n;
++ payloads = d;
++ version = v;
++ }
++ };
++
++ static QString cleanPath(const QString &_path)
++ {
++ QString path = QDir::cleanPath(_path);
++ // QDir::cleanPath does not remove two trailing slashes under _Windows_
++ // due to support for UNC paths. Remove those manually.
++ if (path.startsWith("//"_L1))
++ path.remove(0, 1);
++ return path;
++ }
++ } // unnamed namespace
++
++ typedef QList<QResourceRoot*> ResourceList;
++ namespace {
++ struct QResourceGlobalData
++ {
++ QRecursiveMutex resourceMutex;
++ ResourceList resourceList;
++ };
++ }
++ Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
++
++ static inline QRecursiveMutex &resourceMutex()
++ { return resourceGlobalData->resourceMutex; }
++
++ static inline ResourceList *resourceList()
++ { return &resourceGlobalData->resourceList; }
++
++ /*!
++ \class QResource
++ \inmodule QtCore
++ \brief The QResource class provides an interface for reading directly from resources.
++
++ \ingroup io
++
++ \reentrant
++ \since 4.2
++
++ QResource is an object that represents a set of data (and possibly
++ children) relating to a single resource entity. QResource gives direct
++ access to the bytes in their raw format. In this way direct access
++ allows reading data without buffer copying or indirection. Indirection
++ is often useful when interacting with the resource entity as if it is a
++ file, this can be achieved with QFile. The data and children behind a
++ QResource are normally compiled into an application/library, but it is
++ also possible to load a resource at runtime. When loaded at run time
++ the resource file will be loaded as one big set of data and then given
++ out in pieces via references into the resource tree.
++
++ A QResource can either be loaded with an absolute path, either treated
++ as a file system rooted with a \c{/} character, or in resource notation
++ rooted with a \c{:} character. A relative resource can also be opened
++ which will be found in the list of paths returned by QDir::searchPaths().
++
++ A QResource that is representing a file will have data backing it, this
++ data can possibly be compressed, in which case qUncompress() must be
++ used to access the real data; this happens implicitly when accessed
++ through a QFile. A QResource that is representing a directory will have
++ only children and no data.
++
++ \section1 Dynamic Resource Loading
++
++ A resource can be left out of an application's binary and loaded when
++ it is needed at run-time by using the registerResource() function. The
++ resource file passed into registerResource() must be a binary resource
++ as created by rcc. Further information about binary resources can be
++ found in \l{The Qt Resource System} documentation.
++
++ This can often be useful when loading a large set of application icons
++ that may change based on a setting, or that can be edited by a user and
++ later recreated. The resource is immediately loaded into memory, either
++ as a result of a single file read operation, or as a memory mapped file.
++
++ This approach can prove to be a significant performance gain as only a
++ single file will be loaded, and pieces of data will be given out via the
++ path requested in setFileName().
++
++ The unregisterResource() function removes a reference to a particular
++ file. If there are QResource objects that currently reference resources related
++ to the unregistered file, they will continue to be valid but the resource
++ file itself will be removed from the resource roots, and thus no further
++ QResource can be created pointing into this resource data. The resource
++ itself will be unmapped from memory when the last QResource that points
++ to it is destroyed.
++
++ \section2 Corruption and Security
++
++ The QResource class performs some checks on the file passed to determine
++ whether it is supported by the current version of Qt. Those tests are only
++ to check the file header does not request features (such as Zstandard
++ decompression) that have not been compiled in or that the file is not of a
++ future version of Qt. They do not confirm the validity of the entire file.
++
++ QResource should not be used on files whose provenance cannot be trusted.
++ Applications should be designed to attempt to load only resource files
++ whose provenance is at least as trustworthy as that of the application
++ itself or its plugins.
++
++ \sa {The Qt Resource System}, QFile, QDir, QFileInfo
++ */
++
++ /*!
++ \enum QResource::Compression
++ \since 5.13
++
++ This enum is used by compressionAlgorithm() to indicate which algorithm the
++ RCC tool used to compress the payload.
++
++ \value NoCompression Contents are not compressed
++ \value ZlibCompression Contents are compressed using \l{https://zlib.net}{zlib} and can
++ be decompressed using the qUncompress() function.
++ \value ZstdCompression Contents are compressed using \l{Zstandard Site}{zstd}. To
++ decompress, use the \c{ZSTD_decompress} function from the zstd
++ library.
++
++ \sa compressionAlgorithm()
++ */
++
++ class QResourcePrivate {
++ public:
++ inline QResourcePrivate(QResource *_q, const QString &file, const QLocale &locale)
++ : locale(locale), fileName(file), q_ptr(_q)
++ {
++ clear();
++ }
++ inline ~QResourcePrivate() { clear(); }
++
++ void ensureInitialized() const;
++ void ensureChildren() const;
++ Q_DECL_PURE_FUNCTION qint64 uncompressedSize() const;
++ qsizetype decompress(char *buffer, qsizetype bufferSize) const;
++
++ bool load(const QString &file);
++ void clear();
++
++ static bool mayRemapData(const QResource &resource);
++
++ QLocale locale;
++ QString fileName, absoluteFilePath;
++ QVarLengthArray<QResourceRoot *, 1> related;
++ qint64 size;
++ qint64 lastModified;
++ const uchar *data;
++ mutable QStringList children;
++ quint8 compressionAlgo;
++ bool container;
++ /* 2 or 6 padding bytes */
++
++ QResource *q_ptr;
++ Q_DECLARE_PUBLIC(QResource)
++ };
++
++ void QResourcePrivate::clear()
++ {
++ absoluteFilePath.clear();
++ compressionAlgo = QResource::NoCompression;
++ data = nullptr;
++ size = 0;
++ children.clear();
++ lastModified = 0;
++ container = 0;
++ for (int i = 0; i < related.size(); ++i) {
++ QResourceRoot *root = related.at(i);
++ if (!root->ref.deref())
++ delete root;
++ }
++ related.clear();
++ }
++
++ bool QResourcePrivate::load(const QString &file)
++ {
++ related.clear();
++ const auto locker = qt_scoped_lock(resourceMutex());
++ const ResourceList *list = resourceList();
++ QString cleaned = cleanPath(file);
++ for (int i = 0; i < list->size(); ++i) {
++ QResourceRoot *res = list->at(i);
++ const int node = res->findNode(cleaned, locale);
++ if (node != -1) {
++ if (related.isEmpty()) {
++ container = res->isContainer(node);
++ if (!container) {
++ data = res->data(node, &size);
++ compressionAlgo = res->compressionAlgo(node);
++ } else {
++ data = nullptr;
++ size = 0;
++ compressionAlgo = QResource::NoCompression;
++ }
++ lastModified = res->lastModified(node);
++ } else if (res->isContainer(node) != container) {
++ qWarning("QResourceInfo: Resource [%s] has both data and children!",
++ file.toLatin1().constData());
++ }
++ res->ref.ref();
++ related.append(res);
++ } else if (res->mappingRootSubdir(file)) {
++ container = true;
++ data = nullptr;
++ size = 0;
++ compressionAlgo = QResource::NoCompression;
++ lastModified = 0;
++ res->ref.ref();
++ related.append(res);
++ }
++ }
++ return !related.isEmpty();
++ }
++
++ void QResourcePrivate::ensureInitialized() const
++ {
++ if (!related.isEmpty())
++ return;
++ if (resourceGlobalData.isDestroyed())
++ return;
++ QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
++ if (fileName == ":"_L1)
++ that->fileName += u'/';
++ that->absoluteFilePath = fileName;
++ if (!that->absoluteFilePath.startsWith(u':'))
++ that->absoluteFilePath.prepend(u':');
++
++ QStringView path(fileName);
++ if (path.startsWith(u':'))
++ path = path.mid(1);
++
++ if (path.startsWith(u'/')) {
++ that->load(path.toString());
++ } else {
++ // Should we search QDir::searchPath() before falling back to root ?
++ const QString searchPath(u'/' + path);
++ if (that->load(searchPath))
++ that->absoluteFilePath = u':' + searchPath;
++ }
++ }
++
++ void QResourcePrivate::ensureChildren() const
++ {
++ ensureInitialized();
++ if (!children.isEmpty() || !container || related.isEmpty())
++ return;
++
++ QString path = absoluteFilePath, k;
++ if (path.startsWith(u':'))
++ path = path.mid(1);
++ QDuplicateTracker<QString> kids(related.size());
++ QString cleaned = cleanPath(path);
++ for (int i = 0; i < related.size(); ++i) {
++ QResourceRoot *res = related.at(i);
++ if (res->mappingRootSubdir(path, &k) && !k.isEmpty()) {
++ if (!kids.hasSeen(k))
++ children += k;
++ } else {
++ const int node = res->findNode(cleaned, locale);
++ if (node != -1) {
++ QStringList related_children = res->children(node);
++ for (int kid = 0; kid < related_children.size(); ++kid) {
++ k = related_children.at(kid);
++ if (!kids.hasSeen(k))
++ children += k;
++ }
++ }
++ }
++ }
++ }
++
++ qint64 QResourcePrivate::uncompressedSize() const
++ {
++ switch (compressionAlgo) {
++ case QResource::NoCompression:
++ return size;
++
++ case QResource::ZlibCompression:
++ #ifndef QT_NO_COMPRESS
++ if (size_t(size) >= sizeof(quint32))
++ return qFromBigEndian<quint32>(data);
++ #else
++ Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
++ Q_UNREACHABLE();
++ #endif
++ break;
++
++ case QResource::ZstdCompression: {
++ #if QT_CONFIG(zstd)
++ size_t n = ZSTD_getFrameContentSize(data, size);
++ return ZSTD_isError(n) ? -1 : qint64(n);
++ #else
++ // This should not happen because we've refused to load such resource
++ Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
++ Q_UNREACHABLE();
++ #endif
++ }
++ }
++ return -1;
++ }
++
++ qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
++ {
++ Q_ASSERT(data);
++ #if defined(QT_NO_COMPRESS) && !QT_CONFIG(zstd)
++ Q_UNUSED(buffer);
++ Q_UNUSED(bufferSize);
++ #endif
++
++ switch (compressionAlgo) {
++ case QResource::NoCompression:
++ Q_UNREACHABLE();
++ break;
++
++ case QResource::ZlibCompression: {
++ #ifndef QT_NO_COMPRESS
++ uLong len = uLong(bufferSize);
++ int res = ::uncompress(reinterpret_cast<Bytef *>(buffer), &len, data + sizeof(quint32),
++ uLong(size - sizeof(quint32)));
++ if (res != Z_OK) {
++ qWarning("QResource: error decompressing zlib content (%d)", res);
++ return -1;
++ }
++ return len;
++ #else
++ Q_UNREACHABLE();
++ #endif
++ }
++
++ case QResource::ZstdCompression: {
++ #if QT_CONFIG(zstd)
++ size_t usize = ZSTD_decompress(buffer, bufferSize, data, size);
++ if (ZSTD_isError(usize)) {
++ qWarning("QResource: error decompressing zstd content: %s", ZSTD_getErrorName(usize));
++ return -1;
++ }
++ return usize;
++ #else
++ Q_UNREACHABLE();
++ #endif
++ }
++ }
++
++ return -1;
++ }
++
++ /*!
++ Constructs a QResource pointing to \a file. \a locale is used to
++ load a specific localization of a resource data.
++
++ \sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
++ */
++
++ QResource::QResource(const QString &file, const QLocale &locale)
++ : d_ptr(new QResourcePrivate(this, file, locale))
++ {
++ }
++
++ /*!
++ Releases the resources of the QResource object.
++ */
++ QResource::~QResource()
++ {
++ }
++
++ /*!
++ Sets a QResource to only load the localization of resource to for \a
++ locale. If a resource for the specific locale is not found then the
++ C locale is used.
++
++ \sa setFileName()
++ */
++
++ void QResource::setLocale(const QLocale &locale)
++ {
++ Q_D(QResource);
++ d->clear();
++ d->locale = locale;
++ }
++
++ /*!
++ Returns the locale used to locate the data for the QResource.
++ */
++
++ QLocale QResource::locale() const
++ {
++ Q_D(const QResource);
++ return d->locale;
++ }
++
++ /*!
++ Sets a QResource to point to \a file. \a file can either be absolute,
++ in which case it is opened directly, if relative then the file will be
++ tried to be found in QDir::searchPaths().
++
++ \sa absoluteFilePath()
++ */
++
++ void QResource::setFileName(const QString &file)
++ {
++ Q_D(QResource);
++ d->clear();
++ d->fileName = file;
++ }
++
++ /*!
++ Returns the full path to the file that this QResource represents as it
++ was passed.
++
++ \sa absoluteFilePath()
++ */
++
++ QString QResource::fileName() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->fileName;
++ }
++
++ /*!
++ Returns the real path that this QResource represents, if the resource
++ was found via the QDir::searchPaths() it will be indicated in the path.
++
++ \sa fileName()
++ */
++
++ QString QResource::absoluteFilePath() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->absoluteFilePath;
++ }
++
++ /*!
++ Returns \c true if the resource really exists in the resource hierarchy,
++ false otherwise.
++
++ */
++
++ bool QResource::isValid() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return !d->related.isEmpty();
++ }
++
++ /*!
++ \fn bool QResource::isFile() const
++
++ Returns \c true if the resource represents a file and thus has data
++ backing it, false if it represents a directory.
++
++ \sa isDir()
++ */
++
++ /*!
++ \since 5.13
++
++ Returns the compression type that this resource is compressed with, if any.
++ If it is not compressed, this function returns QResource::NoCompression.
++
++ If this function returns QResource::ZlibCompression, you may decompress the
++ data using the qUncompress() function. Up until Qt 5.13, this was the only
++ possible compression algorithm.
++
++ If this function returns QResource::ZstdCompression, you need to use the
++ Zstandard library functions (\c{<zstd.h>} header). Qt does not provide a
++ wrapper.
++
++ See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
++
++ \sa data(), isFile()
++ */
++ QResource::Compression QResource::compressionAlgorithm() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return Compression(d->compressionAlgo);
++ }
++
++ /*!
++ Returns the size of the stored data backing the resource.
++
++ If the resource is compressed, this function returns the size of the
++ compressed data. See uncompressedSize() for the uncompressed size.
++
++ \sa data(), uncompressedSize(), isFile()
++ */
++
++ qint64 QResource::size() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->size;
++ }
++
++ /*!
++ \since 5.15
++
++ Returns the size of the data in this resource. If the data was not
++ compressed, this function returns the same as size(). If it was, then this
++ function extracts the size of the original uncompressed data from the
++ stored stream.
++
++ \sa size(), uncompressedData(), isFile()
++ */
++ qint64 QResource::uncompressedSize() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->uncompressedSize();
++ }
++
++ /*!
++ Returns direct access to a segment of read-only data, that this resource
++ represents. If the resource is compressed, the data returned is also
++ compressed. The caller must then decompress the data or use
++ uncompressedData(). If the resource is a directory, \c nullptr is returned.
++
++ \sa uncompressedData(), size(), isFile()
++ */
++
++ const uchar *QResource::data() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->data;
++ }
++
++ /*!
++ \since 5.15
++
++ Returns the resource data, decompressing it first, if the data was stored
++ compressed. If the resource is a directory or an error occurs while
++ decompressing, a null QByteArray is returned.
++
++ \note If the data was compressed, this function will decompress every time
++ it is called. The result is not cached between calls.
++
++ \sa uncompressedSize(), size(), compressionAlgorithm(), isFile()
++ */
++
++ QByteArray QResource::uncompressedData() const
++ {
++ Q_D(const QResource);
++ qint64 n = uncompressedSize();
++ if (n < 0)
++ return QByteArray();
++ if (n > std::numeric_limits<QByteArray::size_type>::max()) {
++ qWarning("QResource: compressed content does not fit into a QByteArray; use QFile instead");
++ return QByteArray();
++ }
++ if (d->compressionAlgo == NoCompression)
++ return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), n);
++
++ // decompress
++ QByteArray result(n, Qt::Uninitialized);
++ n = d->decompress(result.data(), n);
++ if (n < 0)
++ result.clear();
++ else
++ result.truncate(n);
++ return result;
++ }
++
++ /*!
++ \since 5.8
++
++ Returns the date and time when the file was last modified before
++ packaging into a resource.
++ */
++ QDateTime QResource::lastModified() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->lastModified ? QDateTime::fromMSecsSinceEpoch(d->lastModified) : QDateTime();
++ }
++
++ /*!
++ Returns \c true if the resource represents a directory and thus may have
++ children() in it, false if it represents a file.
++
++ \sa isFile()
++ */
++
++ bool QResource::isDir() const
++ {
++ Q_D(const QResource);
++ d->ensureInitialized();
++ return d->container;
++ }
++
++ /*!
++ Returns a list of all resources in this directory, if the resource
++ represents a file the list will be empty.
++
++ \sa isDir()
++ */
++
++ QStringList QResource::children() const
++ {
++ Q_D(const QResource);
++ d->ensureChildren();
++ return d->children;
++ }
++
++ inline uint QResourceRoot::hash(int node) const
++ {
++ if (!node) // root
++ return 0;
++ const int offset = findOffset(node);
++ qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
++ name_offset += 2; // jump past name length
++ return qFromBigEndian<quint32>(names + name_offset);
++ }
++ inline QString QResourceRoot::name(int node) const
++ {
++ if (!node) // root
++ return QString();
++ const int offset = findOffset(node);
++
++ QString ret;
++ qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
++ quint16 name_length = qFromBigEndian<qint16>(names + name_offset);
++ name_offset += 2;
++ name_offset += 4; // jump past hash
++
++ ret.resize(name_length);
++ QChar *strData = ret.data();
++ qFromBigEndian<char16_t>(names + name_offset, name_length, strData);
++ return ret;
++ }
++
++ inline bool QResourceRoot::nameMatches(int node, QStringView other) const
++ {
++ if (!node) // root
++ return other.isEmpty();
++ const int offset = findOffset(node);
++
++ qint32 name_offset = qFromBigEndian<qint32>(tree + offset);
++ qsizetype name_length = qFromBigEndian<qint16>(names + name_offset);
++ name_offset += 2;
++ name_offset += 4; //jump past hash
++
++ if (other.size() != name_length)
++ return false;
++ const QChar *strData = reinterpret_cast<const QChar *>(names + name_offset);
++ for (qsizetype ch = 0; ch < name_length; ++ch) {
++ if (other.at(ch) != qFromBigEndian<char16_t>(strData + ch))
++ return false;
++ }
++ return true;
++ }
++
++ int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
++ {
++ QStringView path = _path;
++ {
++ const QStringView root = mappingRoot();
++ if (!root.isEmpty()) {
++ if (root == path) {
++ path = u"/"_sv;
++ } else {
++ const bool rootEndsWithSlash = root.endsWith(u'/');
++ const qsizetype prefixLength = root.size() + !rootEndsWithSlash;
++ if (path.size() >= prefixLength && path.startsWith(root)
++ && (rootEndsWithSlash || path.at(root.size()) == u'/')) {
++ path = path.sliced(prefixLength - 1);
++ }
++ if (path.isEmpty())
++ path = u"/"_sv;
++ }
++ }
++ }
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
++ #endif
++
++ if (path == "/"_L1)
++ return 0;
++
++ // the root node is always first
++ qint32 child_count = qFromBigEndian<qint32>(tree + 6);
++ qint32 child = qFromBigEndian<qint32>(tree + 10);
++
++ // now iterate up the tree
++ int node = -1;
++
++ QStringSplitter splitter(path);
++ while (child_count && splitter.hasNext()) {
++ QStringView segment = splitter.next();
++
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << " CHILDREN" << segment;
++ for (int j = 0; j < child_count; ++j) {
++ qDebug() << " " << child + j << " :: " << name(child + j);
++ }
++ #endif
++ const uint h = qt_hash(segment);
++
++ // do the binary search for the hash
++ int l = 0, r = child_count - 1;
++ int sub_node = (l + r + 1) / 2;
++ while (r != l) {
++ const uint sub_node_hash = hash(child + sub_node);
++ if (h == sub_node_hash)
++ break;
++ else if (h < sub_node_hash)
++ r = sub_node - 1;
++ else
++ l = sub_node;
++ sub_node = (l + r + 1) / 2;
++ }
++ sub_node += child;
++
++ // now do the "harder" compares
++ bool found = false;
++ if (hash(sub_node) == h) {
++ while (sub_node > child && hash(sub_node - 1) == h) // backup for collisions
++ --sub_node;
++ for (; sub_node < child + child_count && hash(sub_node) == h;
++ ++sub_node) { // here we go...
++ if (nameMatches(sub_node, segment)) {
++ found = true;
++ int offset = findOffset(sub_node);
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << " TRY" << sub_node << name(sub_node) << offset;
++ #endif
++ offset += 4; // jump past name
++
++ const qint16 flags = qFromBigEndian<qint16>(tree + offset);
++ offset += 2;
++
++ if (!splitter.hasNext()) {
++ if (!(flags & Directory)) {
++ const qint16 territory = qFromBigEndian<qint16>(tree + offset);
++ offset += 2;
++
++ const qint16 language = qFromBigEndian<qint16>(tree + offset);
++ offset += 2;
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << " " << "LOCALE" << country << language;
++ #endif
++ if (territory == locale.territory() && language == locale.language()) {
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
++ #endif
++ return sub_node;
++ } else if ((territory == QLocale::AnyTerritory
++ && language == locale.language())
++ || (territory == QLocale::AnyTerritory
++ && language == QLocale::C
++ && node == -1)) {
++ node = sub_node;
++ }
++ continue;
++ } else {
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
++ #endif
++
++ return sub_node;
++ }
++ }
++
++ if (!(flags & Directory))
++ return -1;
++
++ child_count = qFromBigEndian<qint32>(tree + offset);
++ offset += 4;
++ child = qFromBigEndian<qint32>(tree + offset);
++ break;
++ }
++ }
++ }
++ if (!found)
++ break;
++ }
++ #ifdef DEBUG_RESOURCE_MATCH
++ qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
++ #endif
++ return node;
++ }
++ short QResourceRoot::flags(int node) const
++ {
++ if (node == -1)
++ return 0;
++ const int offset = findOffset(node) + 4; // jump past name
++ return qFromBigEndian<qint16>(tree + offset);
++ }
++ const uchar *QResourceRoot::data(int node, qint64 *size) const
++ {
++ if (node == -1) {
++ *size = 0;
++ return nullptr;
++ }
++ int offset = findOffset(node) + 4; // jump past name
++
++ const qint16 flags = qFromBigEndian<qint16>(tree + offset);
++ offset += 2;
++
++ offset += 4; // jump past locale
++
++ if (!(flags & Directory)) {
++ const qint32 data_offset = qFromBigEndian<qint32>(tree + offset);
++ const quint32 data_length = qFromBigEndian<quint32>(payloads + data_offset);
++ const uchar *ret = payloads + data_offset + 4;
++ *size = data_length;
++ return ret;
++ }
++ *size = 0;
++ return nullptr;
++ }
++
++ qint64 QResourceRoot::lastModified(int node) const
++ {
++ if (node == -1 || version < 0x02)
++ return 0;
++
++ const int offset = findOffset(node) + 14;
++
++ return qFromBigEndian<qint64>(tree + offset);
++ }
++
++ QStringList QResourceRoot::children(int node) const
++ {
++ if (node == -1)
++ return QStringList();
++ int offset = findOffset(node) + 4; // jump past name
++
++ const qint16 flags = qFromBigEndian<qint16>(tree + offset);
++ offset += 2;
++
++ QStringList ret;
++ if (flags & Directory) {
++ const qint32 child_count = qFromBigEndian<qint32>(tree + offset);
++ offset += 4;
++ const qint32 child_off = qFromBigEndian<qint32>(tree + offset);
++ ret.reserve(child_count);
++ for (int i = child_off; i < child_off + child_count; ++i)
++ ret << name(i);
++ }
++ return ret;
++ }
++ bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
++ {
++ const QStringView root = mappingRoot();
++ if (root.isEmpty())
++ return false;
++
++ QStringSplitter rootIt(root);
++ QStringSplitter pathIt(path);
++ while (rootIt.hasNext()) {
++ if (pathIt.hasNext()) {
++ if (rootIt.next() != pathIt.next()) // mismatch
++ return false;
++ } else {
++ // end of path, but not of root:
++ if (match)
++ *match = rootIt.next().toString();
++ return true;
++ }
++ }
++ // end of root
++ return !pathIt.hasNext();
++ }
++
++ Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
++ const unsigned char *name, const unsigned char *data)
++ {
++ if (resourceGlobalData.isDestroyed())
++ return false;
++ const auto locker = qt_scoped_lock(resourceMutex());
++ ResourceList *list = resourceList();
++ if (version >= 0x01 && version <= 0x3) {
++ bool found = false;
++ QResourceRoot res(version, tree, name, data);
++ for (int i = 0; i < list->size(); ++i) {
++ if (*list->at(i) == res) {
++ found = true;
++ break;
++ }
++ }
++ if (!found) {
++ QResourceRoot *root = new QResourceRoot(version, tree, name, data);
++ root->ref.ref();
++ list->append(root);
++ }
++ return true;
++ }
++ return false;
++ }
++
++ Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
++ const unsigned char *name, const unsigned char *data)
++ {
++ if (resourceGlobalData.isDestroyed())
++ return false;
++
++ const auto locker = qt_scoped_lock(resourceMutex());
++ if (version >= 0x01 && version <= 0x3) {
++ QResourceRoot res(version, tree, name, data);
++ ResourceList *list = resourceList();
++ for (int i = 0; i < list->size();) {
++ if (*list->at(i) == res) {
++ QResourceRoot *root = list->takeAt(i);
++ if (!root->ref.deref())
++ delete root;
++ } else {
++ ++i;
++ }
++ }
++ return true;
++ }
++ return false;
++ }
++
++ namespace {
++ // run time resource creation
++ class QDynamicBufferResourceRoot : public QResourceRoot
++ {
++ QString root;
++ const uchar *buffer;
++
++ public:
++ inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
++ inline ~QDynamicBufferResourceRoot() { }
++ inline const uchar *mappingBuffer() const { return buffer; }
++ QStringView mappingRoot() const override { return root; }
++ ResourceRootType type() const override { return Resource_Buffer; }
++
++ // size == -1 means "unknown"
++ bool registerSelf(const uchar *b, qsizetype size, const QString &source = {})
++ {
++ // 5 int "pointers"
++ if (size >= 0 && size < 20)
++ return false;
++
++ // setup the data now
++ int offset = 0;
++
++ // magic number
++ if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
++ || b[offset + 3] != 's') {
++ return false;
++ }
++ offset += 4;
++
++ const int version = qFromBigEndian<qint32>(b + offset);
++ offset += 4;
++
++ const int tree_offset = qFromBigEndian<qint32>(b + offset);
++ offset += 4;
++
++ const int data_offset = qFromBigEndian<qint32>(b + offset);
++ offset += 4;
++
++ const int name_offset = qFromBigEndian<qint32>(b + offset);
++ offset += 4;
++
++ quint32 file_flags = 0;
++ if (version >= 3) {
++ file_flags = qFromBigEndian<qint32>(b + offset);
++ offset += 4;
++ }
++
++ // Some sanity checking for sizes. This is _not_ a security measure.
++ if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
++ return false;
++
++ // And some sanity checking for features
++ quint32 acceptableFlags = 0;
++ #ifndef QT_NO_COMPRESS
++ acceptableFlags |= Compressed;
++ #endif
++ if (QT_CONFIG(zstd))
++ acceptableFlags |= CompressedZstd;
++ if (const quint32 unsupportedFlags = file_flags & ~acceptableFlags) {
++ const QString id = source.isEmpty() ? QString::asprintf("<buffer at %p>", b) : source;
++ qWarning("QResource: %s uses unsupported compression flags 0x%x (supported: 0x%x)",
++ qUtf8Printable(id), unsupportedFlags, acceptableFlags);
++ return false;
++ }
++
++ if (version >= 0x01 && version <= 0x03) {
++ buffer = b;
++ setSource(version, b + tree_offset, b + name_offset, b + data_offset);
++ return true;
++ }
++ return false;
++ }
++ };
++
++ class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
++ {
++ public:
++ static uchar *map_sys(QFile &file, qint64 base, qsizetype size);
++ static void unmap_sys(void *base, qsizetype size);
++
++ private:
++ QString fileName;
++ // for mmap'ed files, this is what needs to be unmapped.
++ uchar *unmapPointer;
++ qsizetype unmapLength;
++
++ public:
++ QDynamicFileResourceRoot(const QString &_root)
++ : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
++ { }
++ ~QDynamicFileResourceRoot() {
++ if (wasMemoryMapped())
++ unmap_sys(unmapPointer, unmapLength);
++ else
++ delete[] mappingBuffer();
++ }
++ QString mappingFile() const { return fileName; }
++ ResourceRootType type() const override { return Resource_File; }
++ bool wasMemoryMapped() const { return unmapPointer; }
++
++ bool registerSelf(const QString &f);
++ };
++ } // unnamed namespace
++
++ #ifndef MAP_FILE
++ # define MAP_FILE 0
++ #endif
++ #ifndef MAP_FAILED
++ # define MAP_FAILED reinterpret_cast<void *>(-1)
++ #endif
++
++ void QDynamicFileResourceRoot::unmap_sys(void *base, qsizetype size)
++ {
++ #if defined(QT_USE_MMAP)
++ munmap(base, size);
++ #elif defined(Q_OS_WIN)
++ Q_UNUSED(size)
++ UnmapViewOfFile(reinterpret_cast<void *>(base));
++ #endif
++ }
++
++ // Note: caller must ensure \a offset and \a size are acceptable to the OS.
++ uchar *QDynamicFileResourceRoot::map_sys(QFile &file, qint64 offset, qsizetype size)
++ {
++ Q_ASSERT(file.isOpen());
++ void *ptr = nullptr;
++ if (size < 0)
++ size = qMin(file.size() - offset, (std::numeric_limits<qsizetype>::max)());
++ int fd = file.handle();
++ if (fd < 0)
++ return nullptr;
++ // We don't use QFile::map() here because we want to dispose of the QFile object
++ #if defined(QT_USE_MMAP)
++ int protection = PROT_READ; // read-only memory
++ int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
++ ptr = QT_MMAP(nullptr, size, protection, flags, fd, offset);
++ if (ptr == MAP_FAILED)
++ ptr = nullptr;
++ #elif defined(Q_OS_WIN)
++ HANDLE fileHandle = reinterpret_cast<HANDLE>(_get_osfhandle(fd));
++ if (fileHandle != INVALID_HANDLE_VALUE) {
++ HANDLE mapHandle = CreateFileMapping(fileHandle, 0, PAGE_WRITECOPY, 0, 0, 0);
++ if (mapHandle) {
++ ptr = MapViewOfFile(mapHandle, FILE_MAP_COPY, DWORD(offset >> 32), DWORD(offset), size);
++ CloseHandle(mapHandle);
++ }
++ }
++ #endif // QT_USE_MMAP
++ return static_cast<uchar *>(ptr);
++ }
++
++ bool QDynamicFileResourceRoot::registerSelf(const QString &f)
++ {
++ QFile file(f);
++ if (!file.open(QIODevice::ReadOnly))
++ return false;
++
++ qint64 data_len = file.size();
++ if (data_len > std::numeric_limits<qsizetype>::max())
++ return false;
++
++ uchar *data = map_sys(file, 0, data_len);
++ bool fromMM = !!data;
++
++ if (!fromMM) {
++ bool ok = false;
++ data = new uchar[data_len];
++ ok = (data_len == file.read(reinterpret_cast<char *>(data), data_len));
++ if (!ok) {
++ delete[] data;
++ data = nullptr;
++ data_len = 0;
++ return false;
++ }
++ }
++ if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len, f)) {
++ if (fromMM) {
++ unmapPointer = data;
++ unmapLength = data_len;
++ }
++ fileName = f;
++ return true;
++ }
++ return false;
++ }
++
++ static QString qt_resource_fixResourceRoot(QString r)
++ {
++ if (!r.isEmpty()) {
++ if (r.startsWith(u':'))
++ r = r.mid(1);
++ if (!r.isEmpty())
++ r = QDir::cleanPath(r);
++ }
++ return r;
++ }
++
++ /*!
++ \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
++
++ Registers the resource with the given \a rccFileName at the location in the
++ resource tree specified by \a mapRoot, and returns \c true if the file is
++ successfully opened; otherwise returns \c false.
++
++ \sa unregisterResource()
++ */
++
++ bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
++ {
++ QString r = qt_resource_fixResourceRoot(resourceRoot);
++ if (!r.isEmpty() && r[0] != u'/') {
++ qWarning("QDir::registerResource: Registering a resource [%ls] must be rooted in an "
++ "absolute path (start with /) [%ls]",
++ qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
++ return false;
++ }
++
++ QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
++ if (root->registerSelf(rccFilename)) {
++ root->ref.ref();
++ const auto locker = qt_scoped_lock(resourceMutex());
++ resourceList()->append(root);
++ return true;
++ }
++ delete root;
++ return false;
++ }
++
++ /*!
++ \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
++
++ Unregisters the resource with the given \a rccFileName at the location in
++ the resource tree specified by \a mapRoot, and returns \c true if the
++ resource is successfully unloaded and no references exist for the
++ resource; otherwise returns \c false.
++
++ \sa registerResource()
++ */
++
++ bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
++ {
++ QString r = qt_resource_fixResourceRoot(resourceRoot);
++
++ const auto locker = qt_scoped_lock(resourceMutex());
++ ResourceList *list = resourceList();
++ for (int i = 0; i < list->size(); ++i) {
++ QResourceRoot *res = list->at(i);
++ if (res->type() == QResourceRoot::Resource_File) {
++ QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
++ if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
++ list->removeAt(i);
++ if (!root->ref.deref()) {
++ delete root;
++ return true;
++ }
++ return false;
++ }
++ }
++ }
++ return false;
++ }
++
++ /*!
++ \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
++ \since 4.3
++
++ Registers the resource with the given \a rccData at the location in the
++ resource tree specified by \a mapRoot, and returns \c true if the file is
++ successfully opened; otherwise returns \c false.
++
++ \warning The data must remain valid throughout the life of any QFile
++ that may reference the resource data.
++
++ \sa unregisterResource()
++ */
++
++ bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
++ {
++ QString r = qt_resource_fixResourceRoot(resourceRoot);
++ if (!r.isEmpty() && r[0] != u'/') {
++ qWarning("QDir::registerResource: Registering a resource [%p] must be rooted in an "
++ "absolute path (start with /) [%ls]",
++ rccData, qUtf16Printable(resourceRoot));
++ return false;
++ }
++
++ QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
++ if (root->registerSelf(rccData, -1)) {
++ root->ref.ref();
++ const auto locker = qt_scoped_lock(resourceMutex());
++ resourceList()->append(root);
++ return true;
++ }
++ delete root;
++ return false;
++ }
++
++ /*!
++ \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
++ \since 4.3
++
++ Unregisters the resource with the given \a rccData at the location in the
++ resource tree specified by \a mapRoot, and returns \c true if the resource is
++ successfully unloaded and no references exist into the resource; otherwise returns \c false.
++
++ \sa registerResource()
++ */
++
++ bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
++ {
++ QString r = qt_resource_fixResourceRoot(resourceRoot);
++
++ const auto locker = qt_scoped_lock(resourceMutex());
++ ResourceList *list = resourceList();
++ for (int i = 0; i < list->size(); ++i) {
++ QResourceRoot *res = list->at(i);
++ if (res->type() == QResourceRoot::Resource_Buffer) {
++ QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
++ if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
++ list->removeAt(i);
++ if (!root->ref.deref()) {
++ delete root;
++ return true;
++ }
++ return false;
++ }
++ }
++ }
++ return false;
++ }
++
++ #if !defined(QT_BOOTSTRAPPED)
++ // resource engine
++ class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
++ {
++ protected:
++ Q_DECLARE_PUBLIC(QResourceFileEngine)
++ private:
++ uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
++ bool unmap(uchar *ptr);
++ void uncompress() const;
++ void mapUncompressed();
++ bool mapUncompressed_sys();
++ void unmapUncompressed_sys();
++ qint64 offset = 0;
++ QResource resource;
++ mutable QByteArray uncompressed;
++ bool mustUnmap = false;
++
++ // minimum size for which we'll try to re-open ourselves in mapUncompressed()
++ static constexpr qsizetype RemapCompressedThreshold = 16384;
++ protected:
++ QResourceFileEnginePrivate(QAbstractFileEngine *q) :
++ QAbstractFileEnginePrivate(q) {}
++
++ ~QResourceFileEnginePrivate()
++ {
++ if (mustUnmap)
++ unmapUncompressed_sys();
++ }
++ };
++
++ bool QResourceFileEngine::caseSensitive() const
++ {
++ return true;
++ }
++
++ QResourceFileEngine::QResourceFileEngine(const QString &file) :
++ QAbstractFileEngine(*new QResourceFileEnginePrivate(this))
++ {
++ Q_D(QResourceFileEngine);
++ d->resource.setFileName(file);
++ }
++
++ QResourceFileEngine::~QResourceFileEngine()
++ {
++ }
++
++ void QResourceFileEngine::setFileName(const QString &file)
++ {
++ Q_D(QResourceFileEngine);
++ d->resource.setFileName(file);
++ }
++
++ bool QResourceFileEngine::open(QIODevice::OpenMode flags,
++ std::optional<QFile::Permissions> permissions)
++ {
++ Q_UNUSED(permissions);
++
++ Q_D(QResourceFileEngine);
++ if (d->resource.fileName().isEmpty()) {
++ qWarning("QResourceFileEngine::open: Missing file name");
++ return false;
++ }
++ if (flags & QIODevice::WriteOnly)
++ return false;
++ if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
++ d->uncompress();
++ if (d->uncompressed.isNull()) {
++ d->errorString = QSystemError::stdString(EIO);
++ return false;
++ }
++ }
++ if (!d->resource.isValid()) {
++ d->errorString = QSystemError::stdString(ENOENT);
++ return false;
++ }
++ return true;
++ }
++
++ bool QResourceFileEngine::close()
++ {
++ Q_D(QResourceFileEngine);
++ d->offset = 0;
++ return true;
++ }
++
++ bool QResourceFileEngine::flush()
++ {
++ return true;
++ }
++
++ qint64 QResourceFileEngine::read(char *data, qint64 len)
++ {
++ Q_D(QResourceFileEngine);
++ if (len > size() - d->offset)
++ len = size() - d->offset;
++ if (len <= 0)
++ return 0;
++ if (!d->uncompressed.isNull())
++ memcpy(data, d->uncompressed.constData() + d->offset, len);
++ else
++ memcpy(data, d->resource.data() + d->offset, len);
++ d->offset += len;
++ return len;
++ }
++
++ qint64 QResourceFileEngine::size() const
++ {
++ Q_D(const QResourceFileEngine);
++ return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
++ }
++
++ qint64 QResourceFileEngine::pos() const
++ {
++ Q_D(const QResourceFileEngine);
++ return d->offset;
++ }
++
++ bool QResourceFileEngine::atEnd() const
++ {
++ Q_D(const QResourceFileEngine);
++ if (!d->resource.isValid())
++ return true;
++ return d->offset == size();
++ }
++
++ bool QResourceFileEngine::seek(qint64 pos)
++ {
++ Q_D(QResourceFileEngine);
++ if (!d->resource.isValid())
++ return false;
++
++ if (d->offset > size())
++ return false;
++ d->offset = pos;
++ return true;
++ }
++
++ QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
++ {
++ Q_D(const QResourceFileEngine);
++ QAbstractFileEngine::FileFlags ret;
++ if (!d->resource.isValid())
++ return ret;
++
++ if (type & PermsMask)
++ ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
++ | ReadOtherPerm);
++ if (type & TypesMask) {
++ if (d->resource.isDir())
++ ret |= DirectoryType;
++ else
++ ret |= FileType;
++ }
++ if (type & FlagsMask) {
++ ret |= ExistsFlag;
++ if (d->resource.absoluteFilePath() == ":/"_L1)
++ ret |= RootFlag;
++ }
++ return ret;
++ }
++
++ QString QResourceFileEngine::fileName(FileName file) const
++ {
++ Q_D(const QResourceFileEngine);
++ if (file == BaseName) {
++ const qsizetype slash = d->resource.fileName().lastIndexOf(u'/');
++ if (slash == -1)
++ return d->resource.fileName();
++ return d->resource.fileName().mid(slash + 1);
++ } else if (file == PathName || file == AbsolutePathName) {
++ const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
++ : d->resource.fileName();
++ const qsizetype slash = path.lastIndexOf(u'/');
++ if (slash == -1)
++ return ":"_L1;
++ else if (slash <= 1)
++ return ":/"_L1;
++ return path.left(slash);
++
++ } else if (file == CanonicalName || file == CanonicalPathName) {
++ const QString absoluteFilePath = d->resource.absoluteFilePath();
++ if (file == CanonicalPathName) {
++ const qsizetype slash = absoluteFilePath.lastIndexOf(u'/');
++ if (slash != -1)
++ return absoluteFilePath.left(slash);
++ }
++ return absoluteFilePath;
++ }
++ return d->resource.fileName();
++ }
++
++ uint QResourceFileEngine::ownerId(FileOwner) const
++ {
++ static const uint nobodyID = static_cast<uint>(-2);
++ return nobodyID;
++ }
++
++ QDateTime QResourceFileEngine::fileTime(QFile::FileTime time) const
++ {
++ Q_D(const QResourceFileEngine);
++ if (time == QFile::FileModificationTime)
++ return d->resource.lastModified();
++ return QDateTime();
++ }
++
++ /*!
++ \internal
++ */
++ QAbstractFileEngine::IteratorUniquePtr
++ QResourceFileEngine::beginEntryList(const QString &path, QDirListing::IteratorFlags filters,
++ const QStringList &filterNames)
++ {
++ return std::make_unique<QResourceFileEngineIterator>(path, filters, filterNames);
++ }
++
++ bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
++ {
++ Q_D(QResourceFileEngine);
++ if (extension == MapExtension) {
++ const auto *options = static_cast<const MapExtensionOption *>(option);
++ auto *returnValue = static_cast<MapExtensionReturn *>(output);
++ returnValue->address = d->map(options->offset, options->size, options->flags);
++ return (returnValue->address != nullptr);
++ }
++ if (extension == UnMapExtension) {
++ const auto *options = static_cast<const UnMapExtensionOption *>(option);
++ return d->unmap(options->address);
++ }
++ return false;
++ }
++
++ bool QResourceFileEngine::supportsExtension(Extension extension) const
++ {
++ return (extension == UnMapExtension || extension == MapExtension);
++ }
++
++ uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
++ {
++ Q_Q(QResourceFileEngine);
++ Q_ASSERT_X(resource.compressionAlgorithm() == QResource::NoCompression
++ || !uncompressed.isNull(), "QFile::map()",
++ "open() should have uncompressed compressed resources");
++
++ qint64 max = resource.uncompressedSize();
++ qint64 end;
++ if (offset < 0 || size <= 0 || !resource.isValid() ||
++ qAddOverflow(offset, size, &end) || end > max) {
++ q->setError(QFile::UnspecifiedError, QString());
++ return nullptr;
++ }
++
++ const uchar *address = reinterpret_cast<const uchar *>(uncompressed.constBegin());
++ if (!uncompressed.isNull())
++ return const_cast<uchar *>(address) + offset;
++
++ // resource was not compressed
++ address = resource.data();
++ if (flags & QFile::MapPrivateOption) {
++ // We need to provide read-write memory
++ mapUncompressed();
++ address = reinterpret_cast<const uchar *>(uncompressed.constData());
++ }
++
++ return const_cast<uchar *>(address) + offset;
++ }
++
++ bool QResourceFileEnginePrivate::unmap(uchar *ptr)
++ {
++ Q_UNUSED(ptr);
++ return true;
++ }
++
++ void QResourceFileEnginePrivate::uncompress() const
++ {
++ if (resource.compressionAlgorithm() == QResource::NoCompression
++ || !uncompressed.isEmpty() || resource.size() == 0)
++ return; // nothing to do
++ uncompressed = resource.uncompressedData();
++ }
++
++ void QResourceFileEnginePrivate::mapUncompressed()
++ {
++ Q_ASSERT(resource.compressionAlgorithm() == QResource::NoCompression);
++ if (!uncompressed.isNull())
++ return; // nothing to do
++
++ if (resource.uncompressedSize() >= RemapCompressedThreshold) {
++ if (mapUncompressed_sys())
++ return;
++ }
++
++ uncompressed = resource.uncompressedData();
++ uncompressed.detach();
++ }
++
++ #if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
++ inline bool QResourcePrivate::mayRemapData(const QResource &resource)
++ {
++ auto d = resource.d_func();
++
++ // assumptions from load():
++ // - d->related is not empty
++ // - the first item in d->related is the one with our data
++ // by current construction, it's also the only item
++ const QResourceRoot *root = d->related.at(0);
++
++ switch (root->type()) {
++ case QResourceRoot::Resource_Builtin:
++ return true; // always acceptable, memory is read-only
++ case QResourceRoot::Resource_Buffer:
++ return false; // never acceptable, memory is heap
++ case QResourceRoot::Resource_File:
++ break;
++ }
++
++ auto df = static_cast<const QDynamicFileResourceRoot *>(root);
++ return df->wasMemoryMapped();
++ }
++ #endif
++
++ // Returns the page boundaries of where \a location is located in memory.
++ static auto mappingBoundaries(const void *location, qsizetype size)
++ {
++ #ifdef Q_OS_WIN
++ auto getpagesize = [] {
++ SYSTEM_INFO sysinfo;
++ ::GetSystemInfo(&sysinfo);
++ return sysinfo.dwAllocationGranularity;
++ };
++ #endif
++ struct R {
++ void *begin;
++ qsizetype size;
++ qptrdiff offset;
++ } r;
++
++ const quintptr pageMask = getpagesize() - 1;
++ quintptr data = quintptr(location);
++ quintptr begin = data & ~pageMask;
++ quintptr end = (data + size + pageMask) & ~pageMask;
++ r.begin = reinterpret_cast<void *>(begin);
++ r.size = end - begin;
++ r.offset = data & pageMask;
++ return r;
++ }
++
++ bool QResourceFileEnginePrivate::mapUncompressed_sys()
++ {
++ auto r = mappingBoundaries(resource.data(), resource.uncompressedSize());
++ void *ptr = nullptr;
++
++ #if defined(MREMAP_MAYMOVE) && defined(MREMAP_DONTUNMAP)
++ // Use MREMAP_MAYMOVE to tell the kernel to give us a new address and use
++ // MREMAP_DONTUNMAP (supported since kernel 5.7) to request that it create
++ // a new mapping of the same pages, instead of moving. We can only do that
++ // for pages that are read-only, otherwise the kernel replaces the source
++ // with pages full of nulls.
++ if (!QResourcePrivate::mayRemapData(resource))
++ return false;
++
++ ptr = mremap(r.begin, r.size, r.size, MREMAP_MAYMOVE | MREMAP_DONTUNMAP);
++ if (ptr == MAP_FAILED)
++ return false;
++
++ // Allow writing, which the documentation says we allow. This is safe
++ // because MREMAP_DONTUNMAP only works for private mappings.
++ if (mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
++ munmap(ptr, r.size);
++ return false;
++ }
++ #elif defined(Q_OS_DARWIN)
++ mach_port_t self = mach_task_self();
++ vm_address_t addr = 0;
++ vm_address_t mask = 0;
++ bool anywhere = true;
++ bool copy = true;
++ vm_prot_t cur_prot = VM_PROT_READ | VM_PROT_WRITE;
++ vm_prot_t max_prot = VM_PROT_ALL;
++ kern_return_t res = vm_remap(self, &addr, r.size, mask, anywhere,
++ self, vm_address_t(r.begin), copy, &cur_prot,
++ &max_prot, VM_INHERIT_DEFAULT);
++ if (res != KERN_SUCCESS)
++ return false;
++
++ ptr = reinterpret_cast<void *>(addr);
++ if ((max_prot & VM_PROT_WRITE) == 0 || mprotect(ptr, r.size, PROT_READ | PROT_WRITE) != 0) {
++ munmap(ptr, r.size);
++ return false;
++ }
++ #endif
++
++ if (!ptr)
++ return false;
++ const char *newdata = static_cast<char *>(ptr) + r.offset;
++ uncompressed = QByteArray::fromRawData(newdata, resource.uncompressedSize());
++ mustUnmap = true;
++ return true;
++ }
++
++ void QResourceFileEnginePrivate::unmapUncompressed_sys()
++ {
++ auto r = mappingBoundaries(uncompressed.constBegin(), uncompressed.size());
++ QDynamicFileResourceRoot::unmap_sys(r.begin, r.size);
++ }
++
++ #endif // !defined(QT_BOOTSTRAPPED)
++
++ QT_END_NAMESPACE