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 <thiago.macieira@intel.com>
This commit is contained in:
David Faure 2016-12-26 13:46:40 +01:00
parent f163912b5d
commit 6722147696
6 changed files with 56 additions and 52 deletions

View File

@ -108,12 +108,12 @@ QMimeType QMimeDatabasePrivate::mimeTypeForName(const QString &nameOrAlias)
return provider()->mimeTypeForName(provider()->resolveAlias(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('/'))) if (fileName.endsWith(QLatin1Char('/')))
return QStringList() << QLatin1String("inode/directory"); 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 matchingMimeTypes.sort(); // make it deterministic
return matchingMimeTypes; return matchingMimeTypes;
} }
@ -168,13 +168,17 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa
*accuracyPtr = 0; *accuracyPtr = 0;
// Pass 1) Try to match on the file name // Pass 1) Try to match on the file name
QStringList candidatesByName = mimeTypeForFileName(fileName); QMimeGlobMatchResult candidatesByName;
if (candidatesByName.count() == 1) { 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; *accuracyPtr = 100;
const QMimeType mime = mimeTypeForName(candidatesByName.at(0)); const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
if (mime.isValid()) if (mime.isValid())
return mime; return mime;
candidatesByName.clear(); candidatesByName = {};
} }
// Extension is unknown, or matches multiple mimetypes. // Extension is unknown, or matches multiple mimetypes.
@ -193,7 +197,7 @@ QMimeType QMimeDatabasePrivate::mimeTypeForFileNameAndData(const QString &fileNa
// "for glob_match in glob_matches:" // "for glob_match in glob_matches:"
// "if glob_match is subclass or equal to sniffed_type, use glob_match" // "if glob_match is subclass or equal to sniffed_type, use glob_match"
const QString sniffedMime = candidateByData.name(); const QString sniffedMime = candidateByData.name();
for (const QString &m : qAsConst(candidatesByName)) { for (const QString &m : qAsConst(candidatesByName.m_matchingMimeTypes)) {
if (inherits(m, sniffedMime)) { if (inherits(m, sniffedMime)) {
// We have magic + pattern pointing to this, so it's a pretty good match // We have magic + pattern pointing to this, so it's a pretty good match
*accuracyPtr = 100; *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; *accuracyPtr = 20;
const QMimeType mime = mimeTypeForName(candidatesByName.at(0)); const QMimeType mime = mimeTypeForName(candidatesByName.m_matchingMimeTypes.at(0));
if (mime.isValid()) if (mime.isValid())
return mime; return mime;
} }
@ -455,9 +460,7 @@ QList<QMimeType> QMimeDatabase::mimeTypesForFileName(const QString &fileName) co
QString QMimeDatabase::suffixForFileName(const QString &fileName) const QString QMimeDatabase::suffixForFileName(const QString &fileName) const
{ {
QMutexLocker locker(&d->mutex); QMutexLocker locker(&d->mutex);
QString foundSuffix; return d->provider()->findByFileName(QFileInfo(fileName).fileName()).m_foundSuffix;
d->mimeTypeForFileName(fileName, &foundSuffix);
return foundSuffix;
} }
/*! /*!

View File

@ -90,7 +90,7 @@ public:
QMimeType mimeTypeForName(const QString &nameOrAlias); QMimeType mimeTypeForName(const QString &nameOrAlias);
QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device, int *priorityPtr); QMimeType mimeTypeForFileNameAndData(const QString &fileName, QIODevice *device, int *priorityPtr);
QMimeType findByData(const QByteArray &data, 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; mutable QMimeProviderBase *m_provider;
const QString m_defaultMimeType; const QString m_defaultMimeType;

View File

@ -58,9 +58,13 @@ QT_BEGIN_NAMESPACE
void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern) void QMimeGlobMatchResult::addMatch(const QString &mimeType, int weight, const QString &pattern)
{ {
// Is this a lower-weight pattern than the last match? Skip this match then. if (m_allMatchingMimeTypes.contains(mimeType))
if (weight < m_weight)
return; return;
// Is this a lower-weight pattern than the last match? Skip this match then.
if (weight < m_weight) {
m_allMatchingMimeTypes.append(mimeType);
return;
}
bool replace = weight > m_weight; bool replace = weight > m_weight;
if (!replace) { if (!replace) {
// Compare the length of the match // 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)) { if (!m_matchingMimeTypes.contains(mimeType)) {
m_matchingMimeTypes.append(mimeType); m_matchingMimeTypes.append(mimeType);
m_allMatchingMimeTypes.append(mimeType);
if (pattern.startsWith(QLatin1String("*."))) if (pattern.startsWith(QLatin1String("*.")))
m_foundSuffix = pattern.mid(2); 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. // First try the high weight matches (>50), if any.
QMimeGlobMatchResult result; QMimeGlobMatchResult result;
m_highWeightGlobs.match(result, fileName); m_highWeightGlobs.match(result, fileName);
if (result.m_matchingMimeTypes.isEmpty()) {
// Now use the "fast patterns" dict, for simple *.foo patterns with weight 50 // 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) // (which is most of them, so this optimization is definitely worth it)
const int lastDot = fileName.lastIndexOf(QLatin1Char('.')); const int lastDot = fileName.lastIndexOf(QLatin1Char('.'));
if (lastDot != -1) { // if no '.', skip the extension lookup if (lastDot != -1) { // if no '.', skip the extension lookup
const int ext_len = fileName.length() - lastDot - 1; const int ext_len = fileName.length() - lastDot - 1;
const QString simpleExtension = fileName.right(ext_len).toLower(); const QString simpleExtension = fileName.right(ext_len).toLower();
// (toLower because fast patterns are always case-insensitive and saved as lowercase) // (toLower because fast patterns are always case-insensitive and saved as lowercase)
const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension); const QStringList matchingMimeTypes = m_fastPatterns.value(simpleExtension);
const QString simplePattern = QLatin1String("*.") + simpleExtension; const QString simplePattern = QLatin1String("*.") + simpleExtension;
for (const QString &mime : matchingMimeTypes) for (const QString &mime : matchingMimeTypes)
result.addMatch(mime, 50, simplePattern); result.addMatch(mime, 50, simplePattern);
// Can't return yet; *.tar.bz2 has to win over *.bz2, so we need the low-weight mimetypes anyway, // 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. // at least those with weight 50.
}
// Finally, try the low weight matches (<=50)
m_lowWeightGlobs.match(result, fileName);
} }
if (foundSuffix)
*foundSuffix = result.m_foundSuffix; // Finally, try the low weight matches (<=50)
return result.m_matchingMimeTypes; m_lowWeightGlobs.match(result, fileName);
return result;
} }
void QMimeAllGlobPatterns::clear() void QMimeAllGlobPatterns::clear()

View File

@ -68,7 +68,8 @@ struct QMimeGlobMatchResult
void addMatch(const QString &mimeType, int weight, const QString &pattern); 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_weight;
int m_matchingPatternLength; int m_matchingPatternLength;
QString m_foundSuffix; QString m_foundSuffix;
@ -153,7 +154,7 @@ public:
void addGlob(const QMimeGlobPattern &glob); void addGlob(const QMimeGlobPattern &glob);
void removeMimeType(const QString &mimeType); void removeMimeType(const QString &mimeType);
QStringList matchingGlobs(const QString &fileName, QString *foundSuffix) const; QMimeGlobMatchResult matchingGlobs(const QString &fileName) const;
void clear(); void clear();
PatternsMap m_fastPatterns; // example: "doc" -> "application/msword", "text/plain" PatternsMap m_fastPatterns; // example: "doc" -> "application/msword", "text/plain"

View File

@ -287,13 +287,13 @@ QMimeType QMimeBinaryProvider::mimeTypeForName(const QString &name)
return mimeTypeForNameUnchecked(name); return mimeTypeForNameUnchecked(name);
} }
QStringList QMimeBinaryProvider::findByFileName(const QString &fileName, QString *foundSuffix) QMimeGlobMatchResult QMimeBinaryProvider::findByFileName(const QString &fileName)
{ {
checkCache(); checkCache();
if (fileName.isEmpty())
return QStringList();
const QString lowerFileName = fileName.toLower();
QMimeGlobMatchResult result; QMimeGlobMatchResult result;
if (fileName.isEmpty())
return result;
const QString lowerFileName = fileName.toLower();
// TODO this parses in the order (local, global). Check that it handles "NOGLOBS" correctly. // TODO this parses in the order (local, global). Check that it handles "NOGLOBS" correctly.
for (CacheFile *cacheFile : qAsConst(m_cacheFiles)) { for (CacheFile *cacheFile : qAsConst(m_cacheFiles)) {
matchGlobList(result, cacheFile, cacheFile->getUint32(PosLiteralListOffset), fileName); matchGlobList(result, cacheFile, cacheFile->getUint32(PosLiteralListOffset), fileName);
@ -305,9 +305,7 @@ QStringList QMimeBinaryProvider::findByFileName(const QString &fileName, QString
if (result.m_matchingMimeTypes.isEmpty()) if (result.m_matchingMimeTypes.isEmpty())
matchSuffixTree(result, cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true); matchSuffixTree(result, cacheFile, numRoots, firstRootOffset, fileName, fileName.length() - 1, true);
} }
if (foundSuffix) return result;
*foundSuffix = result.m_foundSuffix;
return result.m_matchingMimeTypes;
} }
void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) 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); return m_nameMimeTypeMap.value(name);
} }
QStringList QMimeXMLProvider::findByFileName(const QString &fileName, QString *foundSuffix) QMimeGlobMatchResult QMimeXMLProvider::findByFileName(const QString &fileName)
{ {
ensureLoaded(); ensureLoaded();
const QStringList matchingMimeTypes = m_mimeTypeGlobs.matchingGlobs(fileName, foundSuffix); return m_mimeTypeGlobs.matchingGlobs(fileName);
return matchingMimeTypes;
} }
QMimeType QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr) QMimeType QMimeXMLProvider::findByMagic(const QByteArray &data, int *accuracyPtr)

View File

@ -56,6 +56,7 @@
#ifndef QT_NO_MIMETYPE #ifndef QT_NO_MIMETYPE
#include "qmimeglobpattern_p.h"
#include <QtCore/qdatetime.h> #include <QtCore/qdatetime.h>
#include <QtCore/qset.h> #include <QtCore/qset.h>
#include <QtCore/qelapsedtimer.h> #include <QtCore/qelapsedtimer.h>
@ -72,7 +73,7 @@ public:
virtual bool isValid() = 0; virtual bool isValid() = 0;
virtual QMimeType mimeTypeForName(const QString &name) = 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 QStringList parents(const QString &mime) = 0;
virtual QString resolveAlias(const QString &name) = 0; virtual QString resolveAlias(const QString &name) = 0;
virtual QStringList listAliases(const QString &name) = 0; virtual QStringList listAliases(const QString &name) = 0;
@ -99,7 +100,7 @@ public:
virtual bool isValid() Q_DECL_OVERRIDE; virtual bool isValid() Q_DECL_OVERRIDE;
virtual QMimeType mimeTypeForName(const QString &name) 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 QStringList parents(const QString &mime) Q_DECL_OVERRIDE;
virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE; virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE;
virtual QStringList listAliases(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 bool isValid() Q_DECL_OVERRIDE;
virtual QMimeType mimeTypeForName(const QString &name) 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 QStringList parents(const QString &mime) Q_DECL_OVERRIDE;
virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE; virtual QString resolveAlias(const QString &name) Q_DECL_OVERRIDE;
virtual QStringList listAliases(const QString &name) Q_DECL_OVERRIDE; virtual QStringList listAliases(const QString &name) Q_DECL_OVERRIDE;