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

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