Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qmimedatabase.cpp
Go to the documentation of this file.
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/qduplicatetracker_p.h>
14#include <private/qfilesystementry_p.h>
15
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
28
29using namespace Qt::StringLiterals;
30
32{
33 return QStringLiteral("inode/directory");
34}
36{
37 return QStringLiteral("text/plain");
38}
39
40Q_GLOBAL_STATIC(QMimeDatabasePrivate, staticQMimeDatabase)
41
43{
44 return staticQMimeDatabase();
45}
46
48 : m_defaultMimeType(QStringLiteral("application/octet-stream"))
49{
50}
51
55
56Q_CONSTINIT
57#ifdef QT_BUILD_INTERNAL
58Q_CORE_EXPORT
59#else
60static const
61#endif
63
64bool 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
76
77#if defined(Q_OS_UNIX) && !defined(Q_OS_INTEGRITY)
78# define QT_USE_MMAP
79#endif
80
81void 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(mimeDirs.constBegin(), mimeDirs.constEnd(), [](const QString &mimeDir) -> bool {
86 return QFileInfo::exists(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(m_providers, currentProviders);
93
94 m_providers.reserve(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(currentProviders.begin(), currentProviders.end(), predicate);
104 if (it == currentProviders.end()) {
105 std::unique_ptr<QMimeProviderBase> provider;
106#if defined(QT_USE_MMAP)
107 if (qEnvironmentVariableIsEmpty("QT_NO_MIME_CACHE") && QFileInfo::exists(cacheFile)) {
108 provider.reset(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(new QMimeXMLProvider(this, mimeDir));
117 //qDebug() << "Created XML provider for" << mimeDir;
118 }
119 m_providers.push_back(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(new QMimeXMLProvider(this, mimeDir));
125 //qDebug() << "Created XML provider to replace binary provider for" << mimeDir;
126 }
127 m_providers.push_back(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(currentProviders.begin(), currentProviders.end(), isInternal);
139 if (it == currentProviders.end()) {
140 m_providers.push_back(Providers::value_type(new QMimeXMLProvider(this, QMimeXMLProvider::InternalDatabase)));
141 } else {
142 m_providers.push_back(std::move(*it));
143 }
144 }
145
146 auto it = m_providers.begin();
147 (*it)->setOverrideProvider(nullptr);
148 ++it;
149 const auto end = m_providers.end();
150 for (; it != end; ++it)
151 (*it)->setOverrideProvider((it - 1)->get());
152}
153
154const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers()
155{
156#ifndef Q_OS_WASM // stub implementation always returns true
157 Q_ASSERT(!mutex.tryLock()); // caller should have locked mutex
158#endif
159 if (m_providers.empty()) {
160 loadProviders();
161 m_lastCheck.start();
162 } else {
163 if (shouldCheck())
164 loadProviders();
165 }
166 return m_providers;
167}
168
170{
171 for (const auto &provider : providers()) {
172 const QString ret = provider->resolveAlias(nameOrAlias);
173 if (!ret.isEmpty())
174 return ret;
175 }
176 return nameOrAlias;
177}
178
184{
185 const QString mimeName = resolveAlias(nameOrAlias);
186 for (const auto &provider : providers()) {
187 if (provider->knowsMimeType(mimeName))
188 return QMimeType(QMimeTypePrivate(mimeName));
189 }
190 return {};
191}
192
194{
195 if (fileName.endsWith(u'/'))
196 return { directoryMimeType() };
197
199 QStringList matchingMimeTypes = result.m_matchingMimeTypes;
200 matchingMimeTypes.sort(); // make it deterministic
201 return matchingMimeTypes;
202}
203
205{
207 const QString fileNameExcludingPath = QFileSystemEntry(fileName).fileName();
208 for (const auto &provider : providers())
209 provider->addFileNameMatches(fileNameExcludingPath, result);
210 return result;
211}
212
214{
215 QMutexLocker locker(&mutex);
216 for (const auto &provider : providers()) {
217 auto comments = provider->localeComments(name);
218 if (!comments.isEmpty())
219 return comments; // maybe we want to merge in comments from more global providers, in
220 // case of more translations?
221 }
222 return {};
223}
224
226{
227 QMutexLocker locker(&mutex);
228 QStringList patterns;
229 const auto &providerList = providers();
230 // reverse iteration because we start from most global, add up, clear if delete-all, and add up
231 // again.
232 for (auto rit = providerList.rbegin(); rit != providerList.rend(); ++rit) {
233 auto *provider = rit->get();
234 if (provider->hasGlobDeleteAll(name))
235 patterns.clear();
236 patterns += provider->globPatterns(name);
237 }
238 return patterns;
239}
240
242{
243 QMutexLocker locker(&mutex);
244 for (const auto &provider : providers()) {
245 QString genericIconName = provider->genericIcon(name);
246 if (!genericIconName.isEmpty())
247 return genericIconName;
248 }
249 return {};
250}
251
253{
254 QMutexLocker locker(&mutex);
255 for (const auto &provider : providers()) {
256 QString iconName = provider->icon(name);
257 if (!iconName.isEmpty())
258 return iconName;
259 }
260 return {};
261}
262
263QString QMimeDatabasePrivate::fallbackParent(const QString &mimeTypeName) const
264{
265 const QStringView myGroup = QStringView{mimeTypeName}.left(mimeTypeName.indexOf(u'/'));
266 // All text/* types are subclasses of text/plain.
267 if (myGroup == "text"_L1 && mimeTypeName != plainTextMimeType())
268 return plainTextMimeType();
269 // All real-file mimetypes implicitly derive from application/octet-stream
270 if (myGroup != "inode"_L1 &&
271 // ignore non-file extensions
272 myGroup != "all"_L1 && myGroup != "fonts"_L1 && myGroup != "print"_L1 && myGroup != "uri"_L1
273 && mimeTypeName != defaultMimeType()) {
274 return defaultMimeType();
275 }
276 return QString();
277}
278
280{
281 QMutexLocker locker(&mutex);
282 return parents(mimeName);
283}
284
286{
289 for (const auto &provider : providers())
290 provider->addParents(mimeName, result);
291 if (result.isEmpty()) {
292 const QString parent = fallbackParent(mimeName);
293 if (!parent.isEmpty())
294 result.append(parent);
295 }
296 return result;
297}
298
300{
301 QMutexLocker locker(&mutex);
303 for (const auto &provider : providers())
304 provider->addAliases(mimeName, result);
305 return result;
306}
307
309{
310 QMutexLocker locker(&mutex);
311 return inherits(mime, parent);
312}
313
314static inline bool isTextFile(const QByteArray &data)
315{
316 // UTF16 byte order marks
317 static const char bigEndianBOM[] = "\xFE\xFF";
318 static const char littleEndianBOM[] = "\xFF\xFE";
319 if (data.startsWith(bigEndianBOM) || data.startsWith(littleEndianBOM))
320 return true;
321
322 // Check the first 128 bytes (see shared-mime spec)
323 const char *p = data.constData();
324 const char *e = p + qMin(128, data.size());
325 for ( ; p < e; ++p) {
326 if (static_cast<unsigned char>(*p) < 32 && *p != 9 && *p !=10 && *p != 13)
327 return false;
328 }
329
330 return true;
331}
332
334{
335 if (data.isEmpty()) {
336 *accuracyPtr = 100;
337 return mimeTypeForName(QStringLiteral("application/x-zerosize"));
338 }
339
341 for (const auto &provider : providers())
342 provider->findByMagic(data, result);
343
344 if (result.isValid()) {
345 *accuracyPtr = result.accuracy;
346 return QMimeType(QMimeTypePrivate(result.candidate));
347 }
348
349 if (isTextFile(data)) {
350 *accuracyPtr = 5;
352 }
353
355}
356
358{
359 // First, glob patterns are evaluated. If there is a match with max weight,
360 // this one is selected and we are done. Otherwise, the file contents are
361 // evaluated and the match with the highest value (either a magic priority or
362 // a glob pattern weight) is selected. Matching starts from max level (most
363 // specific) in both cases, even when there is already a suffix matching candidate.
364
365 // Pass 1) Try to match on the file name
366 QMimeGlobMatchResult candidatesByName = findByFileName(fileName);
367 if (candidatesByName.m_allMatchingMimeTypes.size() == 1) {
368 const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
369 if (mime.isValid())
370 return mime;
371 candidatesByName = {};
372 }
373
374 // Extension is unknown, or matches multiple mimetypes.
375 // Pass 2) Match on content, if we can read the data
376 const auto matchOnContent = [this, &candidatesByName](QIODevice *device) {
377 const bool openedByUs = !device->isOpen() && device->open(QIODevice::ReadOnly);
378 if (device->isOpen()) {
379 // Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h).
380 // This is much faster than seeking back and forth into QIODevice.
381 const QByteArray data = device->peek(16384);
382
383 if (openedByUs)
384 device->close();
385
386 int magicAccuracy = 0;
387 QMimeType candidateByData(findByData(data, &magicAccuracy));
388
389 // Disambiguate conflicting extensions (if magic matching found something)
390 if (candidateByData.isValid() && magicAccuracy > 0) {
391 const QString sniffedMime = candidateByData.name();
392 // If the sniffedMime matches a highest-weight glob match, use it
393 if (candidatesByName.m_matchingMimeTypes.contains(sniffedMime)) {
394 return candidateByData;
395 }
396 for (const QString &m : std::as_const(candidatesByName.m_allMatchingMimeTypes)) {
397 if (inherits(m, sniffedMime)) {
398 // We have magic + pattern pointing to this, so it's a pretty good match
399 return mimeTypeForName(m);
400 }
401 }
402 if (candidatesByName.m_allMatchingMimeTypes.isEmpty()) {
403 // No glob, use magic
404 return candidateByData;
405 }
406 }
407 }
408
409 if (candidatesByName.m_allMatchingMimeTypes.size() > 1) {
410 candidatesByName.m_matchingMimeTypes.sort(); // make it deterministic
411 const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
412 if (mime.isValid())
413 return mime;
414 }
415
417 };
418
419 if (device)
420 return matchOnContent(device);
421
422 QFile fallbackFile(fileName);
423 return matchOnContent(&fallbackFile);
424}
425
427{
429 if (matches.isEmpty()) {
431 } else {
432 // We have to pick one in case of multiple matches.
433 return mimeTypeForName(matches.first());
434 }
435}
436
438{
439 int accuracy = 0;
440 const bool openedByUs = !device->isOpen() && device->open(QIODevice::ReadOnly);
441 if (device->isOpen()) {
442 // Read 16K in one go (QIODEVICE_BUFFERSIZE in qiodevice_p.h).
443 // This is much faster than seeking back and forth into QIODevice.
444 const QByteArray data = device->peek(16384);
445 QMimeType result = findByData(data, &accuracy);
446 if (openedByUs)
447 device->close();
448 return result;
449 }
451}
452
454 const QFileInfo &fileInfo,
456{
457 if (false) {
458#ifdef Q_OS_UNIX
459 } else if (fileInfo.isNativePath()) {
460 // If this is a local file, we'll want to do a stat() ourselves so we can
461 // detect additional inode types. In addition we want to follow symlinks.
462 const QByteArray nativeFilePath = QFile::encodeName(fileName);
463 QT_STATBUF statBuffer;
464 if (QT_STAT(nativeFilePath.constData(), &statBuffer) == 0) {
465 if (S_ISDIR(statBuffer.st_mode))
467 if (S_ISCHR(statBuffer.st_mode))
468 return mimeTypeForName(QStringLiteral("inode/chardevice"));
469 if (S_ISBLK(statBuffer.st_mode))
470 return mimeTypeForName(QStringLiteral("inode/blockdevice"));
471 if (S_ISFIFO(statBuffer.st_mode))
472 return mimeTypeForName(QStringLiteral("inode/fifo"));
473 if (S_ISSOCK(statBuffer.st_mode))
474 return mimeTypeForName(QStringLiteral("inode/socket"));
475 }
476#endif
477 } else if (fileInfo.isDir()) {
479 }
480
481 switch (mode) {
483 break;
488 return mimeTypeForData(&file);
489 }
490 }
491 // MatchDefault:
492 return mimeTypeForFileNameAndData(fileName, nullptr);
493}
494
496{
497 QList<QMimeType> result;
498 for (const auto &provider : providers())
499 provider->addAllMimeTypes(result);
500 return result;
501}
502
504{
505 const QString resolvedParent = resolveAlias(parent);
506 QDuplicateTracker<QString> seen;
507 std::stack<QString, QStringList> toCheck;
508 toCheck.push(mime);
509 while (!toCheck.empty()) {
510 if (toCheck.top() == resolvedParent)
511 return true;
512 const QString mimeName = toCheck.top();
513 toCheck.pop();
514 const auto parentList = parents(mimeName);
515 for (const QString &par : parentList) {
516 const QString resolvedPar = resolveAlias(par);
517 if (!seen.hasSeen(resolvedPar))
518 toCheck.push(resolvedPar);
519 }
520 }
521 return false;
522}
523
574 d(staticQMimeDatabase())
575{
576}
577
583{
584 d = nullptr;
585}
586
592{
593 QMutexLocker locker(&d->mutex);
594
595 return d->mimeTypeForName(nameOrAlias);
596}
597
627{
628 QMutexLocker locker(&d->mutex);
629
630 return d->mimeTypeForFile(fileInfo.filePath(), fileInfo, mode);
631}
632
639{
640 QMutexLocker locker(&d->mutex);
641
642 if (mode == MatchExtension) {
644 } else {
645 QFileInfo fileInfo(fileName);
646 return d->mimeTypeForFile(fileName, fileInfo, mode);
647 }
648}
649
663{
664 QMutexLocker locker(&d->mutex);
665
667 QList<QMimeType> mimes;
668 mimes.reserve(matches.size());
669 for (const QString &mime : matches)
670 mimes.append(d->mimeTypeForName(mime));
671 return mimes;
672}
680{
681 QMutexLocker locker(&d->mutex);
682 const qsizetype suffixLength = d->findByFileName(fileName).m_knownSuffixLength;
683 return fileName.right(suffixLength);
684}
685
694{
695 QMutexLocker locker(&d->mutex);
696
697 int accuracy = 0;
698 return d->findByData(data, &accuracy);
699}
700
714
730{
731 if (url.isLocalFile())
733
734 const QString scheme = url.scheme();
735 if (scheme.startsWith("http"_L1) || scheme == "mailto"_L1)
736 return mimeTypeForName(d->defaultMimeType());
737
739}
740
761{
762 QMutexLocker locker(&d->mutex);
763
764 if (fileName.endsWith(u'/'))
766
768 return result;
769}
770
788{
789 QMutexLocker locker(&d->mutex);
790
791 if (fileName.endsWith(u'/'))
793
794 QBuffer buffer(const_cast<QByteArray *>(&data));
797}
798
806QList<QMimeType> QMimeDatabase::allMimeTypes() const
807{
808 QMutexLocker locker(&d->mutex);
809
810 return d->allMimeTypes();
811}
812
IOBluetoothDevice * device
\inmodule QtCore \reentrant
Definition qbuffer.h:16
\inmodule QtCore
Definition qbytearray.h:57
const char * constData() const noexcept
Returns a pointer to the const data stored in the byte array.
Definition qbytearray.h:124
qint64 elapsed() const noexcept
Returns the number of milliseconds since this QElapsedTimer was last started.
void start() noexcept
\typealias QElapsedTimer::Duration Synonym for std::chrono::nanoseconds.
bool isValid() const noexcept
Returns false if the timer has never been started or invalidated by a call to invalidate().
bool isNativePath() const
bool isDir() const
Returns true if this object points to a directory or to a symbolic link to a directory.
QString filePath() const
Returns the path of the file system entry this QFileInfo refers to; the path may be absolute or relat...
bool exists() const
Returns true if the file system entry this QFileInfo refers to exists; otherwise returns false.
Q_AUTOTEST_EXPORT QString fileName() const
\inmodule QtCore
Definition qfile.h:93
static QByteArray encodeName(const QString &fileName)
Converts fileName to an 8-bit encoding that you can use in native APIs.
Definition qfile.h:158
\inmodule QtCore \reentrant
Definition qiodevice.h:34
QString resolveAlias(const QString &nameOrAlias)
QStringList listAliases(const QString &mimeName)
QList< QMimeType > allMimeTypes()
QString genericIcon(const QString &name)
QMimeTypePrivate::LocaleHash localeComments(const QString &name)
bool mimeInherits(const QString &mime, const QString &parent)
QMimeType mimeTypeForFileExtension(const QString &fileName)
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device)
QMimeType mimeTypeForData(QIODevice *device)
QStringList mimeTypeForFileName(const QString &fileName)
bool inherits(const QString &mime, const QString &parent)
QMimeType mimeTypeForName(const QString &nameOrAlias)
QStringList mimeParents(const QString &mimeName)
QStringList globPatterns(const QString &name)
const QString & defaultMimeType() const
QString icon(const QString &name)
QMimeType mimeTypeForFile(const QString &fileName, const QFileInfo &fileInfo, QMimeDatabase::MatchMode mode)
QStringList parents(const QString &mimeName)
QMimeGlobMatchResult findByFileName(const QString &fileName)
QMimeType findByData(const QByteArray &data, int *priorityPtr)
QMimeType mimeTypeForName(const QString &nameOrAlias) const
Returns a MIME type for nameOrAlias or an invalid one if none found.
~QMimeDatabase()
Destroys the QMimeDatabase object.
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device) const
Returns a MIME type for the given fileName and device data.
QList< QMimeType > mimeTypesForFileName(const QString &fileName) const
Returns the MIME types for the file name fileName.
QMimeDatabase()
Constructs a QMimeDatabase object.
QMimeType mimeTypeForUrl(const QUrl &url) const
Returns a MIME type for url.
QMimeType mimeTypeForData(const QByteArray &data) const
Returns a MIME type for data.
QString suffixForFileName(const QString &fileName) const
Returns the suffix for the file fileName, as known by the MIME database.
MatchMode
This enum specifies how matching a file to a MIME type is performed.
QList< QMimeType > allMimeTypes() const
Returns the list of all available MIME types.
QMimeType mimeTypeForFile(const QString &fileName, MatchMode mode=MatchDefault) const
Returns a MIME type for the file named fileName using mode.
\inmodule QtCore
Definition qmimetype.h:25
\inmodule QtCore
Definition qmutex.h:313
bool tryLock(int timeout=0) noexcept
Attempts to lock the mutex.
Definition qmutex.h:287
iterator end()
Definition qset.h:140
static QStringList locateAll(StandardLocation type, const QString &fileName, LocateOptions options=LocateFile)
[0]
\inmodule QtCore
\inmodule QtCore
Definition qstringview.h:78
constexpr QStringView left(qsizetype n) const noexcept
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
bool startsWith(const QString &s, Qt::CaseSensitivity cs=Qt::CaseSensitive) const
Returns true if the string starts with s; otherwise returns false.
Definition qstring.cpp:5455
bool isEmpty() const noexcept
Returns true if the string has no characters; otherwise returns false.
Definition qstring.h:192
\inmodule QtCore
Definition qurl.h:94
bool isLocalFile() const
Definition qurl.cpp:3445
QString scheme() const
Returns the scheme of the URL.
Definition qurl.cpp:1991
QString toLocalFile() const
Returns the path of this URL formatted as a local file path.
Definition qurl.cpp:3425
QString path(ComponentFormattingOptions options=FullyDecoded) const
Returns the path of the URL.
Definition qurl.cpp:2468
const auto predicate
QSet< QString >::iterator it
Combined button and popup list for selecting options.
#define Q_GLOBAL_STATIC(TYPE, NAME,...)
return ret
static Q_CONSTINIT const int qmime_secondsBetweenChecks
static bool isTextFile(const QByteArray &data)
static QStringList locateMimeDirectories()
static QString directoryMimeType()
static QString plainTextMimeType()
constexpr const T & qMin(const T &a, const T &b)
Definition qminmax.h:40
static bool matches(const QJsonObject &object, const QString &osName, const QVersionNumber &kernelVersion, const QString &osRelease, const QOpenGLConfig::Gpu &gpu)
Definition qopengl.cpp:270
GLenum mode
const GLfloat * m
GLuint GLuint end
GLint GLsizei GLsizei GLenum GLenum GLsizei void * data
GLenum GLuint buffer
GLuint name
GLuint64EXT * result
[6]
GLfloat GLfloat p
[1]
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define QStringLiteral(str)
Q_CORE_EXPORT bool qEnvironmentVariableIsEmpty(const char *varName) noexcept
ptrdiff_t qsizetype
Definition qtypes.h:165
QFile file
[0]
QUrl url("example.com")
[constructor-url-reference]
application x qt windows mime
[2]
The QMimeGlobMatchResult class accumulates results from glob matching.