QMimeDatabase: warn instead of asserting on bad magic.

An invalid mime magic definition could lead to an assert. Replaced with
a qWarning. Move all checking to the QMimeMagicRule constructor, and do
keep invalid rules since they are need to parse child rules.

Unit test added, with QTest::ignoreMessage when using the XML backend
(there's no warning from update-mime-database when using the cache).
Also make it easier to add more shared mime info files for tests.

Task-number: QTBUG-44319
Done-with: Eike Ziller <eike.ziller@theqtcompany.com>
Change-Id: Ie39a160a106b650cdcee88778fa7eff9e932a988
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
bb10
David Faure 2015-08-25 14:30:07 +02:00
parent 880a8aa7e9
commit 5e41f4137d
10 changed files with 159 additions and 82 deletions

View File

@ -38,6 +38,7 @@
#ifndef QT_NO_MIMETYPE
#include "qmimetypeparser_p.h"
#include <QtCore/QList>
#include <QtCore/QDebug>
#include <qendian.h>
@ -231,26 +232,53 @@ static inline QByteArray makePattern(const QByteArray &value)
return pattern;
}
QMimeMagicRule::QMimeMagicRule(QMimeMagicRule::Type theType,
// Evaluate a magic match rule like
// <match value="must be converted with BinHex" type="string" offset="11"/>
// <match value="0x9501" type="big16" offset="0:64"/>
QMimeMagicRule::QMimeMagicRule(const QString &typeStr,
const QByteArray &theValue,
int theStartPos,
int theEndPos,
const QByteArray &theMask) :
const QString &offsets,
const QByteArray &theMask,
QString *errorString) :
d(new QMimeMagicRulePrivate)
{
Q_ASSERT(!theValue.isEmpty());
d->type = theType;
d->value = theValue;
d->startPos = theStartPos;
d->endPos = theEndPos;
d->mask = theMask;
d->matchFunction = 0;
d->type = QMimeMagicRule::type(typeStr.toLatin1());
if (d->type == Invalid) {
*errorString = QStringLiteral("Type %s is not supported").arg(typeStr);
}
// Parse for offset as "1" or "1:10"
const int colonIndex = offsets.indexOf(QLatin1Char(':'));
const QString startPosStr = colonIndex == -1 ? offsets : offsets.mid(0, colonIndex);
const QString endPosStr = colonIndex == -1 ? offsets : offsets.mid(colonIndex + 1);
if (!QMimeTypeParserBase::parseNumber(startPosStr, &d->startPos, errorString) ||
!QMimeTypeParserBase::parseNumber(endPosStr, &d->endPos, errorString)) {
d->type = Invalid;
return;
}
if (d->value.isEmpty()) {
d->type = Invalid;
if (errorString)
*errorString = QLatin1String("Invalid empty magic rule value");
return;
}
if (d->type >= Host16 && d->type <= Byte) {
bool ok;
d->number = d->value.toUInt(&ok, 0); // autodetect
Q_ASSERT(ok);
if (!ok) {
d->type = Invalid;
if (errorString)
*errorString = QString::fromLatin1("Invalid magic rule value \"%1\"").arg(
QString::fromLatin1(d->value));
return;
}
d->numberMask = !d->mask.isEmpty() ? d->mask.toUInt(&ok, 0) : 0; // autodetect
}
@ -259,9 +287,23 @@ QMimeMagicRule::QMimeMagicRule(QMimeMagicRule::Type theType,
d->pattern = makePattern(d->value);
d->pattern.squeeze();
if (!d->mask.isEmpty()) {
Q_ASSERT(d->mask.size() >= 4 && d->mask.startsWith("0x"));
d->mask = QByteArray::fromHex(QByteArray::fromRawData(d->mask.constData() + 2, d->mask.size() - 2));
Q_ASSERT(d->mask.size() == d->pattern.size());
if (d->mask.size() < 4 || !d->mask.startsWith("0x")) {
d->type = Invalid;
if (errorString)
*errorString = QString::fromLatin1("Invalid magic rule mask \"%1\"").arg(
QString::fromLatin1(d->mask));
return;
}
const QByteArray &tempMask = QByteArray::fromHex(QByteArray::fromRawData(
d->mask.constData() + 2, d->mask.size() - 2));
if (tempMask.size() != d->pattern.size()) {
d->type = Invalid;
if (errorString)
*errorString = QString::fromLatin1("Invalid magic rule mask size \"%1\"").arg(
QString::fromLatin1(d->mask));
return;
}
d->mask = tempMask;
} else {
d->mask.fill(char(-1), d->pattern.size());
}

View File

@ -61,7 +61,8 @@ class QMimeMagicRule
public:
enum Type { Invalid = 0, String, Host16, Host32, Big16, Big32, Little16, Little32, Byte };
QMimeMagicRule(Type type, const QByteArray &value, int startPos, int endPos, const QByteArray &mask = QByteArray());
QMimeMagicRule(const QString &typeStr, const QByteArray &value, const QString &offsets,
const QByteArray &mask, QString *errorString);
QMimeMagicRule(const QMimeMagicRule &other);
~QMimeMagicRule();

View File

@ -153,8 +153,8 @@ QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState curren
return ParseError;
}
// Parse int number from an (attribute) string)
static bool parseNumber(const QString &n, int *target, QString *errorMessage)
// Parse int number from an (attribute) string
bool QMimeTypeParserBase::parseNumber(const QString &n, int *target, QString *errorMessage)
{
bool ok;
*target = n.toInt(&ok);
@ -165,37 +165,14 @@ static bool parseNumber(const QString &n, int *target, QString *errorMessage)
return true;
}
// Evaluate a magic match rule like
// <match value="must be converted with BinHex" type="string" offset="11"/>
// <match value="0x9501" type="big16" offset="0:64"/>
#ifndef QT_NO_XMLSTREAMREADER
static bool createMagicMatchRule(const QXmlStreamAttributes &atts,
QString *errorMessage, QMimeMagicRule *&rule)
static QMimeMagicRule *createMagicMatchRule(const QXmlStreamAttributes &atts, QString *errorMessage)
{
const QString type = atts.value(QLatin1String(matchTypeAttributeC)).toString();
QMimeMagicRule::Type magicType = QMimeMagicRule::type(type.toLatin1());
if (magicType == QMimeMagicRule::Invalid) {
qWarning("%s: match type %s is not supported.", Q_FUNC_INFO, type.toUtf8().constData());
return true;
}
const QString value = atts.value(QLatin1String(matchValueAttributeC)).toString();
if (value.isEmpty()) {
*errorMessage = QString::fromLatin1("Empty match value detected.");
return false;
}
// Parse for offset as "1" or "1:10"
int startPos, endPos;
const QString offsetS = atts.value(QLatin1String(matchOffsetAttributeC)).toString();
const int colonIndex = offsetS.indexOf(QLatin1Char(':'));
const QString startPosS = colonIndex == -1 ? offsetS : offsetS.mid(0, colonIndex);
const QString endPosS = colonIndex == -1 ? offsetS : offsetS.mid(colonIndex + 1);
if (!parseNumber(startPosS, &startPos, errorMessage) || !parseNumber(endPosS, &endPos, errorMessage))
return false;
const QString offsets = atts.value(QLatin1String(matchOffsetAttributeC)).toString();
const QString mask = atts.value(QLatin1String(matchMaskAttributeC)).toString();
rule = new QMimeMagicRule(magicType, value.toUtf8(), startPos, endPos, mask.toLatin1());
return true;
return new QMimeMagicRule(type, value.toUtf8(), offsets, mask.toLatin1(), errorMessage);
}
#endif
@ -283,9 +260,10 @@ bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString
}
break;
case ParseMagicMatchRule: {
QMimeMagicRule *rule = 0;
if (!createMagicMatchRule(atts, errorMessage, rule))
return false;
QString magicErrorMessage;
QMimeMagicRule *rule = createMagicMatchRule(atts, &magicErrorMessage);
if (!rule->isValid())
qWarning("QMimeDatabase: Error parsing %s\n%s", qPrintable(fileName), qPrintable(magicErrorMessage));
QList<QMimeMagicRule> *ruleList;
if (currentRules.isEmpty())
ruleList = &rules;

View File

@ -66,6 +66,8 @@ public:
bool parse(QIODevice *dev, const QString &fileName, QString *errorMessage);
static bool parseNumber(const QString &n, int *target, QString *errorMessage);
protected:
virtual bool process(const QMimeType &t, QString *errorMessage) = 0;
virtual bool process(const QMimeGlobPattern &t, QString *errorMessage) = 0;

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="text/invalid-magic1">
<comment>wrong value for byte type</comment>
<magic>
<match value="foo" type="byte" offset="0"/>
</magic>
</mime-type>
</mime-info>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="text/invalid-magic2">
<comment>mask doesn't start with 0x</comment>
<magic>
<match value="foo" type="string" mask="ffff" offset="0"/>
</magic>
</mime-type>
</mime-info>

View File

@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<mime-info xmlns='http://www.freedesktop.org/standards/shared-mime-info'>
<mime-type type="text/invalid-magic3">
<comment>mask has wrong size</comment>
<magic>
<match value="foo" type="string" mask="0xffff" offset="0"/>
</magic>
</mime-type>
</mime-info>

View File

@ -4,5 +4,8 @@
<file alias="qml-again.xml">qml-again.xml</file>
<file alias="text-x-objcsrc.xml">text-x-objcsrc.xml</file>
<file alias="test.qml">test.qml</file>
<file>invalid-magic1.xml</file>
<file>invalid-magic2.xml</file>
<file>invalid-magic3.xml</file>
</qresource>
</RCC>

View File

@ -45,9 +45,16 @@
#include <QtTest/QtTest>
static const char yastFileName[] ="yast2-metapackage-handler-mimetypes.xml";
static const char qmlAgainFileName[] ="qml-again.xml";
static const char textXObjCSrcFileName[] ="text-x-objcsrc.xml";
static const char *const additionalMimeFiles[] = {
"yast2-metapackage-handler-mimetypes.xml",
"qml-again.xml",
"text-x-objcsrc.xml",
"invalid-magic1.xml",
"invalid-magic2.xml",
"invalid-magic3.xml",
0
};
#define RESOURCE_PREFIX ":/qt-project.org/qmime/"
void initializeLang()
@ -149,12 +156,12 @@ void tst_QMimeDatabase::initTestCase()
qWarning("%s", qPrintable(testSuiteWarning()));
errorMessage = QString::fromLatin1("Cannot find '%1'");
m_yastMimeTypes = QLatin1String(RESOURCE_PREFIX) + yastFileName;
QVERIFY2(QFile::exists(m_yastMimeTypes), qPrintable(errorMessage.arg(yastFileName)));
m_qmlAgainFileName = QLatin1String(RESOURCE_PREFIX) + qmlAgainFileName;
QVERIFY2(QFile::exists(m_qmlAgainFileName), qPrintable(errorMessage.arg(qmlAgainFileName)));
m_textXObjCSrcFileName = QLatin1String(RESOURCE_PREFIX) + textXObjCSrcFileName;
QVERIFY2(QFile::exists(m_textXObjCSrcFileName), qPrintable(errorMessage.arg(textXObjCSrcFileName)));
for (uint i = 0; i < sizeof additionalMimeFiles / sizeof additionalMimeFiles[0] - 1; i++) {
const QString resourceFilePath = QString::fromLatin1(RESOURCE_PREFIX) + QLatin1String(additionalMimeFiles[i]);
QVERIFY2(QFile::exists(resourceFilePath), qPrintable(errorMessage.arg(resourceFilePath)));
m_additionalMimeFileNames.append(QLatin1String(additionalMimeFiles[i]));
m_additionalMimeFilePaths.append(resourceFilePath);
}
initTestCaseInternal();
m_isUsingCacheProvider = !qEnvironmentVariableIsSet("QT_NO_MIME_CACHE");
@ -859,6 +866,14 @@ static void checkHasMimeType(const QString &mimeType)
QVERIFY(found);
}
static void ignoreInvalidMimetypeWarnings(const QString &mimeDir)
{
const QByteArray basePath = QFile::encodeName(mimeDir) + "/packages/";
QTest::ignoreMessage(QtWarningMsg, ("QMimeDatabase: Error parsing " + basePath + "invalid-magic1.xml\nInvalid magic rule value \"foo\"").constData());
QTest::ignoreMessage(QtWarningMsg, ("QMimeDatabase: Error parsing " + basePath + "invalid-magic2.xml\nInvalid magic rule mask \"ffff\"").constData());
QTest::ignoreMessage(QtWarningMsg, ("QMimeDatabase: Error parsing " + basePath + "invalid-magic3.xml\nInvalid magic rule mask size \"0xffff\"").constData());
}
QT_BEGIN_NAMESPACE
extern Q_CORE_EXPORT int qmime_secondsBetweenChecks; // see qmimeprovider.cpp
QT_END_NAMESPACE
@ -879,23 +894,21 @@ void tst_QMimeDatabase::installNewGlobalMimeType()
const QString mimeDir = m_globalXdgDir + QLatin1String("/mime");
const QString destDir = mimeDir + QLatin1String("/packages/");
const QString destFile = destDir + QLatin1String(yastFileName);
QFile::remove(destFile);
const QString destQmlFile = destDir + QLatin1String(qmlAgainFileName);
QFile::remove(destQmlFile);
const QString destTextXObjCSrcFile = destDir + QLatin1String(textXObjCSrcFileName);
QFile::remove(destTextXObjCSrcFile);
//qDebug() << destFile;
if (!QFileInfo(destDir).isDir())
QVERIFY(QDir(m_globalXdgDir).mkpath(destDir));
QString errorMessage;
QVERIFY2(copyResourceFile(m_yastMimeTypes, destFile, &errorMessage), qPrintable(errorMessage));
QVERIFY2(copyResourceFile(m_qmlAgainFileName, destQmlFile, &errorMessage), qPrintable(errorMessage));
QVERIFY2(copyResourceFile(m_textXObjCSrcFileName, destTextXObjCSrcFile, &errorMessage), qPrintable(errorMessage));
for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) {
const QString destFile = destDir + m_additionalMimeFileNames.at(i);
QFile::remove(destFile);
QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage));
}
if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(mimeDir))
QSKIP("shared-mime-info not found, skipping mime.cache test");
if (!m_isUsingCacheProvider)
ignoreInvalidMimetypeWarnings(mimeDir);
QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
QString::fromLatin1("text/x-SuSE-ymu"));
QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
@ -916,10 +929,9 @@ void tst_QMimeDatabase::installNewGlobalMimeType()
qDebug() << objcsrc.globPatterns();
}
// Now test removing it again
QVERIFY(QFile::remove(destFile));
QVERIFY(QFile::remove(destQmlFile));
QVERIFY(QFile::remove(destTextXObjCSrcFile));
// Now test removing the mimetype definitions again
for (int i = 0; i < m_additionalMimeFileNames.size(); ++i)
QFile::remove(destDir + m_additionalMimeFileNames.at(i));
if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(mimeDir))
QSKIP("shared-mime-info not found, skipping mime.cache test");
QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
@ -936,23 +948,35 @@ void tst_QMimeDatabase::installNewLocalMimeType()
qmime_secondsBetweenChecks = 0;
QMimeDatabase db;
// Check that we're starting clean
QVERIFY(!db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
QVERIFY(!db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid());
const QString destDir = m_localMimeDir + QLatin1String("/packages/");
QDir().mkpath(destDir);
const QString destFile = destDir + QLatin1String(yastFileName);
QFile::remove(destFile);
const QString destQmlFile = destDir + QLatin1String(qmlAgainFileName);
QFile::remove(destQmlFile);
QVERIFY(QDir().mkpath(destDir));
QString errorMessage;
QVERIFY2(copyResourceFile(m_yastMimeTypes, destFile, &errorMessage), qPrintable(errorMessage));
QVERIFY2(copyResourceFile(m_qmlAgainFileName, destQmlFile, &errorMessage), qPrintable(errorMessage));
if (m_isUsingCacheProvider && !runUpdateMimeDatabase(m_localMimeDir)) {
for (int i = 0; i < m_additionalMimeFileNames.size(); ++i) {
const QString destFile = destDir + m_additionalMimeFileNames.at(i);
QFile::remove(destFile);
QVERIFY2(copyResourceFile(m_additionalMimeFilePaths.at(i), destFile, &errorMessage), qPrintable(errorMessage));
}
if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(m_localMimeDir)) {
const QString skipWarning = QStringLiteral("shared-mime-info not found, skipping mime.cache test (")
+ QDir::toNativeSeparators(m_localMimeDir) + QLatin1Char(')');
QSKIP(qPrintable(skipWarning));
}
if (!m_isUsingCacheProvider)
ignoreInvalidMimetypeWarnings(m_localMimeDir);
QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
// These mimetypes have invalid magic, but still do exist.
QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic1")).isValid());
QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic2")).isValid());
QVERIFY(db.mimeTypeForName(QLatin1String("text/invalid-magic3")).isValid());
QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),
QString::fromLatin1("text/x-SuSE-ymu"));
QVERIFY(db.mimeTypeForName(QLatin1String("text/x-suse-ymp")).isValid());
@ -968,8 +992,8 @@ void tst_QMimeDatabase::installNewLocalMimeType()
QString::fromLatin1("text/x-qml"));
// Now test removing the local mimetypes again (note, this leaves a mostly-empty mime.cache file)
QVERIFY(QFile::remove(destFile));
QVERIFY(QFile::remove(destQmlFile));
for (int i = 0; i < m_additionalMimeFileNames.size(); ++i)
QFile::remove(destDir + m_additionalMimeFileNames.at(i));
if (m_isUsingCacheProvider && !waitAndRunUpdateMimeDatabase(m_localMimeDir))
QSKIP("shared-mime-info not found, skipping mime.cache test");
QCOMPARE(db.mimeTypeForFile(QLatin1String("foo.ymu"), QMimeDatabase::MatchExtension).name(),

View File

@ -36,6 +36,7 @@
#include <QtCore/QObject>
#include <QtCore/QTemporaryDir>
#include <QtCore/QStringList>
class tst_QMimeDatabase : public QObject
{
@ -92,9 +93,8 @@ private:
QString m_globalXdgDir;
QString m_localMimeDir;
QString m_yastMimeTypes;
QString m_qmlAgainFileName;
QString m_textXObjCSrcFileName;
QStringList m_additionalMimeFileNames;
QStringList m_additionalMimeFilePaths;
QTemporaryDir m_temporaryDir;
QString m_testSuite;
bool m_isUsingCacheProvider;