diff --git a/src/corelib/mimetypes/qmimedatabase.cpp b/src/corelib/mimetypes/qmimedatabase.cpp index dbe4d43c178..fd6324957f5 100644 --- a/src/corelib/mimetypes/qmimedatabase.cpp +++ b/src/corelib/mimetypes/qmimedatabase.cpp @@ -142,6 +142,19 @@ void QMimeDatabasePrivate::loadProviders() m_providers.push_back(std::move(*it)); } } + + // Handle mimetypes with glob-deleteall tags (from XML providers) + auto it = m_providers.begin(); + const auto end = m_providers.end(); + for (;it != end; ++it) { + const QStringList &list = (*it)->m_mimeTypesWithDeletedGlobs; + if (list.isEmpty()) + continue; + // Each Provider affects Providers with lower precedence + auto nextIt = it + 1; + for (; nextIt != end; ++nextIt) + (*nextIt)->excludeMimeTypeGlobs(list); + } } const QMimeDatabasePrivate::Providers &QMimeDatabasePrivate::providers() diff --git a/src/corelib/mimetypes/qmimeprovider.cpp b/src/corelib/mimetypes/qmimeprovider.cpp index 5c909ec9240..8a88a2674f9 100644 --- a/src/corelib/mimetypes/qmimeprovider.cpp +++ b/src/corelib/mimetypes/qmimeprovider.cpp @@ -51,6 +51,12 @@ QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; +static inline void appendIfNew(QStringList &list, const QString &str) +{ + if (!list.contains(str)) + list.push_back(str); +} + QMimeProviderBase::QMimeProviderBase(QMimeDatabasePrivate *db, const QString &directory) : m_db(db), m_directory(directory) { @@ -228,6 +234,17 @@ void QMimeBinaryProvider::addFileNameMatches(const QString &fileName, QMimeGlobM matchGlobList(result, m_cacheFile, m_cacheFile->getUint32(PosGlobListOffset), fileName); } +bool QMimeBinaryProvider::isMimeTypeGlobsExcluded(const char *mimeTypeName) +{ + return m_mimeTypesWithExcludedGlobs.contains(QLatin1StringView(mimeTypeName)); +} + +void QMimeBinaryProvider::excludeMimeTypeGlobs(const QStringList &toExclude) +{ + for (const auto &mt : toExclude) + appendIfNew(m_mimeTypesWithExcludedGlobs, mt); +} + void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile *cacheFile, int off, const QString &fileName) { const int numGlobs = cacheFile->getUint32(off); @@ -243,8 +260,10 @@ void QMimeBinaryProvider::matchGlobList(QMimeGlobMatchResult &result, CacheFile const char *mimeType = cacheFile->getCharStar(mimeTypeOffset); //qDebug() << pattern << mimeType << weight << caseSensitive; - QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive); + if (isMimeTypeGlobsExcluded(mimeType)) + continue; + QMimeGlobPattern glob(pattern, QString() /*unused*/, weight, qtCaseSensitive); if (glob.matchFileName(fileName)) result.addMatch(QLatin1StringView(mimeType), weight, pattern); } @@ -281,6 +300,8 @@ bool QMimeBinaryProvider::matchSuffixTree(QMimeGlobMatchResult &result, break; const int mimeTypeOffset = cacheFile->getUint32(childOff + 4); const char *mimeType = cacheFile->getCharStar(mimeTypeOffset); + if (isMimeTypeGlobsExcluded(mimeType)) + continue; const int flagsAndWeight = cacheFile->getUint32(childOff + 8); const int weight = flagsAndWeight & 0xff; const bool caseSensitive = flagsAndWeight & 0x100; @@ -716,6 +737,7 @@ void QMimeXMLProvider::ensureLoaded() m_parents.clear(); m_mimeTypeGlobs.clear(); m_magicMatchers.clear(); + m_mimeTypesWithDeletedGlobs.clear(); //qDebug() << "Loading" << m_allFiles; @@ -767,9 +789,31 @@ void QMimeXMLProvider::addGlobPattern(const QMimeGlobPattern &glob) void QMimeXMLProvider::addMimeType(const QMimeType &mt) { Q_ASSERT(!mt.d.data()->fromCache); + + QString name = mt.name(); + if (mt.d->hasGlobDeleteAll) + appendIfNew(m_mimeTypesWithDeletedGlobs, name); m_nameMimeTypeMap.insert(mt.name(), mt); } +/* + \a toExclude is a list of mime type names that should have the the glob patterns + associated with them cleared (because there are mime types with the same names + in a higher precedence Provider that have glob-deleteall tags). + + This method is called from QMimeDatabasePrivate::loadProviders() to exclude mime + type glob patterns in lower precedence Providers. +*/ +void QMimeXMLProvider::excludeMimeTypeGlobs(const QStringList &toExclude) +{ + for (const auto &mt : toExclude) { + auto it = m_nameMimeTypeMap.find(mt); + if (it != m_nameMimeTypeMap.end()) + it->d->globPatterns.clear(); + m_mimeTypeGlobs.removeMimeType(mt); + } +} + void QMimeXMLProvider::addParents(const QString &mime, QStringList &result) { for (const QString &parent : m_parents.value(mime)) { diff --git a/src/corelib/mimetypes/qmimeprovider_p.h b/src/corelib/mimetypes/qmimeprovider_p.h index 7440c500b85..7f4d9542c15 100644 --- a/src/corelib/mimetypes/qmimeprovider_p.h +++ b/src/corelib/mimetypes/qmimeprovider_p.h @@ -48,11 +48,38 @@ public: virtual void loadIcon(QMimeTypePrivate &) {} virtual void loadGenericIcon(QMimeTypePrivate &) {} virtual void ensureLoaded() {} + virtual void excludeMimeTypeGlobs(const QStringList &) {} QString directory() const { return m_directory; } QMimeDatabasePrivate *m_db; QString m_directory; + + /* + MimeTypes with "glob-deleteall" tags are handled differently by each provider + sub-class: + - QMimeBinaryProvider parses glob-deleteall tags lazily, i.e. only when loadMimeTypePrivate() + is called, and clears the glob patterns associated with mimetypes that have this tag + - QMimeXMLProvider parses glob-deleteall from the the start, i.e. when a XML file is + parsed with QMimeTypeParser + + The two lists below are used to let both provider types (XML and Binary) communicate + about mimetypes with glob-deleteall. + */ + /* + List of mimetypes in _this_ Provider that have a "glob-deleteall" tag, + glob patterns for those mimetypes should be ignored in all _other_ lower + precedence Providers. + */ + QStringList m_mimeTypesWithDeletedGlobs; + + /* + List of mimetypes with glob patterns that are "overwritten" in _this_ Provider, + by a "glob-deleteall" tag in a mimetype definition in a _higher precedence_ + Provider. With QMimeBinaryProvider, we can't change the data in the binary mmap'ed + file, hence the need for this list. + */ + QStringList m_mimeTypesWithExcludedGlobs; }; /* @@ -77,6 +104,7 @@ public: void loadIcon(QMimeTypePrivate &) override; void loadGenericIcon(QMimeTypePrivate &) override; void ensureLoaded() override; + void excludeMimeTypeGlobs(const QStringList &toExclude) override; private: struct CacheFile; @@ -86,6 +114,7 @@ private: int firstOffset, const QString &fileName, qsizetype charPos, bool caseSensitiveCheck); bool matchMagicRule(CacheFile *cacheFile, int numMatchlets, int firstOffset, const QByteArray &data); + bool isMimeTypeGlobsExcluded(const char *name); QLatin1StringView iconForMime(CacheFile *cacheFile, int posListOffset, const QByteArray &inputMime); void loadMimeTypeList(); bool checkCacheChanged(); @@ -96,6 +125,7 @@ private: bool m_mimetypeListLoaded; struct MimeTypeExtra { + // Both retrieved on demand in loadMimeTypePrivate QHash localeComments; QStringList globPatterns; }; @@ -133,6 +163,7 @@ public: // Called by the mimetype xml parser void addMimeType(const QMimeType &mt); + void excludeMimeTypeGlobs(const QStringList &toExclude) override; void addGlobPattern(const QMimeGlobPattern &glob); void addParent(const QString &child, const QString &parent); void addAlias(const QString &alias, const QString &name); diff --git a/src/corelib/mimetypes/qmimetype_p.h b/src/corelib/mimetypes/qmimetype_p.h index 232c36c38e3..7b5ed77f40a 100644 --- a/src/corelib/mimetypes/qmimetype_p.h +++ b/src/corelib/mimetypes/qmimetype_p.h @@ -39,6 +39,7 @@ public: bool loaded; // QSharedData leaves a 4 byte gap, so don't put 8 byte members first bool fromCache; // true if this comes from the binary provider + bool hasGlobDeleteAll = false; // true if the mimetype has a glob-deleteall tag QString name; LocaleHash localeComments; QString genericIconName; diff --git a/src/corelib/mimetypes/qmimetypeparser.cpp b/src/corelib/mimetypes/qmimetypeparser.cpp index 349313a01d6..0cb7b248692 100644 --- a/src/corelib/mimetypes/qmimetypeparser.cpp +++ b/src/corelib/mimetypes/qmimetypeparser.cpp @@ -215,6 +215,7 @@ bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString break; case ParseGlobDeleteAll: data.globPatterns.clear(); + data.hasGlobDeleteAll = true; break; case ParseSubClass: { const QString inheritsFrom = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString(); diff --git a/tests/auto/corelib/mimetypes/qmimedatabase/mimetypes-override/mime/packages/webm-glob-deleteall.xml b/tests/auto/corelib/mimetypes/qmimedatabase/mimetypes-override/mime/packages/webm-glob-deleteall.xml new file mode 100644 index 00000000000..05a24de17c6 --- /dev/null +++ b/tests/auto/corelib/mimetypes/qmimedatabase/mimetypes-override/mime/packages/webm-glob-deleteall.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp index 04afb60118e..ca29576dde2 100644 --- a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp +++ b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.cpp @@ -129,7 +129,9 @@ void tst_QMimeDatabase::initTestCase() const QString globalPackageDir = m_globalXdgDir + QStringLiteral("/mime/packages"); QVERIFY(here.mkpath(globalPackageDir)); - qputenv("XDG_DATA_DIRS", QFile::encodeName(m_globalXdgDir)); + QString overrideDir = QFINDTESTDATA("mimetypes-override/"); + QByteArray env = QFile::encodeName(overrideDir) + ':' + QFile::encodeName(m_globalXdgDir); + qputenv("XDG_DATA_DIRS", env); qDebug() << "\nGlobal XDG_DATA_DIRS: " << m_globalXdgDir; const QString freeDesktopXml = QStringLiteral("freedesktop.org.xml"); @@ -299,6 +301,15 @@ void tst_QMimeDatabase::mimeTypesForFileName_data() QTest::newRow("non_ascii") << QString::fromUtf8("AİİA.pdf") << (QStringList() << "application/pdf"); } +static QStringList mimeTypeNames(const QList &mimes) +{ + QStringList mimeNames; + mimeNames.reserve(mimes.size()); + for (const auto &mime : mimes) + mimeNames.append(mime.name()); + return mimeNames; +} + void tst_QMimeDatabase::mimeTypesForFileName() { QFETCH(QString, fileName); @@ -311,6 +322,24 @@ void tst_QMimeDatabase::mimeTypesForFileName() QCOMPARE(mimeNames, expectedMimeTypes); } +void tst_QMimeDatabase::mimeTypesForFileName_glob_deleteall() +{ +#if !defined(USE_XDG_DATA_DIRS) + QSKIP("This test requires XDG_DATA_DIRS"); +#endif + + QMimeDatabase mdb; + QList mimes = mdb.mimeTypesForFileName(u"foo.webm"_s); + + // "*.webm" glob pattern is deleted with "glob-deleteall" + QVERIFY2(mimes.isEmpty(), qPrintable(mimeTypeNames(mimes).join(u','))); + mimes = mdb.mimeTypesForFileName(u"foo.videowebm"_s); + QCOMPARE(mimes.size(), 1); + QCOMPARE(mimes.at(0).globPatterns(), QStringList{"*.videowebm"}); + // Custom "*.videowebm" pattern is used instead + QCOMPARE(mimes.at(0).name(), u"video/webm"); +} + void tst_QMimeDatabase::inheritance() { QMimeDatabase db; diff --git a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.h b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.h index 4622a72536a..65f391fd26a 100644 --- a/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.h +++ b/tests/auto/corelib/mimetypes/qmimedatabase/tst_qmimedatabase.h @@ -25,6 +25,7 @@ private slots: void mimeTypeForFileName(); void mimeTypesForFileName_data(); void mimeTypesForFileName(); + void mimeTypesForFileName_glob_deleteall(); void inheritance(); void aliases(); void listAliases_data();