1// Copyright (C) 2019 The Qt Company Ltd.
2// Copyright (C) 2020 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qresource.h"
6#include "qresource_p.h"
7#include "qresource_iterator_p.h"
8#include "qset.h"
9#include <private/qlocking_p.h>
10#include "qdebug.h"
11#include "qlocale.h"
12#include "qglobal.h"
13#include "qlist.h"
14#include "qdatetime.h"
15#include "qbytearray.h"
16#include "qstringlist.h"
17#include "qendian.h"
18#include <qshareddata.h>
19#include <qplatformdefs.h>
20#include <qendian.h>
21#include "private/qabstractfileengine_p.h"
22#include "private/qduplicatetracker_p.h"
23#include "private/qnumeric_p.h"
24#include "private/qsimd_p.h"
25#include "private/qtools_p.h"
26#include "private/qsystemerror_p.h"
27
28#ifndef QT_NO_COMPRESS
29# include <zconf.h>
30# include <zlib.h>
31#endif
32#if QT_CONFIG(zstd)
33# include <zstd.h>
34#endif
35
36#if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY)
37# define QT_USE_MMAP
38# include <sys/mman.h>
39#endif
40
41//#define DEBUG_RESOURCE_MATCH
42
43QT_BEGIN_NAMESPACE
44
45using namespace Qt::StringLiterals;
46
47// Symbols used by code generated by RCC.
48// They cause compilation errors if the RCC content couldn't
49// be interpreted by this QtCore version.
50#if defined(__ELF__) || defined(__APPLE__) // same as RCC generates
51# define RCC_FEATURE_SYMBOL(feature) \
52 extern Q_CORE_EXPORT const quint8 qt_resourceFeature ## feature; \
53 const quint8 qt_resourceFeature ## feature = 0;
54#else
55# define RCC_FEATURE_SYMBOL(feature) \
56 Q_CORE_EXPORT quint8 qResourceFeature ## feature() { return 0; }
57#endif
58
59#ifndef QT_NO_COMPRESS
60RCC_FEATURE_SYMBOL(Zlib)
61#endif
62#if QT_CONFIG(zstd)
63RCC_FEATURE_SYMBOL(Zstd)
64#endif
65
66#undef RCC_FEATURE_SYMBOL
67
68class QStringSplitter
69{
70public:
71 explicit QStringSplitter(QStringView sv)
72 : m_data(sv.data()), m_len(sv.size())
73 {
74 }
75
76 inline bool hasNext()
77 {
78 while (m_pos < m_len && m_data[m_pos] == m_splitChar)
79 ++m_pos;
80 return m_pos < m_len;
81 }
82
83 inline QStringView next()
84 {
85 const qsizetype start = m_pos;
86 while (m_pos < m_len && m_data[m_pos] != m_splitChar)
87 ++m_pos;
88 return QStringView(m_data + start, m_pos - start);
89 }
90
91 const QChar *m_data;
92 qsizetype m_len;
93 qsizetype m_pos = 0;
94 QChar m_splitChar = u'/';
95};
96
97// resource glue
98class QResourceRoot
99{
100public:
101 enum Flags {
102 // must match rcc.h
103 Compressed = 0x01,
104 Directory = 0x02,
105 CompressedZstd = 0x04
106 };
107
108private:
109 const uchar *tree, *names, *payloads;
110 int version;
111 inline int findOffset(int node) const { return node * (14 + (version >= 0x02 ? 8 : 0)); } //sizeof each tree element
112 uint hash(int node) const;
113 QString name(int node) const;
114 short flags(int node) const;
115public:
116 mutable QAtomicInt ref;
117
118 inline QResourceRoot(): tree(nullptr), names(nullptr), payloads(nullptr), version(0) {}
119 inline QResourceRoot(int version, const uchar *t, const uchar *n, const uchar *d) { setSource(v: version, t, n, d); }
120 virtual ~QResourceRoot() { }
121 int findNode(const QString &path, const QLocale &locale=QLocale()) const;
122 inline bool isContainer(int node) const { return flags(node) & Directory; }
123 QResource::Compression compressionAlgo(int node)
124 {
125 uint compressionFlags = flags(node) & (Compressed | CompressedZstd);
126 if (compressionFlags == Compressed)
127 return QResource::ZlibCompression;
128 if (compressionFlags == CompressedZstd)
129 return QResource::ZstdCompression;
130 return QResource::NoCompression;
131 }
132 const uchar *data(int node, qint64 *size) const;
133 quint64 lastModified(int node) const;
134 QStringList children(int node) const;
135 virtual QString mappingRoot() const { return QString(); }
136 bool mappingRootSubdir(const QString &path, QString *match = nullptr) const;
137 inline bool operator==(const QResourceRoot &other) const
138 { return tree == other.tree && names == other.names && payloads == other.payloads && version == other.version; }
139 inline bool operator!=(const QResourceRoot &other) const
140 { return !operator==(other); }
141 enum ResourceRootType { Resource_Builtin, Resource_File, Resource_Buffer };
142 virtual ResourceRootType type() const { return Resource_Builtin; }
143
144protected:
145 inline void setSource(int v, const uchar *t, const uchar *n, const uchar *d) {
146 tree = t;
147 names = n;
148 payloads = d;
149 version = v;
150 }
151};
152
153static QString cleanPath(const QString &_path)
154{
155 QString path = QDir::cleanPath(path: _path);
156 // QDir::cleanPath does not remove two trailing slashes under _Windows_
157 // due to support for UNC paths. Remove those manually.
158 if (path.startsWith(s: "//"_L1))
159 path.remove(i: 0, len: 1);
160 return path;
161}
162
163Q_DECLARE_TYPEINFO(QResourceRoot, Q_RELOCATABLE_TYPE);
164
165typedef QList<QResourceRoot*> ResourceList;
166struct QResourceGlobalData
167{
168 QRecursiveMutex resourceMutex;
169 ResourceList resourceList;
170};
171Q_GLOBAL_STATIC(QResourceGlobalData, resourceGlobalData)
172
173static inline QRecursiveMutex &resourceMutex()
174{ return resourceGlobalData->resourceMutex; }
175
176static inline ResourceList *resourceList()
177{ return &resourceGlobalData->resourceList; }
178
179/*!
180 \class QResource
181 \inmodule QtCore
182 \brief The QResource class provides an interface for reading directly from resources.
183
184 \ingroup io
185
186 \reentrant
187 \since 4.2
188
189 QResource is an object that represents a set of data (and possibly
190 children) relating to a single resource entity. QResource gives direct
191 access to the bytes in their raw format. In this way direct access
192 allows reading data without buffer copying or indirection. Indirection
193 is often useful when interacting with the resource entity as if it is a
194 file, this can be achieved with QFile. The data and children behind a
195 QResource are normally compiled into an application/library, but it is
196 also possible to load a resource at runtime. When loaded at run time
197 the resource file will be loaded as one big set of data and then given
198 out in pieces via references into the resource tree.
199
200 A QResource can either be loaded with an absolute path, either treated
201 as a file system rooted with a \c{/} character, or in resource notation
202 rooted with a \c{:} character. A relative resource can also be opened
203 which will be found in the list of paths returned by QDir::searchPaths().
204
205 A QResource that is representing a file will have data backing it, this
206 data can possibly be compressed, in which case qUncompress() must be
207 used to access the real data; this happens implicitly when accessed
208 through a QFile. A QResource that is representing a directory will have
209 only children and no data.
210
211 \section1 Dynamic Resource Loading
212
213 A resource can be left out of an application's binary and loaded when
214 it is needed at run-time by using the registerResource() function. The
215 resource file passed into registerResource() must be a binary resource
216 as created by rcc. Further information about binary resources can be
217 found in \l{The Qt Resource System} documentation.
218
219 This can often be useful when loading a large set of application icons
220 that may change based on a setting, or that can be edited by a user and
221 later recreated. The resource is immediately loaded into memory, either
222 as a result of a single file read operation, or as a memory mapped file.
223
224 This approach can prove to be a significant performance gain as only a
225 single file will be loaded, and pieces of data will be given out via the
226 path requested in setFileName().
227
228 The unregisterResource() function removes a reference to a particular
229 file. If there are QResource objects that currently reference resources related
230 to the unregistered file, they will continue to be valid but the resource
231 file itself will be removed from the resource roots, and thus no further
232 QResource can be created pointing into this resource data. The resource
233 itself will be unmapped from memory when the last QResource that points
234 to it is destroyed.
235
236 \sa {The Qt Resource System}, QFile, QDir, QFileInfo
237*/
238
239/*!
240 \enum QResource::Compression
241 \since 5.13
242
243 This enum is used by compressionAlgorithm() to indicate which algorithm the
244 RCC tool used to compress the payload.
245
246 \value NoCompression Contents are not compressed
247 \value ZlibCompression Contents are compressed using \l{https://zlib.net}{zlib} and can
248 be decompressed using the qUncompress() function.
249 \value ZstdCompression Contents are compressed using \l{Zstandard Site}{zstd}. To
250 decompress, use the \c{ZSTD_decompress} function from the zstd
251 library.
252
253 \sa compressionAlgorithm()
254*/
255
256class QResourcePrivate {
257public:
258 inline QResourcePrivate(QResource *_q) : q_ptr(_q) { clear(); }
259 inline ~QResourcePrivate() { clear(); }
260
261 void ensureInitialized() const;
262 void ensureChildren() const;
263 qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION;
264 qsizetype decompress(char *buffer, qsizetype bufferSize) const;
265
266 bool load(const QString &file);
267 void clear();
268
269 QLocale locale;
270 QString fileName, absoluteFilePath;
271 QList<QResourceRoot *> related;
272 mutable qint64 size;
273 mutable quint64 lastModified;
274 mutable const uchar *data;
275 mutable QStringList children;
276 mutable quint8 compressionAlgo;
277 bool container;
278 /* 2 or 6 padding bytes */
279
280 QResource *q_ptr;
281 Q_DECLARE_PUBLIC(QResource)
282};
283
284void QResourcePrivate::clear()
285{
286 absoluteFilePath.clear();
287 compressionAlgo = QResource::NoCompression;
288 data = nullptr;
289 size = 0;
290 children.clear();
291 lastModified = 0;
292 container = 0;
293 for (int i = 0; i < related.size(); ++i) {
294 QResourceRoot *root = related.at(i);
295 if (!root->ref.deref())
296 delete root;
297 }
298 related.clear();
299}
300
301bool QResourcePrivate::load(const QString &file)
302{
303 related.clear();
304 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
305 const ResourceList *list = resourceList();
306 QString cleaned = cleanPath(path: file);
307 for (int i = 0; i < list->size(); ++i) {
308 QResourceRoot *res = list->at(i);
309 const int node = res->findNode(path: cleaned, locale);
310 if (node != -1) {
311 if (related.isEmpty()) {
312 container = res->isContainer(node);
313 if (!container) {
314 data = res->data(node, size: &size);
315 compressionAlgo = res->compressionAlgo(node);
316 } else {
317 data = nullptr;
318 size = 0;
319 compressionAlgo = QResource::NoCompression;
320 }
321 lastModified = res->lastModified(node);
322 } else if (res->isContainer(node) != container) {
323 qWarning(msg: "QResourceInfo: Resource [%s] has both data and children!",
324 file.toLatin1().constData());
325 }
326 res->ref.ref();
327 related.append(t: res);
328 } else if (res->mappingRootSubdir(path: file)) {
329 container = true;
330 data = nullptr;
331 size = 0;
332 compressionAlgo = QResource::NoCompression;
333 lastModified = 0;
334 res->ref.ref();
335 related.append(t: res);
336 }
337 }
338 return !related.isEmpty();
339}
340
341void QResourcePrivate::ensureInitialized() const
342{
343 if (!related.isEmpty())
344 return;
345 QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
346 if (fileName == ":"_L1)
347 that->fileName += u'/';
348 that->absoluteFilePath = fileName;
349 if (!that->absoluteFilePath.startsWith(c: u':'))
350 that->absoluteFilePath.prepend(c: u':');
351
352 QStringView path(fileName);
353 if (path.startsWith(c: u':'))
354 path = path.mid(pos: 1);
355
356 if (path.startsWith(c: u'/')) {
357 that->load(file: path.toString());
358 } else {
359 // Should we search QDir::searchPath() before falling back to root ?
360 const QString searchPath(u'/' + path);
361 if (that->load(file: searchPath))
362 that->absoluteFilePath = u':' + searchPath;
363 }
364}
365
366void QResourcePrivate::ensureChildren() const
367{
368 ensureInitialized();
369 if (!children.isEmpty() || !container || related.isEmpty())
370 return;
371
372 QString path = absoluteFilePath, k;
373 if (path.startsWith(c: u':'))
374 path = path.mid(position: 1);
375 QDuplicateTracker<QString> kids(related.size());
376 QString cleaned = cleanPath(path: path);
377 for (int i = 0; i < related.size(); ++i) {
378 QResourceRoot *res = related.at(i);
379 if (res->mappingRootSubdir(path, match: &k) && !k.isEmpty()) {
380 if (!kids.hasSeen(s: k))
381 children += k;
382 } else {
383 const int node = res->findNode(path: cleaned);
384 if (node != -1) {
385 QStringList related_children = res->children(node);
386 for (int kid = 0; kid < related_children.size(); ++kid) {
387 k = related_children.at(i: kid);
388 if (!kids.hasSeen(s: k))
389 children += k;
390 }
391 }
392 }
393 }
394}
395
396qint64 QResourcePrivate::uncompressedSize() const
397{
398 switch (compressionAlgo) {
399 case QResource::NoCompression:
400 return size;
401
402 case QResource::ZlibCompression:
403#ifndef QT_NO_COMPRESS
404 if (size_t(size) >= sizeof(quint32))
405 return qFromBigEndian<quint32>(src: data);
406#else
407 Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
408 Q_UNREACHABLE();
409#endif
410 break;
411
412 case QResource::ZstdCompression: {
413#if QT_CONFIG(zstd)
414 size_t n = ZSTD_getFrameContentSize(src: data, srcSize: size);
415 return ZSTD_isError(code: n) ? -1 : qint64(n);
416#else
417 // This should not happen because we've refused to load such resource
418 Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
419 Q_UNREACHABLE();
420#endif
421 }
422 }
423 return -1;
424}
425
426qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
427{
428 Q_ASSERT(data);
429#if defined(QT_NO_COMPRESS) && !QT_CONFIG(zstd)
430 Q_UNUSED(buffer);
431 Q_UNUSED(bufferSize);
432#endif
433
434 switch (compressionAlgo) {
435 case QResource::NoCompression:
436 Q_UNREACHABLE();
437 break;
438
439 case QResource::ZlibCompression: {
440#ifndef QT_NO_COMPRESS
441 uLong len = uLong(bufferSize);
442 int res = ::uncompress(dest: reinterpret_cast<Bytef *>(buffer), destLen: &len, source: data + sizeof(quint32),
443 sourceLen: uLong(size - sizeof(quint32)));
444 if (res != Z_OK) {
445 qWarning(msg: "QResource: error decompressing zlib content (%d)", res);
446 return -1;
447 }
448 return len;
449#else
450 Q_UNREACHABLE();
451#endif
452 }
453
454 case QResource::ZstdCompression: {
455#if QT_CONFIG(zstd)
456 size_t usize = ZSTD_decompress(dst: buffer, dstCapacity: bufferSize, src: data, compressedSize: size);
457 if (ZSTD_isError(code: usize)) {
458 qWarning(msg: "QResource: error decompressing zstd content: %s", ZSTD_getErrorName(code: usize));
459 return -1;
460 }
461 return usize;
462#else
463 Q_UNREACHABLE();
464#endif
465 }
466 }
467
468 return -1;
469}
470
471/*!
472 Constructs a QResource pointing to \a file. \a locale is used to
473 load a specific localization of a resource data.
474
475 \sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
476*/
477
478QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
479{
480 Q_D(QResource);
481 d->fileName = file;
482 d->locale = locale;
483}
484
485/*!
486 Releases the resources of the QResource object.
487*/
488QResource::~QResource()
489{
490}
491
492/*!
493 Sets a QResource to only load the localization of resource to for \a
494 locale. If a resource for the specific locale is not found then the
495 C locale is used.
496
497 \sa setFileName()
498*/
499
500void QResource::setLocale(const QLocale &locale)
501{
502 Q_D(QResource);
503 d->clear();
504 d->locale = locale;
505}
506
507/*!
508 Returns the locale used to locate the data for the QResource.
509*/
510
511QLocale QResource::locale() const
512{
513 Q_D(const QResource);
514 return d->locale;
515}
516
517/*!
518 Sets a QResource to point to \a file. \a file can either be absolute,
519 in which case it is opened directly, if relative then the file will be
520 tried to be found in QDir::searchPaths().
521
522 \sa absoluteFilePath()
523*/
524
525void QResource::setFileName(const QString &file)
526{
527 Q_D(QResource);
528 d->clear();
529 d->fileName = file;
530}
531
532/*!
533 Returns the full path to the file that this QResource represents as it
534 was passed.
535
536 \sa absoluteFilePath()
537*/
538
539QString QResource::fileName() const
540{
541 Q_D(const QResource);
542 d->ensureInitialized();
543 return d->fileName;
544}
545
546/*!
547 Returns the real path that this QResource represents, if the resource
548 was found via the QDir::searchPaths() it will be indicated in the path.
549
550 \sa fileName()
551*/
552
553QString QResource::absoluteFilePath() const
554{
555 Q_D(const QResource);
556 d->ensureInitialized();
557 return d->absoluteFilePath;
558}
559
560/*!
561 Returns \c true if the resource really exists in the resource hierarchy,
562 false otherwise.
563
564*/
565
566bool QResource::isValid() const
567{
568 Q_D(const QResource);
569 d->ensureInitialized();
570 return !d->related.isEmpty();
571}
572
573/*!
574 \fn bool QResource::isFile() const
575
576 Returns \c true if the resource represents a file and thus has data
577 backing it, false if it represents a directory.
578
579 \sa isDir()
580*/
581
582/*!
583 \since 5.13
584
585 Returns the compression type that this resource is compressed with, if any.
586 If it is not compressed, this function returns QResource::NoCompression.
587
588 If this function returns QResource::ZlibCompression, you may decompress the
589 data using the qUncompress() function. Up until Qt 5.13, this was the only
590 possible compression algorithm.
591
592 If this function returns QResource::ZstdCompression, you need to use the
593 Zstandard library functions (\c{<zstd.h>} header). Qt does not provide a
594 wrapper.
595
596 See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
597
598 \sa data(), isFile()
599*/
600QResource::Compression QResource::compressionAlgorithm() const
601{
602 Q_D(const QResource);
603 d->ensureInitialized();
604 return Compression(d->compressionAlgo);
605}
606
607/*!
608 Returns the size of the stored data backing the resource.
609
610 If the resource is compressed, this function returns the size of the
611 compressed data. See uncompressedSize() for the uncompressed size.
612
613 \sa data(), uncompressedSize(), isFile()
614*/
615
616qint64 QResource::size() const
617{
618 Q_D(const QResource);
619 d->ensureInitialized();
620 return d->size;
621}
622
623/*!
624 \since 5.15
625
626 Returns the size of the data in this resource. If the data was not
627 compressed, this function returns the same as size(). If it was, then this
628 function extracts the size of the original uncompressed data from the
629 stored stream.
630
631 \sa size(), uncompressedData(), isFile()
632*/
633qint64 QResource::uncompressedSize() const
634{
635 Q_D(const QResource);
636 d->ensureInitialized();
637 return d->uncompressedSize();
638}
639
640/*!
641 Returns direct access to a segment of read-only data, that this resource
642 represents. If the resource is compressed, the data returned is also
643 compressed. The caller must then decompress the data or use
644 uncompressedData(). If the resource is a directory, \c nullptr is returned.
645
646 \sa uncompressedData(), size(), isFile()
647*/
648
649const uchar *QResource::data() const
650{
651 Q_D(const QResource);
652 d->ensureInitialized();
653 return d->data;
654}
655
656/*!
657 \since 5.15
658
659 Returns the resource data, decompressing it first, if the data was stored
660 compressed. If the resource is a directory or an error occurs while
661 decompressing, a null QByteArray is returned.
662
663 \note If the data was compressed, this function will decompress every time
664 it is called. The result is not cached between calls.
665
666 \sa uncompressedSize(), size(), compressionAlgorithm(), isFile()
667*/
668
669QByteArray QResource::uncompressedData() const
670{
671 Q_D(const QResource);
672 qint64 n = uncompressedSize();
673 if (n < 0)
674 return QByteArray();
675 if (n > std::numeric_limits<QByteArray::size_type>::max()) {
676 qWarning(msg: "QResource: compressed content does not fit into a QByteArray; use QFile instead");
677 return QByteArray();
678 }
679 if (d->compressionAlgo == NoCompression)
680 return QByteArray::fromRawData(data: reinterpret_cast<const char *>(d->data), size: n);
681
682 // decompress
683 QByteArray result(n, Qt::Uninitialized);
684 n = d->decompress(buffer: result.data(), bufferSize: n);
685 if (n < 0)
686 result.clear();
687 else
688 result.truncate(pos: n);
689 return result;
690}
691
692/*!
693 \since 5.8
694
695 Returns the date and time when the file was last modified before
696 packaging into a resource.
697*/
698QDateTime QResource::lastModified() const
699{
700 Q_D(const QResource);
701 d->ensureInitialized();
702 return d->lastModified ? QDateTime::fromMSecsSinceEpoch(msecs: d->lastModified) : QDateTime();
703}
704
705/*!
706 Returns \c true if the resource represents a directory and thus may have
707 children() in it, false if it represents a file.
708
709 \sa isFile()
710*/
711
712bool QResource::isDir() const
713{
714 Q_D(const QResource);
715 d->ensureInitialized();
716 return d->container;
717}
718
719/*!
720 Returns a list of all resources in this directory, if the resource
721 represents a file the list will be empty.
722
723 \sa isDir()
724*/
725
726QStringList QResource::children() const
727{
728 Q_D(const QResource);
729 d->ensureChildren();
730 return d->children;
731}
732
733inline uint QResourceRoot::hash(int node) const
734{
735 if (!node) // root
736 return 0;
737 const int offset = findOffset(node);
738 qint32 name_offset = qFromBigEndian<qint32>(src: tree + offset);
739 name_offset += 2; // jump past name length
740 return qFromBigEndian<quint32>(src: names + name_offset);
741}
742inline QString QResourceRoot::name(int node) const
743{
744 if (!node) // root
745 return QString();
746 const int offset = findOffset(node);
747
748 QString ret;
749 qint32 name_offset = qFromBigEndian<qint32>(src: tree + offset);
750 quint16 name_length = qFromBigEndian<qint16>(src: names + name_offset);
751 name_offset += 2;
752 name_offset += 4; // jump past hash
753
754 ret.resize(size: name_length);
755 QChar *strData = ret.data();
756 qFromBigEndian<char16_t>(source: names + name_offset, count: name_length, dest: strData);
757 return ret;
758}
759
760int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
761{
762 QString path = _path;
763 {
764 QString root = mappingRoot();
765 if (!root.isEmpty()) {
766 if (root == path) {
767 path = u'/';
768 } else {
769 if (!root.endsWith(c: u'/'))
770 root += u'/';
771 if (path.size() >= root.size() && path.startsWith(s: root))
772 path = path.mid(position: root.size() - 1);
773 if (path.isEmpty())
774 path = u'/';
775 }
776 }
777 }
778#ifdef DEBUG_RESOURCE_MATCH
779 qDebug() << "!!!!" << "START" << path << locale.territory() << locale.language();
780#endif
781
782 if (path == "/"_L1)
783 return 0;
784
785 // the root node is always first
786 qint32 child_count = qFromBigEndian<qint32>(src: tree + 6);
787 qint32 child = qFromBigEndian<qint32>(src: tree + 10);
788
789 // now iterate up the tree
790 int node = -1;
791
792 QStringSplitter splitter(path);
793 while (child_count && splitter.hasNext()) {
794 QStringView segment = splitter.next();
795
796#ifdef DEBUG_RESOURCE_MATCH
797 qDebug() << " CHILDREN" << segment;
798 for (int j = 0; j < child_count; ++j) {
799 qDebug() << " " << child + j << " :: " << name(child + j);
800 }
801#endif
802 const uint h = qt_hash(key: segment);
803
804 // do the binary search for the hash
805 int l = 0, r = child_count - 1;
806 int sub_node = (l + r + 1) / 2;
807 while (r != l) {
808 const uint sub_node_hash = hash(node: child + sub_node);
809 if (h == sub_node_hash)
810 break;
811 else if (h < sub_node_hash)
812 r = sub_node - 1;
813 else
814 l = sub_node;
815 sub_node = (l + r + 1) / 2;
816 }
817 sub_node += child;
818
819 // now do the "harder" compares
820 bool found = false;
821 if (hash(node: sub_node) == h) {
822 while (sub_node > child && hash(node: sub_node - 1) == h) // backup for collisions
823 --sub_node;
824 for (; sub_node < child + child_count && hash(node: sub_node) == h;
825 ++sub_node) { // here we go...
826 if (name(node: sub_node) == segment) {
827 found = true;
828 int offset = findOffset(node: sub_node);
829#ifdef DEBUG_RESOURCE_MATCH
830 qDebug() << " TRY" << sub_node << name(sub_node) << offset;
831#endif
832 offset += 4; // jump past name
833
834 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
835 offset += 2;
836
837 if (!splitter.hasNext()) {
838 if (!(flags & Directory)) {
839 const qint16 territory = qFromBigEndian<qint16>(src: tree + offset);
840 offset += 2;
841
842 const qint16 language = qFromBigEndian<qint16>(src: tree + offset);
843 offset += 2;
844#ifdef DEBUG_RESOURCE_MATCH
845 qDebug() << " " << "LOCALE" << country << language;
846#endif
847 if (territory == locale.territory() && language == locale.language()) {
848#ifdef DEBUG_RESOURCE_MATCH
849 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
850#endif
851 return sub_node;
852 } else if ((territory == QLocale::AnyTerritory
853 && language == locale.language())
854 || (territory == QLocale::AnyTerritory
855 && language == QLocale::C
856 && node == -1)) {
857 node = sub_node;
858 }
859 continue;
860 } else {
861#ifdef DEBUG_RESOURCE_MATCH
862 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
863#endif
864
865 return sub_node;
866 }
867 }
868
869 if (!(flags & Directory))
870 return -1;
871
872 child_count = qFromBigEndian<qint32>(src: tree + offset);
873 offset += 4;
874 child = qFromBigEndian<qint32>(src: tree + offset);
875 break;
876 }
877 }
878 }
879 if (!found)
880 break;
881 }
882#ifdef DEBUG_RESOURCE_MATCH
883 qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
884#endif
885 return node;
886}
887short QResourceRoot::flags(int node) const
888{
889 if (node == -1)
890 return 0;
891 const int offset = findOffset(node) + 4; // jump past name
892 return qFromBigEndian<qint16>(src: tree + offset);
893}
894const uchar *QResourceRoot::data(int node, qint64 *size) const
895{
896 if (node == -1) {
897 *size = 0;
898 return nullptr;
899 }
900 int offset = findOffset(node) + 4; // jump past name
901
902 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
903 offset += 2;
904
905 offset += 4; // jump past locale
906
907 if (!(flags & Directory)) {
908 const qint32 data_offset = qFromBigEndian<qint32>(src: tree + offset);
909 const quint32 data_length = qFromBigEndian<quint32>(src: payloads + data_offset);
910 const uchar *ret = payloads + data_offset + 4;
911 *size = data_length;
912 return ret;
913 }
914 *size = 0;
915 return nullptr;
916}
917
918quint64 QResourceRoot::lastModified(int node) const
919{
920 if (node == -1 || version < 0x02)
921 return 0;
922
923 const int offset = findOffset(node) + 14;
924
925 return qFromBigEndian<quint64>(src: tree + offset);
926}
927
928QStringList QResourceRoot::children(int node) const
929{
930 if (node == -1)
931 return QStringList();
932 int offset = findOffset(node) + 4; // jump past name
933
934 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
935 offset += 2;
936
937 QStringList ret;
938 if (flags & Directory) {
939 const qint32 child_count = qFromBigEndian<qint32>(src: tree + offset);
940 offset += 4;
941 const qint32 child_off = qFromBigEndian<qint32>(src: tree + offset);
942 ret.reserve(asize: child_count);
943 for (int i = child_off; i < child_off + child_count; ++i)
944 ret << name(node: i);
945 }
946 return ret;
947}
948bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
949{
950 const QString root = mappingRoot();
951 if (root.isEmpty())
952 return false;
953
954 QStringSplitter rootIt(root);
955 QStringSplitter pathIt(path);
956 while (rootIt.hasNext()) {
957 if (pathIt.hasNext()) {
958 if (rootIt.next() != pathIt.next()) // mismatch
959 return false;
960 } else {
961 // end of path, but not of root:
962 if (match)
963 *match = rootIt.next().toString();
964 return true;
965 }
966 }
967 // end of root
968 return !pathIt.hasNext();
969}
970
971Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
972 const unsigned char *name, const unsigned char *data)
973{
974 if (resourceGlobalData.isDestroyed())
975 return false;
976 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
977 ResourceList *list = resourceList();
978 if (version >= 0x01 && version <= 0x3) {
979 bool found = false;
980 QResourceRoot res(version, tree, name, data);
981 for (int i = 0; i < list->size(); ++i) {
982 if (*list->at(i) == res) {
983 found = true;
984 break;
985 }
986 }
987 if (!found) {
988 QResourceRoot *root = new QResourceRoot(version, tree, name, data);
989 root->ref.ref();
990 list->append(t: root);
991 }
992 return true;
993 }
994 return false;
995}
996
997Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
998 const unsigned char *name, const unsigned char *data)
999{
1000 if (resourceGlobalData.isDestroyed())
1001 return false;
1002
1003 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1004 if (version >= 0x01 && version <= 0x3) {
1005 QResourceRoot res(version, tree, name, data);
1006 ResourceList *list = resourceList();
1007 for (int i = 0; i < list->size();) {
1008 if (*list->at(i) == res) {
1009 QResourceRoot *root = list->takeAt(i);
1010 if (!root->ref.deref())
1011 delete root;
1012 } else {
1013 ++i;
1014 }
1015 }
1016 return true;
1017 }
1018 return false;
1019}
1020
1021// run time resource creation
1022
1023class QDynamicBufferResourceRoot : public QResourceRoot
1024{
1025 QString root;
1026 const uchar *buffer;
1027
1028public:
1029 inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
1030 inline ~QDynamicBufferResourceRoot() { }
1031 inline const uchar *mappingBuffer() const { return buffer; }
1032 QString mappingRoot() const override { return root; }
1033 ResourceRootType type() const override { return Resource_Buffer; }
1034
1035 // size == -1 means "unknown"
1036 bool registerSelf(const uchar *b, qsizetype size)
1037 {
1038 // 5 int "pointers"
1039 if (size >= 0 && size < 20)
1040 return false;
1041
1042 // setup the data now
1043 int offset = 0;
1044
1045 // magic number
1046 if (b[offset + 0] != 'q' || b[offset + 1] != 'r' || b[offset + 2] != 'e'
1047 || b[offset + 3] != 's') {
1048 return false;
1049 }
1050 offset += 4;
1051
1052 const int version = qFromBigEndian<qint32>(src: b + offset);
1053 offset += 4;
1054
1055 const int tree_offset = qFromBigEndian<qint32>(src: b + offset);
1056 offset += 4;
1057
1058 const int data_offset = qFromBigEndian<qint32>(src: b + offset);
1059 offset += 4;
1060
1061 const int name_offset = qFromBigEndian<qint32>(src: b + offset);
1062 offset += 4;
1063
1064 quint32 file_flags = 0;
1065 if (version >= 3) {
1066 file_flags = qFromBigEndian<qint32>(src: b + offset);
1067 offset += 4;
1068 }
1069
1070 // Some sanity checking for sizes. This is _not_ a security measure.
1071 if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
1072 return false;
1073
1074 // And some sanity checking for features
1075 quint32 acceptableFlags = 0;
1076#ifndef QT_NO_COMPRESS
1077 acceptableFlags |= Compressed;
1078#endif
1079 if (QT_CONFIG(zstd))
1080 acceptableFlags |= CompressedZstd;
1081 if (file_flags & ~acceptableFlags)
1082 return false;
1083
1084 if (version >= 0x01 && version <= 0x03) {
1085 buffer = b;
1086 setSource(v: version, t: b + tree_offset, n: b + name_offset, d: b + data_offset);
1087 return true;
1088 }
1089 return false;
1090 }
1091};
1092
1093class QDynamicFileResourceRoot : public QDynamicBufferResourceRoot
1094{
1095 QString fileName;
1096 // for mmap'ed files, this is what needs to be unmapped.
1097 uchar *unmapPointer;
1098 qsizetype unmapLength;
1099
1100public:
1101 QDynamicFileResourceRoot(const QString &_root)
1102 : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
1103 { }
1104 ~QDynamicFileResourceRoot() {
1105#if defined(QT_USE_MMAP)
1106 if (unmapPointer) {
1107 munmap(addr: reinterpret_cast<char *>(unmapPointer), len: unmapLength);
1108 unmapPointer = nullptr;
1109 unmapLength = 0;
1110 } else
1111#endif
1112 {
1113 delete[] mappingBuffer();
1114 }
1115 }
1116 QString mappingFile() const { return fileName; }
1117 ResourceRootType type() const override { return Resource_File; }
1118
1119 bool registerSelf(const QString &f);
1120};
1121
1122#ifndef MAP_FILE
1123# define MAP_FILE 0
1124#endif
1125#ifndef MAP_FAILED
1126# define MAP_FAILED reinterpret_cast<void *>(-1)
1127#endif
1128
1129bool QDynamicFileResourceRoot::registerSelf(const QString &f)
1130{
1131 bool fromMM = false;
1132 uchar *data = nullptr;
1133 qsizetype data_len = 0;
1134
1135#if defined(QT_USE_MMAP)
1136 int fd = QT_OPEN(file: QFile::encodeName(fileName: f), O_RDONLY);
1137 if (fd >= 0) {
1138 QT_STATBUF st;
1139 if (!QT_FSTAT(fd: fd, buf: &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) {
1140 int protection = PROT_READ; // read-only memory
1141 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
1142 void *ptr = QT_MMAP(addr: nullptr, len: st.st_size, // any address, whole file
1143 prot: protection, flags: flags,
1144 fd: fd, offset: 0); // from offset 0 of fd
1145 if (ptr != MAP_FAILED) {
1146 data = static_cast<uchar *>(ptr);
1147 data_len = st.st_size;
1148 fromMM = true;
1149 }
1150 }
1151 QT_CLOSE(fd: fd);
1152 }
1153#endif // QT_USE_MMAP
1154 if (!data) {
1155 QFile file(f);
1156 bool ok = false;
1157 if (file.open(flags: QIODevice::ReadOnly)) {
1158 qint64 fsize = file.size();
1159 if (fsize <= std::numeric_limits<qsizetype>::max()) {
1160 data_len = file.size();
1161 data = new uchar[data_len];
1162 ok = (data_len == file.read(data: reinterpret_cast<char *>(data), maxlen: data_len));
1163 }
1164 }
1165 if (!ok) {
1166 delete[] data;
1167 data = nullptr;
1168 data_len = 0;
1169 return false;
1170 }
1171 fromMM = false;
1172 }
1173 if (data && QDynamicBufferResourceRoot::registerSelf(b: data, size: data_len)) {
1174 if (fromMM) {
1175 unmapPointer = data;
1176 unmapLength = data_len;
1177 }
1178 fileName = f;
1179 return true;
1180 }
1181 return false;
1182}
1183
1184static QString qt_resource_fixResourceRoot(QString r)
1185{
1186 if (!r.isEmpty()) {
1187 if (r.startsWith(c: u':'))
1188 r = r.mid(position: 1);
1189 if (!r.isEmpty())
1190 r = QDir::cleanPath(path: r);
1191 }
1192 return r;
1193}
1194
1195/*!
1196 \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
1197
1198 Registers the resource with the given \a rccFileName at the location in the
1199 resource tree specified by \a mapRoot, and returns \c true if the file is
1200 successfully opened; otherwise returns \c false.
1201
1202 \sa unregisterResource()
1203*/
1204
1205bool QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
1206{
1207 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1208 if (!r.isEmpty() && r[0] != u'/') {
1209 qWarning(msg: "QDir::registerResource: Registering a resource [%ls] must be rooted in an "
1210 "absolute path (start with /) [%ls]",
1211 qUtf16Printable(rccFilename), qUtf16Printable(resourceRoot));
1212 return false;
1213 }
1214
1215 QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
1216 if (root->registerSelf(f: rccFilename)) {
1217 root->ref.ref();
1218 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1219 resourceList()->append(t: root);
1220 return true;
1221 }
1222 delete root;
1223 return false;
1224}
1225
1226/*!
1227 \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
1228
1229 Unregisters the resource with the given \a rccFileName at the location in
1230 the resource tree specified by \a mapRoot, and returns \c true if the
1231 resource is successfully unloaded and no references exist for the
1232 resource; otherwise returns \c false.
1233
1234 \sa registerResource()
1235*/
1236
1237bool QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
1238{
1239 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1240
1241 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1242 ResourceList *list = resourceList();
1243 for (int i = 0; i < list->size(); ++i) {
1244 QResourceRoot *res = list->at(i);
1245 if (res->type() == QResourceRoot::Resource_File) {
1246 QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot *>(res);
1247 if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
1248 list->removeAt(i);
1249 if (!root->ref.deref()) {
1250 delete root;
1251 return true;
1252 }
1253 return false;
1254 }
1255 }
1256 }
1257 return false;
1258}
1259
1260/*!
1261 \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
1262 \since 4.3
1263
1264 Registers the resource with the given \a rccData at the location in the
1265 resource tree specified by \a mapRoot, and returns \c true if the file is
1266 successfully opened; otherwise returns \c false.
1267
1268 \warning The data must remain valid throughout the life of any QFile
1269 that may reference the resource data.
1270
1271 \sa unregisterResource()
1272*/
1273
1274bool QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
1275{
1276 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1277 if (!r.isEmpty() && r[0] != u'/') {
1278 qWarning(msg: "QDir::registerResource: Registering a resource [%p] must be rooted in an "
1279 "absolute path (start with /) [%ls]",
1280 rccData, qUtf16Printable(resourceRoot));
1281 return false;
1282 }
1283
1284 QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
1285 if (root->registerSelf(b: rccData, size: -1)) {
1286 root->ref.ref();
1287 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1288 resourceList()->append(t: root);
1289 return true;
1290 }
1291 delete root;
1292 return false;
1293}
1294
1295/*!
1296 \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
1297 \since 4.3
1298
1299 Unregisters the resource with the given \a rccData at the location in the
1300 resource tree specified by \a mapRoot, and returns \c true if the resource is
1301 successfully unloaded and no references exist into the resource; otherwise returns \c false.
1302
1303 \sa registerResource()
1304*/
1305
1306bool QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
1307{
1308 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1309
1310 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1311 ResourceList *list = resourceList();
1312 for (int i = 0; i < list->size(); ++i) {
1313 QResourceRoot *res = list->at(i);
1314 if (res->type() == QResourceRoot::Resource_Buffer) {
1315 QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot *>(res);
1316 if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
1317 list->removeAt(i);
1318 if (!root->ref.deref()) {
1319 delete root;
1320 return true;
1321 }
1322 return false;
1323 }
1324 }
1325 }
1326 return false;
1327}
1328
1329#if !defined(QT_BOOTSTRAPPED)
1330// resource engine
1331class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
1332{
1333protected:
1334 Q_DECLARE_PUBLIC(QResourceFileEngine)
1335private:
1336 uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
1337 bool unmap(uchar *ptr);
1338 void uncompress() const;
1339 qint64 offset;
1340 QResource resource;
1341 mutable QByteArray uncompressed;
1342protected:
1343 QResourceFileEnginePrivate() : offset(0) { }
1344};
1345
1346bool QResourceFileEngine::caseSensitive() const
1347{
1348 return true;
1349}
1350
1351QResourceFileEngine::QResourceFileEngine(const QString &file) :
1352 QAbstractFileEngine(*new QResourceFileEnginePrivate)
1353{
1354 Q_D(QResourceFileEngine);
1355 d->resource.setFileName(file);
1356}
1357
1358QResourceFileEngine::~QResourceFileEngine()
1359{
1360}
1361
1362void QResourceFileEngine::setFileName(const QString &file)
1363{
1364 Q_D(QResourceFileEngine);
1365 d->resource.setFileName(file);
1366}
1367
1368bool QResourceFileEngine::open(QIODevice::OpenMode flags,
1369 std::optional<QFile::Permissions> permissions)
1370{
1371 Q_UNUSED(permissions);
1372
1373 Q_D(QResourceFileEngine);
1374 if (d->resource.fileName().isEmpty()) {
1375 qWarning(msg: "QResourceFileEngine::open: Missing file name");
1376 return false;
1377 }
1378 if (flags & QIODevice::WriteOnly)
1379 return false;
1380 if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
1381 d->uncompress();
1382 if (d->uncompressed.isNull()) {
1383 d->errorString = QSystemError::stdString(EIO);
1384 return false;
1385 }
1386 }
1387 if (!d->resource.isValid()) {
1388 d->errorString = QSystemError::stdString(ENOENT);
1389 return false;
1390 }
1391 return true;
1392}
1393
1394bool QResourceFileEngine::close()
1395{
1396 Q_D(QResourceFileEngine);
1397 d->offset = 0;
1398 return true;
1399}
1400
1401bool QResourceFileEngine::flush()
1402{
1403 return true;
1404}
1405
1406qint64 QResourceFileEngine::read(char *data, qint64 len)
1407{
1408 Q_D(QResourceFileEngine);
1409 if (len > size() - d->offset)
1410 len = size() - d->offset;
1411 if (len <= 0)
1412 return 0;
1413 if (!d->uncompressed.isNull())
1414 memcpy(dest: data, src: d->uncompressed.constData() + d->offset, n: len);
1415 else
1416 memcpy(dest: data, src: d->resource.data() + d->offset, n: len);
1417 d->offset += len;
1418 return len;
1419}
1420
1421qint64 QResourceFileEngine::size() const
1422{
1423 Q_D(const QResourceFileEngine);
1424 return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
1425}
1426
1427qint64 QResourceFileEngine::pos() const
1428{
1429 Q_D(const QResourceFileEngine);
1430 return d->offset;
1431}
1432
1433bool QResourceFileEngine::atEnd() const
1434{
1435 Q_D(const QResourceFileEngine);
1436 if (!d->resource.isValid())
1437 return true;
1438 return d->offset == size();
1439}
1440
1441bool QResourceFileEngine::seek(qint64 pos)
1442{
1443 Q_D(QResourceFileEngine);
1444 if (!d->resource.isValid())
1445 return false;
1446
1447 if (d->offset > size())
1448 return false;
1449 d->offset = pos;
1450 return true;
1451}
1452
1453QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
1454{
1455 Q_D(const QResourceFileEngine);
1456 QAbstractFileEngine::FileFlags ret;
1457 if (!d->resource.isValid())
1458 return ret;
1459
1460 if (type & PermsMask)
1461 ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm | ReadUserPerm | ReadGroupPerm
1462 | ReadOtherPerm);
1463 if (type & TypesMask) {
1464 if (d->resource.isDir())
1465 ret |= DirectoryType;
1466 else
1467 ret |= FileType;
1468 }
1469 if (type & FlagsMask) {
1470 ret |= ExistsFlag;
1471 if (d->resource.absoluteFilePath() == ":/"_L1)
1472 ret |= RootFlag;
1473 }
1474 return ret;
1475}
1476
1477QString QResourceFileEngine::fileName(FileName file) const
1478{
1479 Q_D(const QResourceFileEngine);
1480 if (file == BaseName) {
1481 const qsizetype slash = d->resource.fileName().lastIndexOf(c: u'/');
1482 if (slash == -1)
1483 return d->resource.fileName();
1484 return d->resource.fileName().mid(position: slash + 1);
1485 } else if (file == PathName || file == AbsolutePathName) {
1486 const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath()
1487 : d->resource.fileName();
1488 const qsizetype slash = path.lastIndexOf(c: u'/');
1489 if (slash == -1)
1490 return ":"_L1;
1491 else if (slash <= 1)
1492 return ":/"_L1;
1493 return path.left(n: slash);
1494
1495 } else if (file == CanonicalName || file == CanonicalPathName) {
1496 const QString absoluteFilePath = d->resource.absoluteFilePath();
1497 if (file == CanonicalPathName) {
1498 const qsizetype slash = absoluteFilePath.lastIndexOf(c: u'/');
1499 if (slash != -1)
1500 return absoluteFilePath.left(n: slash);
1501 }
1502 return absoluteFilePath;
1503 }
1504 return d->resource.fileName();
1505}
1506
1507uint QResourceFileEngine::ownerId(FileOwner) const
1508{
1509 static const uint nobodyID = static_cast<uint>(-2);
1510 return nobodyID;
1511}
1512
1513QDateTime QResourceFileEngine::fileTime(FileTime time) const
1514{
1515 Q_D(const QResourceFileEngine);
1516 if (time == ModificationTime)
1517 return d->resource.lastModified();
1518 return QDateTime();
1519}
1520
1521/*!
1522 \internal
1523*/
1524QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
1525 const QStringList &filterNames)
1526{
1527 return new QResourceFileEngineIterator(filters, filterNames);
1528}
1529
1530/*!
1531 \internal
1532*/
1533QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
1534{
1535 return nullptr;
1536}
1537
1538bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
1539{
1540 Q_D(QResourceFileEngine);
1541 if (extension == MapExtension) {
1542 const auto *options = static_cast<const MapExtensionOption *>(option);
1543 auto *returnValue = static_cast<MapExtensionReturn *>(output);
1544 returnValue->address = d->map(offset: options->offset, size: options->size, flags: options->flags);
1545 return (returnValue->address != nullptr);
1546 }
1547 if (extension == UnMapExtension) {
1548 const auto *options = static_cast<const UnMapExtensionOption *>(option);
1549 return d->unmap(ptr: options->address);
1550 }
1551 return false;
1552}
1553
1554bool QResourceFileEngine::supportsExtension(Extension extension) const
1555{
1556 return (extension == UnMapExtension || extension == MapExtension);
1557}
1558
1559uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
1560{
1561 Q_Q(QResourceFileEngine);
1562 Q_UNUSED(flags);
1563
1564 qint64 max = resource.uncompressedSize();
1565 qint64 end;
1566 if (offset < 0 || size <= 0 || !resource.isValid() ||
1567 qAddOverflow(v1: offset, v2: size, r: &end) || end > max) {
1568 q->setError(error: QFile::UnspecifiedError, str: QString());
1569 return nullptr;
1570 }
1571
1572 const uchar *address = resource.data();
1573 if (resource.compressionAlgorithm() != QResource::NoCompression) {
1574 uncompress();
1575 if (uncompressed.isNull())
1576 return nullptr;
1577 address = reinterpret_cast<const uchar *>(uncompressed.constData());
1578 }
1579
1580 return const_cast<uchar *>(address) + offset;
1581}
1582
1583bool QResourceFileEnginePrivate::unmap(uchar *ptr)
1584{
1585 Q_UNUSED(ptr);
1586 return true;
1587}
1588
1589void QResourceFileEnginePrivate::uncompress() const
1590{
1591 if (resource.compressionAlgorithm() == QResource::NoCompression
1592 || !uncompressed.isEmpty() || resource.size() == 0)
1593 return; // nothing to do
1594 uncompressed = resource.uncompressedData();
1595}
1596
1597#endif // !defined(QT_BOOTSTRAPPED)
1598
1599QT_END_NAMESPACE
1600

source code of qtbase/src/corelib/io/qresource.cpp