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

source code of qtbase/src/corelib/mimetypes/qmimeprovider.cpp