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