QMimeDatabase: handle buggy type definitions with circular inheritance

This fixes an infinite loop reported by a user who had both the
definition of text/javascript from shared-mime-info 2.3 and the
definition of text/javascript from shared-mime-info 2.4 installed at the
same time. In 2.3, text/javascript is a subtype of
application/ecmascript, but in 2.4 application/ecmascript is a subtype
of text/javascript. Having both at the same time resulted in circular
inheritance.

https://gitlab.freedesktop.org/xdg/shared-mime-info/-/merge_requests/258#note_2167707

[ChangeLog][QtCore][QMimeDatabase] Added code to detect and break
circular inheritance loops in the MIME data, which were causing infinite
loops

Pick-to: 6.6 6.5
Change-Id: Ic207b1593a49c7bb88e4fd810d8f88aa630087ce
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: David Faure <david.faure@kdab.com>
This commit is contained in:
Alex Henrie 2023-11-15 09:36:06 -07:00
parent 569320f402
commit 54656da9ac
6 changed files with 35 additions and 4 deletions

View File

@ -10,6 +10,7 @@
#include "qmimeprovider_p.h"
#include "qmimetype_p.h"
#include <private/qduplicatetracker_p.h>
#include <private/qfilesystementry_p.h>
#include <QtCore/QMap>
@ -503,6 +504,7 @@ QList<QMimeType> QMimeDatabasePrivate::allMimeTypes()
bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
{
const QString resolvedParent = resolveAlias(parent);
QDuplicateTracker<QString> seen;
std::stack<QString, QStringList> toCheck;
toCheck.push(mime);
while (!toCheck.empty()) {
@ -511,8 +513,11 @@ bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
const QString mimeName = toCheck.top();
toCheck.pop();
const auto parentList = parents(mimeName);
for (const QString &par : parentList)
toCheck.push(resolveAlias(par));
for (const QString &par : parentList) {
const QString resolvedPar = resolveAlias(par);
if (!seen.hasSeen(resolvedPar))
toCheck.push(resolvedPar);
}
}
return false;
}

View File

@ -325,14 +325,17 @@ QStringList QMimeType::parentMimeTypes() const
static void collectParentMimeTypes(const QString &mime, QStringList &allParents)
{
const QStringList parents = QMimeDatabasePrivate::instance()->mimeParents(mime);
QStringList newParents;
for (const QString &parent : parents) {
// I would use QSet, but since order matters I better not
if (!allParents.contains(parent))
if (!allParents.contains(parent)) {
allParents.append(parent);
newParents.append(parent);
}
}
// We want a breadth-first search, so that the least-specific parent (octet-stream) is last
// This means iterating twice, unfortunately.
for (const QString &parent : parents)
for (const QString &parent : newParents)
collectParentMimeTypes(parent, allParents);
}

View File

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns="http://www.freedesktop.org/standards/shared-mime-info">
<mime-type type="application/ecmascript">
<comment>It's more accurate to say that ECMAScript is a subset of JavaScript</comment>
<sub-class-of type="text/javascript"/>
<glob pattern="*.js"/>
</mime-type>
<mime-type type="text/javascript">
<comment>than to say that JavaScript is a subset of ECMAScript</comment>
<sub-class-of type="application/ecmascript"/>
<glob pattern="*.js"/>
</mime-type>
</mime-info>

View File

@ -31,6 +31,7 @@ qt_internal_add_test(tst_qmimedatabase-cache
#)
set(testdata_resource_files
"../add-extension.xml"
"../circular-inheritance.xml"
"../invalid-magic1.xml"
"../invalid-magic2.xml"
"../invalid-magic3.xml"

View File

@ -31,6 +31,7 @@ qt_internal_add_test(tst_qmimedatabase-xml
#)
set(testdata_resource_files
"../add-extension.xml"
"../circular-inheritance.xml"
"../invalid-magic1.xml"
"../invalid-magic2.xml"
"../invalid-magic3.xml"

View File

@ -48,6 +48,7 @@ static const std::array additionalLocalMimeFiles = {
"invalid-magic2.xml",
"invalid-magic3.xml",
"magic-and-hierarchy.xml",
"circular-inheritance.xml",
"webm-glob-deleteall.xml",
};
@ -388,6 +389,13 @@ void tst_QMimeDatabase::inheritance()
const QMimeType mswordTemplate = db.mimeTypeForName(QString::fromLatin1("application/msword-template"));
QVERIFY(mswordTemplate.isValid());
QVERIFY(mswordTemplate.inherits(QLatin1String("application/msword")));
// Check that buggy type definitions that have circular inheritance don't cause an infinite
// loop, especially when resolving a conflict between the file's name and its contents
const QMimeType ecmascript = db.mimeTypeForName(QString::fromLatin1("application/ecmascript"));
QVERIFY(ecmascript.allAncestors().contains("text/plain"));
const QMimeType javascript = db.mimeTypeForFileNameAndData("xml.js", "<?xml?>");
QVERIFY(javascript.inherits(QString::fromLatin1("text/javascript")));
}
void tst_QMimeDatabase::aliases()