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.5 Change-Id: Ic207b1593a49c7bb88e4fd810d8f88aa630087ce Reviewed-by: Thiago Macieira <thiago.macieira@intel.com> Reviewed-by: David Faure <david.faure@kdab.com> (cherry picked from commit 54656da9ace06caf4a0eeb1832989c0ab211a4a0) Reviewed-by: Alex Henrie <alexhenrie24@gmail.com>
This commit is contained in:
parent
7608a0be37
commit
9ceb7cb555
@ -10,6 +10,7 @@
|
|||||||
#include "qmimeprovider_p.h"
|
#include "qmimeprovider_p.h"
|
||||||
#include "qmimetype_p.h"
|
#include "qmimetype_p.h"
|
||||||
|
|
||||||
|
#include <private/qduplicatetracker_p.h>
|
||||||
#include <private/qfilesystementry_p.h>
|
#include <private/qfilesystementry_p.h>
|
||||||
|
|
||||||
#include <QtCore/QMap>
|
#include <QtCore/QMap>
|
||||||
@ -509,6 +510,7 @@ QList<QMimeType> QMimeDatabasePrivate::allMimeTypes()
|
|||||||
bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
|
bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
|
||||||
{
|
{
|
||||||
const QString resolvedParent = resolveAlias(parent);
|
const QString resolvedParent = resolveAlias(parent);
|
||||||
|
QDuplicateTracker<QString> seen;
|
||||||
std::stack<QString, QStringList> toCheck;
|
std::stack<QString, QStringList> toCheck;
|
||||||
toCheck.push(mime);
|
toCheck.push(mime);
|
||||||
while (!toCheck.empty()) {
|
while (!toCheck.empty()) {
|
||||||
@ -517,8 +519,11 @@ bool QMimeDatabasePrivate::inherits(const QString &mime, const QString &parent)
|
|||||||
const QString mimeName = toCheck.top();
|
const QString mimeName = toCheck.top();
|
||||||
toCheck.pop();
|
toCheck.pop();
|
||||||
const auto parentList = parents(mimeName);
|
const auto parentList = parents(mimeName);
|
||||||
for (const QString &par : parentList)
|
for (const QString &par : parentList) {
|
||||||
toCheck.push(resolveAlias(par));
|
const QString resolvedPar = resolveAlias(par);
|
||||||
|
if (!seen.hasSeen(resolvedPar))
|
||||||
|
toCheck.push(resolvedPar);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
@ -358,14 +358,17 @@ QStringList QMimeType::parentMimeTypes() const
|
|||||||
static void collectParentMimeTypes(const QString &mime, QStringList &allParents)
|
static void collectParentMimeTypes(const QString &mime, QStringList &allParents)
|
||||||
{
|
{
|
||||||
const QStringList parents = QMimeDatabasePrivate::instance()->mimeParents(mime);
|
const QStringList parents = QMimeDatabasePrivate::instance()->mimeParents(mime);
|
||||||
|
QStringList newParents;
|
||||||
for (const QString &parent : parents) {
|
for (const QString &parent : parents) {
|
||||||
// I would use QSet, but since order matters I better not
|
// I would use QSet, but since order matters I better not
|
||||||
if (!allParents.contains(parent))
|
if (!allParents.contains(parent)) {
|
||||||
allParents.append(parent);
|
allParents.append(parent);
|
||||||
|
newParents.append(parent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// We want a breadth-first search, so that the least-specific parent (octet-stream) is last
|
// We want a breadth-first search, so that the least-specific parent (octet-stream) is last
|
||||||
// This means iterating twice, unfortunately.
|
// This means iterating twice, unfortunately.
|
||||||
for (const QString &parent : parents)
|
for (const QString &parent : newParents)
|
||||||
collectParentMimeTypes(parent, allParents);
|
collectParentMimeTypes(parent, allParents);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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>
|
@ -23,6 +23,7 @@ qt_internal_add_test(tst_qmimedatabase-cache
|
|||||||
#"mime/packages/freedesktop.org.xml"
|
#"mime/packages/freedesktop.org.xml"
|
||||||
#)
|
#)
|
||||||
set(testdata_resource_files
|
set(testdata_resource_files
|
||||||
|
"../circular-inheritance.xml"
|
||||||
"../invalid-magic1.xml"
|
"../invalid-magic1.xml"
|
||||||
"../invalid-magic2.xml"
|
"../invalid-magic2.xml"
|
||||||
"../invalid-magic3.xml"
|
"../invalid-magic3.xml"
|
||||||
|
@ -23,6 +23,7 @@ qt_internal_add_test(tst_qmimedatabase-xml
|
|||||||
#"mime/packages/freedesktop.org.xml"
|
#"mime/packages/freedesktop.org.xml"
|
||||||
#)
|
#)
|
||||||
set(testdata_resource_files
|
set(testdata_resource_files
|
||||||
|
"../circular-inheritance.xml"
|
||||||
"../invalid-magic1.xml"
|
"../invalid-magic1.xml"
|
||||||
"../invalid-magic2.xml"
|
"../invalid-magic2.xml"
|
||||||
"../invalid-magic3.xml"
|
"../invalid-magic3.xml"
|
||||||
|
@ -38,6 +38,7 @@ static const char *const additionalMimeFiles[] = { "yast2-metapackage-handler-mi
|
|||||||
"invalid-magic2.xml",
|
"invalid-magic2.xml",
|
||||||
"invalid-magic3.xml",
|
"invalid-magic3.xml",
|
||||||
"magic-and-hierarchy.xml",
|
"magic-and-hierarchy.xml",
|
||||||
|
"circular-inheritance.xml",
|
||||||
0 };
|
0 };
|
||||||
|
|
||||||
static const auto s_resourcePrefix = ":/qt-project.org/qmime/"_L1;
|
static const auto s_resourcePrefix = ":/qt-project.org/qmime/"_L1;
|
||||||
@ -403,6 +404,13 @@ void tst_QMimeDatabase::inheritance()
|
|||||||
const QMimeType mswordTemplate = db.mimeTypeForName(QString::fromLatin1("application/msword-template"));
|
const QMimeType mswordTemplate = db.mimeTypeForName(QString::fromLatin1("application/msword-template"));
|
||||||
QVERIFY(mswordTemplate.isValid());
|
QVERIFY(mswordTemplate.isValid());
|
||||||
QVERIFY(mswordTemplate.inherits(QLatin1String("application/msword")));
|
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()
|
void tst_QMimeDatabase::aliases()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user