From 6722147696c7ba603d24163498a3f7de76ad6cea Mon Sep 17 00:00:00 2001 From: David Faure Date: Mon, 26 Dec 2016 13:46:40 +0100 Subject: QMimeDatabase: fix handling of conflicting globs This code always intended to follow the recommended checking order from the spec, which says "if multiple globs match, use file contents". But if the globs had different weights, it would discard globs with lower weights, and then wrongly conclude, if there is only one glob left, that there was no ambiguity. The correct way is rather: remember that multiple globs matched, do determination from contents, and if that didn't work, *then* use (one of) the highest-weight glob(s). This fixes PGP-encrypted *.asc files being detected as text/plain rather than application/pgp-encrypted. (https://bugs.kde.org/show_bug.cgi?id=346754) Change-Id: I734459daf9f502baa95ebb89432819964e0ce304 Reviewed-by: Thiago Macieira --- src/corelib/mimetypes/qmimedatabase.cpp | 27 +++++++++------- src/corelib/mimetypes/qmimedatabase_p.h | 2 +- src/corelib/mimetypes/qmimeglobpattern.cpp | 50 ++++++++++++++++-------------- src/corelib/mimetypes/qmimeglobpattern_p.h | 5 +-- src/corelib/mimetypes/qmimeprovider.cpp | 15 ++++----- src/corelib/mimetypes/qmimeprovider_p.h | 7 +++-- 6 files changed, 55 insertions(+), 51 deletions(-) (limited to 'src/corelib/mimetypes') diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp index 448e6117b1..fda9f01643 100644 --- a/src/corelib/mimetypes/qmimedatabase.cpp +++ b/src/corelib/mimetypes/qmimedatabase.cpp @@ -108,12 +108,12 @@ QMimeType QMimeDatabasePrivate::mimeTypeForName(const QString &nameOrAlias) return provider()->mimeTypeForName(provider()->resolveAlias(nameOrAlias)); } -QStringList QMimeDatabasePrivate::mimeTypeForFileName(const QString &fileName, QString *foundSuffix) +QStringList QMimeDatabasePrivate::mimeTypeForFileName(const QString &fileName) { if (fileName.endsWith(QLatin1Char('/'))) return QStringList() << QLatin1String("inode/directory"); - QStringList matchingMimeTypes = provider()->findByFileName(QFileInfo(fileName).fileName(), foundSuffix); + QStringList matchingMimeTypes = provider()->findByFileName(QFileInfo(fileName).fileName()).m_matchingMimeTypes; matchingMimeTypes.sort(); // make it deterministic return matchingMimeTypes; } @@ -168,13 +168,17 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa *accuracyPtr = 0; // Pass 1) Try to match on the file name - QStringList candidatesByName = mimeTypeForFileName(fileName); - if (candidatesByName.count() == 1) { + QMimeGlobMatchResult candidatesByName; + if (fileName.endsWith(QLatin1Char('/'))) + candidatesByName.addMatch(QLatin1String("inode/directory"), 100, QString()); + else + candidatesByName = provider()->findByFileName(QFileInfo(fileName).fileName()); + if (candidatesByName.m_allMatchingMimeTypes.count() == 1) { *accuracyPtr = 100; - const QMimeType mime = mimeTypeForName(candidatesByName.at(0)); + const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0)); if (mime.isValid()) return mime; - candidatesByName.clear(); + candidatesByName = {}; } // Extension is unknown, or matches multiple mimetypes. @@ -193,7 +197,7 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa // "for glob_match in glob_matches:" // "if glob_match is subclass or equal to sniffed_type, use glob_match" const QString sniffedMime = candidateByData.name(); - for (const QString &m : qAsConst(candidatesByName)) { + for (const QString &m : qAsConst(candidatesByName.m_matchingMimeTypes)) { if (inherits(m, sniffedMime)) { // We have magic + pattern pointing to this, so it's a pretty good match *accuracyPtr = 100; @@ -205,9 +209,10 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa } } - if (candidatesByName.count() > 1) { + if (candidatesByName.m_allMatchingMimeTypes.count() > 1) { + candidatesByName.m_matchingMimeTypes.sort(); // make it deterministic *accuracyPtr = 20; - const QMimeType mime = mimeTypeForName(candidatesByName.at(0)); + const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0)); if (mime.isValid()) return mime; } @@ -455,9 +460,7 @@ QList QMimeDatabase::mimeTypesForFileName(const QString &fileName) co QString QMimeDatabase::suffixForFileName(const QString &fileName) const { QMutexLocker locker(&d->mutex); - QString foundSuffix; - d->mimeTypeForFileName(fileName, &foundSuffix); - return foundSuffix; + return d->provider()->findByFileName(QFileInfo(fileName).fileName()).m_foundSuffix; } /*! diff --git a/src/corelib/mimetypes/qmimedatabase_p.h b/src/corelib/mimetypes/qmimedatabase_p.h index 4ff5110a5b..3f63f5f103 100644 --- a/src/corelib/mimetypes/qmimedatabase_p.h +++ b/src/corelib/mimetypes/qmimedatabase_p.h @@ -90,7 +90,7 @@ public: QMimeType mimeTypeForName(const QString &nameOrAlias); QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device, int *priorityPtr); QMimeType findByData(const QByteArray &data, int *priorityPtr); - QStringList mimeTypeForFileName(const QString &fileName, QString *foundSuffix = 0); + QStringList mimeTypeForFileName(const QString &fileName); mutable QMimeProviderBase *m_provider; const QString m_defaultMimeType; diff --git a/src/corelib/mimetypes/qmimeglobpattern.cpp b/src/corelib/mimetypes/qmimeglobpattern.cpp index 568f9bf4de..a4d2b046fa 100644 --- a/src/corelib/mimetypes/qmimeglobpattern.cpp +++ b/src/corelib/mimetypes/qmimeglobpattern.cpp @@ -58,9 +58,13 @@ QT_BEGIN_NAMESPACE void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern) { + if (m_allMatchingMimeTypes.contains(mimeType)) + return; // Is this a lower-weight pattern than the last match? Skip this match then. - if (weight < m_weight) + if (weight < m_weight) { + m_allMatchingMimeTypes.append(mimeType); return; + } bool replace = weight > m_weight; if (!replace) { // Compare the length of the match @@ -79,6 +83,7 @@ void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const Q } if (!m_matchingMimeTypes.contains(mimeType)) { m_matchingMimeTypes.append(mimeType); + m_allMatchingMimeTypes.append(mimeType); if (pattern.startsWith(QLatin1String("*."))) m_foundSuffix = pattern.mid(2); } @@ -201,35 +206,32 @@ void QMimeGlobPatternList::match(QMimeGlobMatchResult &result, } } -QStringList QMimeAllGlobPatterns::matchingGlobs(const QString &fileName, QString *foundSuffix) const +QMimeGlobMatchResult QMimeAllGlobPatterns::matchingGlobs(const QString &fileName) const { // First try the high weight matches (>50), if any. QMimeGlobMatchResult result; m_highWeightGlobs.match(result, fileName); - if (result.m_matchingMimeTypes.isEmpty()) { - - // Now use the "fast patterns" dict, for simple *.foo patterns with weight 50 - // (which is most of them, so this optimization is definitely worth it) - const int lastDot = fileName.lastIndexOf(QLatin1Char('.')); - if (lastDot != -1) { // if no '.', skip the extension lookup - const int ext_len = fileName.length() - lastDot - 1; - const QString simpleExtension = fileName.right(ext_len).toLower(); - // (toLower because fast patterns are always case-insensitive and saved as lowercase) - - const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension); - const QString simplePattern = QLatin1String("*.") + simpleExtension; - for (const QString &mime : matchingMimeTypes) - result.addMatch(mime, 50, simplePattern); - // Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the low-weight mimetypes anyway, - // at least those with weight 50. - } - // Finally, try the low weight matches (<=50) - m_lowWeightGlobs.match(result, fileName); + // Now use the "fast patterns" dict, for simple *.foo patterns with weight 50 + // (which is most of them, so this optimization is definitely worth it) + const int lastDot = fileName.lastIndexOf(QLatin1Char('.')); + if (lastDot != -1) { // if no '.', skip the extension lookup + const int ext_len = fileName.length() - lastDot - 1; + const QString simpleExtension = fileName.right(ext_len).toLower(); + // (toLower because fast patterns are always case-insensitive and saved as lowercase) + + const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension); + const QString simplePattern = QLatin1String("*.") + simpleExtension; + for (const QString &mime : matchingMimeTypes) + result.addMatch(mime, 50, simplePattern); + // Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the low-weight mimetypes anyway, + // at least those with weight 50. } - if (foundSuffix) - *foundSuffix = result.m_foundSuffix; - return result.m_matchingMimeTypes; + + // Finally, try the low weight matches (<=50) + m_lowWeightGlobs.match(result, fileName); + + return result; } void QMimeAllGlobPatterns::clear() diff --git a/src/corelib/mimetypes/qmimeglobpattern_p.h b/src/corelib/mimetypes/qmimeglobpattern_p.h index 3e4fdb50f6..c8b70464fd 100644 --- a/src/corelib/mimetypes/qmimeglobpattern_p.h +++ b/src/corelib/mimetypes/qmimeglobpattern_p.h @@ -68,7 +68,8 @@ struct QMimeGlobMatchResult void addMatch(const QString &mimeType, int weight, const QString &pattern); - QStringList m_matchingMimeTypes; + QStringList m_matchingMimeTypes; // only those with highest weight + QStringList m_allMatchingMimeTypes; int m_weight; int m_matchingPatternLength; QString m_foundSuffix; @@ -153,7 +154,7 @@ public: void addGlob(const QMimeGlobPattern &glob); void removeMimeType(const QString &mimeType); - QStringList matchingGlobs(const QString &fileName, QString *foundSuffix) const; + QMimeGlobMatchResult matchingGlobs(const QString &fileName) const; void clear(); PatternsMap m_fastPatterns; // example: "doc" -> "application/msword", "text/plain" diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index 65b011b439..959421bf52 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -287,13 +287,13 @@ QMimeType QMimeBinaryProvider::mimeTypeForName(const QString &name) return mimeTypeForNameUnchecked(name); } -QStringList QMimeBinaryProvider::findByFileName(const QString &fileName, QString *foundSuffix) +QMimeGlobMatchResult QMimeBinaryProvider::findByFileName(const QString &fileName) { checkCache(); + QMimeGlobMatchResult result; if (fileName.isEmpty()) - return QStringList(); + return result; const QString lowerFileName = fileName.toLower(); - QMimeGlobMatchResult result; // TODO this parses in the order (local, global). Check that it handles "NOGLOBS" correctly. for (CacheFile *cacheFile : qAsConst(m_cacheFiles)) { matchGlobList(result, cacheFile, cacheFile->getUint32(PosLiteralListOffset), fileName); @@ -305,9 +305,7 @@ QStringList QMimeBinaryProvider::findByFileName(const QString &fileName, QString if (result.m_matchingMimeTypes.isEmpty()) matchSuffixTree(result, cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true); } - if (foundSuffix) - *foundSuffix = result.m_foundSuffix; - return result.m_matchingMimeTypes; + return result; } void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) @@ -728,12 +726,11 @@ QMimeType QMimeXMLProvider::mimeTypeForName(const QString &name) return m_nameMimeTypeMap.value(name); } -QStringList QMimeXMLProvider::findByFileName(const QString &fileName, QString *foundSuffix) +QMimeGlobMatchResult QMimeXMLProvider::findByFileName(const QString &fileName) { ensureLoaded(); - const QStringList matchingMimeTypes = m_mimeTypeGlobs.matchingGlobs(fileName, foundSuffix); - return matchingMimeTypes; + return m_mimeTypeGlobs.matchingGlobs(fileName); } QMimeType QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr) diff --git a/src/corelib/mimetypes/qmimeprovider_p.h b/src/corelib/mimetypes/qmimeprovider_p.h index e6fc47bf80..f410e62267 100644 --- a/src/corelib/mimetypes/qmimeprovider_p.h +++ b/src/corelib/mimetypes/qmimeprovider_p.h @@ -56,6 +56,7 @@ #ifndef QT_NO_MIMETYPE +#include "qmimeglobpattern_p.h" #include #include #include @@ -72,7 +73,7 @@ public: virtual bool isValid() = 0; virtual QMimeType mimeTypeForName(const QString &name) = 0; - virtual QStringList findByFileName(const QString &fileName, QString *foundSuffix) = 0; + virtual QMimeGlobMatchResult findByFileName(const QString &fileName) = 0; virtual QStringList parents(const QString &mime) = 0; virtual QString resolveAlias(const QString &name) = 0; virtual QStringList listAliases(const QString &name) = 0; @@ -99,7 +100,7 @@ public: virtual bool isValid() Q_DECL_OVERRIDE; virtual QMimeType mimeTypeForName(const QString &name) Q_DECL_OVERRIDE; - virtual QStringList findByFileName(const QString &fileName, QString *foundSuffix) Q_DECL_OVERRIDE; + virtual QMimeGlobMatchResult findByFileName(const QString &fileName) Q_DECL_OVERRIDE; virtual QStringList parents(const QString &mime) Q_DECL_OVERRIDE; virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE; virtual QStringList listAliases(const QString &name) Q_DECL_OVERRIDE; @@ -142,7 +143,7 @@ public: virtual bool isValid() Q_DECL_OVERRIDE; virtual QMimeType mimeTypeForName(const QString &name) Q_DECL_OVERRIDE; - virtual QStringList findByFileName(const QString &fileName, QString *foundSuffix) Q_DECL_OVERRIDE; + virtual QMimeGlobMatchResult findByFileName(const QString &fileName) Q_DECL_OVERRIDE; virtual QStringList parents(const QString &mime) Q_DECL_OVERRIDE; virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE; virtual QStringList listAliases(const QString &name) Q_DECL_OVERRIDE; -- cgit v1.2.3