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 | |
85 | QT_BEGIN_NAMESPACE |
86 | |
87 | QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory) |
88 | : m_db(db), m_directory(directory) |
89 | { |
90 | } |
91 | |
92 | |
93 | QMimeBinaryProvider::QMimeBinaryProvider(QMimeDatabasePrivate *db, const QString &directory) |
94 | : QMimeProviderBase(db, directory), m_mimetypeListLoaded(false) |
95 | { |
96 | ensureLoaded(); |
97 | } |
98 | |
99 | struct 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 | |
126 | QMimeBinaryProvider::CacheFile::CacheFile(const QString &fileName) |
127 | : file(fileName), m_valid(false) |
128 | { |
129 | load(); |
130 | } |
131 | |
132 | QMimeBinaryProvider::CacheFile::~CacheFile() |
133 | { |
134 | } |
135 | |
136 | bool 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 | |
150 | bool QMimeBinaryProvider::CacheFile::reload() |
151 | { |
152 | m_valid = false; |
153 | if (file.isOpen()) { |
154 | file.close(); |
155 | } |
156 | data = nullptr; |
157 | return load(); |
158 | } |
159 | |
160 | QMimeBinaryProvider::~QMimeBinaryProvider() |
161 | { |
162 | delete m_cacheFile; |
163 | } |
164 | |
165 | bool QMimeBinaryProvider::isValid() |
166 | { |
167 | return m_cacheFile != nullptr; |
168 | } |
169 | |
170 | bool QMimeBinaryProvider::isInternalDatabase() const |
171 | { |
172 | return false; |
173 | } |
174 | |
175 | // Position of the "list offsets" values, at the beginning of the mime.cache file |
176 | enum { |
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 | |
188 | bool 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 | |
200 | void 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 | |
218 | static 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 | |
230 | QMimeType 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 | |
239 | void 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 | |
258 | void 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 | |
280 | bool 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 | |
324 | bool 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 | |
352 | void 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 | |
375 | void 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 | |
408 | QString 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 | |
434 | void 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 | |
454 | void 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 | |
472 | void 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 | |
487 | void 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 |
584 | QLatin1String 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 | |
608 | void 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 | |
617 | void 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) |
629 | static QString internalMimeFileName() |
630 | { |
631 | return QStringLiteral("<internal MIME data>" ); |
632 | } |
633 | |
634 | QMimeXMLProvider::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. |
675 | QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, InternalDatabaseEnum) |
676 | : QMimeProviderBase(db, QString()) |
677 | { |
678 | Q_UNREACHABLE(); |
679 | } |
680 | #endif // QT_CONFIG(mimetype_database) |
681 | |
682 | QMimeXMLProvider::QMimeXMLProvider(QMimeDatabasePrivate *db, const QString &directory) |
683 | : QMimeProviderBase(db, directory) |
684 | { |
685 | ensureLoaded(); |
686 | } |
687 | |
688 | QMimeXMLProvider::~QMimeXMLProvider() |
689 | { |
690 | } |
691 | |
692 | bool 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 | |
699 | bool QMimeXMLProvider::isInternalDatabase() const |
700 | { |
701 | #if QT_CONFIG(mimetype_database) |
702 | return m_directory == internalMimeFileName(); |
703 | #else |
704 | return false; |
705 | #endif |
706 | } |
707 | |
708 | QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name) |
709 | { |
710 | return m_nameMimeTypeMap.value(akey: name); |
711 | } |
712 | |
713 | void QMimeXMLProvider::addFileNameMatches(const QString &fileName, QMimeGlobMatchResult &result) |
714 | { |
715 | m_mimeTypeGlobs.matchingGlobs(fileName, result); |
716 | } |
717 | |
718 | void 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 | |
736 | void 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 | |
762 | void 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 | |
769 | bool 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) |
786 | void 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 | |
798 | void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob) |
799 | { |
800 | m_mimeTypeGlobs.addGlob(glob); |
801 | } |
802 | |
803 | void QMimeXMLProvider::addMimeType(const QMimeType &mt) |
804 | { |
805 | Q_ASSERT(!mt.d.data()->fromCache); |
806 | m_nameMimeTypeMap.insert(akey: mt.name(), avalue: mt); |
807 | } |
808 | |
809 | void 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 | |
817 | void QMimeXMLProvider::addParent(const QString &child, const QString &parent) |
818 | { |
819 | m_parents[child].append(t: parent); |
820 | } |
821 | |
822 | void 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 | |
834 | QString QMimeXMLProvider::resolveAlias(const QString &name) |
835 | { |
836 | return m_aliases.value(akey: name); |
837 | } |
838 | |
839 | void QMimeXMLProvider::addAlias(const QString &alias, const QString &name) |
840 | { |
841 | m_aliases.insert(akey: alias, avalue: name); |
842 | } |
843 | |
844 | void 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 | |
858 | void QMimeXMLProvider::addMagicMatcher(const QMimeMagicRuleMatcher &matcher) |
859 | { |
860 | m_magicMatchers.append(t: matcher); |
861 | } |
862 | |
863 | QT_END_NAMESPACE |
864 | |