1 /****************************************************************************
2 **
3 ** Copyright (C) 2018 The Qt Company Ltd.
4 ** Copyright (C) 2018 Intel Corporation.
5 ** Contact: https://www.qt.io/licensing/
6 **
7 ** This file is part of the tools applications of the Qt Toolkit.
8 **
9 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
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 General Public License Usage
19 ** Alternatively, this file may be used under the terms of the GNU
20 ** General Public License version 3 as published by the Free Software
21 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
22 ** included in the packaging of this file. Please review the following
23 ** information to ensure the GNU General Public License requirements will
24 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25 **
26 ** $QT_END_LICENSE$
27 **
28 ****************************************************************************/
29 
30 #include "rcc.h"
31 
32 #include <qbytearray.h>
33 #include <qdatetime.h>
34 #include <qdebug.h>
35 #include <qdir.h>
36 #include <qdiriterator.h>
37 #include <qfile.h>
38 #include <qiodevice.h>
39 #include <qlocale.h>
40 #include <qregexp.h>
41 #include <qstack.h>
42 #include <qxmlstream.h>
43 
44 #include <algorithm>
45 
46 #if QT_CONFIG(zstd)
47 #  include <zstd.h>
48 #endif
49 
50 // Note: A copy of this file is used in Qt Designer (qttools/src/designer/src/lib/shared/rcc.cpp)
51 
52 QT_BEGIN_NAMESPACE
53 
54 enum {
55     CONSTANT_USENAMESPACE = 1,
56     CONSTANT_COMPRESSLEVEL_DEFAULT = -1,
57     CONSTANT_ZSTDCOMPRESSLEVEL_CHECK = 1,   // Zstd level to check if compressing is a good idea
58     CONSTANT_ZSTDCOMPRESSLEVEL_STORE = 14,  // Zstd level to actually store the data
59     CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70
60 };
61 
62 #if QT_CONFIG(zstd) && QT_VERSION >= QT_VERSION_CHECK(6,0,0)
63 #  define CONSTANT_COMPRESSALGO_DEFAULT     RCCResourceLibrary::CompressionAlgorithm::Zstd
64 #elif !defined(QT_NO_COMPRESS)
65 #  define CONSTANT_COMPRESSALGO_DEFAULT     RCCResourceLibrary::CompressionAlgorithm::Zlib
66 #else
67 #  define CONSTANT_COMPRESSALGO_DEFAULT     RCCResourceLibrary::CompressionAlgorithm::None
68 #endif
69 
write(const char * str,int len)70 void RCCResourceLibrary::write(const char *str, int len)
71 {
72     int n = m_out.size();
73     m_out.resize(n + len);
74     memcpy(m_out.data() + n, str, len);
75 }
76 
writeByteArray(const QByteArray & other)77 void RCCResourceLibrary::writeByteArray(const QByteArray &other)
78 {
79     if (m_format == Pass2) {
80         m_outDevice->write(other);
81     } else {
82         m_out.append(other);
83     }
84 }
85 
msgOpenReadFailed(const QString & fname,const QString & why)86 static inline QString msgOpenReadFailed(const QString &fname, const QString &why)
87 {
88     return QString::fromLatin1("Unable to open %1 for reading: %2\n").arg(fname, why);
89 }
90 
91 
92 ///////////////////////////////////////////////////////////
93 //
94 // RCCFileInfo
95 //
96 ///////////////////////////////////////////////////////////
97 
98 class RCCFileInfo
99 {
100 public:
101     enum Flags
102     {
103         // must match qresource.cpp
104         NoFlags = 0x00,
105         Compressed = 0x01,
106         Directory = 0x02,
107         CompressedZstd = 0x04
108     };
109 
110     RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(),
111                 QLocale::Language language = QLocale::C,
112                 QLocale::Country country = QLocale::AnyCountry,
113                 uint flags = NoFlags,
114                 RCCResourceLibrary::CompressionAlgorithm compressAlgo = CONSTANT_COMPRESSALGO_DEFAULT,
115                 int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT,
116                 int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT);
117     ~RCCFileInfo();
118 
119     QString resourceName() const;
120 
121 public:
122     qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage);
123     qint64 writeDataName(RCCResourceLibrary &, qint64 offset);
124     void writeDataInfo(RCCResourceLibrary &lib);
125 
126     int m_flags;
127     QString m_name;
128     QLocale::Language m_language;
129     QLocale::Country m_country;
130     QFileInfo m_fileInfo;
131     RCCFileInfo *m_parent;
132     QMultiHash<QString, RCCFileInfo *> m_children;
133     RCCResourceLibrary::CompressionAlgorithm m_compressAlgo;
134     int m_compressLevel;
135     int m_compressThreshold;
136 
137     qint64 m_nameOffset;
138     qint64 m_dataOffset;
139     qint64 m_childOffset;
140 };
141 
RCCFileInfo(const QString & name,const QFileInfo & fileInfo,QLocale::Language language,QLocale::Country country,uint flags,RCCResourceLibrary::CompressionAlgorithm compressAlgo,int compressLevel,int compressThreshold)142 RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo,
143     QLocale::Language language, QLocale::Country country, uint flags,
144     RCCResourceLibrary::CompressionAlgorithm compressAlgo, int compressLevel, int compressThreshold)
145 {
146     m_name = name;
147     m_fileInfo = fileInfo;
148     m_language = language;
149     m_country = country;
150     m_flags = flags;
151     m_parent = 0;
152     m_nameOffset = 0;
153     m_dataOffset = 0;
154     m_childOffset = 0;
155     m_compressAlgo = compressAlgo;
156     m_compressLevel = compressLevel;
157     m_compressThreshold = compressThreshold;
158 }
159 
~RCCFileInfo()160 RCCFileInfo::~RCCFileInfo()
161 {
162     qDeleteAll(m_children);
163 }
164 
resourceName() const165 QString RCCFileInfo::resourceName() const
166 {
167     QString resource = m_name;
168     for (RCCFileInfo *p = m_parent; p; p = p->m_parent)
169         resource = resource.prepend(p->m_name + QLatin1Char('/'));
170     return QLatin1Char(':') + resource;
171 }
172 
writeDataInfo(RCCResourceLibrary & lib)173 void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
174 {
175     const bool text = lib.m_format == RCCResourceLibrary::C_Code;
176     const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
177     const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
178         || lib.m_format == RCCResourceLibrary::Python2_Code;
179     //some info
180     if (text || pass1) {
181         if (m_language != QLocale::C) {
182             lib.writeString("  // ");
183             lib.writeByteArray(resourceName().toLocal8Bit());
184             lib.writeString(" [");
185             lib.writeByteArray(QByteArray::number(m_country));
186             lib.writeString("::");
187             lib.writeByteArray(QByteArray::number(m_language));
188             lib.writeString("[\n  ");
189         } else {
190             lib.writeString("  // ");
191             lib.writeByteArray(resourceName().toLocal8Bit());
192             lib.writeString("\n  ");
193         }
194     }
195 
196     //pointer data
197     if (m_flags & RCCFileInfo::Directory) {
198         // name offset
199         lib.writeNumber4(m_nameOffset);
200 
201         // flags
202         lib.writeNumber2(m_flags);
203 
204         // child count
205         lib.writeNumber4(m_children.size());
206 
207         // first child offset
208         lib.writeNumber4(m_childOffset);
209     } else {
210         // name offset
211         lib.writeNumber4(m_nameOffset);
212 
213         // flags
214         lib.writeNumber2(m_flags);
215 
216         // locale
217         lib.writeNumber2(m_country);
218         lib.writeNumber2(m_language);
219 
220         //data offset
221         lib.writeNumber4(m_dataOffset);
222     }
223     if (text || pass1)
224         lib.writeChar('\n');
225     else if (python)
226         lib.writeString("\\\n");
227 
228     if (lib.formatVersion() >= 2) {
229         // last modified time stamp
230         const QDateTime lastModified = m_fileInfo.lastModified();
231         quint64 lastmod = quint64(lastModified.isValid() ? lastModified.toMSecsSinceEpoch() : 0);
232         static const quint64 sourceDate = 1000 * qgetenv("QT_RCC_SOURCE_DATE_OVERRIDE").toULongLong();
233         if (sourceDate != 0)
234             lastmod = sourceDate;
235         static const quint64 sourceDate2 = 1000 * qgetenv("SOURCE_DATE_EPOCH").toULongLong();
236         if (sourceDate2 != 0)
237             lastmod = sourceDate2;
238         lib.writeNumber8(lastmod);
239         if (text || pass1)
240             lib.writeChar('\n');
241         else if (python)
242             lib.writeString("\\\n");
243     }
244 }
245 
writeDataBlob(RCCResourceLibrary & lib,qint64 offset,QString * errorMessage)246 qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
247     QString *errorMessage)
248 {
249     const bool text = lib.m_format == RCCResourceLibrary::C_Code;
250     const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
251     const bool pass2 = lib.m_format == RCCResourceLibrary::Pass2;
252     const bool binary = lib.m_format == RCCResourceLibrary::Binary;
253     const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
254         || lib.m_format == RCCResourceLibrary::Python2_Code;
255 
256     //capture the offset
257     m_dataOffset = offset;
258 
259     //find the data to be written
260     QFile file(m_fileInfo.absoluteFilePath());
261     if (!file.open(QFile::ReadOnly)) {
262         *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
263         return 0;
264     }
265     QByteArray data = file.readAll();
266 
267     // Check if compression is useful for this file
268     if (data.size() != 0) {
269 #if QT_CONFIG(zstd)
270         if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) {
271             m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zstd;
272             m_compressLevel = 19;   // not ZSTD_maxCLevel(), as 20+ are experimental
273         }
274         if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zstd) {
275             if (lib.m_zstdCCtx == nullptr)
276                 lib.m_zstdCCtx = ZSTD_createCCtx();
277             qsizetype size = data.size();
278             size = ZSTD_COMPRESSBOUND(size);
279 
280             int compressLevel = m_compressLevel;
281             if (compressLevel < 0)
282                 compressLevel = CONSTANT_ZSTDCOMPRESSLEVEL_CHECK;
283 
284             QByteArray compressed(size, Qt::Uninitialized);
285             char *dst = const_cast<char *>(compressed.constData());
286             size_t n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size,
287                                          data.constData(), data.size(),
288                                          compressLevel);
289             if (n * 100.0 < data.size() * 1.0 * (100 - m_compressThreshold) ) {
290                 // compressing is worth it
291                 if (m_compressLevel < 0) {
292                     // heuristic compression, so recompress
293                     n = ZSTD_compressCCtx(lib.m_zstdCCtx, dst, size,
294                                           data.constData(), data.size(),
295                                           CONSTANT_ZSTDCOMPRESSLEVEL_STORE);
296                 }
297                 if (ZSTD_isError(n)) {
298                     QString msg = QString::fromLatin1("%1: error: compression with zstd failed: %2\n")
299                             .arg(m_name, QString::fromUtf8(ZSTD_getErrorName(n)));
300                     lib.m_errorDevice->write(msg.toUtf8());
301                 } else if (lib.verbose()) {
302                     QString msg = QString::fromLatin1("%1: note: compressed using zstd (%2 -> %3)\n")
303                             .arg(m_name).arg(data.size()).arg(n);
304                     lib.m_errorDevice->write(msg.toUtf8());
305                 }
306 
307                 lib.m_overallFlags |= CompressedZstd;
308                 m_flags |= CompressedZstd;
309                 data = std::move(compressed);
310                 data.truncate(n);
311             } else if (lib.verbose()) {
312                 QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name);
313                 lib.m_errorDevice->write(msg.toUtf8());
314             }
315         }
316 #endif
317 #ifndef QT_NO_COMPRESS
318         if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Best) {
319             m_compressAlgo = RCCResourceLibrary::CompressionAlgorithm::Zlib;
320             m_compressLevel = 9;
321         }
322         if (m_compressAlgo == RCCResourceLibrary::CompressionAlgorithm::Zlib) {
323             QByteArray compressed =
324                     qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel);
325 
326             int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size());
327             if (compressRatio >= m_compressThreshold) {
328                 if (lib.verbose()) {
329                     QString msg = QString::fromLatin1("%1: note: compressed using zlib (%2 -> %3)\n")
330                             .arg(m_name).arg(data.size()).arg(compressed.size());
331                     lib.m_errorDevice->write(msg.toUtf8());
332                 }
333                 data = compressed;
334                 lib.m_overallFlags |= Compressed;
335                 m_flags |= Compressed;
336             } else if (lib.verbose()) {
337                 QString msg = QString::fromLatin1("%1: note: not compressed\n").arg(m_name);
338                 lib.m_errorDevice->write(msg.toUtf8());
339             }
340         }
341 #endif // QT_NO_COMPRESS
342     }
343 
344     // some info
345     if (text || pass1) {
346         lib.writeString("  // ");
347         lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit());
348         lib.writeString("\n  ");
349     }
350 
351     // write the length
352     if (text || binary || pass2 || python)
353         lib.writeNumber4(data.size());
354     if (text || pass1)
355         lib.writeString("\n  ");
356     else if (python)
357         lib.writeString("\\\n");
358     offset += 4;
359 
360     // write the payload
361     const char *p = data.constData();
362     if (text || python) {
363         for (int i = data.size(), j = 0; --i >= 0; --j) {
364             lib.writeHex(*p++);
365             if (j == 0) {
366                 if (text)
367                     lib.writeString("\n  ");
368                 else
369                     lib.writeString("\\\n");
370                 j = 16;
371             }
372         }
373     } else if (binary || pass2) {
374         lib.writeByteArray(data);
375     }
376     offset += data.size();
377 
378     // done
379     if (text || pass1)
380         lib.writeString("\n  ");
381     else if (python)
382         lib.writeString("\\\n");
383 
384     return offset;
385 }
386 
writeDataName(RCCResourceLibrary & lib,qint64 offset)387 qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
388 {
389     const bool text = lib.m_format == RCCResourceLibrary::C_Code;
390     const bool pass1 = lib.m_format == RCCResourceLibrary::Pass1;
391     const bool python = lib.m_format == RCCResourceLibrary::Python3_Code
392         || lib.m_format == RCCResourceLibrary::Python2_Code;
393 
394     // capture the offset
395     m_nameOffset = offset;
396 
397     // some info
398     if (text || pass1) {
399         lib.writeString("  // ");
400         lib.writeByteArray(m_name.toLocal8Bit());
401         lib.writeString("\n  ");
402     }
403 
404     // write the length
405     lib.writeNumber2(m_name.length());
406     if (text || pass1)
407         lib.writeString("\n  ");
408     else if (python)
409         lib.writeString("\\\n");
410     offset += 2;
411 
412     // write the hash
413     lib.writeNumber4(qt_hash(m_name));
414     if (text || pass1)
415         lib.writeString("\n  ");
416     else if (python)
417         lib.writeString("\\\n");
418     offset += 4;
419 
420     // write the m_name
421     const QChar *unicode = m_name.unicode();
422     for (int i = 0; i < m_name.length(); ++i) {
423         lib.writeNumber2(unicode[i].unicode());
424         if ((text || pass1) && i % 16 == 0)
425             lib.writeString("\n  ");
426         else if (python && i % 16 == 0)
427             lib.writeString("\\\n");
428     }
429     offset += m_name.length()*2;
430 
431     // done
432     if (text || pass1)
433         lib.writeString("\n  ");
434     else if (python)
435         lib.writeString("\\\n");
436 
437     return offset;
438 }
439 
440 
441 ///////////////////////////////////////////////////////////
442 //
443 // RCCResourceLibrary
444 //
445 ///////////////////////////////////////////////////////////
446 
Strings()447 RCCResourceLibrary::Strings::Strings() :
448    TAG_RCC(QLatin1String("RCC")),
449    TAG_RESOURCE(QLatin1String("qresource")),
450    TAG_FILE(QLatin1String("file")),
451    ATTRIBUTE_LANG(QLatin1String("lang")),
452    ATTRIBUTE_PREFIX(QLatin1String("prefix")),
453    ATTRIBUTE_ALIAS(QLatin1String("alias")),
454    ATTRIBUTE_THRESHOLD(QLatin1String("threshold")),
455    ATTRIBUTE_COMPRESS(QLatin1String("compress")),
456    ATTRIBUTE_COMPRESSALGO(QStringLiteral("compression-algorithm"))
457 {
458 }
459 
RCCResourceLibrary(quint8 formatVersion)460 RCCResourceLibrary::RCCResourceLibrary(quint8 formatVersion)
461   : m_root(0),
462     m_format(C_Code),
463     m_verbose(false),
464     m_compressionAlgo(CONSTANT_COMPRESSALGO_DEFAULT),
465     m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT),
466     m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT),
467     m_treeOffset(0),
468     m_namesOffset(0),
469     m_dataOffset(0),
470     m_overallFlags(0),
471     m_useNameSpace(CONSTANT_USENAMESPACE),
472     m_errorDevice(0),
473     m_outDevice(0),
474     m_formatVersion(formatVersion)
475 {
476     m_out.reserve(30 * 1000 * 1000);
477 #if QT_CONFIG(zstd)
478     m_zstdCCtx = nullptr;
479 #endif
480 }
481 
~RCCResourceLibrary()482 RCCResourceLibrary::~RCCResourceLibrary()
483 {
484     delete m_root;
485 #if QT_CONFIG(zstd)
486     ZSTD_freeCCtx(m_zstdCCtx);
487 #endif
488 }
489 
490 enum RCCXmlTag {
491     RccTag,
492     ResourceTag,
493     FileTag
494 };
495 Q_DECLARE_TYPEINFO(RCCXmlTag, Q_PRIMITIVE_TYPE);
496 
interpretResourceFile(QIODevice * inputDevice,const QString & fname,QString currentPath,bool listMode)497 bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
498     const QString &fname, QString currentPath, bool listMode)
499 {
500     Q_ASSERT(m_errorDevice);
501     const QChar slash = QLatin1Char('/');
502     if (!currentPath.isEmpty() && !currentPath.endsWith(slash))
503         currentPath += slash;
504 
505     QXmlStreamReader reader(inputDevice);
506     QStack<RCCXmlTag> tokens;
507 
508     QString prefix;
509     QLocale::Language language = QLocale::c().language();
510     QLocale::Country country = QLocale::c().country();
511     QString alias;
512     auto compressAlgo = m_compressionAlgo;
513     int compressLevel = m_compressLevel;
514     int compressThreshold = m_compressThreshold;
515 
516     while (!reader.atEnd()) {
517         QXmlStreamReader::TokenType t = reader.readNext();
518         switch (t) {
519         case QXmlStreamReader::StartElement:
520             if (reader.name() == m_strings.TAG_RCC) {
521                 if (!tokens.isEmpty())
522                     reader.raiseError(QLatin1String("expected <RCC> tag"));
523                 else
524                     tokens.push(RccTag);
525             } else if (reader.name() == m_strings.TAG_RESOURCE) {
526                 if (tokens.isEmpty() || tokens.top() != RccTag) {
527                     reader.raiseError(QLatin1String("unexpected <RESOURCE> tag"));
528                 } else {
529                     tokens.push(ResourceTag);
530 
531                     QXmlStreamAttributes attributes = reader.attributes();
532                     language = QLocale::c().language();
533                     country = QLocale::c().country();
534 
535                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) {
536                         QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString();
537                         QLocale lang = QLocale(attribute);
538                         language = lang.language();
539                         if (2 == attribute.length()) {
540                             // Language only
541                             country = QLocale::AnyCountry;
542                         } else {
543                             country = lang.country();
544                         }
545                     }
546 
547                     prefix.clear();
548                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX))
549                         prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString();
550                     if (!prefix.startsWith(slash))
551                         prefix.prepend(slash);
552                     if (!prefix.endsWith(slash))
553                         prefix += slash;
554                 }
555             } else if (reader.name() == m_strings.TAG_FILE) {
556                 if (tokens.isEmpty() || tokens.top() != ResourceTag) {
557                     reader.raiseError(QLatin1String("unexpected <FILE> tag"));
558                 } else {
559                     tokens.push(FileTag);
560 
561                     QXmlStreamAttributes attributes = reader.attributes();
562                     alias.clear();
563                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS))
564                         alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString();
565 
566                     compressAlgo = m_compressionAlgo;
567                     compressLevel = m_compressLevel;
568                     compressThreshold = m_compressThreshold;
569 
570                     QString errorString;
571                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESSALGO))
572                         compressAlgo = parseCompressionAlgorithm(attributes.value(m_strings.ATTRIBUTE_COMPRESSALGO), &errorString);
573                     if (errorString.isEmpty() && attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS)) {
574                         QString value = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString();
575                         compressLevel = parseCompressionLevel(compressAlgo, value, &errorString);
576                     }
577 
578                     // Special case for -no-compress
579                     if (m_compressLevel == -2)
580                         compressAlgo = CompressionAlgorithm::None;
581 
582                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD))
583                         compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt();
584 
585                     if (!errorString.isEmpty())
586                         reader.raiseError(errorString);
587                 }
588             } else {
589                 reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString()));
590             }
591             break;
592 
593         case QXmlStreamReader::EndElement:
594             if (reader.name() == m_strings.TAG_RCC) {
595                 if (!tokens.isEmpty() && tokens.top() == RccTag)
596                     tokens.pop();
597                 else
598                     reader.raiseError(QLatin1String("unexpected closing tag"));
599             } else if (reader.name() == m_strings.TAG_RESOURCE) {
600                 if (!tokens.isEmpty() && tokens.top() == ResourceTag)
601                     tokens.pop();
602                 else
603                     reader.raiseError(QLatin1String("unexpected closing tag"));
604             } else if (reader.name() == m_strings.TAG_FILE) {
605                 if (!tokens.isEmpty() && tokens.top() == FileTag)
606                     tokens.pop();
607                 else
608                     reader.raiseError(QLatin1String("unexpected closing tag"));
609             }
610             break;
611 
612         case QXmlStreamReader::Characters:
613             if (reader.isWhitespace())
614                 break;
615             if (tokens.isEmpty() || tokens.top() != FileTag) {
616                 reader.raiseError(QLatin1String("unexpected text"));
617             } else {
618                 QString fileName = reader.text().toString();
619                 if (fileName.isEmpty()) {
620                     const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname);
621                     m_errorDevice->write(msg.toUtf8());
622                 }
623 
624                 if (alias.isNull())
625                     alias = fileName;
626 
627                 alias = QDir::cleanPath(alias);
628                 while (alias.startsWith(QLatin1String("../")))
629                     alias.remove(0, 3);
630                 alias = QDir::cleanPath(m_resourceRoot) + prefix + alias;
631 
632                 QString absFileName = fileName;
633                 if (QDir::isRelativePath(absFileName))
634                     absFileName.prepend(currentPath);
635                 QFileInfo file(absFileName);
636                 if (file.isDir()) {
637                     QDir dir(file.filePath());
638                     if (!alias.endsWith(slash))
639                         alias += slash;
640 
641                     QStringList filePaths;
642                     QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories);
643                     while (it.hasNext()) {
644                         it.next();
645                         if (it.fileName() == QLatin1String(".")
646                             || it.fileName() == QLatin1String(".."))
647                             continue;
648                         filePaths.append(it.filePath());
649                     }
650 
651                     // make rcc output deterministic
652                     std::sort(filePaths.begin(), filePaths.end());
653 
654                     for (const QString &filePath : filePaths) {
655                         QFileInfo child(filePath);
656                         const bool arc = addFile(
657                                 alias + child.fileName(),
658                                 RCCFileInfo(child.fileName(), child, language, country,
659                                             child.isDir() ? RCCFileInfo::Directory
660                                                           : RCCFileInfo::NoFlags,
661                                             compressAlgo, compressLevel, compressThreshold));
662                         if (!arc)
663                             m_failedResources.push_back(child.fileName());
664                     }
665                 } else if (listMode || file.isFile()) {
666                     const bool arc =
667                         addFile(alias,
668                                 RCCFileInfo(alias.section(slash, -1),
669                                             file,
670                                             language,
671                                             country,
672                                             RCCFileInfo::NoFlags,
673                                             compressAlgo,
674                                             compressLevel,
675                                             compressThreshold)
676                                 );
677                     if (!arc)
678                         m_failedResources.push_back(absFileName);
679                 } else if (file.exists()) {
680                     m_failedResources.push_back(absFileName);
681                     const QString msg = QString::fromLatin1("RCC: Error in '%1': Entry '%2' is neither a file nor a directory\n")
682                                         .arg(fname, fileName);
683                     m_errorDevice->write(msg.toUtf8());
684                     return false;
685                 } else {
686                     m_failedResources.push_back(absFileName);
687                     const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n")
688                                         .arg(fname, fileName);
689                     m_errorDevice->write(msg.toUtf8());
690                     return false;
691                 }
692             }
693             break;
694 
695         default:
696             break;
697         }
698     }
699 
700     if (reader.hasError()) {
701         int errorLine = reader.lineNumber();
702         int errorColumn = reader.columnNumber();
703         QString errorMessage = reader.errorString();
704         QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage);
705         m_errorDevice->write(msg.toUtf8());
706         return false;
707     }
708 
709     if (m_root == 0) {
710         const QString msg = QString::fromLatin1("RCC: Warning: No resources in '%1'.\n").arg(fname);
711         m_errorDevice->write(msg.toUtf8());
712         if (!listMode && m_format == Binary) {
713             // create dummy entry, otherwise loading with QResource will crash
714             m_root = new RCCFileInfo(QString(), QFileInfo(),
715                     QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
716         }
717     }
718 
719     return true;
720 }
721 
addFile(const QString & alias,const RCCFileInfo & file)722 bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file)
723 {
724     Q_ASSERT(m_errorDevice);
725     if (file.m_fileInfo.size() > 0xffffffff) {
726         const QString msg = QString::fromLatin1("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath());
727         m_errorDevice->write(msg.toUtf8());
728         return false;
729     }
730     if (!m_root)
731         m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
732 
733     RCCFileInfo *parent = m_root;
734     const QStringList nodes = alias.split(QLatin1Char('/'));
735     for (int i = 1; i < nodes.size()-1; ++i) {
736         const QString node = nodes.at(i);
737         if (node.isEmpty())
738             continue;
739         if (!parent->m_children.contains(node)) {
740             RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
741             s->m_parent = parent;
742             parent->m_children.insert(node, s);
743             parent = s;
744         } else {
745             parent = *parent->m_children.constFind(node);
746         }
747     }
748 
749     const QString filename = nodes.at(nodes.size()-1);
750     RCCFileInfo *s = new RCCFileInfo(file);
751     s->m_parent = parent;
752     typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator;
753     const ChildConstIterator cbegin = parent->m_children.constFind(filename);
754     const ChildConstIterator cend = parent->m_children.constEnd();
755     for (ChildConstIterator it = cbegin; it != cend; ++it) {
756         if (it.key() == filename && it.value()->m_language == s->m_language &&
757             it.value()->m_country == s->m_country) {
758             for (const QString &name : qAsConst(m_fileNames)) {
759                 qWarning("%s: Warning: potential duplicate alias detected: '%s'",
760                 qPrintable(name), qPrintable(filename));
761             }
762             break;
763         }
764     }
765     parent->m_children.insert(filename, s);
766     return true;
767 }
768 
reset()769 void RCCResourceLibrary::reset()
770 {
771      if (m_root) {
772         delete m_root;
773         m_root = 0;
774     }
775     m_errorDevice = 0;
776     m_failedResources.clear();
777 }
778 
779 
readFiles(bool listMode,QIODevice & errorDevice)780 bool RCCResourceLibrary::readFiles(bool listMode, QIODevice &errorDevice)
781 {
782     reset();
783     m_errorDevice = &errorDevice;
784     //read in data
785     if (m_verbose) {
786         const QString msg = QString::fromLatin1("Processing %1 files [listMode=%2]\n")
787             .arg(m_fileNames.size()).arg(static_cast<int>(listMode));
788         m_errorDevice->write(msg.toUtf8());
789     }
790     for (int i = 0; i < m_fileNames.size(); ++i) {
791         QFile fileIn;
792         QString fname = m_fileNames.at(i);
793         QString pwd;
794         if (fname == QLatin1String("-")) {
795             fname = QLatin1String("(stdin)");
796             pwd = QDir::currentPath();
797             fileIn.setFileName(fname);
798             if (!fileIn.open(stdin, QIODevice::ReadOnly)) {
799                 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
800                 return false;
801             }
802         } else {
803             pwd = QFileInfo(fname).path();
804             fileIn.setFileName(fname);
805             if (!fileIn.open(QIODevice::ReadOnly)) {
806                 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
807                 return false;
808             }
809         }
810         if (m_verbose) {
811             const QString msg = QString::fromLatin1("Interpreting %1\n").arg(fname);
812             m_errorDevice->write(msg.toUtf8());
813         }
814 
815         if (!interpretResourceFile(&fileIn, fname, pwd, listMode))
816             return false;
817     }
818     return true;
819 }
820 
dataFiles() const821 QStringList RCCResourceLibrary::dataFiles() const
822 {
823     QStringList ret;
824     QStack<RCCFileInfo*> pending;
825 
826     if (!m_root)
827         return ret;
828     pending.push(m_root);
829     while (!pending.isEmpty()) {
830         RCCFileInfo *file = pending.pop();
831         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
832             it != file->m_children.end(); ++it) {
833             RCCFileInfo *child = it.value();
834             if (child->m_flags & RCCFileInfo::Directory)
835                 pending.push(child);
836             else
837                 ret.append(child->m_fileInfo.filePath());
838         }
839     }
840     return ret;
841 }
842 
843 // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion
resourceDataFileMapRecursion(const RCCFileInfo * m_root,const QString & path,RCCResourceLibrary::ResourceDataFileMap & m)844 static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
845 {
846     typedef QHash<QString, RCCFileInfo*>::const_iterator ChildConstIterator;
847     const QChar slash = QLatin1Char('/');
848     const ChildConstIterator cend = m_root->m_children.constEnd();
849     for (ChildConstIterator it = m_root->m_children.constBegin(); it != cend; ++it) {
850         const RCCFileInfo *child = it.value();
851         const QString childName = path + slash + child->m_name;
852         if (child->m_flags & RCCFileInfo::Directory) {
853             resourceDataFileMapRecursion(child, childName, m);
854         } else {
855             m.insert(childName, child->m_fileInfo.filePath());
856         }
857     }
858 }
859 
resourceDataFileMap() const860 RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const
861 {
862     ResourceDataFileMap rc;
863     if (m_root)
864         resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')),  rc);
865     return rc;
866 }
867 
parseCompressionAlgorithm(QStringView value,QString * errorMsg)868 RCCResourceLibrary::CompressionAlgorithm RCCResourceLibrary::parseCompressionAlgorithm(QStringView value, QString *errorMsg)
869 {
870     if (value == QLatin1String("best"))
871         return CompressionAlgorithm::Best;
872     if (value == QLatin1String("zlib")) {
873 #ifdef QT_NO_COMPRESS
874         *errorMsg = QLatin1String("zlib support not compiled in");
875 #else
876         return CompressionAlgorithm::Zlib;
877 #endif
878     } else if (value == QLatin1String("zstd")) {
879 #if QT_CONFIG(zstd)
880         return CompressionAlgorithm::Zstd;
881 #else
882         *errorMsg = QLatin1String("Zstandard support not compiled in");
883 #endif
884     } else if (value != QLatin1String("none")) {
885         *errorMsg = QString::fromLatin1("Unknown compression algorithm '%1'").arg(value);
886     }
887 
888     return CompressionAlgorithm::None;
889 }
890 
parseCompressionLevel(CompressionAlgorithm algo,const QString & level,QString * errorMsg)891 int RCCResourceLibrary::parseCompressionLevel(CompressionAlgorithm algo, const QString &level, QString *errorMsg)
892 {
893     bool ok;
894     int c = level.toInt(&ok);
895     if (ok) {
896         switch (algo) {
897         case CompressionAlgorithm::None:
898         case CompressionAlgorithm::Best:
899             return 0;
900         case CompressionAlgorithm::Zlib:
901             if (c >= 1 && c <= 9)
902                 return c;
903             break;
904         case CompressionAlgorithm::Zstd:
905 #if QT_CONFIG(zstd)
906             if (c >= 0 && c <= ZSTD_maxCLevel())
907                 return c;
908 #endif
909             break;
910         }
911     }
912 
913     *errorMsg = QString::fromLatin1("invalid compression level '%1'").arg(level);
914     return 0;
915 }
916 
output(QIODevice & outDevice,QIODevice & tempDevice,QIODevice & errorDevice)917 bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &tempDevice, QIODevice &errorDevice)
918 {
919     m_errorDevice = &errorDevice;
920 
921     if (m_format == Pass2) {
922         const char pattern[] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };
923         bool foundSignature = false;
924 
925         while (true) {
926             char c;
927             for (int i = 0; i < 8; ) {
928                 if (!tempDevice.getChar(&c)) {
929                     if (foundSignature)
930                         return true;
931                     m_errorDevice->write("No data signature found\n");
932                     return false;
933                 }
934                 if (c == pattern[i]) {
935                     ++i;
936                 } else {
937                     for (int k = 0; k < i; ++k)
938                         outDevice.putChar(pattern[k]);
939                     outDevice.putChar(c);
940                     i = 0;
941                 }
942             }
943 
944             m_outDevice = &outDevice;
945             quint64 start = outDevice.pos();
946             writeDataBlobs();
947             quint64 len = outDevice.pos() - start;
948 
949             tempDevice.seek(tempDevice.pos() + len - 8);
950             foundSignature = true;
951         }
952     }
953 
954     //write out
955     if (m_verbose)
956         m_errorDevice->write("Outputting code\n");
957     if (!writeHeader()) {
958         m_errorDevice->write("Could not write header\n");
959         return false;
960     }
961     if (m_root) {
962         if (!writeDataBlobs()) {
963             m_errorDevice->write("Could not write data blobs.\n");
964             return false;
965         }
966         if (!writeDataNames()) {
967             m_errorDevice->write("Could not write file names\n");
968             return false;
969         }
970         if (!writeDataStructure()) {
971             m_errorDevice->write("Could not write data tree\n");
972             return false;
973         }
974     }
975     if (!writeInitializer()) {
976         m_errorDevice->write("Could not write footer\n");
977         return false;
978     }
979     outDevice.write(m_out.constData(), m_out.size());
980     return true;
981 }
982 
writeDecimal(int value)983 void RCCResourceLibrary::writeDecimal(int value)
984 {
985     Q_ASSERT(m_format != RCCResourceLibrary::Binary);
986     char buf[std::numeric_limits<int>::digits10 + 2];
987     int n = snprintf(buf, sizeof(buf), "%d", value);
988     write(buf, n);
989 }
990 
991 static const char hexDigits[] = "0123456789abcdef";
992 
write2HexDigits(quint8 number)993 inline void RCCResourceLibrary::write2HexDigits(quint8 number)
994 {
995     writeChar(hexDigits[number >> 4]);
996     writeChar(hexDigits[number & 0xf]);
997 }
998 
writeHex(quint8 tmp)999 void RCCResourceLibrary::writeHex(quint8 tmp)
1000 {
1001     switch (m_format) {
1002     case RCCResourceLibrary::Python3_Code:
1003     case RCCResourceLibrary::Python2_Code:
1004         if (tmp >= 32 && tmp < 127 && tmp != '"' && tmp != '\\') {
1005             writeChar(char(tmp));
1006         } else {
1007             writeChar('\\');
1008             writeChar('x');
1009             write2HexDigits(tmp);
1010         }
1011         break;
1012     default:
1013         writeChar('0');
1014         writeChar('x');
1015         if (tmp < 16)
1016             writeChar(hexDigits[tmp]);
1017         else
1018             write2HexDigits(tmp);
1019         writeChar(',');
1020         break;
1021     }
1022 }
1023 
writeNumber2(quint16 number)1024 void RCCResourceLibrary::writeNumber2(quint16 number)
1025 {
1026     if (m_format == RCCResourceLibrary::Binary) {
1027         writeChar(number >> 8);
1028         writeChar(number);
1029     } else {
1030         writeHex(number >> 8);
1031         writeHex(number);
1032     }
1033 }
1034 
writeNumber4(quint32 number)1035 void RCCResourceLibrary::writeNumber4(quint32 number)
1036 {
1037     if (m_format == RCCResourceLibrary::Pass2) {
1038         m_outDevice->putChar(char(number >> 24));
1039         m_outDevice->putChar(char(number >> 16));
1040         m_outDevice->putChar(char(number >> 8));
1041         m_outDevice->putChar(char(number));
1042     } else if (m_format == RCCResourceLibrary::Binary) {
1043         writeChar(number >> 24);
1044         writeChar(number >> 16);
1045         writeChar(number >> 8);
1046         writeChar(number);
1047     } else {
1048         writeHex(number >> 24);
1049         writeHex(number >> 16);
1050         writeHex(number >> 8);
1051         writeHex(number);
1052     }
1053 }
1054 
writeNumber8(quint64 number)1055 void RCCResourceLibrary::writeNumber8(quint64 number)
1056 {
1057     if (m_format == RCCResourceLibrary::Pass2) {
1058         m_outDevice->putChar(char(number >> 56));
1059         m_outDevice->putChar(char(number >> 48));
1060         m_outDevice->putChar(char(number >> 40));
1061         m_outDevice->putChar(char(number >> 32));
1062         m_outDevice->putChar(char(number >> 24));
1063         m_outDevice->putChar(char(number >> 16));
1064         m_outDevice->putChar(char(number >> 8));
1065         m_outDevice->putChar(char(number));
1066     } else if (m_format == RCCResourceLibrary::Binary) {
1067         writeChar(number >> 56);
1068         writeChar(number >> 48);
1069         writeChar(number >> 40);
1070         writeChar(number >> 32);
1071         writeChar(number >> 24);
1072         writeChar(number >> 16);
1073         writeChar(number >> 8);
1074         writeChar(number);
1075     } else {
1076         writeHex(number >> 56);
1077         writeHex(number >> 48);
1078         writeHex(number >> 40);
1079         writeHex(number >> 32);
1080         writeHex(number >> 24);
1081         writeHex(number >> 16);
1082         writeHex(number >> 8);
1083         writeHex(number);
1084     }
1085 }
1086 
writeHeader()1087 bool RCCResourceLibrary::writeHeader()
1088 {
1089     switch (m_format) {
1090     case C_Code:
1091     case Pass1:
1092         writeString("/****************************************************************************\n");
1093         writeString("** Resource object code\n");
1094         writeString("**\n");
1095         writeString("** Created by: The Resource Compiler for Qt version ");
1096         writeByteArray(QT_VERSION_STR);
1097         writeString("\n**\n");
1098         writeString("** WARNING! All changes made in this file will be lost!\n");
1099         writeString( "*****************************************************************************/\n\n");
1100         break;
1101     case Python3_Code:
1102     case Python2_Code:
1103         writeString("# Resource object code (Python ");
1104         writeChar(m_format == Python3_Code ? '3' : '2');
1105         writeString(")\n");
1106         writeString("# Created by: object code\n");
1107         writeString("# Created by: The Resource Compiler for Qt version ");
1108         writeByteArray(QT_VERSION_STR);
1109         writeString("\n");
1110         writeString("# WARNING! All changes made in this file will be lost!\n\n");
1111         writeString("from PySide2 import QtCore\n\n");
1112         break;
1113     case Binary:
1114         writeString("qres");
1115         writeNumber4(0);
1116         writeNumber4(0);
1117         writeNumber4(0);
1118         writeNumber4(0);
1119         if (m_formatVersion >= 3)
1120             writeNumber4(m_overallFlags);
1121         break;
1122     default:
1123         break;
1124     }
1125     return true;
1126 }
1127 
writeDataBlobs()1128 bool RCCResourceLibrary::writeDataBlobs()
1129 {
1130     Q_ASSERT(m_errorDevice);
1131     switch (m_format) {
1132     case C_Code:
1133         writeString("static const unsigned char qt_resource_data[] = {\n");
1134         break;
1135     case Python3_Code:
1136         writeString("qt_resource_data = b\"\\\n");
1137         break;
1138     case Python2_Code:
1139         writeString("qt_resource_data = \"\\\n");
1140         break;
1141     case Binary:
1142         m_dataOffset = m_out.size();
1143         break;
1144     default:
1145         break;
1146     }
1147 
1148     if (!m_root)
1149         return false;
1150 
1151     QStack<RCCFileInfo*> pending;
1152     pending.push(m_root);
1153     qint64 offset = 0;
1154     QString errorMessage;
1155     while (!pending.isEmpty()) {
1156         RCCFileInfo *file = pending.pop();
1157         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
1158             it != file->m_children.end(); ++it) {
1159             RCCFileInfo *child = it.value();
1160             if (child->m_flags & RCCFileInfo::Directory)
1161                 pending.push(child);
1162             else {
1163                 offset = child->writeDataBlob(*this, offset, &errorMessage);
1164                 if (offset == 0) {
1165                     m_errorDevice->write(errorMessage.toUtf8());
1166                     return false;
1167                 }
1168             }
1169         }
1170     }
1171     switch (m_format) {
1172     case C_Code:
1173         writeString("\n};\n\n");
1174         break;
1175     case Python3_Code:
1176     case Python2_Code:
1177         writeString("\"\n\n");
1178         break;
1179     case Pass1:
1180         if (offset < 8)
1181             offset = 8;
1182         writeString("\nstatic const unsigned char qt_resource_data[");
1183         writeByteArray(QByteArray::number(offset));
1184         writeString("] = { 'Q', 'R', 'C', '_', 'D', 'A', 'T', 'A' };\n\n");
1185         break;
1186     default:
1187         break;
1188     }
1189     return true;
1190 }
1191 
writeDataNames()1192 bool RCCResourceLibrary::writeDataNames()
1193 {
1194     switch (m_format) {
1195     case C_Code:
1196     case Pass1:
1197         writeString("static const unsigned char qt_resource_name[] = {\n");
1198         break;
1199     case Python3_Code:
1200         writeString("qt_resource_name = b\"\\\n");
1201         break;
1202     case Python2_Code:
1203         writeString("qt_resource_name = \"\\\n");
1204         break;
1205     case Binary:
1206         m_namesOffset = m_out.size();
1207         break;
1208     default:
1209         break;
1210     }
1211 
1212     QHash<QString, int> names;
1213     QStack<RCCFileInfo*> pending;
1214 
1215     if (!m_root)
1216         return false;
1217 
1218     pending.push(m_root);
1219     qint64 offset = 0;
1220     while (!pending.isEmpty()) {
1221         RCCFileInfo *file = pending.pop();
1222         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
1223             it != file->m_children.end(); ++it) {
1224             RCCFileInfo *child = it.value();
1225             if (child->m_flags & RCCFileInfo::Directory)
1226                 pending.push(child);
1227             if (names.contains(child->m_name)) {
1228                 child->m_nameOffset = names.value(child->m_name);
1229             } else {
1230                 names.insert(child->m_name, offset);
1231                 offset = child->writeDataName(*this, offset);
1232             }
1233         }
1234     }
1235     switch (m_format) {
1236     case C_Code:
1237     case Pass1:
1238         writeString("\n};\n\n");
1239         break;
1240     case Python3_Code:
1241     case Python2_Code:
1242         writeString("\"\n\n");
1243         break;
1244     default:
1245         break;
1246     }
1247     return true;
1248 }
1249 
1250 struct qt_rcc_compare_hash
1251 {
1252     typedef bool result_type;
operator ()qt_rcc_compare_hash1253     result_type operator()(const RCCFileInfo *left, const RCCFileInfo *right) const
1254     {
1255         return qt_hash(left->m_name) < qt_hash(right->m_name);
1256     }
1257 };
1258 
writeDataStructure()1259 bool RCCResourceLibrary::writeDataStructure()
1260 {
1261     switch (m_format) {
1262     case C_Code:
1263     case Pass1:
1264         writeString("static const unsigned char qt_resource_struct[] = {\n");
1265         break;
1266     case Python3_Code:
1267         writeString("qt_resource_struct = b\"\\\n");
1268         break;
1269     case Python2_Code:
1270         writeString("qt_resource_struct = \"\\\n");
1271         break;
1272     case Binary:
1273         m_treeOffset = m_out.size();
1274         break;
1275     default:
1276         break;
1277     }
1278 
1279     QStack<RCCFileInfo*> pending;
1280 
1281     if (!m_root)
1282         return false;
1283 
1284     //calculate the child offsets (flat)
1285     pending.push(m_root);
1286     int offset = 1;
1287     while (!pending.isEmpty()) {
1288         RCCFileInfo *file = pending.pop();
1289         file->m_childOffset = offset;
1290 
1291         //sort by hash value for binary lookup
1292         QList<RCCFileInfo*> m_children = file->m_children.values();
1293         std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash());
1294 
1295         //write out the actual data now
1296         for (int i = 0; i < m_children.size(); ++i) {
1297             RCCFileInfo *child = m_children.at(i);
1298             ++offset;
1299             if (child->m_flags & RCCFileInfo::Directory)
1300                 pending.push(child);
1301         }
1302     }
1303 
1304     //write out the structure (ie iterate again!)
1305     pending.push(m_root);
1306     m_root->writeDataInfo(*this);
1307     while (!pending.isEmpty()) {
1308         RCCFileInfo *file = pending.pop();
1309 
1310         //sort by hash value for binary lookup
1311         QList<RCCFileInfo*> m_children = file->m_children.values();
1312         std::sort(m_children.begin(), m_children.end(), qt_rcc_compare_hash());
1313 
1314         //write out the actual data now
1315         for (int i = 0; i < m_children.size(); ++i) {
1316             RCCFileInfo *child = m_children.at(i);
1317             child->writeDataInfo(*this);
1318             if (child->m_flags & RCCFileInfo::Directory)
1319                 pending.push(child);
1320         }
1321     }
1322     switch (m_format) {
1323     case C_Code:
1324     case Pass1:
1325         writeString("\n};\n\n");
1326         break;
1327     case Python3_Code:
1328     case Python2_Code:
1329         writeString("\"\n\n");
1330         break;
1331     default:
1332         break;
1333     }
1334 
1335     return true;
1336 }
1337 
writeMangleNamespaceFunction(const QByteArray & name)1338 void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name)
1339 {
1340     if (m_useNameSpace) {
1341         writeString("QT_RCC_MANGLE_NAMESPACE(");
1342         writeByteArray(name);
1343         writeChar(')');
1344     } else {
1345         writeByteArray(name);
1346     }
1347 }
1348 
writeAddNamespaceFunction(const QByteArray & name)1349 void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name)
1350 {
1351     if (m_useNameSpace) {
1352         writeString("QT_RCC_PREPEND_NAMESPACE(");
1353         writeByteArray(name);
1354         writeChar(')');
1355     } else {
1356         writeByteArray(name);
1357     }
1358 }
1359 
writeInitializer()1360 bool RCCResourceLibrary::writeInitializer()
1361 {
1362     if (m_format == C_Code || m_format == Pass1) {
1363         //write("\nQT_BEGIN_NAMESPACE\n");
1364         QString initNameStr = m_initName;
1365         if (!initNameStr.isEmpty()) {
1366             initNameStr.prepend(QLatin1Char('_'));
1367             initNameStr.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_"));
1368         }
1369         QByteArray initName = initNameStr.toLatin1();
1370 
1371         //init
1372         if (m_useNameSpace) {
1373             writeString("#ifdef QT_NAMESPACE\n"
1374                         "#  define QT_RCC_PREPEND_NAMESPACE(name) ::QT_NAMESPACE::name\n"
1375                         "#  define QT_RCC_MANGLE_NAMESPACE0(x) x\n"
1376                         "#  define QT_RCC_MANGLE_NAMESPACE1(a, b) a##_##b\n"
1377                         "#  define QT_RCC_MANGLE_NAMESPACE2(a, b) QT_RCC_MANGLE_NAMESPACE1(a,b)\n"
1378                         "#  define QT_RCC_MANGLE_NAMESPACE(name) QT_RCC_MANGLE_NAMESPACE2( \\\n"
1379                         "        QT_RCC_MANGLE_NAMESPACE0(name), QT_RCC_MANGLE_NAMESPACE0(QT_NAMESPACE))\n"
1380                         "#else\n"
1381                         "#   define QT_RCC_PREPEND_NAMESPACE(name) name\n"
1382                         "#   define QT_RCC_MANGLE_NAMESPACE(name) name\n"
1383                         "#endif\n\n");
1384 
1385             writeString("#ifdef QT_NAMESPACE\n"
1386                         "namespace QT_NAMESPACE {\n"
1387                         "#endif\n\n");
1388         }
1389 
1390         if (m_root) {
1391             writeString("bool qRegisterResourceData"
1392                 "(int, const unsigned char *, "
1393                 "const unsigned char *, const unsigned char *);\n");
1394             writeString("bool qUnregisterResourceData"
1395                 "(int, const unsigned char *, "
1396                 "const unsigned char *, const unsigned char *);\n\n");
1397 
1398             if (m_overallFlags & (RCCFileInfo::Compressed | RCCFileInfo::CompressedZstd)) {
1399                 // use variable relocations with ELF and Mach-O
1400                 writeString("#if defined(__ELF__) || defined(__APPLE__)\n");
1401                 if (m_overallFlags & RCCFileInfo::Compressed) {
1402                     writeString("static inline unsigned char qResourceFeatureZlib()\n"
1403                                 "{\n"
1404                                 "    extern const unsigned char qt_resourceFeatureZlib;\n"
1405                                 "    return qt_resourceFeatureZlib;\n"
1406                                 "}\n");
1407                 }
1408                 if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1409                     writeString("static inline unsigned char qResourceFeatureZstd()\n"
1410                                 "{\n"
1411                                 "    extern const unsigned char qt_resourceFeatureZstd;\n"
1412                                 "    return qt_resourceFeatureZstd;\n"
1413                                 "}\n");
1414                 }
1415                 writeString("#else\n");
1416                 if (m_overallFlags & RCCFileInfo::Compressed)
1417                     writeString("unsigned char qResourceFeatureZlib();\n");
1418                 if (m_overallFlags & RCCFileInfo::CompressedZstd)
1419                     writeString("unsigned char qResourceFeatureZstd();\n");
1420                 writeString("#endif\n\n");
1421             }
1422         }
1423 
1424         if (m_useNameSpace)
1425             writeString("#ifdef QT_NAMESPACE\n}\n#endif\n\n");
1426 
1427         QByteArray initResources = "qInitResources";
1428         initResources += initName;
1429 
1430         // Work around -Wmissing-declarations warnings.
1431         writeString("int ");
1432         writeMangleNamespaceFunction(initResources);
1433         writeString("();\n");
1434 
1435         writeString("int ");
1436         writeMangleNamespaceFunction(initResources);
1437         writeString("()\n{\n");
1438 
1439         if (m_root) {
1440             writeString("    int version = ");
1441             writeDecimal(m_formatVersion);
1442             writeString(";\n    ");
1443             writeAddNamespaceFunction("qRegisterResourceData");
1444             writeString("\n        (version, qt_resource_struct, "
1445                         "qt_resource_name, qt_resource_data);\n");
1446         }
1447         writeString("    return 1;\n");
1448         writeString("}\n\n");
1449 
1450         //cleanup
1451         QByteArray cleanResources = "qCleanupResources";
1452         cleanResources += initName;
1453 
1454         // Work around -Wmissing-declarations warnings.
1455         writeString("int ");
1456         writeMangleNamespaceFunction(cleanResources);
1457         writeString("();\n");
1458 
1459         writeString("int ");
1460         writeMangleNamespaceFunction(cleanResources);
1461         writeString("()\n{\n");
1462         if (m_root) {
1463             writeString("    int version = ");
1464             writeDecimal(m_formatVersion);
1465             writeString(";\n    ");
1466 
1467             // ODR-use certain symbols from QtCore if we require optional features
1468             if (m_overallFlags & RCCFileInfo::Compressed) {
1469                 writeString("version += ");
1470                 writeAddNamespaceFunction("qResourceFeatureZlib()");
1471                 writeString(";\n    ");
1472             }
1473             if (m_overallFlags & RCCFileInfo::CompressedZstd) {
1474                 writeString("version += ");
1475                 writeAddNamespaceFunction("qResourceFeatureZstd()");
1476                 writeString(";\n    ");
1477             }
1478 
1479             writeAddNamespaceFunction("qUnregisterResourceData");
1480             writeString("\n       (version, qt_resource_struct, "
1481                       "qt_resource_name, qt_resource_data);\n");
1482         }
1483         writeString("    return 1;\n");
1484         writeString("}\n\n");
1485 
1486 
1487         writeString("namespace {\n"
1488                     "   struct initializer {\n");
1489 
1490         if (m_useNameSpace) {
1491             writeByteArray("       initializer() { QT_RCC_MANGLE_NAMESPACE(" + initResources + ")(); }\n"
1492                            "       ~initializer() { QT_RCC_MANGLE_NAMESPACE(" + cleanResources + ")(); }\n");
1493         } else {
1494             writeByteArray("       initializer() { " + initResources + "(); }\n"
1495                            "       ~initializer() { " + cleanResources + "(); }\n");
1496         }
1497         writeString("   } dummy;\n"
1498                     "}\n");
1499 
1500     } else if (m_format == Binary) {
1501         int i = 4;
1502         char *p = m_out.data();
1503         p[i++] = 0;
1504         p[i++] = 0;
1505         p[i++] = 0;
1506         p[i++] = m_formatVersion;
1507 
1508         p[i++] = (m_treeOffset >> 24) & 0xff;
1509         p[i++] = (m_treeOffset >> 16) & 0xff;
1510         p[i++] = (m_treeOffset >>  8) & 0xff;
1511         p[i++] = (m_treeOffset >>  0) & 0xff;
1512 
1513         p[i++] = (m_dataOffset >> 24) & 0xff;
1514         p[i++] = (m_dataOffset >> 16) & 0xff;
1515         p[i++] = (m_dataOffset >>  8) & 0xff;
1516         p[i++] = (m_dataOffset >>  0) & 0xff;
1517 
1518         p[i++] = (m_namesOffset >> 24) & 0xff;
1519         p[i++] = (m_namesOffset >> 16) & 0xff;
1520         p[i++] = (m_namesOffset >>  8) & 0xff;
1521         p[i++] = (m_namesOffset >>  0) & 0xff;
1522 
1523         if (m_formatVersion >= 3) {
1524             p[i++] = (m_overallFlags >> 24) & 0xff;
1525             p[i++] = (m_overallFlags >> 16) & 0xff;
1526             p[i++] = (m_overallFlags >>  8) & 0xff;
1527             p[i++] = (m_overallFlags >>  0) & 0xff;
1528         }
1529     } else if (m_format == Python3_Code || m_format == Python2_Code) {
1530         writeString("def qInitResources():\n");
1531         writeString("    QtCore.qRegisterResourceData(0x");
1532         write2HexDigits(m_formatVersion);
1533         writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1534         writeString("def qCleanupResources():\n");
1535         writeString("    QtCore.qUnregisterResourceData(0x");
1536         write2HexDigits(m_formatVersion);
1537         writeString(", qt_resource_struct, qt_resource_name, qt_resource_data)\n\n");
1538         writeString("qInitResources()\n");
1539     }
1540     return true;
1541 }
1542 
1543 QT_END_NAMESPACE
1544