1/****************************************************************************
2**
3** Copyright (C) 2019 The Qt Company Ltd.
4** Copyright (C) 2020 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 <private/qlocking_p.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#ifndef QT_NO_COMPRESS
64# include <zconf.h>
65# include <zlib.h>
66#endif
67#if QT_CONFIG(zstd)
68# include <zstd.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(v: 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: _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(s: QLatin1String("//")))
191 path.remove(i: 0, len: 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{https://zlib.net}{zlib} and can
284 be decompressed using the qUncompress() function.
285 \value ZstdCompression Contents are compressed using \l{https://zstd.net}{zstd}. 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 qint64 uncompressedSize() const Q_DECL_PURE_FUNCTION;
300 qsizetype decompress(char *buffer, qsizetype bufferSize) const;
301
302 bool load(const QString &file);
303 void clear();
304
305 QLocale locale;
306 QString fileName, absoluteFilePath;
307 QList<QResourceRoot*> related;
308 mutable qint64 size;
309 mutable quint64 lastModified;
310 mutable const uchar *data;
311 mutable QStringList children;
312 mutable quint8 compressionAlgo;
313 bool container;
314 /* 2 or 6 padding bytes */
315
316 QResource *q_ptr;
317 Q_DECLARE_PUBLIC(QResource)
318};
319
320void
321QResourcePrivate::clear()
322{
323 absoluteFilePath.clear();
324 compressionAlgo = QResource::NoCompression;
325 data = nullptr;
326 size = 0;
327 children.clear();
328 lastModified = 0;
329 container = 0;
330 for(int i = 0; i < related.size(); ++i) {
331 QResourceRoot *root = related.at(i);
332 if(!root->ref.deref())
333 delete root;
334 }
335 related.clear();
336}
337
338bool
339QResourcePrivate::load(const QString &file)
340{
341 related.clear();
342 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
343 const ResourceList *list = resourceList();
344 QString cleaned = cleanPath(path: file);
345 for(int i = 0; i < list->size(); ++i) {
346 QResourceRoot *res = list->at(i);
347 const int node = res->findNode(path: cleaned, locale);
348 if(node != -1) {
349 if(related.isEmpty()) {
350 container = res->isContainer(node);
351 if(!container) {
352 data = res->data(node, size: &size);
353 compressionAlgo = res->compressionAlgo(node);
354 } else {
355 data = nullptr;
356 size = 0;
357 compressionAlgo = QResource::NoCompression;
358 }
359 lastModified = res->lastModified(node);
360 } else if(res->isContainer(node) != container) {
361 qWarning(msg: "QResourceInfo: Resource [%s] has both data and children!", file.toLatin1().constData());
362 }
363 res->ref.ref();
364 related.append(t: res);
365 } else if(res->mappingRootSubdir(path: file)) {
366 container = true;
367 data = nullptr;
368 size = 0;
369 compressionAlgo = QResource::NoCompression;
370 lastModified = 0;
371 res->ref.ref();
372 related.append(t: res);
373 }
374 }
375 return !related.isEmpty();
376}
377
378void
379QResourcePrivate::ensureInitialized() const
380{
381 if(!related.isEmpty())
382 return;
383 QResourcePrivate *that = const_cast<QResourcePrivate *>(this);
384 if(fileName == QLatin1String(":"))
385 that->fileName += QLatin1Char('/');
386 that->absoluteFilePath = fileName;
387 if(!that->absoluteFilePath.startsWith(c: QLatin1Char(':')))
388 that->absoluteFilePath.prepend(c: QLatin1Char(':'));
389
390 QStringRef path(&fileName);
391 if(path.startsWith(c: QLatin1Char(':')))
392 path = path.mid(pos: 1);
393
394 if(path.startsWith(c: QLatin1Char('/'))) {
395 that->load(file: path.toString());
396 } else {
397 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
398 QStringList searchPaths = *resourceSearchPaths();
399 searchPaths << QLatin1String("");
400 for(int i = 0; i < searchPaths.size(); ++i) {
401 const QString searchPath(searchPaths.at(i) + QLatin1Char('/') + path);
402 if(that->load(file: searchPath)) {
403 that->absoluteFilePath = QLatin1Char(':') + searchPath;
404 break;
405 }
406 }
407 }
408}
409
410void
411QResourcePrivate::ensureChildren() const
412{
413 ensureInitialized();
414 if(!children.isEmpty() || !container || related.isEmpty())
415 return;
416
417 QString path = absoluteFilePath, k;
418 if(path.startsWith(c: QLatin1Char(':')))
419 path = path.mid(position: 1);
420 QSet<QString> kids;
421 QString cleaned = cleanPath(path: path);
422 for(int i = 0; i < related.size(); ++i) {
423 QResourceRoot *res = related.at(i);
424 if(res->mappingRootSubdir(path, match: &k) && !k.isEmpty()) {
425 if(!kids.contains(value: k)) {
426 children += k;
427 kids.insert(value: k);
428 }
429 } else {
430 const int node = res->findNode(path: cleaned);
431 if(node != -1) {
432 QStringList related_children = res->children(node);
433 for(int kid = 0; kid < related_children.size(); ++kid) {
434 k = related_children.at(i: kid);
435 if(!kids.contains(value: k)) {
436 children += k;
437 kids.insert(value: k);
438 }
439 }
440 }
441 }
442 }
443}
444
445qint64 QResourcePrivate::uncompressedSize() const
446{
447 switch (compressionAlgo) {
448 case QResource::NoCompression:
449 return size;
450
451 case QResource::ZlibCompression:
452#ifndef QT_NO_COMPRESS
453 if (size_t(size) >= sizeof(quint32))
454 return qFromBigEndian<quint32>(src: data);
455#else
456 Q_ASSERT(!"QResource: Qt built without support for Zlib compression");
457 Q_UNREACHABLE();
458#endif
459 break;
460
461 case QResource::ZstdCompression: {
462#if QT_CONFIG(zstd)
463 size_t n = ZSTD_getFrameContentSize(src: data, srcSize: size);
464 return ZSTD_isError(code: n) ? -1 : qint64(n);
465#else
466 // This should not happen because we've refused to load such resource
467 Q_ASSERT(!"QResource: Qt built without support for Zstd compression");
468 Q_UNREACHABLE();
469#endif
470 }
471
472 }
473 return -1;
474}
475
476qsizetype QResourcePrivate::decompress(char *buffer, qsizetype bufferSize) const
477{
478 Q_ASSERT(data);
479
480 switch (compressionAlgo) {
481 case QResource::NoCompression:
482 Q_UNREACHABLE();
483 break;
484
485 case QResource::ZlibCompression: {
486#ifndef QT_NO_COMPRESS
487 uLong len = uLong(bufferSize);
488 int res = ::uncompress(dest: reinterpret_cast<Bytef *>(buffer), destLen: &len,
489 source: data + sizeof(quint32), sourceLen: uLong(size - sizeof(quint32)));
490 if (res != Z_OK) {
491 qWarning(msg: "QResource: error decompressing zlib content (%d)", res);
492 return -1;
493 }
494 return len;
495#else
496 Q_UNREACHABLE();
497#endif
498 }
499
500 case QResource::ZstdCompression: {
501#if QT_CONFIG(zstd)
502 size_t usize = ZSTD_decompress(dst: buffer, dstCapacity: bufferSize, src: data, compressedSize: size);
503 if (ZSTD_isError(code: usize)) {
504 qWarning(msg: "QResource: error decompressing zstd content: %s", ZSTD_getErrorName(code: usize));
505 return -1;
506 }
507 return usize;
508#else
509 Q_UNREACHABLE();
510#endif
511 }
512 }
513
514 return -1;
515}
516
517/*!
518 Constructs a QResource pointing to \a file. \a locale is used to
519 load a specific localization of a resource data.
520
521 \sa QFileInfo, QDir::searchPaths(), setFileName(), setLocale()
522*/
523
524QResource::QResource(const QString &file, const QLocale &locale) : d_ptr(new QResourcePrivate(this))
525{
526 Q_D(QResource);
527 d->fileName = file;
528 d->locale = locale;
529}
530
531/*!
532 Releases the resources of the QResource object.
533*/
534QResource::~QResource()
535{
536}
537
538/*!
539 Sets a QResource to only load the localization of resource to for \a
540 locale. If a resource for the specific locale is not found then the
541 C locale is used.
542
543 \sa setFileName()
544*/
545
546void QResource::setLocale(const QLocale &locale)
547{
548 Q_D(QResource);
549 d->clear();
550 d->locale = locale;
551}
552
553/*!
554 Returns the locale used to locate the data for the QResource.
555*/
556
557QLocale QResource::locale() const
558{
559 Q_D(const QResource);
560 return d->locale;
561}
562
563/*!
564 Sets a QResource to point to \a file. \a file can either be absolute,
565 in which case it is opened directly, if relative then the file will be
566 tried to be found in QDir::searchPaths().
567
568 \sa absoluteFilePath()
569*/
570
571void QResource::setFileName(const QString &file)
572{
573 Q_D(QResource);
574 d->clear();
575 d->fileName = file;
576}
577
578/*!
579 Returns the full path to the file that this QResource represents as it
580 was passed.
581
582 \sa absoluteFilePath()
583*/
584
585QString QResource::fileName() const
586{
587 Q_D(const QResource);
588 d->ensureInitialized();
589 return d->fileName;
590}
591
592/*!
593 Returns the real path that this QResource represents, if the resource
594 was found via the QDir::searchPaths() it will be indicated in the path.
595
596 \sa fileName()
597*/
598
599QString QResource::absoluteFilePath() const
600{
601 Q_D(const QResource);
602 d->ensureInitialized();
603 return d->absoluteFilePath;
604}
605
606/*!
607 Returns \c true if the resource really exists in the resource hierarchy,
608 false otherwise.
609
610*/
611
612bool QResource::isValid() const
613{
614 Q_D(const QResource);
615 d->ensureInitialized();
616 return !d->related.isEmpty();
617}
618
619/*!
620 \fn bool QResource::isFile() const
621
622 Returns \c true if the resource represents a file and thus has data
623 backing it, false if it represents a directory.
624
625 \sa isDir()
626*/
627
628#if QT_DEPRECATED_SINCE(5, 13)
629/*!
630 \obsolete
631
632 Returns \c true if the resource represents a file and the data backing it
633 is in a compressed format, false otherwise. If the data is compressed,
634 check compressionAlgorithm() to verify what algorithm to use to decompress
635 the data.
636
637 \note This function is deprecated and can be replaced with
638 \code
639 compressionAlgorithm() != NoCompression
640 \endcode
641
642 \sa data(), compressionAlgorithm(), isFile()
643*/
644
645bool QResource::isCompressed() const
646{
647 return compressionAlgorithm() != NoCompression;
648}
649#endif
650
651/*!
652 \since 5.13
653
654 Returns the compression type that this resource is compressed with, if any.
655 If it is not compressed, this function returns QResource::NoCompression.
656
657 If this function returns QResource::ZlibCompression, you may decompress the
658 data using the qUncompress() function. Up until Qt 5.13, this was the only
659 possible compression algorithm.
660
661 If this function returns QResource::ZstdCompression, you need to use the
662 Zstandard library functios (\c{<zstd.h>} header). Qt does not provide a
663 wrapper.
664
665 See \l{http://facebook.github.io/zstd/zstd_manual.html}{Zstandard manual}.
666
667 \sa data(), isFile()
668*/
669QResource::Compression QResource::compressionAlgorithm() const
670{
671 Q_D(const QResource);
672 d->ensureInitialized();
673 return Compression(d->compressionAlgo);
674}
675
676/*!
677 Returns the size of the stored data backing the resource.
678
679 If the resource is compressed, this function returns the size of the
680 compressed data. See uncompressedSize() for the uncompressed size.
681
682 \sa data(), uncompressedSize(), isFile()
683*/
684
685qint64 QResource::size() const
686{
687 Q_D(const QResource);
688 d->ensureInitialized();
689 return d->size;
690}
691
692/*!
693 \since 5.15
694
695 Returns the size of the data in this resource. If the data was not
696 compressed, this function returns the same as size(). If it was, then this
697 function extracts the size of the original uncompressed data from the
698 stored stream.
699
700 \sa size(), uncompressedData(), isFile()
701*/
702qint64 QResource::uncompressedSize() const
703{
704 Q_D(const QResource);
705 d->ensureInitialized();
706 return d->uncompressedSize();
707}
708
709/*!
710 Returns direct access to a segment of read-only data, that this resource
711 represents. If the resource is compressed, the data returned is also
712 compressed. The caller must then decompress the data or use
713 uncompressedData(). If the resource is a directory, \c nullptr is returned.
714
715 \sa uncompressedData(), size(), isFile()
716*/
717
718const uchar *QResource::data() const
719{
720 Q_D(const QResource);
721 d->ensureInitialized();
722 return d->data;
723}
724
725/*!
726 \since 5.15
727
728 Returns the resource data, decompressing it first, if the data was stored
729 compressed. If the resource is a directory or an error occurs while
730 decompressing, a null QByteArray is returned.
731
732 \note If the data was compressed, this function will decompress every time
733 it is called. The result is not cached between calls.
734
735 \sa uncompressedSize(), size(), isCompressed(), isFile()
736*/
737
738QByteArray QResource::uncompressedData() const
739{
740 Q_D(const QResource);
741 qint64 n = uncompressedSize();
742 if (n < 0)
743 return QByteArray();
744 if (n > std::numeric_limits<QByteArray::size_type>::max()) {
745 qWarning(msg: "QResource: compressed content does not fit into a QByteArray; use QFile instead");
746 return QByteArray();
747 }
748 if (d->compressionAlgo == NoCompression)
749 return QByteArray::fromRawData(reinterpret_cast<const char *>(d->data), size: n);
750
751 // decompress
752 QByteArray result(n, Qt::Uninitialized);
753 n = d->decompress(buffer: result.data(), bufferSize: n);
754 if (n < 0)
755 result.clear();
756 else
757 result.truncate(pos: n);
758 return result;
759}
760
761/*!
762 \since 5.8
763
764 Returns the date and time when the file was last modified before
765 packaging into a resource.
766*/
767QDateTime QResource::lastModified() const
768{
769 Q_D(const QResource);
770 d->ensureInitialized();
771 return d->lastModified ? QDateTime::fromMSecsSinceEpoch(msecs: d->lastModified) : QDateTime();
772}
773
774/*!
775 Returns \c true if the resource represents a directory and thus may have
776 children() in it, false if it represents a file.
777
778 \sa isFile()
779*/
780
781bool QResource::isDir() const
782{
783 Q_D(const QResource);
784 d->ensureInitialized();
785 return d->container;
786}
787
788/*!
789 Returns a list of all resources in this directory, if the resource
790 represents a file the list will be empty.
791
792 \sa isDir()
793*/
794
795QStringList QResource::children() const
796{
797 Q_D(const QResource);
798 d->ensureChildren();
799 return d->children;
800}
801
802#if QT_DEPRECATED_SINCE(5, 13)
803/*!
804 \obsolete
805
806 Use QDir::addSearchPath() with a prefix instead.
807
808 Adds \a path to the search paths searched in to find resources that are
809 not specified with an absolute path. The \a path must be an absolute
810 path (start with \c{/}).
811
812 The default search path is to search only in the root (\c{:/}). The last
813 path added will be consulted first upon next QResource creation.
814*/
815void
816QResource::addSearchPath(const QString &path)
817{
818 if (!path.startsWith(c: QLatin1Char('/'))) {
819 qWarning(msg: "QResource::addResourceSearchPath: Search paths must be absolute (start with /) [%s]",
820 path.toLocal8Bit().data());
821 return;
822 }
823 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
824 resourceSearchPaths()->prepend(t: path);
825}
826
827/*!
828 \obsolete
829
830 Use QDir::searchPaths() instead.
831
832 Returns the current search path list. This list is consulted when
833 creating a relative resource.
834
835 \sa QDir::addSearchPath(), QDir::setSearchPaths()
836*/
837
838QStringList
839QResource::searchPaths()
840{
841 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
842 return *resourceSearchPaths();
843}
844#endif
845
846inline uint QResourceRoot::hash(int node) const
847{
848 if(!node) //root
849 return 0;
850 const int offset = findOffset(node);
851 qint32 name_offset = qFromBigEndian<qint32>(src: tree + offset);
852 name_offset += 2; //jump past name length
853 return qFromBigEndian<quint32>(src: names + name_offset);
854}
855inline QString QResourceRoot::name(int node) const
856{
857 if(!node) // root
858 return QString();
859 const int offset = findOffset(node);
860
861 QString ret;
862 qint32 name_offset = qFromBigEndian<qint32>(src: tree + offset);
863 quint16 name_length = qFromBigEndian<qint16>(src: names + name_offset);
864 name_offset += 2;
865 name_offset += 4; //jump past hash
866
867 ret.resize(size: name_length);
868 QChar *strData = ret.data();
869 qFromBigEndian<ushort>(source: names + name_offset, count: name_length, dest: strData);
870 return ret;
871}
872
873int QResourceRoot::findNode(const QString &_path, const QLocale &locale) const
874{
875 QString path = _path;
876 {
877 QString root = mappingRoot();
878 if(!root.isEmpty()) {
879 if(root == path) {
880 path = QLatin1Char('/');
881 } else {
882 if(!root.endsWith(c: QLatin1Char('/')))
883 root += QLatin1Char('/');
884 if(path.size() >= root.size() && path.startsWith(s: root))
885 path = path.mid(position: root.length()-1);
886 if(path.isEmpty())
887 path = QLatin1Char('/');
888 }
889 }
890 }
891#ifdef DEBUG_RESOURCE_MATCH
892 qDebug() << "!!!!" << "START" << path << locale.country() << locale.language();
893#endif
894
895 if(path == QLatin1String("/"))
896 return 0;
897
898 //the root node is always first
899 qint32 child_count = qFromBigEndian<qint32>(src: tree + 6);
900 qint32 child = qFromBigEndian<qint32>(src: tree + 10);
901
902 //now iterate up the tree
903 int node = -1;
904
905 QStringSplitter splitter(path);
906 while (child_count && splitter.hasNext()) {
907 QStringView segment = splitter.next();
908
909#ifdef DEBUG_RESOURCE_MATCH
910 qDebug() << " CHILDREN" << segment;
911 for(int j = 0; j < child_count; ++j) {
912 qDebug() << " " << child+j << " :: " << name(child+j);
913 }
914#endif
915 const uint h = qt_hash(key: segment);
916
917 //do the binary search for the hash
918 int l = 0, r = child_count-1;
919 int sub_node = (l+r+1)/2;
920 while(r != l) {
921 const uint sub_node_hash = hash(node: child+sub_node);
922 if(h == sub_node_hash)
923 break;
924 else if(h < sub_node_hash)
925 r = sub_node - 1;
926 else
927 l = sub_node;
928 sub_node = (l + r + 1) / 2;
929 }
930 sub_node += child;
931
932 //now do the "harder" compares
933 bool found = false;
934 if(hash(node: sub_node) == h) {
935 while(sub_node > child && hash(node: sub_node-1) == h) //backup for collisions
936 --sub_node;
937 for(; sub_node < child+child_count && hash(node: sub_node) == h; ++sub_node) { //here we go...
938 if(name(node: sub_node) == segment) {
939 found = true;
940 int offset = findOffset(node: sub_node);
941#ifdef DEBUG_RESOURCE_MATCH
942 qDebug() << " TRY" << sub_node << name(sub_node) << offset;
943#endif
944 offset += 4; //jump past name
945
946 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
947 offset += 2;
948
949 if(!splitter.hasNext()) {
950 if(!(flags & Directory)) {
951 const qint16 country = qFromBigEndian<qint16>(src: tree + offset);
952 offset += 2;
953
954 const qint16 language = qFromBigEndian<qint16>(src: tree + offset);
955 offset += 2;
956#ifdef DEBUG_RESOURCE_MATCH
957 qDebug() << " " << "LOCALE" << country << language;
958#endif
959 if(country == locale.country() && language == locale.language()) {
960#ifdef DEBUG_RESOURCE_MATCH
961 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
962#endif
963 return sub_node;
964 } else if((country == QLocale::AnyCountry && language == locale.language()) ||
965 (country == QLocale::AnyCountry && language == QLocale::C && node == -1)) {
966 node = sub_node;
967 }
968 continue;
969 } else {
970#ifdef DEBUG_RESOURCE_MATCH
971 qDebug() << "!!!!" << "FINISHED" << __LINE__ << sub_node;
972#endif
973
974 return sub_node;
975 }
976 }
977
978 if(!(flags & Directory))
979 return -1;
980
981 child_count = qFromBigEndian<qint32>(src: tree + offset);
982 offset += 4;
983 child = qFromBigEndian<qint32>(src: tree + offset);
984 break;
985 }
986 }
987 }
988 if(!found)
989 break;
990 }
991#ifdef DEBUG_RESOURCE_MATCH
992 qDebug() << "!!!!" << "FINISHED" << __LINE__ << node;
993#endif
994 return node;
995}
996short QResourceRoot::flags(int node) const
997{
998 if(node == -1)
999 return 0;
1000 const int offset = findOffset(node) + 4; //jump past name
1001 return qFromBigEndian<qint16>(src: tree + offset);
1002}
1003const uchar *QResourceRoot::data(int node, qint64 *size) const
1004{
1005 if(node == -1) {
1006 *size = 0;
1007 return nullptr;
1008 }
1009 int offset = findOffset(node) + 4; //jump past name
1010
1011 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
1012 offset += 2;
1013
1014 offset += 4; //jump past locale
1015
1016 if(!(flags & Directory)) {
1017 const qint32 data_offset = qFromBigEndian<qint32>(src: tree + offset);
1018 const quint32 data_length = qFromBigEndian<quint32>(src: payloads + data_offset);
1019 const uchar *ret = payloads+data_offset+4;
1020 *size = data_length;
1021 return ret;
1022 }
1023 *size = 0;
1024 return nullptr;
1025}
1026
1027quint64 QResourceRoot::lastModified(int node) const
1028{
1029 if (node == -1 || version < 0x02)
1030 return 0;
1031
1032 const int offset = findOffset(node) + 14;
1033
1034 return qFromBigEndian<quint64>(src: tree + offset);
1035}
1036
1037QStringList QResourceRoot::children(int node) const
1038{
1039 if(node == -1)
1040 return QStringList();
1041 int offset = findOffset(node) + 4; //jump past name
1042
1043 const qint16 flags = qFromBigEndian<qint16>(src: tree + offset);
1044 offset += 2;
1045
1046 QStringList ret;
1047 if(flags & Directory) {
1048 const qint32 child_count = qFromBigEndian<qint32>(src: tree + offset);
1049 offset += 4;
1050 const qint32 child_off = qFromBigEndian<qint32>(src: tree + offset);
1051 ret.reserve(alloc: child_count);
1052 for(int i = child_off; i < child_off+child_count; ++i)
1053 ret << name(node: i);
1054 }
1055 return ret;
1056}
1057bool QResourceRoot::mappingRootSubdir(const QString &path, QString *match) const
1058{
1059 const QString root = mappingRoot();
1060 if (root.isEmpty())
1061 return false;
1062
1063 QStringSplitter rootIt(root);
1064 QStringSplitter pathIt(path);
1065 while (rootIt.hasNext()) {
1066 if (pathIt.hasNext()) {
1067 if (rootIt.next() != pathIt.next()) // mismatch
1068 return false;
1069 } else {
1070 // end of path, but not of root:
1071 if (match)
1072 *match = rootIt.next().toString();
1073 return true;
1074 }
1075 }
1076 // end of root
1077 return !pathIt.hasNext();
1078}
1079
1080Q_CORE_EXPORT bool qRegisterResourceData(int version, const unsigned char *tree,
1081 const unsigned char *name, const unsigned char *data)
1082{
1083 if (resourceGlobalData.isDestroyed())
1084 return false;
1085 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1086 ResourceList *list = resourceList();
1087 if (version >= 0x01 && version <= 0x3) {
1088 bool found = false;
1089 QResourceRoot res(version, tree, name, data);
1090 for (int i = 0; i < list->size(); ++i) {
1091 if (*list->at(i) == res) {
1092 found = true;
1093 break;
1094 }
1095 }
1096 if(!found) {
1097 QResourceRoot *root = new QResourceRoot(version, tree, name, data);
1098 root->ref.ref();
1099 list->append(t: root);
1100 }
1101 return true;
1102 }
1103 return false;
1104}
1105
1106Q_CORE_EXPORT bool qUnregisterResourceData(int version, const unsigned char *tree,
1107 const unsigned char *name, const unsigned char *data)
1108{
1109 if (resourceGlobalData.isDestroyed())
1110 return false;
1111
1112 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1113 if (version >= 0x01 && version <= 0x3) {
1114 QResourceRoot res(version, tree, name, data);
1115 ResourceList *list = resourceList();
1116 for (int i = 0; i < list->size(); ) {
1117 if (*list->at(i) == res) {
1118 QResourceRoot *root = list->takeAt(i);
1119 if(!root->ref.deref())
1120 delete root;
1121 } else {
1122 ++i;
1123 }
1124 }
1125 return true;
1126 }
1127 return false;
1128}
1129
1130//run time resource creation
1131
1132class QDynamicBufferResourceRoot: public QResourceRoot
1133{
1134 QString root;
1135 const uchar *buffer;
1136
1137public:
1138 inline QDynamicBufferResourceRoot(const QString &_root) : root(_root), buffer(nullptr) { }
1139 inline ~QDynamicBufferResourceRoot() { }
1140 inline const uchar *mappingBuffer() const { return buffer; }
1141 QString mappingRoot() const override { return root; }
1142 ResourceRootType type() const override { return Resource_Buffer; }
1143
1144 // size == -1 means "unknown"
1145 bool registerSelf(const uchar *b, qsizetype size)
1146 {
1147 // 5 int "pointers"
1148 if (size >= 0 && size < 20)
1149 return false;
1150
1151 //setup the data now
1152 int offset = 0;
1153
1154 //magic number
1155 if(b[offset+0] != 'q' || b[offset+1] != 'r' ||
1156 b[offset+2] != 'e' || b[offset+3] != 's') {
1157 return false;
1158 }
1159 offset += 4;
1160
1161 const int version = qFromBigEndian<qint32>(src: b + offset);
1162 offset += 4;
1163
1164 const int tree_offset = qFromBigEndian<qint32>(src: b + offset);
1165 offset += 4;
1166
1167 const int data_offset = qFromBigEndian<qint32>(src: b + offset);
1168 offset += 4;
1169
1170 const int name_offset = qFromBigEndian<qint32>(src: b + offset);
1171 offset += 4;
1172
1173 quint32 file_flags = 0;
1174 if (version >= 3) {
1175 file_flags = qFromBigEndian<qint32>(src: b + offset);
1176 offset += 4;
1177 }
1178
1179 // Some sanity checking for sizes. This is _not_ a security measure.
1180 if (size >= 0 && (tree_offset >= size || data_offset >= size || name_offset >= size))
1181 return false;
1182
1183 // And some sanity checking for features
1184 quint32 acceptableFlags = 0;
1185#ifndef QT_NO_COMPRESS
1186 acceptableFlags |= Compressed;
1187#endif
1188 if (QT_CONFIG(zstd))
1189 acceptableFlags |= CompressedZstd;
1190 if (file_flags & ~acceptableFlags)
1191 return false;
1192
1193 if (version >= 0x01 && version <= 0x03) {
1194 buffer = b;
1195 setSource(v: version, t: b+tree_offset, n: b+name_offset, d: b+data_offset);
1196 return true;
1197 }
1198 return false;
1199 }
1200};
1201
1202class QDynamicFileResourceRoot: public QDynamicBufferResourceRoot
1203{
1204 QString fileName;
1205 // for mmap'ed files, this is what needs to be unmapped.
1206 uchar *unmapPointer;
1207 qsizetype unmapLength;
1208
1209public:
1210 QDynamicFileResourceRoot(const QString &_root)
1211 : QDynamicBufferResourceRoot(_root), unmapPointer(nullptr), unmapLength(0)
1212 { }
1213 ~QDynamicFileResourceRoot() {
1214#if defined(QT_USE_MMAP)
1215 if (unmapPointer) {
1216 munmap(addr: (char*)unmapPointer, len: unmapLength);
1217 unmapPointer = nullptr;
1218 unmapLength = 0;
1219 } else
1220#endif
1221 {
1222 delete [] mappingBuffer();
1223 }
1224 }
1225 QString mappingFile() const { return fileName; }
1226 ResourceRootType type() const override { return Resource_File; }
1227
1228 bool registerSelf(const QString &f);
1229};
1230
1231#ifndef MAP_FILE
1232# define MAP_FILE 0
1233#endif
1234#ifndef MAP_FAILED
1235# define MAP_FAILED reinterpret_cast<void *>(-1)
1236#endif
1237
1238bool QDynamicFileResourceRoot::registerSelf(const QString &f)
1239{
1240 bool fromMM = false;
1241 uchar *data = nullptr;
1242 qsizetype data_len = 0;
1243
1244#ifdef QT_USE_MMAP
1245 int fd = QT_OPEN(file: QFile::encodeName(fileName: f), O_RDONLY,
1246#if defined(Q_OS_WIN)
1247 _S_IREAD | _S_IWRITE
1248#else
1249 0666
1250#endif
1251 );
1252 if (fd >= 0) {
1253 QT_STATBUF st;
1254 if (!QT_FSTAT(fd: fd, buf: &st) && st.st_size <= std::numeric_limits<qsizetype>::max()) {
1255 int protection = PROT_READ; // read-only memory
1256 int flags = MAP_FILE | MAP_PRIVATE; // swap-backed map from file
1257 void *ptr = QT_MMAP(addr: nullptr, len: st.st_size, // any address, whole file
1258 prot: protection, flags: flags,
1259 fd: fd, offset: 0); // from offset 0 of fd
1260 if (ptr != MAP_FAILED) {
1261 data = static_cast<uchar *>(ptr);
1262 data_len = st.st_size;
1263 fromMM = true;
1264 }
1265 }
1266 QT_CLOSE(fd: fd);
1267 }
1268#endif // QT_USE_MMAP
1269 if (!data) {
1270 QFile file(f);
1271 bool ok = false;
1272 if (file.open(flags: QIODevice::ReadOnly)) {
1273 qint64 fsize = file.size();
1274 if (fsize <= std::numeric_limits<qsizetype>::max()) {
1275 data_len = file.size();
1276 data = new uchar[data_len];
1277 ok = (data_len == file.read(data: (char*)data, maxlen: data_len));
1278 }
1279 }
1280 if (!ok) {
1281 delete [] data;
1282 data = nullptr;
1283 data_len = 0;
1284 return false;
1285 }
1286 fromMM = false;
1287 }
1288 if (data && QDynamicBufferResourceRoot::registerSelf(b: data, size: data_len)) {
1289 if (fromMM) {
1290 unmapPointer = data;
1291 unmapLength = data_len;
1292 }
1293 fileName = f;
1294 return true;
1295 }
1296 return false;
1297}
1298
1299static QString qt_resource_fixResourceRoot(QString r) {
1300 if(!r.isEmpty()) {
1301 if(r.startsWith(c: QLatin1Char(':')))
1302 r = r.mid(position: 1);
1303 if(!r.isEmpty())
1304 r = QDir::cleanPath(path: r);
1305 }
1306 return r;
1307}
1308
1309
1310/*!
1311 \fn bool QResource::registerResource(const QString &rccFileName, const QString &mapRoot)
1312
1313 Registers the resource with the given \a rccFileName at the location in the
1314 resource tree specified by \a mapRoot, and returns \c true if the file is
1315 successfully opened; otherwise returns \c false.
1316
1317 \sa unregisterResource()
1318*/
1319
1320bool
1321QResource::registerResource(const QString &rccFilename, const QString &resourceRoot)
1322{
1323 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1324 if(!r.isEmpty() && r[0] != QLatin1Char('/')) {
1325 qWarning(msg: "QDir::registerResource: Registering a resource [%s] must be rooted in an absolute path (start with /) [%s]",
1326 rccFilename.toLocal8Bit().data(), resourceRoot.toLocal8Bit().data());
1327 return false;
1328 }
1329
1330 QDynamicFileResourceRoot *root = new QDynamicFileResourceRoot(r);
1331 if(root->registerSelf(f: rccFilename)) {
1332 root->ref.ref();
1333 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1334 resourceList()->append(t: root);
1335 return true;
1336 }
1337 delete root;
1338 return false;
1339}
1340
1341/*!
1342 \fn bool QResource::unregisterResource(const QString &rccFileName, const QString &mapRoot)
1343
1344 Unregisters the resource with the given \a rccFileName at the location in
1345 the resource tree specified by \a mapRoot, and returns \c true if the
1346 resource is successfully unloaded and no references exist for the
1347 resource; otherwise returns \c false.
1348
1349 \sa registerResource()
1350*/
1351
1352bool
1353QResource::unregisterResource(const QString &rccFilename, const QString &resourceRoot)
1354{
1355 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1356
1357 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1358 ResourceList *list = resourceList();
1359 for(int i = 0; i < list->size(); ++i) {
1360 QResourceRoot *res = list->at(i);
1361 if(res->type() == QResourceRoot::Resource_File) {
1362 QDynamicFileResourceRoot *root = reinterpret_cast<QDynamicFileResourceRoot*>(res);
1363 if (root->mappingFile() == rccFilename && root->mappingRoot() == r) {
1364 list->removeAt(i);
1365 if(!root->ref.deref()) {
1366 delete root;
1367 return true;
1368 }
1369 return false;
1370 }
1371 }
1372 }
1373 return false;
1374}
1375
1376
1377/*!
1378 \fn bool QResource::registerResource(const uchar *rccData, const QString &mapRoot)
1379 \since 4.3
1380
1381 Registers the resource with the given \a rccData at the location in the
1382 resource tree specified by \a mapRoot, and returns \c true if the file is
1383 successfully opened; otherwise returns \c false.
1384
1385 \warning The data must remain valid throughout the life of any QFile
1386 that may reference the resource data.
1387
1388 \sa unregisterResource()
1389*/
1390
1391bool
1392QResource::registerResource(const uchar *rccData, const QString &resourceRoot)
1393{
1394 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1395 if(!r.isEmpty() && r[0] != QLatin1Char('/')) {
1396 qWarning(msg: "QDir::registerResource: Registering a resource [%p] must be rooted in an absolute path (start with /) [%s]",
1397 rccData, resourceRoot.toLocal8Bit().data());
1398 return false;
1399 }
1400
1401 QDynamicBufferResourceRoot *root = new QDynamicBufferResourceRoot(r);
1402 if (root->registerSelf(b: rccData, size: -1)) {
1403 root->ref.ref();
1404 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1405 resourceList()->append(t: root);
1406 return true;
1407 }
1408 delete root;
1409 return false;
1410}
1411
1412/*!
1413 \fn bool QResource::unregisterResource(const uchar *rccData, const QString &mapRoot)
1414 \since 4.3
1415
1416 Unregisters the resource with the given \a rccData at the location in the
1417 resource tree specified by \a mapRoot, and returns \c true if the resource is
1418 successfully unloaded and no references exist into the resource; otherwise returns \c false.
1419
1420 \sa registerResource()
1421*/
1422
1423bool
1424QResource::unregisterResource(const uchar *rccData, const QString &resourceRoot)
1425{
1426 QString r = qt_resource_fixResourceRoot(r: resourceRoot);
1427
1428 const auto locker = qt_scoped_lock(mutex&: resourceMutex());
1429 ResourceList *list = resourceList();
1430 for(int i = 0; i < list->size(); ++i) {
1431 QResourceRoot *res = list->at(i);
1432 if(res->type() == QResourceRoot::Resource_Buffer) {
1433 QDynamicBufferResourceRoot *root = reinterpret_cast<QDynamicBufferResourceRoot*>(res);
1434 if (root->mappingBuffer() == rccData && root->mappingRoot() == r) {
1435 list->removeAt(i);
1436 if(!root->ref.deref()) {
1437 delete root;
1438 return true;
1439 }
1440 return false;
1441 }
1442 }
1443 }
1444 return false;
1445}
1446
1447#if !defined(QT_BOOTSTRAPPED)
1448//resource engine
1449class QResourceFileEnginePrivate : public QAbstractFileEnginePrivate
1450{
1451protected:
1452 Q_DECLARE_PUBLIC(QResourceFileEngine)
1453private:
1454 uchar *map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags);
1455 bool unmap(uchar *ptr);
1456 void uncompress() const;
1457 qint64 offset;
1458 QResource resource;
1459 mutable QByteArray uncompressed;
1460protected:
1461 QResourceFileEnginePrivate() : offset(0) { }
1462};
1463
1464bool QResourceFileEngine::mkdir(const QString &, bool) const
1465{
1466 return false;
1467}
1468
1469bool QResourceFileEngine::rmdir(const QString &, bool) const
1470{
1471 return false;
1472}
1473
1474bool QResourceFileEngine::setSize(qint64)
1475{
1476 return false;
1477}
1478
1479QStringList QResourceFileEngine::entryList(QDir::Filters filters, const QStringList &filterNames) const
1480{
1481 return QAbstractFileEngine::entryList(filters, filterNames);
1482}
1483
1484bool QResourceFileEngine::caseSensitive() const
1485{
1486 return true;
1487}
1488
1489QResourceFileEngine::QResourceFileEngine(const QString &file) :
1490 QAbstractFileEngine(*new QResourceFileEnginePrivate)
1491{
1492 Q_D(QResourceFileEngine);
1493 d->resource.setFileName(file);
1494}
1495
1496QResourceFileEngine::~QResourceFileEngine()
1497{
1498}
1499
1500void QResourceFileEngine::setFileName(const QString &file)
1501{
1502 Q_D(QResourceFileEngine);
1503 d->resource.setFileName(file);
1504}
1505
1506bool QResourceFileEngine::open(QIODevice::OpenMode flags)
1507{
1508 Q_D(QResourceFileEngine);
1509 if (d->resource.fileName().isEmpty()) {
1510 qWarning(msg: "QResourceFileEngine::open: Missing file name");
1511 return false;
1512 }
1513 if (flags & QIODevice::WriteOnly)
1514 return false;
1515 if (d->resource.compressionAlgorithm() != QResource::NoCompression) {
1516 d->uncompress();
1517 if (d->uncompressed.isNull()) {
1518 d->errorString = QSystemError::stdString(EIO);
1519 return false;
1520 }
1521 }
1522 if (!d->resource.isValid()) {
1523 d->errorString = QSystemError::stdString(ENOENT);
1524 return false;
1525 }
1526 return true;
1527}
1528
1529bool QResourceFileEngine::close()
1530{
1531 Q_D(QResourceFileEngine);
1532 d->offset = 0;
1533 return true;
1534}
1535
1536bool QResourceFileEngine::flush()
1537{
1538 return true;
1539}
1540
1541qint64 QResourceFileEngine::read(char *data, qint64 len)
1542{
1543 Q_D(QResourceFileEngine);
1544 if(len > size()-d->offset)
1545 len = size()-d->offset;
1546 if(len <= 0)
1547 return 0;
1548 if (!d->uncompressed.isNull())
1549 memcpy(dest: data, src: d->uncompressed.constData()+d->offset, n: len);
1550 else
1551 memcpy(dest: data, src: d->resource.data()+d->offset, n: len);
1552 d->offset += len;
1553 return len;
1554}
1555
1556qint64 QResourceFileEngine::write(const char *, qint64)
1557{
1558 return -1;
1559}
1560
1561bool QResourceFileEngine::remove()
1562{
1563 return false;
1564}
1565
1566bool QResourceFileEngine::copy(const QString &)
1567{
1568 return false;
1569}
1570
1571bool QResourceFileEngine::rename(const QString &)
1572{
1573 return false;
1574}
1575
1576bool QResourceFileEngine::link(const QString &)
1577{
1578 return false;
1579}
1580
1581qint64 QResourceFileEngine::size() const
1582{
1583 Q_D(const QResourceFileEngine);
1584 return d->resource.isValid() ? d->resource.uncompressedSize() : 0;
1585}
1586
1587qint64 QResourceFileEngine::pos() const
1588{
1589 Q_D(const QResourceFileEngine);
1590 return d->offset;
1591}
1592
1593bool QResourceFileEngine::atEnd() const
1594{
1595 Q_D(const QResourceFileEngine);
1596 if(!d->resource.isValid())
1597 return true;
1598 return d->offset == size();
1599}
1600
1601bool QResourceFileEngine::seek(qint64 pos)
1602{
1603 Q_D(QResourceFileEngine);
1604 if(!d->resource.isValid())
1605 return false;
1606
1607 if(d->offset > size())
1608 return false;
1609 d->offset = pos;
1610 return true;
1611}
1612
1613bool QResourceFileEngine::isSequential() const
1614{
1615 return false;
1616}
1617
1618QAbstractFileEngine::FileFlags QResourceFileEngine::fileFlags(QAbstractFileEngine::FileFlags type) const
1619{
1620 Q_D(const QResourceFileEngine);
1621 QAbstractFileEngine::FileFlags ret;
1622 if(!d->resource.isValid())
1623 return ret;
1624
1625 if(type & PermsMask)
1626 ret |= QAbstractFileEngine::FileFlags(ReadOwnerPerm|ReadUserPerm|ReadGroupPerm|ReadOtherPerm);
1627 if(type & TypesMask) {
1628 if(d->resource.isDir())
1629 ret |= DirectoryType;
1630 else
1631 ret |= FileType;
1632 }
1633 if(type & FlagsMask) {
1634 ret |= ExistsFlag;
1635 if(d->resource.absoluteFilePath() == QLatin1String(":/"))
1636 ret |= RootFlag;
1637 }
1638 return ret;
1639}
1640
1641bool QResourceFileEngine::setPermissions(uint)
1642{
1643 return false;
1644}
1645
1646QString QResourceFileEngine::fileName(FileName file) const
1647{
1648 Q_D(const QResourceFileEngine);
1649 if(file == BaseName) {
1650 int slash = d->resource.fileName().lastIndexOf(c: QLatin1Char('/'));
1651 if (slash == -1)
1652 return d->resource.fileName();
1653 return d->resource.fileName().mid(position: slash + 1);
1654 } else if(file == PathName || file == AbsolutePathName) {
1655 const QString path = (file == AbsolutePathName) ? d->resource.absoluteFilePath() : d->resource.fileName();
1656 const int slash = path.lastIndexOf(c: QLatin1Char('/'));
1657 if (slash == -1)
1658 return QLatin1String(":");
1659 else if (slash <= 1)
1660 return QLatin1String(":/");
1661 return path.left(n: slash);
1662
1663 } else if(file == CanonicalName || file == CanonicalPathName) {
1664 const QString absoluteFilePath = d->resource.absoluteFilePath();
1665 if(file == CanonicalPathName) {
1666 const int slash = absoluteFilePath.lastIndexOf(c: QLatin1Char('/'));
1667 if (slash != -1)
1668 return absoluteFilePath.left(n: slash);
1669 }
1670 return absoluteFilePath;
1671 }
1672 return d->resource.fileName();
1673}
1674
1675bool QResourceFileEngine::isRelativePath() const
1676{
1677 return false;
1678}
1679
1680uint QResourceFileEngine::ownerId(FileOwner) const
1681{
1682 static const uint nobodyID = (uint) -2;
1683 return nobodyID;
1684}
1685
1686QString QResourceFileEngine::owner(FileOwner) const
1687{
1688 return QString();
1689}
1690
1691QDateTime QResourceFileEngine::fileTime(FileTime time) const
1692{
1693 Q_D(const QResourceFileEngine);
1694 if (time == ModificationTime)
1695 return d->resource.lastModified();
1696 return QDateTime();
1697}
1698
1699/*!
1700 \internal
1701*/
1702QAbstractFileEngine::Iterator *QResourceFileEngine::beginEntryList(QDir::Filters filters,
1703 const QStringList &filterNames)
1704{
1705 return new QResourceFileEngineIterator(filters, filterNames);
1706}
1707
1708/*!
1709 \internal
1710*/
1711QAbstractFileEngine::Iterator *QResourceFileEngine::endEntryList()
1712{
1713 return nullptr;
1714}
1715
1716bool QResourceFileEngine::extension(Extension extension, const ExtensionOption *option, ExtensionReturn *output)
1717{
1718 Q_D(QResourceFileEngine);
1719 if (extension == MapExtension) {
1720 const MapExtensionOption *options = (const MapExtensionOption*)(option);
1721 MapExtensionReturn *returnValue = static_cast<MapExtensionReturn*>(output);
1722 returnValue->address = d->map(offset: options->offset, size: options->size, flags: options->flags);
1723 return (returnValue->address != nullptr);
1724 }
1725 if (extension == UnMapExtension) {
1726 const UnMapExtensionOption *options = (const UnMapExtensionOption*)option;
1727 return d->unmap(ptr: options->address);
1728 }
1729 return false;
1730}
1731
1732bool QResourceFileEngine::supportsExtension(Extension extension) const
1733{
1734 return (extension == UnMapExtension || extension == MapExtension);
1735}
1736
1737uchar *QResourceFileEnginePrivate::map(qint64 offset, qint64 size, QFile::MemoryMapFlags flags)
1738{
1739 Q_Q(QResourceFileEngine);
1740 Q_UNUSED(flags);
1741
1742 qint64 max = resource.uncompressedSize();
1743 qint64 end;
1744 if (offset < 0 || size <= 0 || !resource.isValid() ||
1745 add_overflow(v1: offset, v2: size, r: &end) || end > max) {
1746 q->setError(error: QFile::UnspecifiedError, str: QString());
1747 return nullptr;
1748 }
1749
1750 const uchar *address = resource.data();
1751 if (resource.compressionAlgorithm() != QResource::NoCompression) {
1752 uncompress();
1753 if (uncompressed.isNull())
1754 return nullptr;
1755 address = reinterpret_cast<const uchar *>(uncompressed.constData());
1756 }
1757
1758 return const_cast<uchar *>(address) + offset;
1759}
1760
1761bool QResourceFileEnginePrivate::unmap(uchar *ptr)
1762{
1763 Q_UNUSED(ptr);
1764 return true;
1765}
1766
1767void QResourceFileEnginePrivate::uncompress() const
1768{
1769 if (resource.compressionAlgorithm() == QResource::NoCompression
1770 || !uncompressed.isEmpty() || resource.size() == 0)
1771 return; // nothing to do
1772 uncompressed = resource.uncompressedData();
1773}
1774
1775#endif // !defined(QT_BOOTSTRAPPED)
1776
1777QT_END_NAMESPACE
1778

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