1 | // Copyright (C) 2016 The Qt Company Ltd. |
2 | // Copyright (C) 2015 Klaralvdalens Datakonsult AB, a KDAB Group company, info@kdab.com, author David Faure <david.faure@kdab.com> |
3 | // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only |
4 | |
5 | #include <qplatformdefs.h> // always first |
6 | |
7 | #include "qmimedatabase.h" |
8 | #include "qmimedatabase_p.h" |
9 | |
10 | #include "qmimeprovider_p.h" |
11 | #include "qmimetype_p.h" |
12 | |
13 | #include <private/qfilesystementry_p.h> |
14 | |
15 | #include <QtCore/QMap> |
16 | #include <QtCore/QFile> |
17 | #include <QtCore/QFileInfo> |
18 | #include <QtCore/QStandardPaths> |
19 | #include <QtCore/QBuffer> |
20 | #include <QtCore/QUrl> |
21 | #include <QtCore/QDebug> |
22 | |
23 | #include <algorithm> |
24 | #include <functional> |
25 | #include <stack> |
26 | |
27 | QT_BEGIN_NAMESPACE |
28 | |
29 | using namespace Qt::StringLiterals; |
30 | |
31 | static QString directoryMimeType() |
32 | { |
33 | return QStringLiteral("inode/directory" ); |
34 | } |
35 | static QString plainTextMimeType() |
36 | { |
37 | return QStringLiteral("text/plain" ); |
38 | } |
39 | |
40 | Q_GLOBAL_STATIC(QMimeDatabasePrivate, staticQMimeDatabase) |
41 | |
42 | QMimeDatabasePrivate *QMimeDatabasePrivate::instance() |
43 | { |
44 | return staticQMimeDatabase(); |
45 | } |
46 | |
47 | QMimeDatabasePrivate::QMimeDatabasePrivate() |
48 | : m_defaultMimeType(QStringLiteral("application/octet-stream" )) |
49 | { |
50 | } |
51 | |
52 | QMimeDatabasePrivate::~QMimeDatabasePrivate() |
53 | { |
54 | } |
55 | |
56 | Q_CONSTINIT |
57 | #ifdef QT_BUILD_INTERNAL |
58 | Q_CORE_EXPORT |
59 | #else |
60 | static const |
61 | #endif |
62 | int qmime_secondsBetweenChecks = 5; |
63 | |
64 | bool QMimeDatabasePrivate::shouldCheck() |
65 | { |
66 | if (m_lastCheck.isValid() && m_lastCheck.elapsed() < qmime_secondsBetweenChecks * 1000) |
67 | return false; |
68 | m_lastCheck.start(); |
69 | return true; |
70 | } |
71 | |
72 | static QStringList locateMimeDirectories() |
73 | { |
74 | return QStandardPaths::locateAll(type: QStandardPaths::GenericDataLocation, QStringLiteral("mime" ), options: QStandardPaths::LocateDirectory); |
75 | } |
76 | |
77 | #if defined(Q_OS_UNIX) && !defined(Q_OS_NACL) && !defined(Q_OS_INTEGRITY) |
78 | # define QT_USE_MMAP |
79 | #endif |
80 | |
81 | void QMimeDatabasePrivate::loadProviders() |
82 | { |
83 | // We use QStandardPaths every time to check if new files appeared |
84 | const QStringList mimeDirs = locateMimeDirectories(); |
85 | const auto fdoIterator = std::find_if(first: mimeDirs.constBegin(), last: mimeDirs.constEnd(), pred: [](const QString &mimeDir) -> bool { |
86 | return QFileInfo::exists(file: mimeDir + "/packages/freedesktop.org.xml"_L1 ); } |
87 | ); |
88 | const bool needInternalDB = QMimeXMLProvider::InternalDatabaseAvailable && fdoIterator == mimeDirs.constEnd(); |
89 | //qDebug() << "mime dirs:" << mimeDirs; |
90 | |
91 | Providers currentProviders; |
92 | std::swap(x&: m_providers, y&: currentProviders); |
93 | |
94 | m_providers.reserve(n: mimeDirs.size() + (needInternalDB ? 1 : 0)); |
95 | |
96 | for (const QString &mimeDir : mimeDirs) { |
97 | const QString cacheFile = mimeDir + "/mime.cache"_L1 ; |
98 | // Check if we already have a provider for this dir |
99 | const auto predicate = [mimeDir](const std::unique_ptr<QMimeProviderBase> &prov) |
100 | { |
101 | return prov && prov->directory() == mimeDir; |
102 | }; |
103 | const auto it = std::find_if(first: currentProviders.begin(), last: currentProviders.end(), pred: predicate); |
104 | if (it == currentProviders.end()) { |
105 | std::unique_ptr<QMimeProviderBase> provider; |
106 | #if defined(QT_USE_MMAP) |
107 | if (qEnvironmentVariableIsEmpty(varName: "QT_NO_MIME_CACHE" ) && QFileInfo::exists(file: cacheFile)) { |
108 | provider.reset(p: new QMimeBinaryProvider(this, mimeDir)); |
109 | //qDebug() << "Created binary provider for" << mimeDir; |
110 | if (!provider->isValid()) { |
111 | provider.reset(); |
112 | } |
113 | } |
114 | #endif |
115 | if (!provider) { |
116 | provider.reset(p: new QMimeXMLProvider(this, mimeDir)); |
117 | //qDebug() << "Created XML provider for" << mimeDir; |
118 | } |
119 | m_providers.push_back(x: std::move(provider)); |
120 | } else { |
121 | auto provider = std::move(*it); // take provider out of the vector |
122 | provider->ensureLoaded(); |
123 | if (!provider->isValid()) { |
124 | provider.reset(p: new QMimeXMLProvider(this, mimeDir)); |
125 | //qDebug() << "Created XML provider to replace binary provider for" << mimeDir; |
126 | } |
127 | m_providers.push_back(x: std::move(provider)); |
128 | } |
129 | } |
130 | // mimeDirs is sorted "most local first, most global last" |
131 | // so the internal XML DB goes at the end |
132 | if (needInternalDB) { |
133 | // Check if we already have a provider for the InternalDatabase |
134 | const auto isInternal = [](const std::unique_ptr<QMimeProviderBase> &prov) |
135 | { |
136 | return prov && prov->isInternalDatabase(); |
137 | }; |
138 | const auto it = std::find_if(first: currentProviders.begin(), last: currentProviders.end(), pred: isInternal); |
139 | if (it == currentProviders.end()) { |
140 | m_providers.push_back(x: Providers::value_type(new QMimeXMLProvider(this, QMimeXMLProvider::InternalDatabase))); |
141 | } else { |
142 | m_providers.push_back(x: std::move(*it)); |
143 | } |
144 | } |
145 | |
146 | // Handle mimetypes with glob-deleteall tags (from XML providers) |
147 | auto it = m_providers.begin(); |
148 | const auto end = m_providers.end(); |
149 | for (;it != end; ++it) { |
150 | const QStringList &list = (*it)->m_mimeTypesWithDeletedGlobs; |
151 | if (list.isEmpty()) |
152 | continue; |
153 | // Each Provider affects Providers with lower precedence |
154 | auto nextIt = it + 1; |
155 | for (; nextIt != end; ++nextIt) |
156 | (*nextIt)->excludeMimeTypeGlobs(list); |
157 | } |
158 | } |
159 | |
160 | const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers() |
161 | { |
162 | #ifndef Q_OS_WASM // stub implementation always returns true |
163 | Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex |
164 | #endif |
165 | if (m_providers.empty()) { |
166 | loadProviders(); |
167 | m_lastCheck.start(); |
168 | } else { |
169 | if (shouldCheck()) |
170 | loadProviders(); |
171 | } |
172 | return m_providers; |
173 | } |
174 | |
175 | QString QMimeDatabasePrivate::resolveAlias(const QString &nameOrAlias) |
176 | { |
177 | for (const auto &provider : providers()) { |
178 | const QString ret = provider->resolveAlias(name: nameOrAlias); |
179 | if (!ret.isEmpty()) |
180 | return ret; |
181 | } |
182 | return nameOrAlias; |
183 | } |
184 | |
185 | /*! |
186 | \internal |
187 | Returns a MIME type or an invalid one if none found |
188 | */ |
189 | QMimeType QMimeDatabasePrivate::mimeTypeForName(const QString &nameOrAlias) |
190 | { |
191 | const QString mimeName = resolveAlias(nameOrAlias); |
192 | for (const auto &provider : providers()) { |
193 | QMimeType mime = provider->mimeTypeForName(name: mimeName); |
194 | if (mime.isValid()) |
195 | return mime; |
196 | } |
197 | return {}; |
198 | } |
199 | |
200 | QStringList QMimeDatabasePrivate::mimeTypeForFileName(const QString &fileName) |
201 | { |
202 | if (fileName.endsWith(c: u'/')) |
203 | return { directoryMimeType() }; |
204 | |
205 | const QMimeGlobMatchResult result = findByFileName(fileName); |
206 | QStringList matchingMimeTypes = result.m_matchingMimeTypes; |
207 | matchingMimeTypes.sort(); // make it deterministic |
208 | return matchingMimeTypes; |
209 | } |
210 | |
211 | QMimeGlobMatchResult QMimeDatabasePrivate::findByFileName(const QString &fileName) |
212 | { |
213 | QMimeGlobMatchResult result; |
214 | const QString fileNameExcludingPath = QFileSystemEntry(fileName).fileName(); |
215 | for (const auto &provider : providers()) |
216 | provider->addFileNameMatches(fileName: fileNameExcludingPath, result); |
217 | return result; |
218 | } |
219 | |
220 | void QMimeDatabasePrivate::loadMimeTypePrivate(QMimeTypePrivate &mimePrivate) |
221 | { |
222 | QMutexLocker locker(&mutex); |
223 | if (mimePrivate.name.isEmpty()) |
224 | return; // invalid mimetype |
225 | if (!mimePrivate.loaded) { // XML provider sets loaded=true, binary provider does this on demand |
226 | Q_ASSERT(mimePrivate.fromCache); |
227 | bool found = false; |
228 | for (const auto &provider : providers()) { |
229 | if (provider->loadMimeTypePrivate(mimePrivate)) { |
230 | found = true; |
231 | break; |
232 | } |
233 | } |
234 | if (!found) { |
235 | const QString file = mimePrivate.name + ".xml"_L1 ; |
236 | qWarning() << "No file found for" << file << ", even though update-mime-info said it would exist.\n" |
237 | "Either it was just removed, or the directory doesn't have executable permission..." |
238 | << locateMimeDirectories(); |
239 | } |
240 | mimePrivate.loaded = true; |
241 | } |
242 | } |
243 | |
244 | void QMimeDatabasePrivate::loadGenericIcon(QMimeTypePrivate &mimePrivate) |
245 | { |
246 | QMutexLocker locker(&mutex); |
247 | if (mimePrivate.fromCache) { |
248 | mimePrivate.genericIconName.clear(); |
249 | for (const auto &provider : providers()) { |
250 | provider->loadGenericIcon(mimePrivate); |
251 | if (!mimePrivate.genericIconName.isEmpty()) |
252 | break; |
253 | } |
254 | } |
255 | } |
256 | |
257 | void QMimeDatabasePrivate::loadIcon(QMimeTypePrivate &mimePrivate) |
258 | { |
259 | QMutexLocker locker(&mutex); |
260 | if (mimePrivate.fromCache) { |
261 | mimePrivate.iconName.clear(); |
262 | for (const auto &provider : providers()) { |
263 | provider->loadIcon(mimePrivate); |
264 | if (!mimePrivate.iconName.isEmpty()) |
265 | break; |
266 | } |
267 | } |
268 | } |
269 | |
270 | QString QMimeDatabasePrivate::fallbackParent(const QString &mimeTypeName) const |
271 | { |
272 | const QStringView myGroup = QStringView{mimeTypeName}.left(n: mimeTypeName.indexOf(c: u'/')); |
273 | // All text/* types are subclasses of text/plain. |
274 | if (myGroup == "text"_L1 && mimeTypeName != plainTextMimeType()) |
275 | return plainTextMimeType(); |
276 | // All real-file mimetypes implicitly derive from application/octet-stream |
277 | if (myGroup != "inode"_L1 && |
278 | // ignore non-file extensions |
279 | myGroup != "all"_L1 && myGroup != "fonts"_L1 && myGroup != "print"_L1 && myGroup != "uri"_L1 |
280 | && mimeTypeName != defaultMimeType()) { |
281 | return defaultMimeType(); |
282 | } |
283 | return QString(); |
284 | } |
285 | |
286 | QStringList QMimeDatabasePrivate::mimeParents(const QString &mimeName) |
287 | { |
288 | QMutexLocker locker(&mutex); |
289 | return parents(mimeName); |
290 | } |
291 | |
292 | QStringList QMimeDatabasePrivate::parents(const QString &mimeName) |
293 | { |
294 | Q_ASSERT(!mutex.tryLock()); |
295 | QStringList result; |
296 | for (const auto &provider : providers()) |
297 | provider->addParents(mime: mimeName, result); |
298 | if (result.isEmpty()) { |
299 | const QString parent = fallbackParent(mimeTypeName: mimeName); |
300 | if (!parent.isEmpty()) |
301 | result.append(t: parent); |
302 | } |
303 | return result; |
304 | } |
305 | |
306 | QStringList QMimeDatabasePrivate::listAliases(const QString &mimeName) |
307 | { |
308 | QMutexLocker locker(&mutex); |
309 | QStringList result; |
310 | for (const auto &provider : providers()) |
311 | provider->addAliases(name: mimeName, result); |
312 | return result; |
313 | } |
314 | |
315 | bool QMimeDatabasePrivate::mimeInherits(const QString &mime, const QString &parent) |
316 | { |
317 | QMutexLocker locker(&mutex); |
318 | return inherits(mime, parent); |
319 | } |
320 | |
321 | static inline bool isTextFile(const QByteArray &data) |
322 | { |
323 | // UTF16 byte order marks |
324 | static const char bigEndianBOM[] = "\xFE\xFF" ; |
325 | static const char littleEndianBOM[] = "\xFF\xFE" ; |
326 | if (data.startsWith(bv: bigEndianBOM) || data.startsWith(bv: littleEndianBOM)) |
327 | return true; |
328 | |
329 | // Check the first 128 bytes (see shared-mime spec) |
330 | const char *p = data.constData(); |
331 | const char *e = p + qMin(a: 128, b: data.size()); |
332 | for ( ; p < e; ++p) { |
333 | if (static_cast<unsigned char>(*p) < 32 && *p != 9 && *p !=10 && *p != 13) |
334 | return false; |
335 | } |
336 | |
337 | return true; |
338 | } |
339 | |
340 | QMimeType QMimeDatabasePrivate::findByData(const QByteArray &data, int *accuracyPtr) |
341 | { |
342 | if (data.isEmpty()) { |
343 | *accuracyPtr = 100; |
344 | return mimeTypeForName(QStringLiteral("application/x-zerosize" )); |
345 | } |
346 | |
347 | *accuracyPtr = 0; |
348 | QMimeType candidate; |
349 | for (const auto &provider : providers()) |
350 | provider->findByMagic(data, accuracyPtr, candidate); |
351 | |
352 | if (candidate.isValid()) |
353 | return candidate; |
354 | |
355 | if (isTextFile(data)) { |
356 | *accuracyPtr = 5; |
357 | return mimeTypeForName(nameOrAlias: plainTextMimeType()); |
358 | } |
359 | |
360 | return mimeTypeForName(nameOrAlias: defaultMimeType()); |
361 | } |
362 | |
363 | QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) |
364 | { |
365 | // First, glob patterns are evaluated. If there is a match with max weight, |
366 | // this one is selected and we are done. Otherwise, the file contents are |
367 | // evaluated and the match with the highest value (either a magic priority or |
368 | // a glob pattern weight) is selected. Matching starts from max level (most |
369 | // specific) in both cases, even when there is already a suffix matching candidate. |
370 | |
371 | // Pass 1) Try to match on the file name |
372 | QMimeGlobMatchResult candidatesByName = findByFileName(fileName); |
373 | if (candidatesByName.m_allMatchingMimeTypes.size() == 1) { |
374 | const QMimeType mime = mimeTypeForName(nameOrAlias: candidatesByName.m_matchingMimeTypes.at(i: 0)); |
375 | if (mime.isValid()) |
376 | return mime; |
377 | candidatesByName = {}; |
378 | } |
379 | |
380 | // Extension is unknown, or matches multiple mimetypes. |
381 | // Pass 2) Match on content, if we can read the data |
382 | const auto matchOnContent = [this, &candidatesByName](QIODevice *device) { |
383 | const bool openedByUs = !device->isOpen() && device->open(mode: QIODevice::ReadOnly); |
384 | if (device->isOpen()) { |
385 | // Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h). |
386 | // This is much faster than seeking back and forth into QIODevice. |
387 | const QByteArray data = device->peek(maxlen: 16384); |
388 | |
389 | if (openedByUs) |
390 | device->close(); |
391 | |
392 | int magicAccuracy = 0; |
393 | QMimeType candidateByData(findByData(data, accuracyPtr: &magicAccuracy)); |
394 | |
395 | // Disambiguate conflicting extensions (if magic matching found something) |
396 | if (candidateByData.isValid() && magicAccuracy > 0) { |
397 | const QString sniffedMime = candidateByData.name(); |
398 | // If the sniffedMime matches a highest-weight glob match, use it |
399 | if (candidatesByName.m_matchingMimeTypes.contains(str: sniffedMime)) { |
400 | return candidateByData; |
401 | } |
402 | for (const QString &m : std::as_const(t&: candidatesByName.m_allMatchingMimeTypes)) { |
403 | if (inherits(mime: m, parent: sniffedMime)) { |
404 | // We have magic + pattern pointing to this, so it's a pretty good match |
405 | return mimeTypeForName(nameOrAlias: m); |
406 | } |
407 | } |
408 | if (candidatesByName.m_allMatchingMimeTypes.isEmpty()) { |
409 | // No glob, use magic |
410 | return candidateByData; |
411 | } |
412 | } |
413 | } |
414 | |
415 | if (candidatesByName.m_allMatchingMimeTypes.size() > 1) { |
416 | candidatesByName.m_matchingMimeTypes.sort(); // make it deterministic |
417 | const QMimeType mime = mimeTypeForName(nameOrAlias: candidatesByName.m_matchingMimeTypes.at(i: 0)); |
418 | if (mime.isValid()) |
419 | return mime; |
420 | } |
421 | |
422 | return mimeTypeForName(nameOrAlias: defaultMimeType()); |
423 | }; |
424 | |
425 | if (device) |
426 | return matchOnContent(device); |
427 | |
428 | QFile fallbackFile(fileName); |
429 | return matchOnContent(&fallbackFile); |
430 | } |
431 | |
432 | QMimeType QMimeDatabasePrivate::mimeTypeForFileExtension(const QString &fileName) |
433 | { |
434 | const QStringList matches = mimeTypeForFileName(fileName); |
435 | if (matches.isEmpty()) { |
436 | return mimeTypeForName(nameOrAlias: defaultMimeType()); |
437 | } else { |
438 | // We have to pick one in case of multiple matches. |
439 | return mimeTypeForName(nameOrAlias: matches.first()); |
440 | } |
441 | } |
442 | |
443 | QMimeType QMimeDatabasePrivate::mimeTypeForData(QIODevice *device) |
444 | { |
445 | int accuracy = 0; |
446 | const bool openedByUs = !device->isOpen() && device->open(mode: QIODevice::ReadOnly); |
447 | if (device->isOpen()) { |
448 | // Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h). |
449 | // This is much faster than seeking back and forth into QIODevice. |
450 | const QByteArray data = device->peek(maxlen: 16384); |
451 | QMimeType result = findByData(data, accuracyPtr: &accuracy); |
452 | if (openedByUs) |
453 | device->close(); |
454 | return result; |
455 | } |
456 | return mimeTypeForName(nameOrAlias: defaultMimeType()); |
457 | } |
458 | |
459 | QMimeType QMimeDatabasePrivate::mimeTypeForFile(const QString &fileName, |
460 | const QFileInfo &fileInfo, |
461 | QMimeDatabase::MatchMode mode) |
462 | { |
463 | if (false) { |
464 | #ifdef Q_OS_UNIX |
465 | } else if (fileInfo.isNativePath()) { |
466 | // If this is a local file, we'll want to do a stat() ourselves so we can |
467 | // detect additional inode types. In addition we want to follow symlinks. |
468 | const QByteArray nativeFilePath = QFile::encodeName(fileName); |
469 | QT_STATBUF statBuffer; |
470 | if (QT_STAT(file: nativeFilePath.constData(), buf: &statBuffer) == 0) { |
471 | if (S_ISDIR(statBuffer.st_mode)) |
472 | return mimeTypeForName(nameOrAlias: directoryMimeType()); |
473 | if (S_ISCHR(statBuffer.st_mode)) |
474 | return mimeTypeForName(QStringLiteral("inode/chardevice" )); |
475 | if (S_ISBLK(statBuffer.st_mode)) |
476 | return mimeTypeForName(QStringLiteral("inode/blockdevice" )); |
477 | if (S_ISFIFO(statBuffer.st_mode)) |
478 | return mimeTypeForName(QStringLiteral("inode/fifo" )); |
479 | if (S_ISSOCK(statBuffer.st_mode)) |
480 | return mimeTypeForName(QStringLiteral("inode/socket" )); |
481 | } |
482 | #endif |
483 | } else if (fileInfo.isDir()) { |
484 | return mimeTypeForName(nameOrAlias: directoryMimeType()); |
485 | } |
486 | |
487 | switch (mode) { |
488 | case QMimeDatabase::MatchDefault: |
489 | break; |
490 | case QMimeDatabase::MatchExtension: |
491 | return mimeTypeForFileExtension(fileName); |
492 | case QMimeDatabase::MatchContent: { |
493 | QFile file(fileName); |
494 | return mimeTypeForData(device: &file); |
495 | } |
496 | } |
497 | // MatchDefault: |
498 | return mimeTypeForFileNameAndData(fileName, device: nullptr); |
499 | } |
500 | |
501 | QList<QMimeType> QMimeDatabasePrivate::allMimeTypes() |
502 | { |
503 | QList<QMimeType> result; |
504 | for (const auto &provider : providers()) |
505 | provider->addAllMimeTypes(result); |
506 | return result; |
507 | } |
508 | |
509 | bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent) |
510 | { |
511 | const QString resolvedParent = resolveAlias(nameOrAlias: parent); |
512 | std::stack<QString, QStringList> toCheck; |
513 | toCheck.push(x: mime); |
514 | while (!toCheck.empty()) { |
515 | if (toCheck.top() == resolvedParent) |
516 | return true; |
517 | const QString mimeName = toCheck.top(); |
518 | toCheck.pop(); |
519 | const auto parentList = parents(mimeName); |
520 | for (const QString &par : parentList) |
521 | toCheck.push(x: resolveAlias(nameOrAlias: par)); |
522 | } |
523 | return false; |
524 | } |
525 | |
526 | /*! |
527 | \class QMimeDatabase |
528 | \inmodule QtCore |
529 | \brief The QMimeDatabase class maintains a database of MIME types. |
530 | |
531 | \since 5.0 |
532 | |
533 | The MIME type database is provided by the freedesktop.org shared-mime-info |
534 | project. If the MIME type database cannot be found on the system, as is the case |
535 | on most Windows, \macos, and iOS systems, Qt will use its own copy of it. |
536 | |
537 | Applications which want to define custom MIME types need to install an |
538 | XML file into the locations searched for MIME definitions. |
539 | These locations can be queried with |
540 | \snippet code/src_corelib_mimetype_qmimedatabase.cpp 1 |
541 | On a typical Unix system, this will be /usr/share/mime/packages/, but it is also |
542 | possible to extend the list of directories by setting the environment variable |
543 | \c XDG_DATA_DIRS. For instance adding /opt/myapp/share to \c XDG_DATA_DIRS will result |
544 | in /opt/myapp/share/mime/packages/ being searched for MIME definitions. |
545 | |
546 | Here is an example of MIME XML: |
547 | \snippet code/src_corelib_mimetype_qmimedatabase.cpp 2 |
548 | |
549 | For more details about the syntax of XML MIME definitions, including defining |
550 | "magic" in order to detect MIME types based on data as well, read the |
551 | Shared Mime Info specification at |
552 | http://standards.freedesktop.org/shared-mime-info-spec/shared-mime-info-spec-latest.html |
553 | |
554 | On Unix systems, a binary cache is used for more performance. This cache is generated |
555 | by the command "update-mime-database path", where path would be /opt/myapp/share/mime |
556 | in the above example. Make sure to run this command when installing the MIME type |
557 | definition file. |
558 | |
559 | \threadsafe |
560 | |
561 | \snippet code/src_corelib_mimetype_qmimedatabase.cpp 0 |
562 | |
563 | \sa QMimeType, {MIME Type Browser Example} |
564 | */ |
565 | |
566 | /*! |
567 | \fn QMimeDatabase::QMimeDatabase(); |
568 | Constructs a QMimeDatabase object. |
569 | |
570 | It is perfectly OK to create an instance of QMimeDatabase every time you need to |
571 | perform a lookup. |
572 | The parsing of mimetypes is done on demand (when shared-mime-info is installed) |
573 | or when the very first instance is constructed (when parsing XML files directly). |
574 | */ |
575 | QMimeDatabase::QMimeDatabase() : |
576 | d(staticQMimeDatabase()) |
577 | { |
578 | } |
579 | |
580 | /*! |
581 | \fn QMimeDatabase::~QMimeDatabase(); |
582 | Destroys the QMimeDatabase object. |
583 | */ |
584 | QMimeDatabase::~QMimeDatabase() |
585 | { |
586 | d = nullptr; |
587 | } |
588 | |
589 | /*! |
590 | \fn QMimeType QMimeDatabase::mimeTypeForName(const QString &nameOrAlias) const; |
591 | Returns a MIME type for \a nameOrAlias or an invalid one if none found. |
592 | */ |
593 | QMimeType QMimeDatabase::mimeTypeForName(const QString &nameOrAlias) const |
594 | { |
595 | QMutexLocker locker(&d->mutex); |
596 | |
597 | return d->mimeTypeForName(nameOrAlias); |
598 | } |
599 | |
600 | /*! |
601 | Returns a MIME type for \a fileInfo. |
602 | |
603 | A valid MIME type is always returned. |
604 | |
605 | The default matching algorithm looks at both the file name and the file |
606 | contents, if necessary. The file extension has priority over the contents, |
607 | but the contents will be used if the file extension is unknown, or |
608 | matches multiple MIME types. |
609 | If \a fileInfo is a Unix symbolic link, the file that it refers to |
610 | will be used instead. |
611 | If the file doesn't match any known pattern or data, the default MIME type |
612 | (application/octet-stream) is returned. |
613 | |
614 | When \a mode is set to MatchExtension, only the file name is used, not |
615 | the file contents. The file doesn't even have to exist. If the file name |
616 | doesn't match any known pattern, the default MIME type (application/octet-stream) |
617 | is returned. |
618 | If multiple MIME types match this file, the first one (alphabetically) is returned. |
619 | |
620 | When \a mode is set to MatchContent, and the file is readable, only the |
621 | file contents are used to determine the MIME type. This is equivalent to |
622 | calling mimeTypeForData with a QFile as input device. |
623 | |
624 | \a fileInfo may refer to an absolute or relative path. |
625 | |
626 | \sa QMimeType::isDefault(), mimeTypeForData() |
627 | */ |
628 | QMimeType QMimeDatabase::mimeTypeForFile(const QFileInfo &fileInfo, MatchMode mode) const |
629 | { |
630 | QMutexLocker locker(&d->mutex); |
631 | |
632 | return d->mimeTypeForFile(fileName: fileInfo.filePath(), fileInfo, mode); |
633 | } |
634 | |
635 | /*! |
636 | Returns a MIME type for the file named \a fileName using \a mode. |
637 | |
638 | \overload |
639 | */ |
640 | QMimeType QMimeDatabase::mimeTypeForFile(const QString &fileName, MatchMode mode) const |
641 | { |
642 | QMutexLocker locker(&d->mutex); |
643 | |
644 | if (mode == MatchExtension) { |
645 | return d->mimeTypeForFileExtension(fileName); |
646 | } else { |
647 | QFileInfo fileInfo(fileName); |
648 | return d->mimeTypeForFile(fileName, fileInfo, mode); |
649 | } |
650 | } |
651 | |
652 | /*! |
653 | Returns the MIME types for the file name \a fileName. |
654 | |
655 | If the file name doesn't match any known pattern, an empty list is returned. |
656 | If multiple MIME types match this file, they are all returned. |
657 | |
658 | This function does not try to open the file. To also use the content |
659 | when determining the MIME type, use mimeTypeForFile() or |
660 | mimeTypeForFileNameAndData() instead. |
661 | |
662 | \sa mimeTypeForFile() |
663 | */ |
664 | QList<QMimeType> QMimeDatabase::mimeTypesForFileName(const QString &fileName) const |
665 | { |
666 | QMutexLocker locker(&d->mutex); |
667 | |
668 | const QStringList matches = d->mimeTypeForFileName(fileName); |
669 | QList<QMimeType> mimes; |
670 | mimes.reserve(asize: matches.size()); |
671 | for (const QString &mime : matches) |
672 | mimes.append(t: d->mimeTypeForName(nameOrAlias: mime)); |
673 | return mimes; |
674 | } |
675 | /*! |
676 | Returns the suffix for the file \a fileName, as known by the MIME database. |
677 | |
678 | This allows to pre-select "tar.bz2" for foo.tar.bz2, but still only |
679 | "txt" for my.file.with.dots.txt. |
680 | */ |
681 | QString QMimeDatabase::suffixForFileName(const QString &fileName) const |
682 | { |
683 | QMutexLocker locker(&d->mutex); |
684 | const qsizetype suffixLength = d->findByFileName(fileName).m_knownSuffixLength; |
685 | return fileName.right(n: suffixLength); |
686 | } |
687 | |
688 | /*! |
689 | Returns a MIME type for \a data. |
690 | |
691 | A valid MIME type is always returned. If \a data doesn't match any |
692 | known MIME type data, the default MIME type (application/octet-stream) |
693 | is returned. |
694 | */ |
695 | QMimeType QMimeDatabase::mimeTypeForData(const QByteArray &data) const |
696 | { |
697 | QMutexLocker locker(&d->mutex); |
698 | |
699 | int accuracy = 0; |
700 | return d->findByData(data, accuracyPtr: &accuracy); |
701 | } |
702 | |
703 | /*! |
704 | Returns a MIME type for the data in \a device. |
705 | |
706 | A valid MIME type is always returned. If the data in \a device doesn't match any |
707 | known MIME type data, the default MIME type (application/octet-stream) |
708 | is returned. |
709 | */ |
710 | QMimeType QMimeDatabase::mimeTypeForData(QIODevice *device) const |
711 | { |
712 | QMutexLocker locker(&d->mutex); |
713 | |
714 | return d->mimeTypeForData(device); |
715 | } |
716 | |
717 | /*! |
718 | Returns a MIME type for \a url. |
719 | |
720 | If the URL is a local file, this calls mimeTypeForFile. |
721 | |
722 | Otherwise the matching is done based on the file name only, |
723 | except for schemes where file names don't mean much, like HTTP. |
724 | This method always returns the default mimetype for HTTP URLs, |
725 | use QNetworkAccessManager to handle HTTP URLs properly. |
726 | |
727 | A valid MIME type is always returned. If \a url doesn't match any |
728 | known MIME type data, the default MIME type (application/octet-stream) |
729 | is returned. |
730 | */ |
731 | QMimeType QMimeDatabase::mimeTypeForUrl(const QUrl &url) const |
732 | { |
733 | if (url.isLocalFile()) |
734 | return mimeTypeForFile(fileName: url.toLocalFile()); |
735 | |
736 | const QString scheme = url.scheme(); |
737 | if (scheme.startsWith(s: "http"_L1 ) || scheme == "mailto"_L1 ) |
738 | return mimeTypeForName(nameOrAlias: d->defaultMimeType()); |
739 | |
740 | return mimeTypeForFile(fileName: url.path(), mode: MatchExtension); |
741 | } |
742 | |
743 | /*! |
744 | Returns a MIME type for the given \a fileName and \a device data. |
745 | |
746 | This overload can be useful when the file is remote, and we started to |
747 | download some of its data in a device. This allows to do full MIME type |
748 | matching for remote files as well. |
749 | |
750 | If the device is not open, it will be opened by this function, and closed |
751 | after the MIME type detection is completed. |
752 | |
753 | A valid MIME type is always returned. If \a device data doesn't match any |
754 | known MIME type data, the default MIME type (application/octet-stream) |
755 | is returned. |
756 | |
757 | This method looks at both the file name and the file contents, |
758 | if necessary. The file extension has priority over the contents, |
759 | but the contents will be used if the file extension is unknown, or |
760 | matches multiple MIME types. |
761 | */ |
762 | QMimeType QMimeDatabase::mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const |
763 | { |
764 | QMutexLocker locker(&d->mutex); |
765 | |
766 | if (fileName.endsWith(c: u'/')) |
767 | return d->mimeTypeForName(nameOrAlias: directoryMimeType()); |
768 | |
769 | const QMimeType result = d->mimeTypeForFileNameAndData(fileName, device); |
770 | return result; |
771 | } |
772 | |
773 | /*! |
774 | Returns a MIME type for the given \a fileName and device \a data. |
775 | |
776 | This overload can be useful when the file is remote, and we started to |
777 | download some of its data. This allows to do full MIME type matching for |
778 | remote files as well. |
779 | |
780 | A valid MIME type is always returned. If \a data doesn't match any |
781 | known MIME type data, the default MIME type (application/octet-stream) |
782 | is returned. |
783 | |
784 | This method looks at both the file name and the file contents, |
785 | if necessary. The file extension has priority over the contents, |
786 | but the contents will be used if the file extension is unknown, or |
787 | matches multiple MIME types. |
788 | */ |
789 | QMimeType QMimeDatabase::mimeTypeForFileNameAndData(const QString &fileName, const QByteArray &data) const |
790 | { |
791 | QMutexLocker locker(&d->mutex); |
792 | |
793 | if (fileName.endsWith(c: u'/')) |
794 | return d->mimeTypeForName(nameOrAlias: directoryMimeType()); |
795 | |
796 | QBuffer buffer(const_cast<QByteArray *>(&data)); |
797 | buffer.open(openMode: QIODevice::ReadOnly); |
798 | return d->mimeTypeForFileNameAndData(fileName, device: &buffer); |
799 | } |
800 | |
801 | /*! |
802 | Returns the list of all available MIME types. |
803 | |
804 | This can be useful for showing all MIME types to the user, for instance |
805 | in a MIME type editor. Do not use unless really necessary in other cases |
806 | though, prefer using the \l {mimeTypeForData()}{mimeTypeForXxx()} methods for performance reasons. |
807 | */ |
808 | QList<QMimeType> QMimeDatabase::allMimeTypes() const |
809 | { |
810 | QMutexLocker locker(&d->mutex); |
811 | |
812 | return d->allMimeTypes(); |
813 | } |
814 | |
815 | /*! |
816 | \enum QMimeDatabase::MatchMode |
817 | |
818 | This enum specifies how matching a file to a MIME type is performed. |
819 | |
820 | \value MatchDefault Both the file name and content are used to look for a match |
821 | |
822 | \value MatchExtension Only the file name is used to look for a match |
823 | |
824 | \value MatchContent The file content is used to look for a match |
825 | */ |
826 | |
827 | QT_END_NAMESPACE |
828 | |