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
54struct QMimeBinaryProvider::CacheFile
55{
56 CacheFile(const QString &fileName);
58
59 bool isValid() const { return m_valid; }
60 inline quint16 getUint16(int offset) const
61 {
62 return qFromBigEndian(*reinterpret_cast<quint16 *>(data + offset));
63 }
64 inline quint32 getUint32(int offset) const
65 {
66 return qFromBigEndian(*reinterpret_cast<quint32 *>(data + offset));
67 }
68 inline const char *getCharStar(int offset) const
69 {
70 return reinterpret_cast<const char *>(data + offset);
71 }
72 bool load();
73 bool reload();
74
78 bool m_valid;
79};
80
81static inline void appendIfNew(QStringList &list, const QString &str)
82{
83 if (!list.contains(str))
84 list.push_back(str);
85}
86
87QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory)
88 : m_db(db), m_directory(directory)
89{
90}
91
96
101
102bool QMimeProviderBase::isMimeTypeGlobsExcluded(const QString &name) const
103{
104 if (m_overrideProvider) {
105 if (m_overrideProvider->hasGlobDeleteAll(name))
106 return true;
107 return m_overrideProvider->isMimeTypeGlobsExcluded(name);
108 }
109 return false;
110}
111
112QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory)
113 : QMimeProviderBase(db, directory), m_mimetypeListLoaded(false)
114{
116}
117
118QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName)
119 : file(fileName), m_valid(false)
120{
121 load();
122}
123
124QMimeBinaryProvider::CacheFile::~CacheFile()
125{
126}
127
128bool QMimeBinaryProvider::CacheFile::load()
129{
130 if (!file.open(QIODevice::ReadOnly))
131 return false;
132 data = file.map(0, file.size());
133 if (data) {
134 const int major = getUint16(0);
135 const int minor = getUint16(2);
136 m_valid = (major == 1 && minor >= 1 && minor <= 2);
137 }
138 m_mtime = QFileInfo(file).lastModified(QTimeZone::UTC);
139 return m_valid;
140}
141
142bool QMimeBinaryProvider::CacheFile::reload()
143{
144 m_valid = false;
145 if (file.isOpen()) {
146 file.close();
147 }
148 data = nullptr;
149 return load();
150}
151
152QMimeBinaryProvider::~QMimeBinaryProvider() = default;
153
154bool QMimeBinaryProvider::isValid()
155{
156 return m_cacheFile != nullptr;
157}
158
159bool QMimeBinaryProvider::isInternalDatabase() const
160{
161 return false;
162}
163
164// Position of the "list offsets" values, at the beginning of the mime.cache file
165enum {
172 // PosNamespaceListOffset = 28,
175};
176
177bool QMimeBinaryProvider::checkCacheChanged()
178{
179 QFileInfo fileInfo(m_cacheFile->file);
180 if (fileInfo.lastModified(QTimeZone::UTC) > m_cacheFile->m_mtime) {
181 // Deletion can't happen by just running update-mime-database.
182 // But the user could use rm -rf :-)
183 m_cacheFile->reload(); // will mark itself as invalid on failure
184 return true;
185 }
186 return false;
187}
188
189void QMimeBinaryProvider::ensureLoaded()
190{
191 if (!m_cacheFile) {
192 const QString cacheFileName = m_directory + "/mime.cache"_L1;
193 m_cacheFile = std::make_unique<CacheFile>(cacheFileName);
194 m_mimetypeListLoaded = false;
195 m_mimetypeExtra.clear();
196 } else {
197 if (checkCacheChanged()) {
198 m_mimetypeListLoaded = false;
199 m_mimetypeExtra.clear();
200 } else {
201 return; // nothing to do
202 }
203 }
204 if (!m_cacheFile->isValid()) // verify existence and version
205 m_cacheFile.reset();
206}
207
208bool QMimeBinaryProvider::knowsMimeType(const QString &name)
209{
210 if (!m_mimetypeListLoaded)
211 loadMimeTypeList();
212 return m_mimetypeNames.contains(name);
213}
214
215void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
216{
217 if (fileName.isEmpty())
218 return;
219 Q_ASSERT(m_cacheFile);
220 int numMatches = 0;
221 // Check literals (e.g. "Makefile")
222 numMatches = matchGlobList(result, m_cacheFile.get(),
223 m_cacheFile->getUint32(PosLiteralListOffset), fileName);
224 // Check the very common *.txt cases with the suffix tree
225 if (numMatches == 0) {
226 const QString lowerFileName = fileName.toLower();
227 const int reverseSuffixTreeOffset = m_cacheFile->getUint32(PosReverseSuffixTreeOffset);
228 const int numRoots = m_cacheFile->getUint32(reverseSuffixTreeOffset);
229 const int firstRootOffset = m_cacheFile->getUint32(reverseSuffixTreeOffset + 4);
230 if (matchSuffixTree(result, m_cacheFile.get(), numRoots, firstRootOffset, lowerFileName,
231 lowerFileName.size() - 1, false)) {
232 ++numMatches;
233 } else if (matchSuffixTree(result, m_cacheFile.get(), numRoots, firstRootOffset, fileName,
234 fileName.size() - 1, true)) {
235 ++numMatches;
236 }
237 }
238 // Check complex globs (e.g. "callgrind.out[0-9]*" or "README*")
239 if (numMatches == 0)
240 matchGlobList(result, m_cacheFile.get(), m_cacheFile->getUint32(PosGlobListOffset),
241 fileName);
242}
243
244int QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off,
245 const QString &fileName)
246{
247 int numMatches = 0;
248 const int numGlobs = cacheFile->getUint32(off);
249 //qDebug() << "Loading" << numGlobs << "globs from" << cacheFile->file.fileName() << "at offset" << cacheFile->globListOffset;
250 for (int i = 0; i < numGlobs; ++i) {
251 const int globOffset = cacheFile->getUint32(off + 4 + 12 * i);
252 const int mimeTypeOffset = cacheFile->getUint32(off + 4 + 12 * i + 4);
253 const int flagsAndWeight = cacheFile->getUint32(off + 4 + 12 * i + 8);
254 const int weight = flagsAndWeight & 0xff;
255 const bool caseSensitive = flagsAndWeight & 0x100;
256 const Qt::CaseSensitivity qtCaseSensitive = caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive;
257 const QString pattern = QLatin1StringView(cacheFile->getCharStar(globOffset));
258
259 const QLatin1StringView mimeType(cacheFile->getCharStar(mimeTypeOffset));
260 //qDebug() << pattern << mimeType << weight << caseSensitive;
261 if (isMimeTypeGlobsExcluded(mimeType))
262 continue;
263
264 QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive);
265 if (glob.matchFileName(fileName)) {
266 result.addMatch(mimeType, weight, pattern);
267 ++numMatches;
268 }
269 }
270 return numMatches;
271}
272
273bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result,
274 QMimeBinaryProvider::CacheFile *cacheFile, int numEntries,
275 int firstOffset, const QString &fileName,
276 qsizetype charPos, bool caseSensitiveCheck)
277{
278 QChar fileChar = fileName[charPos];
279 if (fileChar.isNull())
280 return false;
281 int min = 0;
282 int max = numEntries - 1;
283 while (min <= max) {
284 const int mid = (min + max) / 2;
285 const int off = firstOffset + 12 * mid;
286 const QChar ch = char16_t(cacheFile->getUint32(off));
287 if (ch < fileChar)
288 min = mid + 1;
289 else if (ch > fileChar)
290 max = mid - 1;
291 else {
292 --charPos;
293 int numChildren = cacheFile->getUint32(off + 4);
294 int childrenOffset = cacheFile->getUint32(off + 8);
295 bool success = false;
296 if (charPos > 0)
297 success = matchSuffixTree(result, cacheFile, numChildren, childrenOffset, fileName, charPos, caseSensitiveCheck);
298 if (!success) {
299 for (int i = 0; i < numChildren; ++i) {
300 const int childOff = childrenOffset + 12 * i;
301 const int mch = cacheFile->getUint32(childOff);
302 if (mch != 0)
303 break;
304 const int mimeTypeOffset = cacheFile->getUint32(childOff + 4);
305 const QLatin1StringView mimeType(cacheFile->getCharStar(mimeTypeOffset));
306 if (isMimeTypeGlobsExcluded(mimeType))
307 continue;
308 const int flagsAndWeight = cacheFile->getUint32(childOff + 8);
309 const int weight = flagsAndWeight & 0xff;
310 const bool caseSensitive = flagsAndWeight & 0x100;
311 if (caseSensitiveCheck || !caseSensitive) {
312 result.addMatch(mimeType, weight,
313 u'*' + QStringView{ fileName }.mid(charPos + 1),
314 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 qsizetype 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, QMimeMagicResult &result)
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.get(), numMatchlets, firstMatchletOffset, data)) {
365 const int mimeTypeOffset = m_cacheFile->getUint32(off + 4);
366 const char *mimeType = m_cacheFile->getCharStar(mimeTypeOffset);
367 const int accuracy = static_cast<int>(m_cacheFile->getUint32(off));
368 if (accuracy > result.accuracy) {
369 result.accuracy = accuracy;
370 result.candidate = QString::fromLatin1(mimeType);
371 // Return the first match, mime.cache is sorted
372 return;
373 }
374 }
375 }
376}
377
378void QMimeBinaryProvider::addParents(const QString &mime, QStringList &result)
379{
380 const QByteArray mimeStr = mime.toLatin1();
381 const int parentListOffset = m_cacheFile->getUint32(PosParentListOffset);
382 const int numEntries = m_cacheFile->getUint32(parentListOffset);
383
384 int begin = 0;
385 int end = numEntries - 1;
386 while (begin <= end) {
387 const int medium = (begin + end) / 2;
388 const int off = parentListOffset + 4 + 8 * medium;
389 const int mimeOffset = m_cacheFile->getUint32(off);
390 const char *aMime = m_cacheFile->getCharStar(mimeOffset);
391 const int cmp = qstrcmp(aMime, mimeStr);
392 if (cmp < 0) {
393 begin = medium + 1;
394 } else if (cmp > 0) {
395 end = medium - 1;
396 } else {
397 const int parentsOffset = m_cacheFile->getUint32(off + 4);
398 const int numParents = m_cacheFile->getUint32(parentsOffset);
399 for (int i = 0; i < numParents; ++i) {
400 const int parentOffset = m_cacheFile->getUint32(parentsOffset + 4 + 4 * i);
401 const char *aParent = m_cacheFile->getCharStar(parentOffset);
402 const QString strParent = QString::fromLatin1(aParent);
403 appendIfNew(result, strParent);
404 }
405 break;
406 }
407 }
408}
409
410QString QMimeBinaryProvider::resolveAlias(const QString &name)
411{
412 const QByteArray input = name.toLatin1();
413 const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
414 const int numEntries = m_cacheFile->getUint32(aliasListOffset);
415 int begin = 0;
416 int end = numEntries - 1;
417 while (begin <= end) {
418 const int medium = (begin + end) / 2;
419 const int off = aliasListOffset + 4 + 8 * medium;
420 const int aliasOffset = m_cacheFile->getUint32(off);
421 const char *alias = m_cacheFile->getCharStar(aliasOffset);
422 const int cmp = qstrcmp(alias, input);
423 if (cmp < 0) {
424 begin = medium + 1;
425 } else if (cmp > 0) {
426 end = medium - 1;
427 } else {
428 const int mimeOffset = m_cacheFile->getUint32(off + 4);
429 const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
430 return QLatin1StringView(mimeType);
431 }
432 }
433 return QString();
434}
435
436void QMimeBinaryProvider::addAliases(const QString &name, QStringList &result)
437{
438 const QByteArray input = name.toLatin1();
439 const int aliasListOffset = m_cacheFile->getUint32(PosAliasListOffset);
440 const int numEntries = m_cacheFile->getUint32(aliasListOffset);
441 for (int pos = 0; pos < numEntries; ++pos) {
442 const int off = aliasListOffset + 4 + 8 * pos;
443 const int mimeOffset = m_cacheFile->getUint32(off + 4);
444 const char *mimeType = m_cacheFile->getCharStar(mimeOffset);
445
446 if (input == mimeType) {
447 const int aliasOffset = m_cacheFile->getUint32(off);
448 const char *alias = m_cacheFile->getCharStar(aliasOffset);
449 const QString strAlias = QString::fromLatin1(alias);
450 appendIfNew(result, 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 + QStringView(u"/types"));
463 if (file.open(QIODevice::ReadOnly)) {
464 QByteArray line;
465 while (file.readLineInto(&line)) {
466 auto lineView = QByteArrayView(line);
467 if (lineView.endsWith('\n'))
468 lineView.chop(1);
469 m_mimetypeNames.insert(QString::fromLatin1(lineView));
470 }
471 }
472 }
473}
474
475void QMimeBinaryProvider::addAllMimeTypes(QList<QMimeType> &result)
476{
477 loadMimeTypeList();
478 if (result.isEmpty()) {
479 result.reserve(m_mimetypeNames.size());
480 for (const QString &name : std::as_const(m_mimetypeNames))
481 result.append(QMimeType(QMimeTypePrivate(name)));
482 } else {
483 for (const QString &name : std::as_const(m_mimetypeNames))
484 if (std::find_if(result.constBegin(), result.constEnd(), [name](const QMimeType &mime) -> bool { return mime.name() == name; })
485 == result.constEnd())
486 result.append(QMimeType(QMimeTypePrivate(name)));
487 }
488}
489
490QMimeTypePrivate::LocaleHash QMimeBinaryProvider::localeComments(const QString &name)
491{
492 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
493 if (it != m_mimetypeExtra.cend())
494 return it->second.localeComments;
495 return {};
496}
497
498bool QMimeBinaryProvider::hasGlobDeleteAll(const QString &name)
499{
500 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
501 if (it != m_mimetypeExtra.cend())
502 return it->second.hasGlobDeleteAll;
503 return {};
504}
505
506QStringList QMimeBinaryProvider::globPatterns(const QString &name)
507{
508 MimeTypeExtraMap::const_iterator it = loadMimeTypeExtra(name);
509 if (it != m_mimetypeExtra.cend())
510 return it->second.globPatterns;
511 return {};
512}
513
514QMimeBinaryProvider::MimeTypeExtraMap::const_iterator
515QMimeBinaryProvider::loadMimeTypeExtra(const QString &mimeName)
516{
517#if QT_CONFIG(xmlstreamreader)
518 auto it = m_mimetypeExtra.find(mimeName);
519 if (it == m_mimetypeExtra.cend()) {
520 // load comment and globPatterns
521
522 // shared-mime-info since 1.3 lowercases the xml files
523 QString mimeFile = m_directory + u'/' + mimeName.toLower() + ".xml"_L1;
524 if (!QFile::exists(mimeFile))
525 mimeFile = m_directory + u'/' + mimeName + ".xml"_L1; // pre-1.3
526
527 QFile qfile(mimeFile);
528 if (!qfile.open(QFile::ReadOnly))
529 return m_mimetypeExtra.cend();
530
531 it = m_mimetypeExtra.try_emplace(mimeName).first;
532 MimeTypeExtra &extra = it->second;
533 QString mainPattern;
534
535 QXmlStreamReader xml(&qfile);
536 if (xml.readNextStartElement()) {
537 if (xml.name() != "mime-type"_L1) {
538 return m_mimetypeExtra.cend();
539 }
540 const auto name = xml.attributes().value("type"_L1);
541 if (name.isEmpty())
542 return m_mimetypeExtra.cend();
543 if (name.compare(mimeName, Qt::CaseInsensitive))
544 qWarning() << "Got name" << name << "in file" << mimeFile << "expected" << mimeName;
545
546 while (xml.readNextStartElement()) {
547 const auto tag = xml.name();
548 if (tag == "comment"_L1) {
549 QString lang = xml.attributes().value("xml:lang"_L1).toString();
550 const QString text = xml.readElementText();
551 if (lang.isEmpty()) {
552 lang = "default"_L1; // no locale attribute provided, treat it as default.
553 }
554 extra.localeComments.insert(lang, text);
555 continue; // we called readElementText, so we're at the EndElement already.
556 } else if (tag == "glob-deleteall"_L1) { // as written out by shared-mime-info >= 0.70
557 extra.hasGlobDeleteAll = true;
558 } else if (tag == "glob"_L1) { // as written out by shared-mime-info >= 0.70
559 const QString pattern = xml.attributes().value("pattern"_L1).toString();
560 if (mainPattern.isEmpty() && pattern.startsWith(u'*')) {
561 mainPattern = pattern;
562 }
563 appendIfNew(extra.globPatterns, pattern);
564 }
565 xml.skipCurrentElement();
566 }
567 Q_ASSERT(xml.name() == "mime-type"_L1);
568 }
569
570 // Let's assume that shared-mime-info is at least version 0.70
571 // Otherwise we would need 1) a version check, and 2) code for parsing patterns from the globs file.
572 if (!mainPattern.isEmpty() &&
573 (extra.globPatterns.isEmpty() || extra.globPatterns.constFirst() != mainPattern)) {
574 // ensure it's first in the list of patterns
575 extra.globPatterns.removeAll(mainPattern);
576 extra.globPatterns.prepend(mainPattern);
577 }
578 }
579 return it;
580#else
581 Q_UNUSED(mimeName);
582 qWarning("Cannot load mime type since QXmlStreamReader is not available.");
583 return m_mimetypeExtra.cend();
584#endif // feature xmlstreamreader
585}
586
587// Binary search in the icons or generic-icons list
588QLatin1StringView QMimeBinaryProvider::iconForMime(CacheFile *cacheFile, int posListOffset,
589 const QByteArray &inputMime)
590{
591 const int iconsListOffset = cacheFile->getUint32(posListOffset);
592 const int numIcons = cacheFile->getUint32(iconsListOffset);
593 int begin = 0;
594 int end = numIcons - 1;
595 while (begin <= end) {
596 const int medium = (begin + end) / 2;
597 const int off = iconsListOffset + 4 + 8 * medium;
598 const int mimeOffset = cacheFile->getUint32(off);
599 const char *mime = cacheFile->getCharStar(mimeOffset);
600 const int cmp = qstrcmp(mime, inputMime);
601 if (cmp < 0)
602 begin = medium + 1;
603 else if (cmp > 0)
604 end = medium - 1;
605 else {
606 const int iconOffset = cacheFile->getUint32(off + 4);
607 return QLatin1StringView(cacheFile->getCharStar(iconOffset));
608 }
609 }
610 return QLatin1StringView();
611}
612
613QString QMimeBinaryProvider::icon(const QString &name)
614{
615 const QByteArray inputMime = name.toLatin1();
616 return iconForMime(m_cacheFile.get(), PosIconsListOffset, inputMime);
617}
618
619QString QMimeBinaryProvider::genericIcon(const QString &name)
620{
621 const QByteArray inputMime = name.toLatin1();
622 return iconForMime(m_cacheFile.get(), PosGenericIconsListOffset, inputMime);
623}
624
625////
626
627#if QT_CONFIG(mimetype_database)
628static QString internalMimeFileName()
629{
630 return QStringLiteral("<internal MIME data>");
631}
632
633QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum)
634 : QMimeProviderBase(db, internalMimeFileName())
635{
636 static_assert(sizeof(mimetype_database), "Bundled MIME database is empty");
637 static_assert(sizeof(mimetype_database) <= MimeTypeDatabaseOriginalSize,
638 "Compressed MIME database is larger than the original size");
639 static_assert(MimeTypeDatabaseOriginalSize <= 16*1024*1024,
640 "Bundled MIME database is too big");
641 const char *data = reinterpret_cast<const char *>(mimetype_database);
642 qsizetype size = MimeTypeDatabaseOriginalSize;
643
644#ifdef MIME_DATABASE_IS_ZSTD
645 // uncompress with libzstd
646 std::unique_ptr<char []> uncompressed(new char[size]);
647 size = ZSTD_decompress(uncompressed.get(), size, mimetype_database, sizeof(mimetype_database));
648 Q_ASSERT(!ZSTD_isError(size));
649 data = uncompressed.get();
650#elif defined(MIME_DATABASE_IS_GZIP)
651 std::unique_ptr<char []> uncompressed(new char[size]);
652 z_stream zs = {};
653 zs.next_in = const_cast<Bytef *>(mimetype_database);
654 zs.avail_in = sizeof(mimetype_database);
655 zs.next_out = reinterpret_cast<Bytef *>(uncompressed.get());
656 zs.avail_out = size;
657
658 int res = inflateInit2(&zs, MAX_WBITS | 32);
659 Q_ASSERT(res == Z_OK);
660 res = inflate(&zs, Z_FINISH);
661 Q_ASSERT(res == Z_STREAM_END);
662 res = inflateEnd(&zs);
663 Q_ASSERT(res == Z_OK);
664
665 data = uncompressed.get();
666 size = zs.total_out;
667#endif
668
669 load(data, size);
670}
671#else // !QT_CONFIG(mimetype_database)
672// never called in release mode, but some debug builds may need
673// this to be defined.
675 : QMimeProviderBase(db, QString())
676{
677 Q_UNREACHABLE();
678}
679#endif // QT_CONFIG(mimetype_database)
680
681QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory)
682 : QMimeProviderBase(db, directory)
683{
685}
686
687QMimeXMLProvider::~QMimeXMLProvider()
688{
689}
690
691bool QMimeXMLProvider::isValid()
692{
693 // If you change this method, adjust the logic in QMimeDatabasePrivate::loadProviders,
694 // which assumes isValid==false is only possible in QMimeBinaryProvider.
695 return true;
696}
697
698bool QMimeXMLProvider::isInternalDatabase() const
699{
700#if QT_CONFIG(mimetype_database)
701 return m_directory == internalMimeFileName();
702#else
703 return false;
704#endif
705}
706
707bool QMimeXMLProvider::knowsMimeType(const QString &name)
708{
709 return m_nameMimeTypeMap.contains(name);
710}
711
712void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result)
713{
714 auto filterFunc = [this](const QString &name) { return !isMimeTypeGlobsExcluded(name); };
715 m_mimeTypeGlobs.matchingGlobs(fileName, result, filterFunc);
716}
717
718void QMimeXMLProvider::findByMagic(const QByteArray &data, QMimeMagicResult &result)
719{
720 for (const QMimeMagicRuleMatcher &matcher : std::as_const(m_magicMatchers)) {
721 if (matcher.matches(data)) {
722 const int priority = matcher.priority();
723 if (priority < result.accuracy)
724 continue;
725 if (priority == result.accuracy) {
726 if (m_db->inherits(result.candidate, matcher.mimetype()))
727 continue;
728
729 if (!m_db->inherits(matcher.mimetype(), result.candidate)) {
730 // Two or more magic rules matching, both with the same priority but not
731 // connected with one another should not happen:
732 qWarning("QMimeXMLProvider: MimeType is ambiguous between %ls and %ls",
733 qUtf16Printable(result.candidate),
734 qUtf16Printable(matcher.mimetype()));
735 continue;
736 }
737 }
738
739 result.accuracy = priority;
740 result.candidate = matcher.mimetype();
741 }
742 }
743}
744
745void QMimeXMLProvider::ensureLoaded()
746{
747 QStringList allFiles;
748 const QString packageDir = m_directory + QStringView(u"/packages");
749 for (const auto &entry : QDirListing(packageDir, QDirListing::IteratorFlag::FilesOnly
750 | QDirListing::IteratorFlag::ResolveSymlinks))
751 allFiles.emplace_back(packageDir + u'/' + entry.fileName());
752
753 if (m_allFiles == allFiles)
754 return;
755 m_allFiles = allFiles;
756
757 m_nameMimeTypeMap.clear();
758 m_aliases.clear();
759 m_parents.clear();
760 m_mimeTypeGlobs.clear();
761 m_magicMatchers.clear();
762
763 //qDebug() << "Loading" << m_allFiles;
764
765 for (const QString &file : std::as_const(allFiles))
766 load(file);
767}
768
769QMimeTypePrivate::LocaleHash QMimeXMLProvider::localeComments(const QString &name)
770{
771 return m_nameMimeTypeMap.value(name).localeComments;
772}
773
774bool QMimeXMLProvider::hasGlobDeleteAll(const QString &name)
775{
776 return m_nameMimeTypeMap.value(name).hasGlobDeleteAll;
777}
778
779QStringList QMimeXMLProvider::globPatterns(const QString &name)
780{
781 return m_nameMimeTypeMap.value(name).globPatterns;
782}
783
784QString QMimeXMLProvider::icon(const QString &name)
785{
786 return m_nameMimeTypeMap.value(name).iconName;
787}
788
789QString QMimeXMLProvider::genericIcon(const QString &name)
790{
791 return m_nameMimeTypeMap.value(name).genericIconName;
792}
793
794void QMimeXMLProvider::load(const QString &fileName)
795{
796 QString errorMessage;
797 if (!load(fileName, &errorMessage))
798 qWarning("QMimeDatabase: Error loading %ls\n%ls", qUtf16Printable(fileName), qUtf16Printable(errorMessage));
799}
800
801bool QMimeXMLProvider::load(const QString &fileName, QString *errorMessage)
802{
803 QFile file(fileName);
804 if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
805 if (errorMessage)
806 *errorMessage = "Cannot open "_L1 + fileName + ": "_L1 + file.errorString();
807 return false;
808 }
809
810 if (errorMessage)
811 errorMessage->clear();
812
813 QMimeTypeParser parser(*this);
814 return parser.parse(&file, fileName, errorMessage);
815}
816
817#if QT_CONFIG(mimetype_database)
818void QMimeXMLProvider::load(const char *data, qsizetype len)
819{
820 QBuffer buffer;
821 buffer.setData(QByteArray::fromRawData(data, len));
822 buffer.open(QIODevice::ReadOnly);
823 QString errorMessage;
824 QMimeTypeParser parser(*this);
825 if (!parser.parse(&buffer, internalMimeFileName(), &errorMessage))
826 qWarning("QMimeDatabase: Error loading internal MIME data\n%s", qPrintable(errorMessage));
827}
828#endif
829
830void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob)
831{
832 m_mimeTypeGlobs.addGlob(glob);
833}
834
835void QMimeXMLProvider::addMimeType(const QMimeTypeXMLData &mt)
836{
837 m_nameMimeTypeMap.insert(mt.name, mt);
838}
839
840void QMimeXMLProvider::addParents(const QString &mime, QStringList &result)
841{
842 for (const QString &parent : m_parents.value(mime)) {
843 if (!result.contains(parent))
844 result.append(parent);
845 }
846}
847
848void QMimeXMLProvider::addParent(const QString &child, const QString &parent)
849{
850 m_parents[child].append(parent);
851}
852
853void QMimeXMLProvider::addAliases(const QString &name, QStringList &result)
854{
855 // Iterate through the whole hash. This method is rarely used.
856 for (const auto &[alias, mimeName] : std::as_const(m_aliases).asKeyValueRange()) {
857 if (mimeName == name)
858 appendIfNew(result, alias);
859 }
860}
861
862QString QMimeXMLProvider::resolveAlias(const QString &name)
863{
864 return m_aliases.value(name);
865}
866
867void QMimeXMLProvider::addAlias(const QString &alias, const QString &name)
868{
869 m_aliases.insert(alias, name);
870}
871
872void QMimeXMLProvider::addAllMimeTypes(QList<QMimeType> &result)
873{
874 if (result.isEmpty()) { // fast path
875 for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd();
876 it != end; ++it) {
877 result.append(QMimeType(QMimeTypePrivate(it.value().name)));
878 }
879 } else {
880 for (auto it = m_nameMimeTypeMap.constBegin(), end = m_nameMimeTypeMap.constEnd() ; it != end ; ++it) {
881 const QString newMime = it.key();
882 if (std::find_if(result.constBegin(), result.constEnd(), [newMime](const QMimeType &mime) -> bool { return mime.name() == newMime; })
883 == result.constEnd())
884 result.append(QMimeType(QMimeTypePrivate(it.value().name)));
885 }
886 }
887}
888
889void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher)
890{
891 m_magicMatchers.append(matcher);
892}
893
894QT_END_NAMESPACE
void addGlob(const QMimeGlobPattern &glob)
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
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)
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
static void appendIfNew(QStringList &list, const QString &str)
const char * getCharStar(int offset) const
quint16 getUint16(int offset) const
CacheFile(const QString &fileName)
quint32 getUint32(int offset) const