// 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

#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 "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>
#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;
    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() { }
    int findNode(const QString &path, const QLocale &locale=QLocale()) 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;
    quint64 lastModified(int node) const;
    QStringList children(int node) const;
    virtual QString mappingRoot() const { return QString(); }
    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

Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);

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) : q_ptr(_q) { clear(); }
    inline ~QResourcePrivate() { clear(); }

    void ensureInitialized() const;
    void ensureChildren() const;
    qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION;
    qsizetype decompress(char *buffer, qsizetype bufferSize) const;

    bool load(const QString &file);
    void clear();

    QLocale locale;
    QString fileName, absoluteFilePath;
    QList<QResourceRoot *> related;
    mutable qint64 size;
    mutable quint64 lastModified;
    mutable const uchar *data;
    mutable QStringList children;
    mutable 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;
    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);
            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))
{
    Q_D(QResource);
    d->fileName = file;
    d->locale = 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;
}

int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
{
    QString path = _path;
    {
        QString root = mappingRoot();
        if (!root.isEmpty()) {
            if (root == path) {
                path = u'/';
            } else {
                if (!root.endsWith(u'/'))
                    root += u'/';
                if (path.size() >= root.size() && path.startsWith(root))
                    path = path.mid(root.size() - 1);
                if (path.isEmpty())
                    path = u'/';
            }
        }
    }
#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 (name(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;
}

quint64 QResourceRoot::lastModified(int node) const
{
    if (node == -1 || version < 0x02)
        return 0;

    const int offset = findOffset(node) + 14;

    return qFromBigEndian<quint64>(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 QString 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; }
    QString mappingRoot() const override { return root; }
    ResourceRootType type() const override { return Resource_Buffer; }

    // size == -1 means "unknown"
    bool registerSelf(const uchar *b, qsizetype size)
    {
        // 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 (file_flags & ~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
{
    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 defined(QT_USE_MMAP)
        if (unmapPointer) {
            munmap(reinterpret_cast<char *>(unmapPointer), unmapLength);
            unmapPointer = nullptr;
            unmapLength = 0;
        } else
#endif
        {
            delete[] mappingBuffer();
        }
    }
    QString mappingFile() const { return fileName; }
    ResourceRootType type() const override { return Resource_File; }

    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

bool QDynamicFileResourceRoot::registerSelf(const QString &f)
{
    bool fromMM = false;
    uchar *data = nullptr;
    qsizetype data_len = 0;

#if defined(QT_USE_MMAP)
    int fd = QT_OPEN(QFile::encodeName(f), O_RDONLY);
    if (fd >= 0) {
        QT_STATBUF st;
        if (!QT_FSTAT(fd, &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) {
            int protection = PROT_READ;                 // read-only memory
            int flags = MAP_FILE | MAP_PRIVATE;         // swap-backed map from file
            void *ptr = QT_MMAP(nullptr, st.st_size,    // any address, whole file
                                protection, flags,
                                fd, 0);                 // from offset 0 of fd
            if (ptr != MAP_FAILED) {
                data = static_cast<uchar *>(ptr);
                data_len = st.st_size;
                fromMM = true;
            }
        }
        QT_CLOSE(fd);
    }
#endif // QT_USE_MMAP
    if (!data) {
        QFile file(f);
        bool ok = false;
        if (file.open(QIODevice::ReadOnly)) {
            qint64 fsize = file.size();
            if (fsize <= std::numeric_limits<qsizetype>::max()) {
                data_len = file.size();
                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;
        }
        fromMM = false;
    }
    if (data && QDynamicBufferResourceRoot::registerSelf(data, data_len)) {
        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();
    qint64 offset;
    QResource resource;
    mutable QByteArray uncompressed;
protected:
    QResourceFileEnginePrivate() : offset(0) { }
};

bool QResourceFileEngine::caseSensitive() const
{
    return true;
}

QResourceFileEngine::QResourceFileEngine(const QString &file) :
    QAbstractFileEngine(*new QResourceFileEnginePrivate)
{
    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::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
                                                                   const QStringList &filterNames)
{
    return new QResourceFileEngineIterator(filters, filterNames);
}

/*!
    \internal
*/
QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
{
    return nullptr;
}

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
    uncompressed = resource.uncompressedData();
    uncompressed.detach();
}

#endif // !defined(QT_BOOTSTRAPPED)

QT_END_NAMESPACE
