1/****************************************************************************
2**
3** Copyright (C) 2018 The Qt Company Ltd.
4** Copyright (C) 2018 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com>
5** Copyright (C) 2019 Intel Corporation.
6** Contact: https://www.qt.io/licensing/
7**
8** This file is part of the QtCore module of the Qt Toolkit.
9**
10** $QT_BEGIN_LICENSE:LGPL$
11** Commercial License Usage
12** Licensees holding valid commercial Qt licenses may use this file in
13** accordance with the commercial license agreement provided with the
14** Software or, alternatively, in accordance with the terms contained in
15** a written agreement between you and The Qt Company. For licensing terms
16** and conditions see https://www.qt.io/terms-conditions. For further
17** information use the contact form at https://www.qt.io/contact-us.
18**
19** GNU Lesser General Public License Usage
20** Alternatively, this file may be used under the terms of the GNU Lesser
21** General Public License version 3 as published by the Free Software
22** Foundation and appearing in the file LICENSE.LGPL3 included in the
23** packaging of this file. Please review the following information to
24** ensure the GNU Lesser General Public License version 3 requirements
25** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
26**
27** GNU General Public License Usage
28** Alternatively, this file may be used under the terms of the GNU
29** General Public License version 2.0 or (at your option) the GNU General
30** Public license version 3 or any later version approved by the KDE Free
31** Qt Foundation. The licenses are as published by the Free Software
32** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
33** included in the packaging of this file. Please review the following
34** information to ensure the GNU General Public License requirements will
35** be met: https://www.gnu.org/licenses/gpl-2.0.html and
36** https://www.gnu.org/licenses/gpl-3.0.html.
37**
38** $QT_END_LICENSE$
39**
40****************************************************************************/
41
42#include "qmimeprovider_p.h"
43
44#include "qmimetypeparser_p.h"
45#include <qstandardpaths.h>
46#include "qmimemagicrulematcher_p.h"
47
48#include <QXmlStreamReader>
49#include <QBuffer>
50#include <QDir>
51#include <QFile>
52#include <QByteArrayMatcher>
53#include <QDebug>
54#include <QDateTime>
55#include <QtEndian>
56
57#if QT_CONFIG(mimetype_database)
58# if defined(Q_CC_MSVC)
59# pragma section(".qtmimedatabase", read, shared)
60__declspec(allocate(".qtmimedatabase")) __declspec(align(4096))
61# elif defined(Q_OS_DARWIN)
62__attribute__((section("__TEXT,.qtmimedatabase"), aligned(4096)))
63# elif (defined(Q_OF_ELF) || defined(Q_OS_WIN)) && defined(Q_CC_GNU)
64__attribute__((section(".qtmimedatabase"), aligned(4096)))
65# endif
66
67# include "qmimeprovider_database.cpp"
68
69# ifdef MIME_DATABASE_IS_ZSTD
70# if !QT_CONFIG(zstd)
71# error "MIME database is zstd but no support compiled in!"
72# endif
73# include <zstd.h>
74# endif
75# ifdef MIME_DATABASE_IS_GZIP
76# ifdef QT_NO_COMPRESS
77# error "MIME database is zlib but no support compiled in!"
78# endif
79# define ZLIB_CONST
80# include <zconf.h>
81# include <zlib.h>
82# endif
83#endif
84
85QT_BEGIN_NAMESPACE
86
87QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory)
88 : m_db(db), m_directory(directory)
89{
90}
91
92
93QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory)
94 : QMimeProviderBase(db, directory), m_mimetypeListLoaded(false)
95{
96 ensureLoaded();
97}
98
99struct QMimeBinaryProvider::CacheFile
100{
101 CacheFile(const QString &fileName);
102 ~CacheFile();
103
104 bool isValid() const { return m_valid; }
105 inline quint16 getUint16(int offset) const
106 {
107 return qFromBigEndian(*reinterpret_cast<quint16 *>(data + offset));
108 }
109 inline quint32 getUint32(int offset) const
110 {
111 return qFromBigEndian(*reinterpret_cast<quint32 *>(data + offset));
112 }
113 inline const char *getCharStar(int offset) const
114 {
115 return reinterpret_cast<const char *>(data + offset);
116 }
117 bool load();
118 bool reload();
119
120 QFile file;
121 uchar *data;
122 QDateTime m_mtime;
123 bool m_valid;
124};
125
126QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName)
127 : file(fileName), m_valid(false)
128{
129 load();
130}
131
132QMimeBinaryProvider::CacheFile::~CacheFile()
133{
134}
135
136bool QMimeBinaryProvider::CacheFile::load()
137{
138 if (!file.open(QIODevice::ReadOnly))
139 return false;
140 data = file.map(0, file.size());
141 if (data) {
142 const int major = getUint16(0);
143 const int minor = getUint16(2);
144 m_valid = (major == 1 && minor >= 1 && minor <= 2);
145 }
146 m_mtime = QFileInfo(file).lastModified();
147 return m_valid;
148}
149
150bool QMimeBinaryProvider::CacheFile::reload()
151{
152 m_valid = false;
153 if (file.isOpen()) {
154 file.close();
155 }
156 data = nullptr;
157 return load();
158}
159
160QMimeBinaryProvider::~QMimeBinaryProvider()
161{
162 delete m_cacheFile;
163}
164
165bool QMimeBinaryProvider::isValid()
166{
167 return m_cacheFile != nullptr;
168}
169
170bool QMimeBinaryProvider::isInternalDatabase() const
171{
172 return false;
173}
174
175// Position of the "list offsets" values, at the beginning of the mime.cache file
176enum {
177 PosAliasListOffset = 4,
178 PosParentListOffset = 8,
179 PosLiteralListOffset = 12,
180 PosReverseSuffixTreeOffset = 16,
181 PosGlobListOffset = 20,
182 PosMagicListOffset = 24,
183 // PosNamespaceListOffset = 28,
184 PosIconsListOffset = 32,
185 PosGenericIconsListOffset = 36
186};
187
188bool QMimeBinaryProvider::checkCacheChanged()
189{
190 QFileInfo fileInfo(m_cacheFile->file);
191 if (fileInfo.lastModified() > m_cacheFile->m_mtime) {
192 // Deletion can't happen by just running update-mime-database.
193 // But the user could use rm -rf :-)
194 m_cacheFile->reload(); // will mark itself as invalid on failure
195 return true;
196 }
197 return false;
198}
199
200void QMimeBinaryProvider::ensureLoaded()
201{
202 if (!m_cacheFile) {
203 const QString cacheFileName = m_directory + QLatin1String("/mime.cache");
204 m_cacheFile = new CacheFile(cacheFileName);
205 m_mimetypeListLoaded = false;
206 } else {
207 if (checkCacheChanged())
208 m_mimetypeListLoaded = false;
209 else
210 return; // nothing to do
211 }
212 if (!m_cacheFile->isValid()) { // verify existence and version
213 delete m_cacheFile;
214 m_cacheFile = nullptr;
215 }
216}
217
218static QMimeType mimeTypeForNameUnchecked(const QString &name)
219{
220 QMimeTypePrivate data;
221 data.name = name;
222 data.fromCache = true;
223 // The rest is retrieved on demand.
224 // comment and globPatterns: in loadMimeTypePrivate
225 // iconName: in loadIcon
226 // genericIconName: in loadGenericIcon
227 return QMimeType(data);
228}
229
230QMimeType QMimeBinaryProvider::mimeTypeForName(const QString &name)
231{
232 if (!m_mimetypeListLoaded)
233 loadMimeTypeList();
234 if (!m_mimetypeNames.contains(name))
235 return QMimeType(); // unknown mimetype
236 return mimeTypeForNameUnchecked(name);
237}
238
239void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
240{
241 if (fileName.isEmpty())
242 return;
243 Q_ASSERT(m_cacheFile);
244 const QString lowerFileName = fileName.toLower();
245 // Check literals (e.g. "Makefile")
246 matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosLiteralListOffset), fileName);
247 // Check complex globs (e.g. "callgrind.out[0-9]*")
248 matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName);
249 // Check the very common *.txt cases with the suffix tree
250 const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset);
251 const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset);
252 const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4);
253 matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, lowerFileName, lowerFileName.length() - 1, false);
254 if (result.m_matchingMimeTypes.isEmpty())
255 matchSuffixTree(result, m_cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true);
256}
257
258void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName)
259{
260 const int numGlobs = cacheFile->getUint32(off);
261 //qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset;
262 for (int i = 0; i < numGlobs; ++i) {
263 const int globOffset = cacheFile->getUint32(off + 4 + 12 * i);
264 const int mimeTypeOffset = cacheFile->getUint32(off + 4 + 12 * i + 4);
265 const int flagsAndWeight = cacheFile->getUint32(off + 4 + 12 * i + 8);
266 const int weight = flagsAndWeight & 0xff;
267 const bool caseSensitive = flagsAndWeight & 0x100;
268 const Qt::CaseSensitivity qtCaseSensitive = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
269 const QString pattern = QLatin1String(cacheFile->getCharStar(globOffset));
270
271 const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
272 //qDebug() << pattern << mimeType << weight << caseSensitive;
273 QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive);
274
275 // TODO: this could be done faster for literals where a simple == would do.
276 if (glob.matchFileName(fileName))
277 result.addMatch(QLatin1String(mimeType), weight, pattern);
278 }
279}
280
281bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, QMimeBinaryProvider::CacheFile *cacheFile, int numEntries, int firstOffset, const QString &fileName, int charPos, bool caseSensitiveCheck)
282{
283 QChar fileChar = fileName[charPos];
284 int min = 0;
285 int max = numEntries - 1;
286 while (min <= max) {
287 const int mid = (min + max) / 2;
288 const int off = firstOffset + 12 * mid;
289 const QChar ch = char16_t(cacheFile->getUint32(off));
290 if (ch < fileChar)
291 min = mid + 1;
292 else if (ch > fileChar)
293 max = mid - 1;
294 else {
295 --charPos;
296 int numChildren = cacheFile->getUint32(off + 4);
297 int childrenOffset = cacheFile->getUint32(off + 8);
298 bool success = false;
299 if (charPos > 0)
300 success = matchSuffixTree(result, cacheFile, numChildren, childrenOffset, fileName, charPos, caseSensitiveCheck);
301 if (!success) {
302 for (int i = 0; i < numChildren; ++i) {
303 const int childOff = childrenOffset + 12 * i;
304 const int mch = cacheFile->getUint32(childOff);
305 if (mch != 0)
306 break;
307 const int mimeTypeOffset = cacheFile->getUint32(childOff + 4);
308 const char *mimeType = cacheFile->getCharStar(mimeTypeOffset);
309 const int flagsAndWeight = cacheFile->getUint32(childOff + 8);
310 const int weight = flagsAndWeight & 0xff;
311 const bool caseSensitive = flagsAndWeight & 0x100;
312 if (caseSensitiveCheck || !caseSensitive) {
313 result.addMatch(QLatin1String(mimeType), weight,
314 QLatin1Char('*') + QStringView{fileName}.mid(charPos + 1), fileName.size() - charPos - 2);
315 success = true;
316 }
317 }
318 }
319 return success;
320 }
321 }
322 return false;
323}
324
325bool QMimeBinaryProvider::matchMagicRule(QMimeBinaryProvider::CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data)
326{
327 const char *dataPtr = data.constData();
328 const int dataSize = data.size();
329 for (int matchlet = 0; matchlet < numMatchlets; ++matchlet) {
330 const int off = firstOffset + matchlet * 32;
331 const int rangeStart = cacheFile->getUint32(off);
332 const int rangeLength = cacheFile->getUint32(off + 4);
333 //const int wordSize = cacheFile->getUint32(off + 8);
334 const int valueLength = cacheFile->getUint32(off + 12);
335 const int valueOffset = cacheFile->getUint32(off + 16);
336 const int maskOffset = cacheFile->getUint32(off + 20);
337 const char *mask = maskOffset ? cacheFile->getCharStar(maskOffset) : nullptr;
338
339 if (!QMimeMagicRule::matchSubstring(dataPtr, dataSize, rangeStart, rangeLength, valueLength, cacheFile->getCharStar(valueOffset), mask))
340 continue;
341
342 const int numChildren = cacheFile->getUint32(off + 24);
343 const int firstChildOffset = cacheFile->getUint32(off + 28);
344 if (numChildren == 0) // No submatch? Then we are done.
345 return true;
346 // Check that one of the submatches matches too
347 if (matchMagicRule(cacheFile, numChildren, firstChildOffset, data))
348 return true;
349 }
350 return false;
351}
352
353void QMimeBinaryProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate)
354{
355 const int magicListOffset = m_cacheFile->getUint32(PosMagicListOffset);
356 const int numMatches = m_cacheFile->getUint32(magicListOffset);
357 //const int maxExtent = cacheFile->getUint32(magicListOffset + 4);
358 const int firstMatchOffset = m_cacheFile->getUint32(magicListOffset + 8);
359
360 for (int i = 0; i < numMatches; ++i) {
361 const int off = firstMatchOffset + i * 16;
362 const int numMatchlets = m_cacheFile->getUint32(off + 8);
363 const int firstMatchletOffset = m_cacheFile->getUint32(off + 12);
364 if (matchMagicRule(m_cacheFile, numMatchlets, firstMatchletOffset, data)) {
365 const int mimeTypeOffset = m_cacheFile->getUint32(off + 4);
366 const char *mimeType = m_cacheFile->getCharStar(mimeTypeOffset);
367 *accuracyPtr = m_cacheFile->getUint32(off);
368 // Return the first match. We have no rules for conflicting magic data...
369 // (mime.cache itself is sorted, but what about local overrides with a lower prio?)
370 candidate = mimeTypeForNameUnchecked(QLatin1String(mimeType));
371 return;
372 }
373 }
374}
375
376void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
377{
378 const QByteArray mimeStr = mime.toLatin1();
379 const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset);
380 const int numEntries = m_cacheFile->getUint32(parentListOffset);
381
382 int begin = 0;
383 int end = numEntries - 1;
384 while (begin <= end) {
385 const int medium = (begin + end) / 2;
386 const int off = parentListOffset + 4 + 8 * medium;
387 const int mimeOffset = m_cacheFile->getUint32(off);
388 const char *aMime = m_cacheFile->getCharStar(mimeOffset);
389 const int cmp = qstrcmp(aMime, mimeStr);
390 if (cmp < 0) {
391 begin = medium + 1;
392 } else if (cmp > 0) {
393 end = medium - 1;
394 } else {
395 const int parentsOffset = m_cacheFile->getUint32(off + 4);
396 const int numParents = m_cacheFile->getUint32(parentsOffset);
397 for (int i = 0; i < numParents; ++i) {
398 const int parentOffset = m_cacheFile->getUint32(parentsOffset + 4 + 4 * i);
399 const char *aParent = m_cacheFile->getCharStar(parentOffset);
400 const QString strParent = QString::fromLatin1(aParent);
401 if (!result.contains(strParent))
402 result.append(strParent);
403 }
404 break;
405 }
406 }
407}
408
409QString QMimeBinaryProvider::resolveAlias(const QString &name)
410{
411 const QByteArray input = name.toLatin1();
412 const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
413 const int numEntries = m_cacheFile->getUint32(aliasListOffset);
414 int begin = 0;
415 int end = numEntries - 1;
416 while (begin <= end) {
417 const int medium = (begin + end) / 2;
418 const int off = aliasListOffset + 4 + 8 * medium;
419 const int aliasOffset = m_cacheFile->getUint32(off);
420 const char *alias = m_cacheFile->getCharStar(aliasOffset);
421 const int cmp = qstrcmp(alias, input);
422 if (cmp < 0) {
423 begin = medium + 1;
424 } else if (cmp > 0) {
425 end = medium - 1;
426 } else {
427 const int mimeOffset = m_cacheFile->getUint32(off + 4);
428 const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
429 return QLatin1String(mimeType);
430 }
431 }
432 return QString();
433}
434
435void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
436{
437 const QByteArray input = name.toLatin1();
438 const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
439 const int numEntries = m_cacheFile->getUint32(aliasListOffset);
440 for (int pos = 0; pos < numEntries; ++pos) {
441 const int off = aliasListOffset + 4 + 8 * pos;
442 const int mimeOffset = m_cacheFile->getUint32(off + 4);
443 const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
444
445 if (input == mimeType) {
446 const int aliasOffset = m_cacheFile->getUint32(off);
447 const char *alias = m_cacheFile->getCharStar(aliasOffset);
448 const QString strAlias = QString::fromLatin1(alias);
449 if (!result.contains(strAlias))
450 result.append(strAlias);
451 }
452 }
453}
454
455void QMimeBinaryProvider::loadMimeTypeList()
456{
457 if (!m_mimetypeListLoaded) {
458 m_mimetypeListLoaded = true;
459 m_mimetypeNames.clear();
460 // Unfortunately mime.cache doesn't have a full list of all mimetypes.
461 // So we have to parse the plain-text files called "types".
462 QFile file(m_directory + QStringLiteral("/types"));
463 if (file.open(QIODevice::ReadOnly)) {
464 while (!file.atEnd()) {
465 QByteArray line = file.readLine();
466 if (line.endsWith('\n'))
467 line.chop(1);
468 m_mimetypeNames.insert(QString::fromLatin1(line));
469 }
470 }
471 }
472}
473
474void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result)
475{
476 loadMimeTypeList();
477 if (result.isEmpty()) {
478 result.reserve(m_mimetypeNames.count());
479 for (const QString &name : qAsConst(m_mimetypeNames))
480 result.append(mimeTypeForNameUnchecked(name));
481 } else {
482 for (const QString &name : qAsConst(m_mimetypeNames))
483 if (std::find_if(result.constBegin(), result.constEnd(), [name](const QMimeType &mime) -> bool { return mime.name() == name; })
484 == result.constEnd())
485 result.append(mimeTypeForNameUnchecked(name));
486 }
487}
488
489void QMimeBinaryProvider::loadMimeTypePrivate(QMimeTypePrivate &data)
490{
491#ifdef QT_NO_XMLSTREAMREADER
492 Q_UNUSED(data);
493 qWarning("Cannot load mime type since QXmlStreamReader is not available.");
494 return;
495#else
496 if (data.loaded)
497 return;
498 data.loaded = true;
499 // load comment and globPatterns
500
501 const QString file = data.name + QLatin1String(".xml");
502 // shared-mime-info since 1.3 lowercases the xml files
503 QStringList mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/") + file.toLower());
504 if (mimeFiles.isEmpty())
505 mimeFiles = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime/") + file); // pre-1.3
506 if (mimeFiles.isEmpty()) {
507 qWarning() << "No file found for" << file << ", even though update-mime-info said it would exist.\n"
508 "Either it was just removed, or the directory doesn't have executable permission..."
509 << QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QLatin1String("mime"), QStandardPaths::LocateDirectory);
510 return;
511 }
512
513 QString mainPattern;
514
515 for (QStringList::const_reverse_iterator it = mimeFiles.crbegin(), end = mimeFiles.crend(); it != end; ++it) { // global first, then local.
516 QFile qfile(*it);
517 if (!qfile.open(QFile::ReadOnly))
518 continue;
519
520 QXmlStreamReader xml(&qfile);
521 if (xml.readNextStartElement()) {
522 if (xml.name() != QLatin1String("mime-type")) {
523 continue;
524 }
525 const auto name = xml.attributes().value(QLatin1String("type"));
526 if (name.isEmpty())
527 continue;
528 if (name.compare(data.name, Qt::CaseInsensitive))
529 qWarning() << "Got name" << name << "in file" << file << "expected" << data.name;
530
531 while (xml.readNextStartElement()) {
532 const auto tag = xml.name();
533 if (tag == QLatin1String("comment")) {
534 QString lang = xml.attributes().value(QLatin1String("xml:lang")).toString();
535 const QString text = xml.readElementText();
536 if (lang.isEmpty()) {
537 lang = QLatin1String("default"); // no locale attribute provided, treat it as default.
538 }
539 data.localeComments.insert(lang, text);
540 continue; // we called readElementText, so we're at the EndElement already.
541 } else if (tag == QLatin1String("icon")) { // as written out by shared-mime-info >= 0.40
542 data.iconName = xml.attributes().value(QLatin1String("name")).toString();
543 } else if (tag == QLatin1String("glob-deleteall")) { // as written out by shared-mime-info >= 0.70
544 data.globPatterns.clear();
545 mainPattern.clear();
546 } else if (tag == QLatin1String("glob")) { // as written out by shared-mime-info >= 0.70
547 const QString pattern = xml.attributes().value(QLatin1String("pattern")).toString();
548 if (mainPattern.isEmpty() && pattern.startsWith(QLatin1Char('*'))) {
549 mainPattern = pattern;
550 }
551 if (!data.globPatterns.contains(pattern))
552 data.globPatterns.append(pattern);
553 }
554 xml.skipCurrentElement();
555 }
556 Q_ASSERT(xml.name() == QLatin1String("mime-type"));
557 }
558 }
559
560 // Let's assume that shared-mime-info is at least version 0.70
561 // Otherwise we would need 1) a version check, and 2) code for parsing patterns from the globs file.
562#if 1
563 if (!mainPattern.isEmpty() && (data.globPatterns.isEmpty() || data.globPatterns.constFirst() != mainPattern)) {
564 // ensure it's first in the list of patterns
565 data.globPatterns.removeAll(mainPattern);
566 data.globPatterns.prepend(mainPattern);
567 }
568#else
569 const bool globsInXml = sharedMimeInfoVersion() >= QT_VERSION_CHECK(0, 70, 0);
570 if (globsInXml) {
571 if (!mainPattern.isEmpty() && data.globPatterns.constFirst() != mainPattern) {
572 // ensure it's first in the list of patterns
573 data.globPatterns.removeAll(mainPattern);
574 data.globPatterns.prepend(mainPattern);
575 }
576 } else {
577 // Fallback: get the patterns from the globs file
578 // TODO: This would be the only way to support shared-mime-info < 0.70
579 // But is this really worth the effort?
580 }
581#endif
582#endif //QT_NO_XMLSTREAMREADER
583}
584
585// Binary search in the icons or generic-icons list
586QLatin1String QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime)
587{
588 const int iconsListOffset = cacheFile->getUint32(posListOffset);
589 const int numIcons = cacheFile->getUint32(iconsListOffset);
590 int begin = 0;
591 int end = numIcons - 1;
592 while (begin <= end) {
593 const int medium = (begin + end) / 2;
594 const int off = iconsListOffset + 4 + 8 * medium;
595 const int mimeOffset = cacheFile->getUint32(off);
596 const char *mime = cacheFile->getCharStar(mimeOffset);
597 const int cmp = qstrcmp(mime, inputMime);
598 if (cmp < 0)
599 begin = medium + 1;
600 else if (cmp > 0)
601 end = medium - 1;
602 else {
603 const int iconOffset = cacheFile->getUint32(off + 4);
604 return QLatin1String(cacheFile->getCharStar(iconOffset));
605 }
606 }
607 return QLatin1String();
608}
609
610void QMimeBinaryProvider::loadIcon(QMimeTypePrivate &data)
611{
612 const QByteArray inputMime = data.name.toLatin1();
613 const QLatin1String icon = iconForMime(m_cacheFile, PosIconsListOffset, inputMime);
614 if (!icon.isEmpty()) {
615 data.iconName = icon;
616 }
617}
618
619void QMimeBinaryProvider::loadGenericIcon(QMimeTypePrivate &data)
620{
621 const QByteArray inputMime = data.name.toLatin1();
622 const QLatin1String icon = iconForMime(m_cacheFile, PosGenericIconsListOffset, inputMime);
623 if (!icon.isEmpty()) {
624 data.genericIconName = icon;
625 }
626}
627
628////
629
630#if QT_CONFIG(mimetype_database)
631static QString internalMimeFileName()
632{
633 return QStringLiteral("<internal MIME data>");
634}
635
636QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
637 : QMimeProviderBase(db, internalMimeFileName())
638{
639 static_assert(sizeof(mimetype_database), "Bundled MIME database is empty");
640 static_assert(sizeof(mimetype_database) <= MimeTypeDatabaseOriginalSize,
641 "Compressed MIME database is larger than the original size");
642 static_assert(MimeTypeDatabaseOriginalSize <= 16*1024*1024,
643 "Bundled MIME database is too big");
644 const char *data = reinterpret_cast<const char *>(mimetype_database);
645 qsizetype size = MimeTypeDatabaseOriginalSize;
646
647#ifdef MIME_DATABASE_IS_ZSTD
648 // uncompress with libzstd
649 std::unique_ptr<char []> uncompressed(new char[size]);
650 size = ZSTD_decompress(uncompressed.get(), size, mimetype_database, sizeof(mimetype_database));
651 Q_ASSERT(!ZSTD_isError(size));
652 data = uncompressed.get();
653#elif defined(MIME_DATABASE_IS_GZIP)
654 std::unique_ptr<char []> uncompressed(new char[size]);
655 z_stream zs = {};
656 zs.next_in = const_cast<Bytef *>(mimetype_database);
657 zs.avail_in = sizeof(mimetype_database);
658 zs.next_out = reinterpret_cast<Bytef *>(uncompressed.get());
659 zs.avail_out = size;
660
661 int res = inflateInit2(&zs, MAX_WBITS | 32);
662 Q_ASSERT(res == Z_OK);
663 res = inflate(&zs, Z_FINISH);
664 Q_ASSERT(res == Z_STREAM_END);
665 res = inflateEnd(&zs);
666 Q_ASSERT(res == Z_OK);
667
668 data = uncompressed.get();
669 size = zs.total_out;
670#endif
671
672 load(data, size);
673}
674#else // !QT_CONFIG(mimetype_database)
675// never called in release mode, but some debug builds may need
676// this to be defined.
677QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
678 : QMimeProviderBase(db, QString())
679{
680 Q_UNREACHABLE();
681}
682#endif // QT_CONFIG(mimetype_database)
683
684QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory)
685 : QMimeProviderBase(db, directory)
686{
687 ensureLoaded();
688}
689
690QMimeXMLProvider::~QMimeXMLProvider()
691{
692}
693
694bool QMimeXMLProvider::isValid()
695{
696 // If you change this method, adjust the logic in QMimeDatabasePrivate::loadProviders,
697 // which assumes isValid==false is only possible in QMimeBinaryProvider.
698 return true;
699}
700
701bool QMimeXMLProvider::isInternalDatabase() const
702{
703#if QT_CONFIG(mimetype_database)
704 return m_directory == internalMimeFileName();
705#else
706 return false;
707#endif
708}
709
710QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name)
711{
712 return m_nameMimeTypeMap.value(name);
713}
714
715void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
716{
717 m_mimeTypeGlobs.matchingGlobs(fileName, result);
718}
719
720void QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr, QMimeType &candidate)
721{
722 QString candidateName;
723 bool foundOne = false;
724 for (const QMimeMagicRuleMatcher &matcher : qAsConst(m_magicMatchers)) {
725 if (matcher.matches(data)) {
726 const int priority = matcher.priority();
727 if (priority > *accuracyPtr) {
728 *accuracyPtr = priority;
729 candidateName = matcher.mimetype();
730 foundOne = true;
731 }
732 }
733 }
734 if (foundOne)
735 candidate = mimeTypeForName(candidateName);
736}
737
738void QMimeXMLProvider::ensureLoaded()
739{
740 QStringList allFiles;
741 const QString packageDir = m_directory + QStringLiteral("/packages");
742 QDir dir(packageDir);
743 const QStringList files = dir.entryList(QDir::Files | QDir::NoDotAndDotDot);
744 allFiles.reserve(files.count());
745 for (const QString &xmlFile : files)
746 allFiles.append(packageDir + QLatin1Char('/') + xmlFile);
747
748 if (m_allFiles == allFiles)
749 return;
750 m_allFiles = allFiles;
751
752 m_nameMimeTypeMap.clear();
753 m_aliases.clear();
754 m_parents.clear();
755 m_mimeTypeGlobs.clear();
756 m_magicMatchers.clear();
757
758 //qDebug() << "Loading" << m_allFiles;
759
760 for (const QString &file : qAsConst(allFiles))
761 load(file);
762}
763
764void QMimeXMLProvider::load(const QString &fileName)
765{
766 QString errorMessage;
767 if (!load(fileName, &errorMessage))
768 qWarning("QMimeDatabase: Error loading %ls\n%ls", qUtf16Printable(fileName), qUtf16Printable(errorMessage));
769}
770
771bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage)
772{
773 QFile file(fileName);
774 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
775 if (errorMessage)
776 *errorMessage = QLatin1String("Cannot open ") + fileName + QLatin1String(": ") + file.errorString();
777 return false;
778 }
779
780 if (errorMessage)
781 errorMessage->clear();
782
783 QMimeTypeParser parser(*this);
784 return parser.parse(&file, fileName, errorMessage);
785}
786
787#if QT_CONFIG(mimetype_database)
788void QMimeXMLProvider::load(const char *data, qsizetype len)
789{
790 QBuffer buffer;
791 buffer.setData(QByteArray::fromRawData(data, len));
792 buffer.open(QIODevice::ReadOnly);
793 QString errorMessage;
794 QMimeTypeParser parser(*this);
795 if (!parser.parse(&buffer, internalMimeFileName(), &errorMessage))
796 qWarning("QMimeDatabase: Error loading internal MIME data\n%s", qPrintable(errorMessage));
797}
798#endif
799
800void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob)
801{
802 m_mimeTypeGlobs.addGlob(glob);
803}
804
805void QMimeXMLProvider::addMimeType(const QMimeType &mt)
806{
807 Q_ASSERT(!mt.d.data()->fromCache);
808 m_nameMimeTypeMap.insert(mt.name(), mt);
809}
810
811void QMimeXMLProvider::addParents(const QString &mime, QStringList &result)
812{
813 for (const QString &parent : m_parents.value(mime)) {
814 if (!result.contains(parent))
815 result.append(parent);
816 }
817}
818
819void QMimeXMLProvider::addParent(const QString &child, const QString &parent)
820{
821 m_parents[child].append(parent);
822}
823
824void QMimeXMLProvider::addAliases(const QString &name, QStringList &result)
825{
826 // Iterate through the whole hash. This method is rarely used.
827 for (auto it = m_aliases.constBegin(), end = m_aliases.constEnd() ; it != end ; ++it) {
828 if (it.value() == name) {
829 if (!result.contains(it.key()))
830 result.append(it.key());
831 }
832 }
833
834}
835
836QString QMimeXMLProvider::resolveAlias(const QString &name)
837{
838 return m_aliases.value(name);
839}
840
841void QMimeXMLProvider::addAlias(const QString &alias, const QString &name)
842{
843 m_aliases.insert(alias, name);
844}
845
846void QMimeXMLProvider::addAllMimeTypes(QList<QMimeType> &result)
847{
848 if (result.isEmpty()) { // fast path
849 result = m_nameMimeTypeMap.values();
850 } else {
851 for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd() ; it != end ; ++it) {
852 const QString newMime = it.key();
853 if (std::find_if(result.constBegin(), result.constEnd(), [newMime](const QMimeType &mime) -> bool { return mime.name() == newMime; })
854 == result.constEnd())
855 result.append(it.value());
856 }
857 }
858}
859
860void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
861{
862 m_magicMatchers.append(matcher);
863}
864
865QT_END_NAMESPACE
866