1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the Qt Designer of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21 ** included in the packaging of this file. Please review the following
22 ** information to ensure the GNU General Public License requirements will
23 ** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24 **
25 ** $QT_END_LICENSE$
26 **
27 ****************************************************************************/
28 
29 /* Note: This is a copy of qtbase/src/tools/rcc/rcc.cpp. */
30 
31 #include "rcc_p.h"
32 
33 #include <QtCore/qbytearray.h>
34 #include <QtCore/qdatetime.h>
35 #include <QtCore/qdebug.h>
36 #include <QtCore/qdir.h>
37 #include <QtCore/qdiriterator.h>
38 #include <QtCore/qfile.h>
39 #include <QtCore/qiodevice.h>
40 #include <QtCore/qlocale.h>
41 #include <QtCore/qregexp.h>
42 #include <QtCore/qstack.h>
43 #include <QtCore/qxmlstream.h>
44 
45 #include <algorithm>
46 
47 QT_BEGIN_NAMESPACE
48 
49 enum {
50     CONSTANT_USENAMESPACE = 1,
51     CONSTANT_COMPRESSLEVEL_DEFAULT = -1,
52     CONSTANT_COMPRESSTHRESHOLD_DEFAULT = 70
53 };
54 
55 
56 #define writeString(s) write(s, sizeof(s))
57 
write(const char * str,int len)58 void RCCResourceLibrary::write(const char *str, int len)
59 {
60     --len; // trailing \0 on string literals...
61     int n = m_out.size();
62     m_out.resize(n + len);
63     memcpy(m_out.data() + n, str, len);
64 }
65 
writeByteArray(const QByteArray & other)66 void RCCResourceLibrary::writeByteArray(const QByteArray &other)
67 {
68     m_out.append(other);
69 }
70 
msgOpenReadFailed(const QString & fname,const QString & why)71 static inline QString msgOpenReadFailed(const QString &fname, const QString &why)
72 {
73     return QString::fromUtf8("Unable to open %1 for reading: %2\n").arg(fname).arg(why);
74 }
75 
76 
77 ///////////////////////////////////////////////////////////
78 //
79 // RCCFileInfo
80 //
81 ///////////////////////////////////////////////////////////
82 
83 class RCCFileInfo
84 {
85 public:
86     enum Flags
87     {
88         NoFlags = 0x00,
89         Compressed = 0x01,
90         Directory = 0x02
91     };
92 
93     RCCFileInfo(const QString &name = QString(), const QFileInfo &fileInfo = QFileInfo(),
94                 QLocale::Language language = QLocale::C,
95                 QLocale::Country country = QLocale::AnyCountry,
96                 uint flags = NoFlags,
97                 int compressLevel = CONSTANT_COMPRESSLEVEL_DEFAULT,
98                 int compressThreshold = CONSTANT_COMPRESSTHRESHOLD_DEFAULT);
99     ~RCCFileInfo();
100 
101     QString resourceName() const;
102 
103 public:
104     qint64 writeDataBlob(RCCResourceLibrary &lib, qint64 offset, QString *errorMessage);
105     qint64 writeDataName(RCCResourceLibrary &, qint64 offset);
106     void writeDataInfo(RCCResourceLibrary &lib);
107 
108     int m_flags;
109     QString m_name;
110     QLocale::Language m_language;
111     QLocale::Country m_country;
112     QFileInfo m_fileInfo;
113     RCCFileInfo *m_parent;
114     QHash<QString, RCCFileInfo*> m_children;
115     int m_compressLevel;
116     int m_compressThreshold;
117 
118     qint64 m_nameOffset;
119     qint64 m_dataOffset;
120     qint64 m_childOffset;
121 };
122 
RCCFileInfo(const QString & name,const QFileInfo & fileInfo,QLocale::Language language,QLocale::Country country,uint flags,int compressLevel,int compressThreshold)123 RCCFileInfo::RCCFileInfo(const QString &name, const QFileInfo &fileInfo,
124     QLocale::Language language, QLocale::Country country, uint flags,
125     int compressLevel, int compressThreshold)
126 {
127     m_name = name;
128     m_fileInfo = fileInfo;
129     m_language = language;
130     m_country = country;
131     m_flags = flags;
132     m_parent = nullptr;
133     m_nameOffset = 0;
134     m_dataOffset = 0;
135     m_childOffset = 0;
136     m_compressLevel = compressLevel;
137     m_compressThreshold = compressThreshold;
138 }
139 
~RCCFileInfo()140 RCCFileInfo::~RCCFileInfo()
141 {
142     qDeleteAll(m_children);
143 }
144 
resourceName() const145 QString RCCFileInfo::resourceName() const
146 {
147     QString resource = m_name;
148     for (RCCFileInfo *p = m_parent; p; p = p->m_parent)
149         resource = resource.prepend(p->m_name + QLatin1Char('/'));
150     return QLatin1Char(':') + resource;
151 }
152 
writeDataInfo(RCCResourceLibrary & lib)153 void RCCFileInfo::writeDataInfo(RCCResourceLibrary &lib)
154 {
155     const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
156     //some info
157     if (text) {
158         if (m_language != QLocale::C) {
159             lib.writeString("  // ");
160             lib.writeByteArray(resourceName().toLocal8Bit());
161             lib.writeString(" [");
162             lib.writeByteArray(QByteArray::number(m_country));
163             lib.writeString("::");
164             lib.writeByteArray(QByteArray::number(m_language));
165             lib.writeString("[\n  ");
166         } else {
167             lib.writeString("  // ");
168             lib.writeByteArray(resourceName().toLocal8Bit());
169             lib.writeString("\n  ");
170         }
171     }
172 
173     //pointer data
174     if (m_flags & RCCFileInfo::Directory) {
175         // name offset
176         lib.writeNumber4(m_nameOffset);
177 
178         // flags
179         lib.writeNumber2(m_flags);
180 
181         // child count
182         lib.writeNumber4(m_children.size());
183 
184         // first child offset
185         lib.writeNumber4(m_childOffset);
186     } else {
187         // name offset
188         lib.writeNumber4(m_nameOffset);
189 
190         // flags
191         lib.writeNumber2(m_flags);
192 
193         // locale
194         lib.writeNumber2(m_country);
195         lib.writeNumber2(m_language);
196 
197         //data offset
198         lib.writeNumber4(m_dataOffset);
199     }
200     if (text)
201         lib.writeChar('\n');
202 }
203 
writeDataBlob(RCCResourceLibrary & lib,qint64 offset,QString * errorMessage)204 qint64 RCCFileInfo::writeDataBlob(RCCResourceLibrary &lib, qint64 offset,
205     QString *errorMessage)
206 {
207     const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
208 
209     //capture the offset
210     m_dataOffset = offset;
211 
212     //find the data to be written
213     QFile file(m_fileInfo.absoluteFilePath());
214     if (!file.open(QFile::ReadOnly)) {
215         *errorMessage = msgOpenReadFailed(m_fileInfo.absoluteFilePath(), file.errorString());
216         return 0;
217     }
218     QByteArray data = file.readAll();
219 
220 #ifndef QT_NO_COMPRESS
221     // Check if compression is useful for this file
222     if (m_compressLevel != 0 && data.size() != 0) {
223         QByteArray compressed =
224             qCompress(reinterpret_cast<uchar *>(data.data()), data.size(), m_compressLevel);
225 
226         int compressRatio = int(100.0 * (data.size() - compressed.size()) / data.size());
227         if (compressRatio >= m_compressThreshold) {
228             data = compressed;
229             m_flags |= Compressed;
230         }
231     }
232 #endif // QT_NO_COMPRESS
233 
234     // some info
235     if (text) {
236         lib.writeString("  // ");
237         lib.writeByteArray(m_fileInfo.absoluteFilePath().toLocal8Bit());
238         lib.writeString("\n  ");
239     }
240 
241     // write the length
242 
243     lib.writeNumber4(data.size());
244     if (text)
245         lib.writeString("\n  ");
246     offset += 4;
247 
248     // write the payload
249     const char *p = data.constData();
250     if (text) {
251         for (int i = data.size(), j = 0; --i >= 0; --j) {
252             lib.writeHex(*p++);
253             if (j == 0) {
254                 lib.writeString("\n  ");
255                 j = 16;
256             }
257         }
258     } else {
259         for (int i = data.size(); --i >= 0; )
260            lib.writeChar(*p++);
261     }
262     offset += data.size();
263 
264     // done
265     if (text)
266         lib.writeString("\n  ");
267     return offset;
268 }
269 
writeDataName(RCCResourceLibrary & lib,qint64 offset)270 qint64 RCCFileInfo::writeDataName(RCCResourceLibrary &lib, qint64 offset)
271 {
272     const bool text = (lib.m_format == RCCResourceLibrary::C_Code);
273 
274     // capture the offset
275     m_nameOffset = offset;
276 
277     // some info
278     if (text) {
279         lib.writeString("  // ");
280         lib.writeByteArray(m_name.toLocal8Bit());
281         lib.writeString("\n  ");
282     }
283 
284     // write the length
285     lib.writeNumber2(m_name.length());
286     if (text)
287         lib.writeString("\n  ");
288     offset += 2;
289 
290     // write the hash
291     lib.writeNumber4(qt_hash(m_name));
292     if (text)
293         lib.writeString("\n  ");
294     offset += 4;
295 
296     // write the m_name
297     const QChar *unicode = m_name.unicode();
298     for (int i = 0; i < m_name.length(); ++i) {
299         lib.writeNumber2(unicode[i].unicode());
300         if (text && i % 16 == 0)
301             lib.writeString("\n  ");
302     }
303     offset += m_name.length()*2;
304 
305     // done
306     if (text)
307         lib.writeString("\n  ");
308     return offset;
309 }
310 
311 
312 ///////////////////////////////////////////////////////////
313 //
314 // RCCResourceLibrary
315 //
316 ///////////////////////////////////////////////////////////
317 
Strings()318 RCCResourceLibrary::Strings::Strings() :
319    TAG_RCC(QLatin1String("RCC")),
320    TAG_RESOURCE(QLatin1String("qresource")),
321    TAG_FILE(QLatin1String("file")),
322    ATTRIBUTE_LANG(QLatin1String("lang")),
323    ATTRIBUTE_PREFIX(QLatin1String("prefix")),
324    ATTRIBUTE_ALIAS(QLatin1String("alias")),
325    ATTRIBUTE_THRESHOLD(QLatin1String("threshold")),
326    ATTRIBUTE_COMPRESS(QLatin1String("compress"))
327 {
328 }
329 
RCCResourceLibrary()330 RCCResourceLibrary::RCCResourceLibrary()
331   : m_root(nullptr),
332     m_format(C_Code),
333     m_verbose(false),
334     m_compressLevel(CONSTANT_COMPRESSLEVEL_DEFAULT),
335     m_compressThreshold(CONSTANT_COMPRESSTHRESHOLD_DEFAULT),
336     m_treeOffset(0),
337     m_namesOffset(0),
338     m_dataOffset(0),
339     m_useNameSpace(CONSTANT_USENAMESPACE),
340     m_errorDevice(0)
341 {
342     m_out.reserve(30 * 1000 * 1000);
343 }
344 
~RCCResourceLibrary()345 RCCResourceLibrary::~RCCResourceLibrary()
346 {
347     delete m_root;
348 }
349 
350 enum RCCXmlTag {
351     RccTag,
352     ResourceTag,
353     FileTag
354 };
355 
interpretResourceFile(QIODevice * inputDevice,const QString & fname,QString currentPath,bool ignoreErrors)356 bool RCCResourceLibrary::interpretResourceFile(QIODevice *inputDevice,
357     const QString &fname, QString currentPath, bool ignoreErrors)
358 {
359     Q_ASSERT(m_errorDevice);
360     const QChar slash = QLatin1Char('/');
361     if (!currentPath.isEmpty() && !currentPath.endsWith(slash))
362         currentPath += slash;
363 
364     QXmlStreamReader reader(inputDevice);
365     QStack<RCCXmlTag> tokens;
366 
367     QString prefix;
368     QLocale::Language language = QLocale::c().language();
369     QLocale::Country country = QLocale::c().country();
370     QString alias;
371     int compressLevel = m_compressLevel;
372     int compressThreshold = m_compressThreshold;
373 
374     while (!reader.atEnd()) {
375         QXmlStreamReader::TokenType t = reader.readNext();
376         switch (t) {
377         case QXmlStreamReader::StartElement:
378             if (reader.name() == m_strings.TAG_RCC) {
379                 if (!tokens.isEmpty())
380                     reader.raiseError(QLatin1String("expected <RCC> tag"));
381                 else
382                     tokens.push(RccTag);
383             } else if (reader.name() == m_strings.TAG_RESOURCE) {
384                 if (tokens.isEmpty() || tokens.top() != RccTag) {
385                     reader.raiseError(QLatin1String("unexpected <RESOURCE> tag"));
386                 } else {
387                     tokens.push(ResourceTag);
388 
389                     QXmlStreamAttributes attributes = reader.attributes();
390                     language = QLocale::c().language();
391                     country = QLocale::c().country();
392 
393                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_LANG)) {
394                         QString attribute = attributes.value(m_strings.ATTRIBUTE_LANG).toString();
395                         QLocale lang = QLocale(attribute);
396                         language = lang.language();
397                         if (2 == attribute.length()) {
398                             // Language only
399                             country = QLocale::AnyCountry;
400                         } else {
401                             country = lang.country();
402                         }
403                     }
404 
405                     prefix.clear();
406                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_PREFIX))
407                         prefix = attributes.value(m_strings.ATTRIBUTE_PREFIX).toString();
408                     if (!prefix.startsWith(slash))
409                         prefix.prepend(slash);
410                     if (!prefix.endsWith(slash))
411                         prefix += slash;
412                 }
413             } else if (reader.name() == m_strings.TAG_FILE) {
414                 if (tokens.isEmpty() || tokens.top() != ResourceTag) {
415                     reader.raiseError(QLatin1String("unexpected <FILE> tag"));
416                 } else {
417                     tokens.push(FileTag);
418 
419                     QXmlStreamAttributes attributes = reader.attributes();
420                     alias.clear();
421                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_ALIAS))
422                         alias = attributes.value(m_strings.ATTRIBUTE_ALIAS).toString();
423 
424                     compressLevel = m_compressLevel;
425                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_COMPRESS))
426                         compressLevel = attributes.value(m_strings.ATTRIBUTE_COMPRESS).toString().toInt();
427 
428                     compressThreshold = m_compressThreshold;
429                     if (attributes.hasAttribute(m_strings.ATTRIBUTE_THRESHOLD))
430                         compressThreshold = attributes.value(m_strings.ATTRIBUTE_THRESHOLD).toString().toInt();
431 
432                     // Special case for -no-compress. Overrides all other settings.
433                     if (m_compressLevel == -2)
434                         compressLevel = 0;
435                 }
436             } else {
437                 reader.raiseError(QString(QLatin1String("unexpected tag: %1")).arg(reader.name().toString()));
438             }
439             break;
440 
441         case QXmlStreamReader::EndElement:
442             if (reader.name() == m_strings.TAG_RCC) {
443                 if (!tokens.isEmpty() && tokens.top() == RccTag)
444                     tokens.pop();
445                 else
446                     reader.raiseError(QLatin1String("unexpected closing tag"));
447             } else if (reader.name() == m_strings.TAG_RESOURCE) {
448                 if (!tokens.isEmpty() && tokens.top() == ResourceTag)
449                     tokens.pop();
450                 else
451                     reader.raiseError(QLatin1String("unexpected closing tag"));
452             } else if (reader.name() == m_strings.TAG_FILE) {
453                 if (!tokens.isEmpty() && tokens.top() == FileTag)
454                     tokens.pop();
455                 else
456                     reader.raiseError(QLatin1String("unexpected closing tag"));
457             }
458             break;
459 
460         case QXmlStreamReader::Characters:
461             if (reader.isWhitespace())
462                 break;
463             if (tokens.isEmpty() || tokens.top() != FileTag) {
464                 reader.raiseError(QLatin1String("unexpected text"));
465             } else {
466                 QString fileName = reader.text().toString();
467                 if (fileName.isEmpty()) {
468                     const QString msg = QString::fromLatin1("RCC: Warning: Null node in XML of '%1'\n").arg(fname);
469                     m_errorDevice->write(msg.toUtf8());
470                 }
471 
472                 if (alias.isNull())
473                     alias = fileName;
474 
475                 alias = QDir::cleanPath(alias);
476                 while (alias.startsWith(QLatin1String("../")))
477                     alias.remove(0, 3);
478                 alias = QDir::cleanPath(m_resourceRoot) + prefix + alias;
479 
480                 QString absFileName = fileName;
481                 if (QDir::isRelativePath(absFileName))
482                     absFileName.prepend(currentPath);
483                 QFileInfo file(absFileName);
484                 if (!file.exists()) {
485                     m_failedResources.push_back(absFileName);
486                     const QString msg = QString::fromLatin1("RCC: Error in '%1': Cannot find file '%2'\n").arg(fname).arg(fileName);
487                     m_errorDevice->write(msg.toUtf8());
488                     if (ignoreErrors)
489                         continue;
490                     else
491                         return false;
492                 } else if (file.isFile()) {
493                     const bool arc =
494                         addFile(alias,
495                                 RCCFileInfo(alias.section(slash, -1),
496                                             file,
497                                             language,
498                                             country,
499                                             RCCFileInfo::NoFlags,
500                                             compressLevel,
501                                             compressThreshold)
502                                 );
503                     if (!arc)
504                         m_failedResources.push_back(absFileName);
505                 } else {
506                     QDir dir;
507                     if (file.isDir()) {
508                         dir.setPath(file.filePath());
509                     } else {
510                         dir.setPath(file.path());
511                         dir.setNameFilters(QStringList(file.fileName()));
512                         if (alias.endsWith(file.fileName()))
513                             alias = alias.left(alias.length()-file.fileName().length());
514                     }
515                     if (!alias.endsWith(slash))
516                         alias += slash;
517                     QDirIterator it(dir, QDirIterator::FollowSymlinks|QDirIterator::Subdirectories);
518                     while (it.hasNext()) {
519                         it.next();
520                         QFileInfo child(it.fileInfo());
521                         if (child.fileName() != QLatin1String(".") && child.fileName() != QLatin1String("..")) {
522                             const bool arc =
523                                 addFile(alias + child.fileName(),
524                                         RCCFileInfo(child.fileName(),
525                                                     child,
526                                                     language,
527                                                     country,
528                                                     RCCFileInfo::NoFlags,
529                                                     compressLevel,
530                                                     compressThreshold)
531                                         );
532                             if (!arc)
533                                 m_failedResources.push_back(child.fileName());
534                         }
535                     }
536                 }
537             }
538             break;
539 
540         default:
541             break;
542         }
543     }
544 
545     if (reader.hasError()) {
546         if (ignoreErrors)
547             return true;
548         int errorLine = reader.lineNumber();
549         int errorColumn = reader.columnNumber();
550         QString errorMessage = reader.errorString();
551         QString msg = QString::fromLatin1("RCC Parse Error: '%1' Line: %2 Column: %3 [%4]\n").arg(fname).arg(errorLine).arg(errorColumn).arg(errorMessage);
552         m_errorDevice->write(msg.toUtf8());
553         return false;
554     }
555 
556     if (m_root == nullptr) {
557         const QString msg = QString::fromUtf8("RCC: Warning: No resources in '%1'.\n").arg(fname);
558         m_errorDevice->write(msg.toUtf8());
559         if (!ignoreErrors && m_format == Binary) {
560             // create dummy entry, otherwise loading with QResource will crash
561             m_root = new RCCFileInfo(QString(), QFileInfo(),
562                     QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
563         }
564     }
565 
566     return true;
567 }
568 
addFile(const QString & alias,const RCCFileInfo & file)569 bool RCCResourceLibrary::addFile(const QString &alias, const RCCFileInfo &file)
570 {
571     Q_ASSERT(m_errorDevice);
572     if (file.m_fileInfo.size() > 0xffffffff) {
573         const QString msg = QString::fromUtf8("File too big: %1\n").arg(file.m_fileInfo.absoluteFilePath());
574         m_errorDevice->write(msg.toUtf8());
575         return false;
576     }
577     if (!m_root)
578         m_root = new RCCFileInfo(QString(), QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
579 
580     RCCFileInfo *parent = m_root;
581     const QStringList nodes = alias.split(QLatin1Char('/'));
582     for (int i = 1; i < nodes.size()-1; ++i) {
583         const QString node = nodes.at(i);
584         if (node.isEmpty())
585             continue;
586         if (!parent->m_children.contains(node)) {
587             RCCFileInfo *s = new RCCFileInfo(node, QFileInfo(), QLocale::C, QLocale::AnyCountry, RCCFileInfo::Directory);
588             s->m_parent = parent;
589             parent->m_children.insert(node, s);
590             parent = s;
591         } else {
592             parent = *parent->m_children.constFind(node);
593         }
594     }
595 
596     const QString filename = nodes.at(nodes.size()-1);
597     RCCFileInfo *s = new RCCFileInfo(file);
598     s->m_parent = parent;
599     if (parent->m_children.contains(filename)) {
600         for (const QString &fileName : qAsConst(m_fileNames)) {
601             qWarning("%s: Warning: potential duplicate alias detected: '%s'",
602                      qPrintable(fileName), qPrintable(filename));
603         }
604     }
605     parent->m_children.insert(filename, s);
606     return true;
607 }
608 
reset()609 void RCCResourceLibrary::reset()
610 {
611      if (m_root) {
612         delete m_root;
613         m_root = nullptr;
614     }
615     m_errorDevice = nullptr;
616     m_failedResources.clear();
617 }
618 
619 
readFiles(bool ignoreErrors,QIODevice & errorDevice)620 bool RCCResourceLibrary::readFiles(bool ignoreErrors, QIODevice &errorDevice)
621 {
622     reset();
623     m_errorDevice = &errorDevice;
624     //read in data
625     if (m_verbose) {
626         const QString msg = QString::fromUtf8("Processing %1 files [%2]\n")
627             .arg(m_fileNames.size()).arg(static_cast<int>(ignoreErrors));
628         m_errorDevice->write(msg.toUtf8());
629     }
630     for (int i = 0; i < m_fileNames.size(); ++i) {
631         QFile fileIn;
632         QString fname = m_fileNames.at(i);
633         QString pwd;
634         if (fname == QLatin1String("-")) {
635             fname = QLatin1String("(stdin)");
636             pwd = QDir::currentPath();
637             fileIn.setFileName(fname);
638             if (!fileIn.open(stdin, QIODevice::ReadOnly)) {
639                 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
640                 return false;
641             }
642         } else {
643             pwd = QFileInfo(fname).path();
644             fileIn.setFileName(fname);
645             if (!fileIn.open(QIODevice::ReadOnly)) {
646                 m_errorDevice->write(msgOpenReadFailed(fname, fileIn.errorString()).toUtf8());
647                 return false;
648             }
649         }
650         if (m_verbose) {
651             const QString msg = QString::fromUtf8("Interpreting %1\n").arg(fname);
652             m_errorDevice->write(msg.toUtf8());
653         }
654 
655         if (!interpretResourceFile(&fileIn, fname, pwd, ignoreErrors))
656             return false;
657     }
658     return true;
659 }
660 
dataFiles() const661 QStringList RCCResourceLibrary::dataFiles() const
662 {
663     QStringList ret;
664     QStack<RCCFileInfo*> pending;
665 
666     if (!m_root)
667         return ret;
668     pending.push(m_root);
669     while (!pending.isEmpty()) {
670         RCCFileInfo *file = pending.pop();
671         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
672             it != file->m_children.end(); ++it) {
673             RCCFileInfo *child = it.value();
674             if (child->m_flags & RCCFileInfo::Directory)
675                 pending.push(child);
676             ret.append(child->m_fileInfo.filePath());
677         }
678     }
679     return ret;
680 }
681 
682 // Determine map of resource identifier (':/newPrefix/images/p1.png') to file via recursion
resourceDataFileMapRecursion(const RCCFileInfo * m_root,const QString & path,RCCResourceLibrary::ResourceDataFileMap & m)683 static void resourceDataFileMapRecursion(const RCCFileInfo *m_root, const QString &path, RCCResourceLibrary::ResourceDataFileMap &m)
684 {
685     const QChar slash = QLatin1Char('/');
686     for (auto  it = m_root->m_children.constBegin(), cend = m_root->m_children.constEnd(); it != cend; ++it) {
687         const RCCFileInfo *child = it.value();
688         QString childName = path;
689         childName += slash;
690         childName += child->m_name;
691         if (child->m_flags & RCCFileInfo::Directory) {
692             resourceDataFileMapRecursion(child, childName, m);
693         } else {
694             m.insert(childName, child->m_fileInfo.filePath());
695         }
696     }
697 }
698 
resourceDataFileMap() const699 RCCResourceLibrary::ResourceDataFileMap RCCResourceLibrary::resourceDataFileMap() const
700 {
701     ResourceDataFileMap rc;
702     if (m_root)
703         resourceDataFileMapRecursion(m_root, QString(QLatin1Char(':')),  rc);
704     return rc;
705 }
706 
output(QIODevice & outDevice,QIODevice & errorDevice)707 bool RCCResourceLibrary::output(QIODevice &outDevice, QIODevice &errorDevice)
708 {
709     m_errorDevice = &errorDevice;
710     //write out
711     if (m_verbose)
712         m_errorDevice->write("Outputting code\n");
713     if (!writeHeader()) {
714         m_errorDevice->write("Could not write header\n");
715         return false;
716     }
717     if (m_root) {
718         if (!writeDataBlobs()) {
719             m_errorDevice->write("Could not write data blobs.\n");
720             return false;
721         }
722         if (!writeDataNames()) {
723             m_errorDevice->write("Could not write file names\n");
724             return false;
725         }
726         if (!writeDataStructure()) {
727             m_errorDevice->write("Could not write data tree\n");
728             return false;
729         }
730     }
731     if (!writeInitializer()) {
732         m_errorDevice->write("Could not write footer\n");
733         return false;
734     }
735     outDevice.write(m_out.constData(), m_out.size());
736     return true;
737 }
738 
writeHex(quint8 tmp)739 void RCCResourceLibrary::writeHex(quint8 tmp)
740 {
741     const char digits[] = "0123456789abcdef";
742     writeChar('0');
743     writeChar('x');
744     if (tmp < 16) {
745         writeChar(digits[tmp]);
746     } else {
747         writeChar(digits[tmp >> 4]);
748         writeChar(digits[tmp & 0xf]);
749     }
750     writeChar(',');
751 }
752 
writeNumber2(quint16 number)753 void RCCResourceLibrary::writeNumber2(quint16 number)
754 {
755     if (m_format == RCCResourceLibrary::Binary) {
756         writeChar(number >> 8);
757         writeChar(number);
758     } else {
759         writeHex(number >> 8);
760         writeHex(number);
761     }
762 }
763 
writeNumber4(quint32 number)764 void RCCResourceLibrary::writeNumber4(quint32 number)
765 {
766     if (m_format == RCCResourceLibrary::Binary) {
767         writeChar(number >> 24);
768         writeChar(number >> 16);
769         writeChar(number >> 8);
770         writeChar(number);
771     } else {
772         writeHex(number >> 24);
773         writeHex(number >> 16);
774         writeHex(number >> 8);
775         writeHex(number);
776     }
777 }
778 
writeHeader()779 bool RCCResourceLibrary::writeHeader()
780 {
781     if (m_format == C_Code) {
782         writeString("/****************************************************************************\n");
783         writeString("** Resource object code\n");
784         writeString("**\n");
785         writeString("** Created: ");
786         writeByteArray(QDateTime::currentDateTime().toString().toLatin1());
787         writeString("\n**      by: The Resource Compiler for Qt version ");
788         writeByteArray(QT_VERSION_STR);
789         writeString("\n**\n");
790         writeString("** WARNING! All changes made in this file will be lost!\n");
791         writeString( "*****************************************************************************/\n\n");
792         writeString("#include <QtCore/qglobal.h>\n\n");
793     } else if (m_format == Binary) {
794         writeString("qres");
795         writeNumber4(0);
796         writeNumber4(0);
797         writeNumber4(0);
798         writeNumber4(0);
799     }
800     return true;
801 }
802 
writeDataBlobs()803 bool RCCResourceLibrary::writeDataBlobs()
804 {
805     Q_ASSERT(m_errorDevice);
806     if (m_format == C_Code)
807         writeString("static const unsigned char qt_resource_data[] = {\n");
808     else if (m_format == Binary)
809         m_dataOffset = m_out.size();
810     QStack<RCCFileInfo*> pending;
811 
812     if (!m_root)
813         return false;
814 
815     pending.push(m_root);
816     qint64 offset = 0;
817     QString errorMessage;
818     while (!pending.isEmpty()) {
819         RCCFileInfo *file = pending.pop();
820         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
821             it != file->m_children.end(); ++it) {
822             RCCFileInfo *child = it.value();
823             if (child->m_flags & RCCFileInfo::Directory)
824                 pending.push(child);
825             else {
826                 offset = child->writeDataBlob(*this, offset, &errorMessage);
827                 if (offset == 0) {
828                     m_errorDevice->write(errorMessage.toUtf8());
829                     return false;
830                 }
831             }
832         }
833     }
834     if (m_format == C_Code)
835         writeString("\n};\n\n");
836     return true;
837 }
838 
writeDataNames()839 bool RCCResourceLibrary::writeDataNames()
840 {
841     if (m_format == C_Code)
842         writeString("static const unsigned char qt_resource_name[] = {\n");
843     else if (m_format == Binary)
844         m_namesOffset = m_out.size();
845 
846     QHash<QString, int> names;
847     QStack<RCCFileInfo*> pending;
848 
849     if (!m_root)
850         return false;
851 
852     pending.push(m_root);
853     qint64 offset = 0;
854     while (!pending.isEmpty()) {
855         RCCFileInfo *file = pending.pop();
856         for (QHash<QString, RCCFileInfo*>::iterator it = file->m_children.begin();
857             it != file->m_children.end(); ++it) {
858             RCCFileInfo *child = it.value();
859             if (child->m_flags & RCCFileInfo::Directory)
860                 pending.push(child);
861             if (names.contains(child->m_name)) {
862                 child->m_nameOffset = names.value(child->m_name);
863             } else {
864                 names.insert(child->m_name, offset);
865                 offset = child->writeDataName(*this, offset);
866             }
867         }
868     }
869     if (m_format == C_Code)
870         writeString("\n};\n\n");
871     return true;
872 }
873 
qt_rcc_compare_hash(const RCCFileInfo * left,const RCCFileInfo * right)874 static bool qt_rcc_compare_hash(const RCCFileInfo *left, const RCCFileInfo *right)
875 {
876     return qt_hash(left->m_name) < qt_hash(right->m_name);
877 }
878 
writeDataStructure()879 bool RCCResourceLibrary::writeDataStructure()
880 {
881     if (m_format == C_Code)
882         writeString("static const unsigned char qt_resource_struct[] = {\n");
883     else if (m_format == Binary)
884         m_treeOffset = m_out.size();
885     QStack<RCCFileInfo*> pending;
886 
887     if (!m_root)
888         return false;
889 
890     //calculate the child offsets (flat)
891     pending.push(m_root);
892     int offset = 1;
893     while (!pending.isEmpty()) {
894         RCCFileInfo *file = pending.pop();
895         file->m_childOffset = offset;
896 
897         //sort by hash value for binary lookup
898         auto children = file->m_children.values();
899         std::sort(children.begin(), children.end(), qt_rcc_compare_hash);
900 
901         //write out the actual data now
902         for (RCCFileInfo *child : children) {
903             ++offset;
904             if (child->m_flags & RCCFileInfo::Directory)
905                 pending.push(child);
906         }
907     }
908 
909     //write out the structure (ie iterate again!)
910     pending.push(m_root);
911     m_root->writeDataInfo(*this);
912     while (!pending.isEmpty()) {
913         RCCFileInfo *file = pending.pop();
914 
915         //sort by hash value for binary lookup
916         auto children = file->m_children.values();
917         std::sort(children.begin(), children.end(), qt_rcc_compare_hash);
918 
919         //write out the actual data now
920         for (RCCFileInfo *child : children) {
921             child->writeDataInfo(*this);
922             if (child->m_flags & RCCFileInfo::Directory)
923                 pending.push(child);
924         }
925     }
926     if (m_format == C_Code)
927         writeString("\n};\n\n");
928 
929     return true;
930 }
931 
writeMangleNamespaceFunction(const QByteArray & name)932 void RCCResourceLibrary::writeMangleNamespaceFunction(const QByteArray &name)
933 {
934     if (m_useNameSpace) {
935         writeString("QT_MANGLE_NAMESPACE(");
936         writeByteArray(name);
937         writeChar(')');
938     } else {
939         writeByteArray(name);
940     }
941 }
942 
writeAddNamespaceFunction(const QByteArray & name)943 void RCCResourceLibrary::writeAddNamespaceFunction(const QByteArray &name)
944 {
945     if (m_useNameSpace) {
946         writeString("QT_PREPEND_NAMESPACE(");
947         writeByteArray(name);
948         writeChar(')');
949     } else {
950         writeByteArray(name);
951     }
952 }
953 
writeInitializer()954 bool RCCResourceLibrary::writeInitializer()
955 {
956     if (m_format == C_Code) {
957         //write("\nQT_BEGIN_NAMESPACE\n");
958         QString initName = m_initName;
959         if (!initName.isEmpty()) {
960             initName.prepend(QLatin1Char('_'));
961             initName.replace(QRegExp(QLatin1String("[^a-zA-Z0-9_]")), QLatin1String("_"));
962         }
963 
964         //init
965         if (m_useNameSpace)
966             writeString("QT_BEGIN_NAMESPACE\n\n");
967         if (m_root) {
968             writeString("extern Q_CORE_EXPORT bool qRegisterResourceData\n    "
969                 "(int, const unsigned char *, "
970                 "const unsigned char *, const unsigned char *);\n\n");
971             writeString("extern Q_CORE_EXPORT bool qUnregisterResourceData\n    "
972                 "(int, const unsigned char *, "
973                 "const unsigned char *, const unsigned char *);\n\n");
974         }
975         if (m_useNameSpace)
976             writeString("QT_END_NAMESPACE\n\n\n");
977         QString initResources = QLatin1String("qInitResources");
978         initResources += initName;
979         writeString("int ");
980         writeMangleNamespaceFunction(initResources.toLatin1());
981         writeString("()\n{\n");
982 
983         if (m_root) {
984             writeString("    ");
985             writeAddNamespaceFunction("qRegisterResourceData");
986             writeString("\n        (0x01, qt_resource_struct, "
987                        "qt_resource_name, qt_resource_data);\n");
988         }
989         writeString("    return 1;\n");
990         writeString("}\n\n");
991         writeString("Q_CONSTRUCTOR_FUNCTION(");
992         writeMangleNamespaceFunction(initResources.toLatin1());
993         writeString(")\n\n");
994 
995         //cleanup
996         QString cleanResources = QLatin1String("qCleanupResources");
997         cleanResources += initName;
998         writeString("int ");
999         writeMangleNamespaceFunction(cleanResources.toLatin1());
1000         writeString("()\n{\n");
1001         if (m_root) {
1002             writeString("    ");
1003             writeAddNamespaceFunction("qUnregisterResourceData");
1004             writeString("\n       (0x01, qt_resource_struct, "
1005                       "qt_resource_name, qt_resource_data);\n");
1006         }
1007         writeString("    return 1;\n");
1008         writeString("}\n\n");
1009         writeString("Q_DESTRUCTOR_FUNCTION(");
1010         writeMangleNamespaceFunction(cleanResources.toLatin1());
1011         writeString(")\n\n");
1012     } else if (m_format == Binary) {
1013         int i = 4;
1014         char *p = m_out.data();
1015         p[i++] = 0; // 0x01
1016         p[i++] = 0;
1017         p[i++] = 0;
1018         p[i++] = 1;
1019 
1020         p[i++] = (m_treeOffset >> 24) & 0xff;
1021         p[i++] = (m_treeOffset >> 16) & 0xff;
1022         p[i++] = (m_treeOffset >>  8) & 0xff;
1023         p[i++] = (m_treeOffset >>  0) & 0xff;
1024 
1025         p[i++] = (m_dataOffset >> 24) & 0xff;
1026         p[i++] = (m_dataOffset >> 16) & 0xff;
1027         p[i++] = (m_dataOffset >>  8) & 0xff;
1028         p[i++] = (m_dataOffset >>  0) & 0xff;
1029 
1030         p[i++] = (m_namesOffset >> 24) & 0xff;
1031         p[i++] = (m_namesOffset >> 16) & 0xff;
1032         p[i++] = (m_namesOffset >>  8) & 0xff;
1033         p[i++] = (m_namesOffset >>  0) & 0xff;
1034     }
1035     return true;
1036 }
1037 
1038 QT_END_NAMESPACE
1039