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));
}
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<QMimeType> 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;
}
/*!

View File

@ -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;

View File

@ -58,9 +58,13 @@ QT_BEGIN_NAMESPACE
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 (weight < m_weight)
if (m_allMatchingMimeTypes.contains(mimeType))
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;
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)
// 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);
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()

View File

@ -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"

View File

@ -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();
if (fileName.isEmpty())
return QStringList();
const QString lowerFileName = fileName.toLower();
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.
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)

View File

@ -56,6 +56,7 @@
#ifndef QT_NO_MIMETYPE
#include "qmimeglobpattern_p.h"
#include <QtCore/qdatetime.h>
#include <QtCore/qset.h>
#include <QtCore/qelapsedtimer.h>
@ -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;