Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmimeprovider.cpp
Go to the documentation of this file.
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// Qt-Security score:critical reason:data-parser
6
8
10#include <qstandardpaths.h>
12
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// Position of the "list offsets" values, at the beginning of the mime.cache file
54enum {
61 // PosNamespaceListOffset = 28,
64};
65
66// Size of one record of each of the lists, as defined by the mime.cache format
67enum {
75};
76
77enum {
80};
81
82/*
83 The depth limit protects the stack, but it does not limit the time. In a
84 corrupt mime.cache, the child offset of a matchlet can point back to the
85 list that contains it. For example, take a list of 2 matchlets where the
86 children of each one are that same list. Each path still stops at
87 MaxRecursionDepth, but there are 2^depth paths, and a file of a few
88 hundred bytes is enough to describe them.
89
90 So one findByMagic() or checkMagicRules() call also has a budget: the
91 number of input data bytes it may scan, summed over all matchlets it
92 checks. Each checked matchlet costs 1 + min(rangeLength, data.size())
93 bytes: the input bytes it may compare, plus 1 so that no matchlet is free.
94 When the budget is used up, the lookup stops and reports no match.
95
96 Checking every matchlet in the shared-mime-info database costs about 76k
97 bytes, so this limit is far above what a valid mime.cache needs.
98 */
99static constexpr qint64 MagicMatchBudget = 1 << 21;
100
101/*
102 A list of fixed-size records in the mime.cache file, whose bounds have been
103 checked once: for any index below count, the whole record at
104 recordOffset(index) is inside the file, so walking the list needs no further
105 checking - read the fields with CacheFile::recordUint32(). An empty list
106 means either a mime.cache is empty or corrupted.
107
108 Anything reached by following an offset stored inside a record is still
109 untrusted and has to be checked where it is used.
110 */
111class QMimeBinaryProvider::RecordList
112{
113public:
114 // The lists whose offsets live in the first 40 bytes of the file are read
115 // by these two functions, once, when the cache file is loaded.
116 bool load(const CacheFile *cacheFile, quint64 posListOffset, quint32 stride);
117 bool loadIndirect(const CacheFile *cacheFile, quint64 posListOffset,
118 quint64 firstRecordPos, quint32 stride);
119
120 quint32 count() const { return m_count; } // N_ALIASES/N_ENTRIES/N_PARENTS/etc => CARD32 type
121 quint32 stride() const { return m_stride; }
122 quint64 firstRecord() const { return m_firstRecord; }
123 quint64 recordOffset(quint32 index) const
124 {
125 Q_ASSERT(index < m_count);
126 return m_firstRecord + quint64(index) * m_stride;
127 }
128
129private:
130 // The state is private, and setRecords() is the only thing that writes it,
131 // so a RecordList that is not empty has been bounds-checked.
132 bool setRecords(const CacheFile *cacheFile, quint64 firstRecord,
133 quint32 count, quint32 stride);
134
135 quint64 m_firstRecord = 0;
136 quint32 m_stride = 0;
137 quint32 m_count = 0;
138};
139
140struct QMimeBinaryProvider::CacheFile
141{
142 CacheFile(const QString &fileName);
144
145 bool isValid() const { return m_valid; }
146 inline std::optional<quint16> getUint16(quint64 offset) const
147 {
148 if (offset > m_fileSize || m_fileSize - offset < sizeof(quint16))
149 return std::nullopt;
150 return qFromBigEndian<quint16>(m_data + offset);
151 }
152 inline std::optional<quint32> getUint32(quint64 offset, quint64 extra = 0) const
153 {
154 if (offset > m_fileSize || extra > m_fileSize - offset)
155 return std::nullopt;
156 const quint64 pos = offset + extra;
157 if (m_fileSize - pos < sizeof(quint32))
158 return std::nullopt;
159 return qFromBigEndian<quint32>(m_data + pos);
160 }
161 inline QLatin1StringView getLatin1String(quint64 offset) const
162 {
163 if (offset >= m_fileSize)
164 return QLatin1StringView();
165 const char *str = reinterpret_cast<const char *>(m_data + offset);
166 const void *end = std::memchr(str, '\0', m_fileSize - offset);
167 if (!end)
168 return QLatin1StringView();
169 return QLatin1StringView(str, static_cast<const char *>(end));
170 }
171 inline const char *getData(quint64 offset, quint64 length) const
172 {
173 if (offset > m_fileSize || length > m_fileSize - offset)
174 return nullptr;
175 return reinterpret_cast<const char *>(m_data + offset);
176 }
177 // Check base + headerSize + index * stride can not overflow.
178 std::optional<quint64> safeRecordOffset(quint64 base, quint64 headerSize,
179 quint32 index, quint32 stride) const
180 {
181 if (base > m_fileSize || headerSize > m_fileSize - base)
182 return std::nullopt;
183 const quint64 avail = m_fileSize - base - headerSize;
184 // A zero stride would make every index alias the same record: corrupt cache.
185 if (!stride || index >= avail / stride)
186 return std::nullopt;
187 return base + headerSize + quint64(index) * stride;
188 }
189 // A quint32 field of a record of a bounds-checked RecordList. load() has
190 // verified that every record below list.count() lies inside the mapping.
191 inline quint32 recordUint32(const RecordList &list, quint32 index, quint32 fieldOffset) const
192 {
193 // note: fieldOffset is in range: { 0, 4, 8, 12 }
194 const quint64 pos = list.recordOffset(index) + fieldOffset;
195 return qFromBigEndian<quint32>(m_data + pos);
196 }
197 bool load();
198 bool reload();
199
201 uchar *m_data = nullptr;
204 bool m_valid = false;
205
206 // The lists whose offsets are stored in the first 40 bytes of the file,
207 // bounds-checked once by load() so that lookups don't have to.
216};
217
218// Leaves the list empty if the records don't all fit.
219bool QMimeBinaryProvider::RecordList::setRecords(const CacheFile *cacheFile, quint64 firstRecord,
220 quint32 count, quint32 stride)
221{
222 if (!stride || (firstRecord > cacheFile->m_fileSize)) // corrupt cache
223 return false;
224 if (count > (cacheFile->m_fileSize - firstRecord) / stride)
225 return false; // corrupt cache, or more records than can possibly fit
226 m_firstRecord = firstRecord;
227 m_stride = stride;
228 m_count = count;
229 return true;
230}
231
232// Reads the list header pointed to by the offset at posListOffset: the number
233// of records sits at +0 and the records themselves follow at +4.
234bool QMimeBinaryProvider::RecordList::load(const CacheFile *cacheFile, quint64 posListOffset,
235 quint32 stride)
236{
237 const std::optional<quint32> listOffset = cacheFile->getUint32(posListOffset);
238 if (!listOffset)
239 return false; // corrupt cache
240 const std::optional<quint32> n = cacheFile->getUint32(*listOffset);
241 if (!n)
242 return false; // corrupt cache
243 return setRecords(cacheFile, quint64(*listOffset) + 4, *n, stride);
244}
245
246bool QMimeBinaryProvider::RecordList::loadIndirect(const CacheFile *cacheFile,
247 quint64 posListOffset, quint64 firstRecordPos,
248 quint32 stride)
249{
250 const std::optional<quint32> listOffset = cacheFile->getUint32(posListOffset);
251 if (!listOffset)
252 return false; // corrupt cache
253 const std::optional<quint32> n = cacheFile->getUint32(*listOffset);
254 const std::optional<quint32> first = cacheFile->getUint32(*listOffset + firstRecordPos);
255 if (!n || !first)
256 return false; // corrupt cache
257 return setRecords(cacheFile, *first, *n, stride);
258}
259
260static inline void appendIfNew(QStringList &list, const QString &str)
261{
262 if (!list.contains(str))
263 list.push_back(str);
264}
265
266QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory)
267 : m_db(db), m_directory(directory)
268{
269}
270
275
280
281bool QMimeProviderBase::isMimeTypeGlobsExcluded(const QString &name) const
282{
283 if (m_overrideProvider) {
284 if (m_overrideProvider->hasGlobDeleteAll(name))
285 return true;
286 return m_overrideProvider->isMimeTypeGlobsExcluded(name);
287 }
288 return false;
289}
290
291QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory)
292 : QMimeProviderBase(db, directory), m_mimetypeListLoaded(false)
293{
295}
296
297QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName)
299{
300 load();
301}
302
303QMimeBinaryProvider::CacheFile::~CacheFile()
304{
305}
306
307bool QMimeBinaryProvider::CacheFile::load()
308{
309 if (!m_file.open(QIODevice::ReadOnly))
310 return false;
311 const qint64 fileSize = m_file.size();
312 m_fileSize = fileSize > 0 ? fileSize : 0;
313 m_data = m_file.map(0, m_fileSize);
314 if (m_data && m_fileSize >= 4) {
315 const quint16 major = *getUint16(0);
316 const quint16 minor = *getUint16(2);
317 m_valid = (major == 1 && minor >= 1 && minor <= 2)
318 && m_aliases.load(this, PosAliasListOffset, AliasRecordSize)
319 && m_parents.load(this, PosParentListOffset, ParentRecordSize)
320 && m_literals.load(this, PosLiteralListOffset, GlobRecordSize)
321 && m_globs.load(this, PosGlobListOffset, GlobRecordSize)
322 && m_icons.load(this, PosIconsListOffset, IconRecordSize)
323 && m_genericIcons.load(this, PosGenericIconsListOffset, IconRecordSize)
324 && m_suffixTreeRoots.loadIndirect(this, PosReverseSuffixTreeOffset,
325 PosFirstRootOffset, SuffixNodeSize)
326 && m_magicMatches.loadIndirect(this, PosMagicListOffset, PosFirstMatchOffset,
327 MagicMatchSize);
328 }
329 m_mtime = QFileInfo(m_file).lastModified(QTimeZone::UTC);
330 return m_valid;
331}
332
333bool QMimeBinaryProvider::CacheFile::reload()
334{
335 m_valid = false;
336 if (m_file.isOpen()) {
337 m_file.close();
338 }
339 m_data = nullptr;
340 m_fileSize = 0;
341 m_aliases = RecordList();
342 m_parents = RecordList();
343 m_literals = RecordList();
344 m_globs = RecordList();
345 m_suffixTreeRoots = RecordList();
346 m_magicMatches = RecordList();
347 m_icons = RecordList();
348 m_genericIcons = RecordList();
349 return load();
350}
351
352QMimeBinaryProvider::~QMimeBinaryProvider() = default;
353
354bool QMimeBinaryProvider::isValid()
355{
356 return m_cacheFile != nullptr;
357}
358
359bool QMimeBinaryProvider::isInternalDatabase() const
360{
361 return false;
362}
363
364bool QMimeBinaryProvider::checkCacheChanged()
365{
366 QFileInfo fileInfo(m_cacheFile->m_file);
367 if (fileInfo.lastModified(QTimeZone::UTC) > m_cacheFile->m_mtime) {
368 // Deletion can't happen by just running update-mime-database.
369 // But the user could use rm -rf :-)
370 m_cacheFile->reload(); // will mark itself as invalid on failure
371 return true;
372 }
373 return false;
374}
375
376void QMimeBinaryProvider::ensureLoaded()
377{
378 if (!m_cacheFile) {
379 const QString cacheFileName = m_directory + "/mime.cache"_L1;
380 m_cacheFile = std::make_unique<CacheFile>(cacheFileName);
381 m_mimetypeListLoaded = false;
382 m_mimetypeExtra.clear();
383 } else {
384 if (checkCacheChanged()) {
385 m_mimetypeListLoaded = false;
386 m_mimetypeExtra.clear();
387 } else {
388 return; // nothing to do
389 }
390 }
391 if (!m_cacheFile->isValid()) // verify existence and version
392 m_cacheFile.reset();
393}
394
395bool QMimeBinaryProvider::knowsMimeType(const QString &name)
396{
397 if (!m_mimetypeListLoaded)
398 loadMimeTypeList();
399 return m_mimetypeNames.contains(name);
400}
401
402void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
403{
404 if (fileName.isEmpty())
405 return;
406 Q_ASSERT(m_cacheFile);
407 // Check literals (e.g. "Makefile")
408 quint32 numMatches = matchGlobList(result, m_cacheFile.get(), m_cacheFile->m_literals, fileName);
409 // Check the very common *.txt cases with the suffix tree
410 if (numMatches == 0) {
411 const QString lowerFileName = fileName.toLower();
412 const RecordList &roots = m_cacheFile->m_suffixTreeRoots;
413 if (matchSuffixTree(result, m_cacheFile.get(), roots.count(), roots.firstRecord(),
414 lowerFileName, lowerFileName.size() - 1, false)) {
415 ++numMatches;
416 } else if (matchSuffixTree(result, m_cacheFile.get(), roots.count(), roots.firstRecord(),
417 fileName, fileName.size() - 1, true)) {
418 ++numMatches;
419 }
420 }
421 // Check complex globs (e.g. "callgrind.out[0-9]*" or "README*")
422 if (numMatches == 0)
423 matchGlobList(result, m_cacheFile.get(), m_cacheFile->m_globs, fileName);
424}
425
426quint32 QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile,
427 const RecordList &globs, const QString &fileName)
428{
429 quint32 numMatches = 0;
430 //qDebug() << "Loading" << globs.count() << "globs from" << cacheFile->m_file.fileName();
431 for (quint32 i = 0; i < globs.count(); ++i) {
432 const quint32 globOffset = cacheFile->recordUint32(globs, i, 0);
433 const quint32 mimeTypeOffset = cacheFile->recordUint32(globs, i, 4);
434 const quint32 flagsAndWeight = cacheFile->recordUint32(globs, i, 8);
435 const quint32 weight = flagsAndWeight & 0xff;
436 const bool caseSensitive = flagsAndWeight & 0x100;
437 const Qt::CaseSensitivity qtCaseSensitive = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
438 const QLatin1StringView patternL1 = cacheFile->getLatin1String(globOffset);
439 const QLatin1StringView mimeType = cacheFile->getLatin1String(mimeTypeOffset);
440 if (patternL1.isNull() || mimeType.isNull()) // corrupt cache: skip this record
441 continue;
442
443 //qDebug() << pattern << mimeType << weight << caseSensitive;
444 if (isMimeTypeGlobsExcluded(mimeType))
445 continue;
446
447 const QString pattern = patternL1;
448 QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive);
449 if (glob.matchFileName(fileName)) {
450 result.addMatch(mimeType, weight, pattern);
451 ++numMatches;
452 }
453 }
454 return numMatches;
455}
456
457bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result,
458 QMimeBinaryProvider::CacheFile *cacheFile, quint32 numEntries,
459 quint64 firstOffset, const QString &fileName,
460 qsizetype charPos, bool caseSensitiveCheck)
461{
462 QChar fileChar = fileName[charPos];
463 if (fileChar.isNull())
464 return false;
465 if (numEntries == 0)
466 return false;
467 quint32 min = 0;
468 quint32 max = numEntries - 1;
469 while (min <= max) {
470 const quint32 mid = (min + max) / 2;
471 const std::optional<quint64> off = cacheFile->safeRecordOffset(firstOffset, 0, mid, SuffixNodeSize);
472 if (!off) // corrupt cache
473 break;
474 const std::optional<quint32> chValue = cacheFile->getUint32(*off);
475 if (!chValue) // corrupt cache
476 break;
477 const QChar ch = char16_t(*chValue);
478 if (ch < fileChar) {
479 min = mid + 1;
480 } else if (ch > fileChar) {
481 if (!mid)
482 break;
483 max = mid - 1;
484 } else {
485 --charPos;
486 const std::optional<quint32> numChildrenOpt = cacheFile->getUint32(*off, 4);
487 const std::optional<quint32> childrenOffsetOpt = cacheFile->getUint32(*off, 8);
488 if (!numChildrenOpt || !childrenOffsetOpt) // corrupt cache
489 return false;
490 const quint32 numChildren = *numChildrenOpt;
491 const quint32 childrenOffset = *childrenOffsetOpt;
492 bool success = false;
493 if (charPos > 0)
494 success = matchSuffixTree(result, cacheFile, numChildren, childrenOffset, fileName, charPos, caseSensitiveCheck);
495 if (!success) {
496 for (quint32 i = 0; i < numChildren; ++i) {
497 const std::optional<quint64> childOff =
498 cacheFile->safeRecordOffset(childrenOffset, 0, i, SuffixNodeSize);
499 if (!childOff) // corrupt cache, or numChildren bigger than can possibly fit
500 break;
501 const std::optional<quint32> mchOpt = cacheFile->getUint32(*childOff);
502 if (!mchOpt) // corrupt cache: no later record can be inside the file either
503 break;
504 if (*mchOpt != 0) // not a leaf entry: end of the leaf list
505 break;
506 const std::optional<quint32> mimeTypeOffset = cacheFile->getUint32(*childOff, 4);
507 const std::optional<quint32> flagsAndWeight = cacheFile->getUint32(*childOff, 8);
508 if (!mimeTypeOffset || !flagsAndWeight) // corrupt cache: nor is any later one
509 break;
510 const QLatin1StringView mimeType = cacheFile->getLatin1String(*mimeTypeOffset);
511 if (mimeType.isNull()) // corrupt cache: skip this record
512 continue;
513 if (isMimeTypeGlobsExcluded(mimeType))
514 continue;
515 const quint32 weight = *flagsAndWeight & 0xff;
516 const bool caseSensitive = *flagsAndWeight & 0x100;
517 if (caseSensitiveCheck || !caseSensitive) {
518 result.addMatch(mimeType, weight,
519 u'*' + QStringView{ fileName }.mid(charPos + 1),
520 fileName.size() - charPos - 2);
521 success = true;
522 }
523 }
524 }
525 return success;
526 }
527 }
528 return false;
529}
530
531bool QMimeBinaryProvider::matchMagicRule(QMimeBinaryProvider::CacheFile *cacheFile,
532 quint32 numMatchlets, quint64 firstOffset,
533 const QByteArray &data, int depth, qint64 &budget)
534{
535 // The child offsets come straight from the cache file, so a corrupt (or
536 // hostile) mime.cache can describe a cyclic or unreasonably deep matchlet
537 // tree. Stop following it instead of exhausting the stack.
538 if (depth <= 0)
539 return false;
540
541 const char *dataPtr = data.constData();
542 const qsizetype dataSize = data.size();
543 for (quint32 matchlet = 0; matchlet < numMatchlets; ++matchlet) {
544 const std::optional<quint64> off = cacheFile->safeRecordOffset(firstOffset, 0, matchlet, MagicMatchletSize);
545 if (!off) // corrupt cache, or numMatchlets bigger than can possibly fit
546 break;
547 const std::optional<quint32> rangeStartOpt = cacheFile->getUint32(*off);
548 const std::optional<quint32> rangeLengthOpt = cacheFile->getUint32(*off, 4);
549 //const auto wordSize = cacheFile->getUint32(*off, 8);
550 const std::optional<quint32> valueLengthOpt = cacheFile->getUint32(*off, 12);
551 const std::optional<quint32> valueOffsetOpt = cacheFile->getUint32(*off, 16);
552 const std::optional<quint32> maskOffsetOpt = cacheFile->getUint32(*off, 20);
553 if (!rangeStartOpt || !rangeLengthOpt || !valueLengthOpt || !valueOffsetOpt
554 || !maskOffsetOpt) // corrupt cache: no later matchlet can be inside the file either
555 break;
556 const quint32 rangeStart = *rangeStartOpt;
557 const quint32 rangeLength = *rangeLengthOpt;
558 const quint32 valueLength = *valueLengthOpt;
559 const quint32 valueOffset = *valueOffsetOpt;
560 const quint32 maskOffset = *maskOffsetOpt;
561 budget -= 1 + qMin(quint64(rangeLength), quint64(dataSize));
562 if (budget < 0) // corrupt cache: give up on this record
563 return false;
564 const char *value = cacheFile->getData(valueOffset, valueLength);
565 const char *mask = maskOffset ? cacheFile->getData(maskOffset, valueLength) : nullptr;
566 if (!value || (maskOffset && !mask)) // corrupt cache
567 continue;
568
569 if (!QMimeMagicRule::matchSubstring(dataPtr, dataSize, rangeStart, rangeLength, valueLength, value, mask))
570 continue;
571
572 const std::optional<quint32> numChildrenOpt = cacheFile->getUint32(*off, 24);
573 const std::optional<quint32> firstChildOffsetOpt = cacheFile->getUint32(*off, 28);
574 if (!numChildrenOpt || !firstChildOffsetOpt) // corrupt cache: nor is any later matchlet
575 break;
576 const quint32 numChildren = *numChildrenOpt;
577 const quint32 firstChildOffset = *firstChildOffsetOpt;
578 if (numChildren == 0) // No submatch? Then we are done.
579 return true;
580 // Check that one of the submatches matches too
581 if (matchMagicRule(cacheFile, numChildren, firstChildOffset, data, depth - 1, budget))
582 return true;
583 }
584 return false;
585}
586
587bool QMimeBinaryProvider::magicRecordMatches(quint32 index, const QByteArray &data,
588 qint64 &budget) const
589{
590 const RecordList &matches = m_cacheFile->m_magicMatches;
591 const quint32 numMatchlets = m_cacheFile->recordUint32(matches, index, 8);
592 const quint32 firstMatchletOffset = m_cacheFile->recordUint32(matches, index, 12);
593 return matchMagicRule(m_cacheFile.get(), numMatchlets, firstMatchletOffset, data,
594 QMimeMagicRule::MaxRecursionDepth, budget);
595}
596
597void QMimeBinaryProvider::findByMagic(const QByteArray &data, QMimeMagicResult &result)
598{
599 const RecordList &matches = m_cacheFile->m_magicMatches;
600 qint64 budget = MagicMatchBudget;
601 for (quint32 i = 0; i < matches.count() && budget >= 0; ++i) {
602 if (magicRecordMatches(i, data, budget)) {
603 const int accuracy = static_cast<int>(m_cacheFile->recordUint32(matches, i, 0));
604 if (accuracy > result.accuracy) {
605 const quint32 mimeTypeOffset = m_cacheFile->recordUint32(matches, i, 4);
606 const QLatin1StringView candidate = m_cacheFile->getLatin1String(mimeTypeOffset);
607 if (candidate.isNull()) // corrupt cache: skip this record
608 continue;
609 result.accuracy = accuracy;
610 result.candidate = candidate;
611 // Return the first match, mime.cache is sorted
612 return;
613 }
614 }
615 }
616}
617
619QMimeBinaryProvider::checkMagicRules(const QString &mime, const QByteArray &data) const
620{
622 const RecordList &matches = m_cacheFile->m_magicMatches;
623 qint64 budget = MagicMatchBudget;
624 for (quint32 i = 0; i < matches.count() && budget >= 0; ++i) {
625 const quint32 mimeTypeOffset = m_cacheFile->recordUint32(matches, i, 4);
626 if (m_cacheFile->getLatin1String(mimeTypeOffset) != mime)
627 continue;
628 result.hasRules = true;
629 if (magicRecordMatches(i, data, budget)) {
630 result.matched = true;
631 break;
632 }
633 }
634 return result;
635}
636
637void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
638{
639 const RecordList &parentList = m_cacheFile->m_parents;
640 const quint32 count = parentList.count();
641 if (!count)
642 return;
643 quint32 begin = 0;
644 quint32 end = count - 1;
645 while (begin <= end) {
646 const quint32 medium = (begin + end) / 2;
647 const quint32 mimeOffset = m_cacheFile->recordUint32(parentList, medium, 0);
648 const QLatin1StringView aMime = m_cacheFile->getLatin1String(mimeOffset);
649 if (aMime.isNull()) // corrupt cache: the search cannot continue
650 break;
651 const int cmp = aMime.compare(mime);
652 if (cmp < 0) {
653 begin = medium + 1;
654 } else if (cmp > 0) {
655 if (!medium)
656 break;
657 end = medium - 1;
658 } else {
659 // The parent list is reached by following an offset stored in the
660 // record, so it has to be bounds-checked here.
661 const quint32 parentsOffset = m_cacheFile->recordUint32(parentList, medium, 4);
662 const std::optional<quint32> numParentsOpt = m_cacheFile->getUint32(parentsOffset);
663 if (!numParentsOpt) // corrupt cache
664 break;
665 const quint32 numParents = *numParentsOpt;
666 for (quint32 i = 0; i < numParents; ++i) {
667 const std::optional<quint64> parentOff =
668 m_cacheFile->safeRecordOffset(parentsOffset, 4, i, 4);
669 if (!parentOff) // past end of file: no later index can be inside it
670 break;
671 const std::optional<quint32> parentOffsetOpt = m_cacheFile->getUint32(*parentOff);
672 if (!parentOffsetOpt) // corrupt cache: no later index can be inside the file either
673 break;
674 const QLatin1StringView parentL1 = m_cacheFile->getLatin1String(*parentOffsetOpt);
675 if (parentL1.isNull()) // corrupt cache: skip this record
676 break;
677 appendIfNew(result, QString(parentL1));
678 }
679 break;
680 }
681 }
682}
683
684QString QMimeBinaryProvider::resolveAlias(const QString &name)
685{
686 const RecordList &aliases = m_cacheFile->m_aliases;
687 quint32 begin = 0;
688 const quint32 count = aliases.count();
689 if (!count)
690 return QString();
691 quint32 end = count - 1;
692 while (begin <= end) {
693 const quint32 medium = (begin + end) / 2;
694 const quint32 aliasOffset = m_cacheFile->recordUint32(aliases, medium, 0);
695 const QLatin1StringView alias = m_cacheFile->getLatin1String(aliasOffset);
696 if (alias.isNull()) // corrupt cache: the search cannot continue
697 break;
698 const int cmp = alias.compare(name);
699 if (cmp < 0) {
700 begin = medium + 1;
701 } else if (cmp > 0) {
702 if (!medium)
703 break;
704 end = medium - 1;
705 } else {
706 const quint32 mimeOffset = m_cacheFile->recordUint32(aliases, medium, 4);
707 // if corrupt cache, returns QLatin1StringView(), which converts to a null QString
708 return m_cacheFile->getLatin1String(mimeOffset);
709 }
710 }
711 return QString();
712}
713
714void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
715{
716 const RecordList &aliases = m_cacheFile->m_aliases;
717 for (quint32 pos = 0; pos < aliases.count(); ++pos) {
718 const quint32 mimeOffset = m_cacheFile->recordUint32(aliases, pos, 4);
719 const QLatin1StringView mimeType = m_cacheFile->getLatin1String(mimeOffset);
720 if (mimeType.isNull()) // corrupt cache: skip this record
721 continue;
722
723 if (name == QLatin1StringView(mimeType)) {
724 const quint32 aliasOffset = m_cacheFile->recordUint32(aliases, pos, 0);
725 const QLatin1StringView aliasL1 = m_cacheFile->getLatin1String(aliasOffset);
726 if (aliasL1.isNull()) // corrupt cache: skip this record
727 continue;
728 appendIfNew(result, QString(aliasL1));
729 }
730 }
731}
732
733void QMimeBinaryProvider::loadMimeTypeList()
734{
735 if (!m_mimetypeListLoaded) {
736 m_mimetypeListLoaded = true;
737 m_mimetypeNames.clear();
738 // Unfortunately mime.cache doesn't have a full list of all mimetypes.
739 // So we have to parse the plain-text files called "types".
740 QFile file(m_directory + QStringView(u"/types"));
741 if (file.open(QIODevice::ReadOnly)) {
742 QByteArray line;
743 while (file.readLineInto(&line)) {
744 auto lineView = QByteArrayView(line);
745 if (lineView.endsWith('\n'))
746 lineView.chop(1);
747 m_mimetypeNames.insert(QString::fromLatin1(lineView));
748 }
749 }
750 }
751}
752
753void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result)
754{
755 loadMimeTypeList();
756 if (result.isEmpty()) {
757 result.reserve(m_mimetypeNames.size());
758 for (const QString &name : std::as_const(m_mimetypeNames))
759 result.append(QMimeType(QMimeTypePrivate(name)));
760 } else {
761 for (const QString &name : std::as_const(m_mimetypeNames))
762 if (std::find_if(result.constBegin(), result.constEnd(), [name](const QMimeType &mime) -> bool { return mime.name() == name; })
763 == result.constEnd())
764 result.append(QMimeType(QMimeTypePrivate(name)));
765 }
766}
767
768QMimeTypePrivate::LocaleHash QMimeBinaryProvider::localeComments(const QString &name)
769{
770 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
771 if (it != m_mimetypeExtra.cend())
772 return it->second.localeComments;
773 return {};
774}
775
776bool QMimeBinaryProvider::hasGlobDeleteAll(const QString &name)
777{
778 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
779 if (it != m_mimetypeExtra.cend())
780 return it->second.hasGlobDeleteAll;
781 return {};
782}
783
784QStringList QMimeBinaryProvider::globPatterns(const QString &name)
785{
786 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
787 if (it != m_mimetypeExtra.cend())
788 return it->second.globPatterns;
789 return {};
790}
791
792QMimeBinaryProvider::MimeTypeExtraMap::const_iterator
793QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName)
794{
795#if QT_CONFIG(xmlstreamreader)
796 auto [it, insertionOccurred] = m_mimetypeExtra.try_emplace(mimeName);
797 if (insertionOccurred) {
798 // load comment and globPatterns
799
800 // shared-mime-info since 1.3 lowercases the xml files
801 QFile qfile;
802 const QString mimeFile = m_directory + u'/' + mimeName.toLower() + ".xml"_L1;
803 qfile.setFileName(mimeFile);
804 if (!qfile.open(QFile::ReadOnly)) {
805 const QString fallbackMimeFile = m_directory + u'/' + mimeName + ".xml"_L1; // pre-1.3
806 qfile.setFileName(fallbackMimeFile);
807 if (!qfile.open(QFile::ReadOnly))
808 return it;
809 }
810
811 MimeTypeExtra &extra = it->second;
812 QString mainPattern;
813
814 QXmlStreamReader xml(&qfile);
815 if (xml.readNextStartElement()) {
816 if (xml.name() != "mime-type"_L1) {
817 return m_mimetypeExtra.cend();
818 }
819 const auto name = xml.attributes().value("type"_L1);
820 if (name.isEmpty())
821 return m_mimetypeExtra.cend();
822 if (name.compare(mimeName, Qt::CaseInsensitive))
823 qWarning() << "Got name" << name << "in file" << mimeFile << "expected" << mimeName;
824
825 while (xml.readNextStartElement()) {
826 const auto tag = xml.name();
827 if (tag == "comment"_L1) {
828 QString lang = xml.attributes().value("xml:lang"_L1).toString();
829 const QString text = xml.readElementText();
830 if (lang.isEmpty()) {
831 lang = "default"_L1; // no locale attribute provided, treat it as default.
832 }
833 extra.localeComments.insert(lang, text);
834 continue; // we called readElementText, so we're at the EndElement already.
835 } else if (tag == "glob-deleteall"_L1) { // as written out by shared-mime-info >= 0.70
836 extra.hasGlobDeleteAll = true;
837 } else if (tag == "glob"_L1) { // as written out by shared-mime-info >= 0.70
838 const QString pattern = xml.attributes().value("pattern"_L1).toString();
839 if (mainPattern.isEmpty() && pattern.startsWith(u'*')) {
840 mainPattern = pattern;
841 }
842 appendIfNew(extra.globPatterns, pattern);
843 }
844 xml.skipCurrentElement();
845 }
846 Q_ASSERT(xml.name() == "mime-type"_L1);
847 }
848
849 // Let's assume that shared-mime-info is at least version 0.70
850 // Otherwise we would need 1) a version check, and 2) code for parsing patterns from the globs file.
851 if (!mainPattern.isEmpty() &&
852 (extra.globPatterns.isEmpty() || extra.globPatterns.constFirst() != mainPattern)) {
853 // ensure it's first in the list of patterns
854 extra.globPatterns.removeAll(mainPattern);
855 extra.globPatterns.prepend(mainPattern);
856 }
857 }
858 return it;
859#else
860 Q_UNUSED(mimeName);
861 qWarning("Cannot load mime type since QXmlStreamReader is not available.");
862 return m_mimetypeExtra.cend();
863#endif // feature xmlstreamreader
864}
865
866// Binary search in the icons or generic-icons list
867QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, const RecordList &icons,
868 QStringView inputMime)
869{
870 quint32 begin = 0;
871 const quint32 count = icons.count();
872 if (!count)
873 return QLatin1StringView();
874 quint32 end = count - 1;
875 while (begin <= end) {
876 const quint32 medium = (begin + end) / 2;
877 const quint32 mimeOffset = cacheFile->recordUint32(icons, medium, 0);
878 const QLatin1StringView mime = cacheFile->getLatin1String(mimeOffset);
879 if (mime.isNull()) // corrupt cache: the search cannot continue
880 break;
881 const int cmp = mime.compare(inputMime);
882 if (cmp < 0) {
883 begin = medium + 1;
884 } else if (cmp > 0) {
885 if (!medium)
886 break;
887 end = medium - 1;
888 } else {
889 const quint32 iconOffset = cacheFile->recordUint32(icons, medium, 4);
890 // if corrupt cache, returns QLatin1StringView()
891 return cacheFile->getLatin1String(iconOffset);
892 }
893 }
894 return QLatin1StringView();
895}
896
897QString QMimeBinaryProvider::icon(const QString &name)
898{
899 return iconForMime(m_cacheFile.get(), m_cacheFile->m_icons, name);
900}
901
902QString QMimeBinaryProvider::genericIcon(const QString &name)
903{
904 return iconForMime(m_cacheFile.get(), m_cacheFile->m_genericIcons, name);
905}
906
907////
908
909#if QT_CONFIG(mimetype_database)
910static QString internalMimeFileName()
911{
912 return QStringLiteral("<internal MIME data>");
913}
914
915QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
916 : QMimeProviderBase(db, internalMimeFileName())
917{
918 static_assert(sizeof(mimetype_database), "Bundled MIME database is empty");
919 static_assert(sizeof(mimetype_database) <= MimeTypeDatabaseOriginalSize,
920 "Compressed MIME database is larger than the original size");
921 static_assert(MimeTypeDatabaseOriginalSize <= 16*1024*1024,
922 "Bundled MIME database is too big");
923 const char *data = reinterpret_cast<const char *>(mimetype_database);
924 qsizetype size = MimeTypeDatabaseOriginalSize;
925
926#ifdef MIME_DATABASE_IS_ZSTD
927 // uncompress with libzstd
928 std::unique_ptr<char []> uncompressed(new char[size]);
929 size = ZSTD_decompress(uncompressed.get(), size, mimetype_database, sizeof(mimetype_database));
930 Q_ASSERT(!ZSTD_isError(size));
931 data = uncompressed.get();
932#elif defined(MIME_DATABASE_IS_GZIP)
933 std::unique_ptr<char []> uncompressed(new char[size]);
934 z_stream zs = {};
935 zs.next_in = const_cast<Bytef *>(mimetype_database);
936 zs.avail_in = sizeof(mimetype_database);
937 zs.next_out = reinterpret_cast<Bytef *>(uncompressed.get());
938 zs.avail_out = size;
939
940 int res = inflateInit2(&zs, MAX_WBITS | 32);
941 Q_ASSERT(res == Z_OK);
942 res = inflate(&zs, Z_FINISH);
943 Q_ASSERT(res == Z_STREAM_END);
944 res = inflateEnd(&zs);
945 Q_ASSERT(res == Z_OK);
946
947 data = uncompressed.get();
948 size = zs.total_out;
949#endif
950
951 load(data, size);
952 sortMagicMatchers();
953}
954#else // !QT_CONFIG(mimetype_database)
955// never called in release mode, but some debug builds may need
956// this to be defined.
958 : QMimeProviderBase(db, QString())
959{
960 Q_UNREACHABLE();
961}
962#endif // QT_CONFIG(mimetype_database)
963
964QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory)
965 : QMimeProviderBase(db, directory)
966{
968}
969
970QMimeXMLProvider::~QMimeXMLProvider()
971{
972}
973
974bool QMimeXMLProvider::isValid()
975{
976 // If you change this method, adjust the logic in QMimeDatabasePrivate::loadProviders,
977 // which assumes isValid==false is only possible in QMimeBinaryProvider.
978 return true;
979}
980
981bool QMimeXMLProvider::isInternalDatabase() const
982{
983#if QT_CONFIG(mimetype_database)
984 return m_directory == internalMimeFileName();
985#else
986 return false;
987#endif
988}
989
990bool QMimeXMLProvider::knowsMimeType(const QString &name)
991{
992 return m_nameMimeTypeMap.contains(name);
993}
994
995void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
996{
997 auto filterFunc = [this](const QString &name) { return !isMimeTypeGlobsExcluded(name); };
998 m_mimeTypeGlobs.matchingGlobs(fileName, result, filterFunc);
999}
1000
1001void QMimeXMLProvider::findByMagic(const QByteArray &data, QMimeMagicResult &result)
1002{
1003 for (const QMimeMagicRuleMatcher &matcher : std::as_const(m_magicMatchers)) {
1004 if (matcher.matches(data)) {
1005 const int priority = matcher.priority();
1006 if (priority > result.accuracy) {
1007 result.accuracy = priority;
1008 result.candidate = matcher.mimetype();
1009 }
1010 }
1011 }
1012}
1013
1015QMimeXMLProvider::checkMagicRules(const QString &mime, const QByteArray &data) const
1016{
1017 QMimeMagicCheckResult result;
1018 for (const QMimeMagicRuleMatcher &matcher : m_magicMatchers) {
1019 if (matcher.mimetype() != mime)
1020 continue;
1021 result.hasRules = true;
1022 if (matcher.matches(data)) {
1023 result.matched = true;
1024 break;
1025 }
1026 }
1027 return result;
1028}
1029
1030void QMimeXMLProvider::ensureLoaded()
1031{
1032 QStringList allFiles;
1033 const QString packageDir = m_directory + QStringView(u"/packages");
1034 for (const auto &entry : QDirListing(packageDir, QDirListing::IteratorFlag::FilesOnly
1035 | QDirListing::IteratorFlag::ResolveSymlinks))
1036 allFiles.emplace_back(packageDir + u'/' + entry.fileName());
1037
1038 if (m_allFiles == allFiles)
1039 return;
1040 m_allFiles = allFiles;
1041
1042 m_nameMimeTypeMap.clear();
1043 m_aliases.clear();
1044 m_parents.clear();
1045 m_mimeTypeGlobs.clear();
1046 m_magicMatchers.clear();
1047
1048 //qDebug() << "Loading" << m_allFiles;
1049
1050 for (const QString &file : std::as_const(allFiles))
1051 load(file);
1052 sortMagicMatchers();
1053}
1054
1055// Same order as in mime.cache: highest priority first, then by mimetype name,
1056// so that both providers pick the same mimetype when several rules match.
1057void QMimeXMLProvider::sortMagicMatchers()
1058{
1059 std::stable_sort(m_magicMatchers.begin(), m_magicMatchers.end(),
1060 [](const QMimeMagicRuleMatcher &a, const QMimeMagicRuleMatcher &b) {
1061 if (a.priority() != b.priority())
1062 return a.priority() > b.priority();
1063 return a.mimetype() < b.mimetype();
1064 });
1065}
1066
1067QMimeTypePrivate::LocaleHash QMimeXMLProvider::localeComments(const QString &name)
1068{
1069 return m_nameMimeTypeMap.value(name).localeComments;
1070}
1071
1072bool QMimeXMLProvider::hasGlobDeleteAll(const QString &name)
1073{
1074 return m_nameMimeTypeMap.value(name).hasGlobDeleteAll;
1075}
1076
1077QStringList QMimeXMLProvider::globPatterns(const QString &name)
1078{
1079 return m_nameMimeTypeMap.value(name).globPatterns;
1080}
1081
1082QString QMimeXMLProvider::icon(const QString &name)
1083{
1084 return m_nameMimeTypeMap.value(name).iconName;
1085}
1086
1087QString QMimeXMLProvider::genericIcon(const QString &name)
1088{
1089 return m_nameMimeTypeMap.value(name).genericIconName;
1090}
1091
1092void QMimeXMLProvider::load(const QString &fileName)
1093{
1094 QString errorMessage;
1095 if (!load(fileName, &errorMessage))
1096 qWarning("QMimeDatabase: Error loading %ls\n%ls", qUtf16Printable(fileName), qUtf16Printable(errorMessage));
1097}
1098
1099bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage)
1100{
1101 QFile file(fileName);
1102 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
1103 if (errorMessage)
1104 *errorMessage = "Cannot open "_L1 + fileName + ": "_L1 + file.errorString();
1105 return false;
1106 }
1107
1108 if (errorMessage)
1109 errorMessage->clear();
1110
1111 QMimeTypeParser parser(*this);
1112 return parser.parse(&file, fileName, errorMessage);
1113}
1114
1115#if QT_CONFIG(mimetype_database)
1116void QMimeXMLProvider::load(const char *data, qsizetype len)
1117{
1118 QBuffer buffer;
1119 buffer.setData(QByteArray::fromRawData(data, len));
1120 buffer.open(QIODevice::ReadOnly);
1121 QString errorMessage;
1122 QMimeTypeParser parser(*this);
1123 if (!parser.parse(&buffer, internalMimeFileName(), &errorMessage))
1124 qWarning("QMimeDatabase: Error loading internal MIME data\n%s", qPrintable(errorMessage));
1125}
1126#endif
1127
1128void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob)
1129{
1130 m_mimeTypeGlobs.addGlob(glob);
1131}
1132
1133void QMimeXMLProvider::addMimeType(const QMimeTypeXMLData &mt)
1134{
1135 m_nameMimeTypeMap.insert(mt.name, mt);
1136}
1137
1138void QMimeXMLProvider::addParents(const QString &mime, QStringList &result)
1139{
1140 const QStringList parents = m_parents.value(mime);
1141 for (const QString &parent : parents) {
1142 if (!result.contains(parent))
1143 result.append(parent);
1144 }
1145}
1146
1147void QMimeXMLProvider::addParent(const QString &child, const QString &parent)
1148{
1149 m_parents[child].append(parent);
1150}
1151
1152void QMimeXMLProvider::addAliases(const QString &name, QStringList &result)
1153{
1154 // Iterate through the whole hash. This method is rarely used.
1155 for (const auto &[alias, mimeName] : std::as_const(m_aliases).asKeyValueRange()) {
1156 if (mimeName == name)
1157 appendIfNew(result, alias);
1158 }
1159}
1160
1161QString QMimeXMLProvider::resolveAlias(const QString &name)
1162{
1163 return m_aliases.value(name);
1164}
1165
1166void QMimeXMLProvider::addAlias(const QString &alias, const QString &name)
1167{
1168 m_aliases.insert(alias, name);
1169}
1170
1171void QMimeXMLProvider::addAllMimeTypes(QList<QMimeType> &result)
1172{
1173 if (result.isEmpty()) { // fast path
1174 for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd();
1175 it != end; ++it) {
1176 result.append(QMimeType(QMimeTypePrivate(it.value().name)));
1177 }
1178 } else {
1179 for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd() ; it != end ; ++it) {
1180 const QString newMime = it.key();
1181 if (std::find_if(result.constBegin(), result.constEnd(), [newMime](const QMimeType &mime) -> bool { return mime.name() == newMime; })
1182 == result.constEnd())
1183 result.append(QMimeType(QMimeTypePrivate(it.value().name)));
1184 }
1185 }
1186}
1187
1188void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
1189{
1190 m_magicMatchers.append(matcher);
1191}
1192
1193QT_END_NAMESPACE
void addGlob(const QMimeGlobPattern &glob)
quint64 recordOffset(quint32 index) const
bool loadIndirect(const CacheFile *cacheFile, quint64 posListOffset, quint64 firstRecordPos, quint32 stride)
bool load(const CacheFile *cacheFile, quint64 posListOffset, quint32 stride)
void addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) override
QString resolveAlias(const QString &name) override
void findByMagic(const QByteArray &data, QMimeMagicResult &result) override
bool knowsMimeType(const QString &name) override
QString icon(const QString &name) override
bool hasGlobDeleteAll(const QString &name) override
void ensureLoaded() override
bool isValid() override
void addAliases(const QString &name, QStringList &result) override
virtual ~QMimeBinaryProvider()
void addAllMimeTypes(QList< QMimeType > &result) override
void addParents(const QString &mime, QStringList &result) override
QMimeMagicCheckResult checkMagicRules(const QString &mime, const QByteArray &data) const override
QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory)
QStringList globPatterns(const QString &name) override
QString genericIcon(const QString &name) override
bool isInternalDatabase() const override
QMimeTypePrivate::LocaleHash localeComments(const QString &name) override
The QMimeGlobPattern class contains the glob pattern for file names for MIME type matching.
bool isMimeTypeGlobsExcluded(const QString &name) const
QMimeProviderBase * m_overrideProvider
QMimeProviderBase * overrideProvider() const
void setOverrideProvider(QMimeProviderBase *provider)
QMimeDatabasePrivate * m_db
\inmodule QtCore
QMimeTypeParser(QMimeXMLProvider &provider)
void addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) override
void addGlobPattern(const QMimeGlobPattern &glob)
void addAliases(const QString &name, QStringList &result) override
void addAlias(const QString &alias, const QString &name)
QMimeTypePrivate::LocaleHash localeComments(const QString &name) override
QStringList globPatterns(const QString &name) override
bool load(const QString &fileName, QString *errorMessage)
QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory)
void ensureLoaded() override
bool knowsMimeType(const QString &name) override
bool isValid() override
bool isInternalDatabase() const override
QString resolveAlias(const QString &name) override
void addMimeType(const QMimeTypeXMLData &mt)
QMimeMagicCheckResult checkMagicRules(const QString &mime, const QByteArray &data) const override
void findByMagic(const QByteArray &data, QMimeMagicResult &result) override
QString icon(const QString &name) override
void addParents(const QString &mime, QStringList &result) override
void addAllMimeTypes(QList< QMimeType > &result) override
QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
void addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
bool hasGlobDeleteAll(const QString &name) override
void addParent(const QString &child, const QString &parent)
QString genericIcon(const QString &name) override
@ PosMagicListOffset
@ PosGenericIconsListOffset
@ PosIconsListOffset
@ PosParentListOffset
@ PosLiteralListOffset
@ PosAliasListOffset
@ PosGlobListOffset
@ PosReverseSuffixTreeOffset
@ AliasRecordSize
@ SuffixNodeSize
@ ParentRecordSize
@ IconRecordSize
@ GlobRecordSize
@ MagicMatchletSize
@ MagicMatchSize
@ PosFirstRootOffset
@ PosFirstMatchOffset
static void appendIfNew(QStringList &list, const QString &str)
static constexpr qint64 MagicMatchBudget
std::optional< quint32 > getUint32(quint64 offset, quint64 extra=0) const
std::optional< quint16 > getUint16(quint64 offset) const
quint32 recordUint32(const RecordList &list, quint32 index, quint32 fieldOffset) const
QLatin1StringView getLatin1String(quint64 offset) const
CacheFile(const QString &fileName)
std::optional< quint64 > safeRecordOffset(quint64 base, quint64 headerSize, quint32 index, quint32 stride) const
const char * getData(quint64 offset, quint64 length) const