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