1
/****************************************************************************
3
** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
4
** Contact: http://www.qt-project.org/
6
** This file is part of the QtCore module of the Qt Toolkit.
8
** $QT_BEGIN_LICENSE:LGPL$
9
** GNU Lesser General Public License Usage
10
** This file may be used under the terms of the GNU Lesser General Public
11
** License version 2.1 as published by the Free Software Foundation and
12
** appearing in the file LICENSE.LGPL included in the packaging of this
13
** file. Please review the following information to ensure the GNU Lesser
14
** General Public License version 2.1 requirements will be met:
15
** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
17
** In addition, as a special exception, Nokia gives you certain additional
18
** rights. These rights are described in the Nokia Qt LGPL Exception
19
** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
21
** GNU General Public License Usage
22
** Alternatively, this file may be used under the terms of the GNU General
23
** Public License version 3.0 as published by the Free Software Foundation
24
** and appearing in the file LICENSE.GPL included in the packaging of this
25
** file. Please review the following information to ensure the GNU General
26
** Public License version 3.0 requirements will be met:
27
** http://www.gnu.org/copyleft/gpl.html.
30
** Alternatively, this file may be used in accordance with the terms and
31
** conditions contained in a signed written agreement between you and Nokia.
40
****************************************************************************/
42
#define QT_NO_CAST_FROM_ASCII
44
#include "qmimetypeparser_p.h"
46
#include "qmimetype_p.h"
47
#include "qmimemagicrulematcher_p.h"
49
#include <QtCore/QCoreApplication>
50
#include <QtCore/QDebug>
51
#include <QtCore/QDir>
52
#include <QtCore/QPair>
53
#include <QtCore/QXmlStreamReader>
54
#include <QtCore/QXmlStreamWriter>
55
#include <QtCore/QStack>
59
// XML tags in MIME files
60
static const char mimeInfoTagC[] = "mime-info";
61
static const char mimeTypeTagC[] = "mime-type";
62
static const char mimeTypeAttributeC[] = "type";
63
static const char subClassTagC[] = "sub-class-of";
64
static const char commentTagC[] = "comment";
65
static const char genericIconTagC[] = "generic-icon";
66
static const char iconTagC[] = "icon";
67
static const char nameAttributeC[] = "name";
68
static const char globTagC[] = "glob";
69
static const char aliasTagC[] = "alias";
70
static const char patternAttributeC[] = "pattern";
71
static const char weightAttributeC[] = "weight";
72
static const char caseSensitiveAttributeC[] = "case-sensitive";
73
static const char localeAttributeC[] = "xml:lang";
75
static const char magicTagC[] = "magic";
76
static const char priorityAttributeC[] = "priority";
78
static const char matchTagC[] = "match";
79
static const char matchValueAttributeC[] = "value";
80
static const char matchTypeAttributeC[] = "type";
81
static const char matchOffsetAttributeC[] = "offset";
82
static const char matchMaskAttributeC[] = "mask";
85
\class QMimeTypeParser
87
\brief The QMimeTypeParser class parses MIME types, and builds a MIME database hierarchy by adding to QMimeDatabasePrivate.
89
Populates QMimeDataBase
91
\sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
96
\class QMimeTypeParserBase
98
\brief The QMimeTypeParserBase class parses for a sequence of <mime-type> in a generic way.
100
Calls abstract handler function process for QMimeType it finds.
102
\sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
107
\fn virtual bool QMimeTypeParserBase::process(const QMimeType &t, QString *errorMessage) = 0;
108
Overwrite to process the sequence of parsed data
111
QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState currentState, const QStringRef &startElement)
113
switch (currentState) {
115
if (startElement == QLatin1String(mimeInfoTagC))
116
return ParseMimeInfo;
117
if (startElement == QLatin1String(mimeTypeTagC))
118
return ParseMimeType;
121
return startElement == QLatin1String(mimeTypeTagC) ? ParseMimeType : ParseError;
124
case ParseGenericIcon:
126
case ParseGlobPattern:
129
case ParseOtherMimeTypeSubTag:
130
case ParseMagicMatchRule:
131
if (startElement == QLatin1String(mimeTypeTagC)) // Sequence of <mime-type>
132
return ParseMimeType;
133
if (startElement == QLatin1String(commentTagC ))
135
if (startElement == QLatin1String(genericIconTagC))
136
return ParseGenericIcon;
137
if (startElement == QLatin1String(iconTagC))
139
if (startElement == QLatin1String(globTagC))
140
return ParseGlobPattern;
141
if (startElement == QLatin1String(subClassTagC))
142
return ParseSubClass;
143
if (startElement == QLatin1String(aliasTagC))
145
if (startElement == QLatin1String(magicTagC))
147
if (startElement == QLatin1String(matchTagC))
148
return ParseMagicMatchRule;
149
return ParseOtherMimeTypeSubTag;
151
if (startElement == QLatin1String(matchTagC))
152
return ParseMagicMatchRule;
160
// Parse int number from an (attribute) string)
161
static bool parseNumber(const QString &n, int *target, QString *errorMessage)
164
*target = n.toInt(&ok);
166
*errorMessage = QString::fromLatin1("Not a number '%1'.").arg(n);
172
// Evaluate a magic match rule like
173
// <match value="must be converted with BinHex" type="string" offset="11"/>
174
// <match value="0x9501" type="big16" offset="0:64"/>
175
static bool createMagicMatchRule(const QXmlStreamAttributes &atts,
176
QString *errorMessage, QMimeMagicRule *&rule)
178
const QString type = atts.value(QLatin1String(matchTypeAttributeC)).toString();
179
QMimeMagicRule::Type magicType = QMimeMagicRule::type(type.toLatin1());
180
if (magicType == QMimeMagicRule::Invalid) {
181
qWarning("%s: match type %s is not supported.", Q_FUNC_INFO, type.toUtf8().constData());
184
const QString value = atts.value(QLatin1String(matchValueAttributeC)).toString();
185
if (value.isEmpty()) {
186
*errorMessage = QString::fromLatin1("Empty match value detected.");
189
// Parse for offset as "1" or "1:10"
190
int startPos, endPos;
191
const QString offsetS = atts.value(QLatin1String(matchOffsetAttributeC)).toString();
192
const int colonIndex = offsetS.indexOf(QLatin1Char(':'));
193
const QString startPosS = colonIndex == -1 ? offsetS : offsetS.mid(0, colonIndex);
194
const QString endPosS = colonIndex == -1 ? offsetS : offsetS.mid(colonIndex + 1);
195
if (!parseNumber(startPosS, &startPos, errorMessage) || !parseNumber(endPosS, &endPos, errorMessage))
197
const QString mask = atts.value(QLatin1String(matchMaskAttributeC)).toString();
199
rule = new QMimeMagicRule(magicType, value.toUtf8(), startPos, endPos, mask.toLatin1());
204
bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
206
QMimeTypePrivate data;
208
QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
209
QList<QMimeMagicRule> rules; // toplevel rules
210
QXmlStreamReader reader(dev);
211
ParseState ps = ParseBeginning;
212
QXmlStreamAttributes atts;
213
while (!reader.atEnd()) {
214
switch (reader.readNext()) {
215
case QXmlStreamReader::StartElement:
216
ps = nextState(ps, reader.name());
217
atts = reader.attributes();
219
case ParseMimeType: { // start parsing a MIME type name
220
const QString name = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
221
if (name.isEmpty()) {
222
reader.raiseError(QString::fromLatin1("Missing '%1'-attribute").arg(QString::fromLatin1(mimeTypeAttributeC)));
228
case ParseGenericIcon:
229
data.genericIconName = atts.value(QLatin1String(nameAttributeC)).toString();
232
data.iconName = atts.value(QLatin1String(nameAttributeC)).toString();
234
case ParseGlobPattern: {
235
const QString pattern = atts.value(QLatin1String(patternAttributeC)).toString();
236
unsigned weight = atts.value(QLatin1String(weightAttributeC)).toString().toInt();
237
const bool caseSensitive = atts.value(QLatin1String(caseSensitiveAttributeC)).toString() == QLatin1String("true");
240
weight = QMimeGlobPattern::DefaultWeight;
242
Q_ASSERT(!data.name.isEmpty());
243
const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
244
if (!process(glob, errorMessage)) // for actual glob matching
246
data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
249
case ParseSubClass: {
250
const QString inheritsFrom = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
251
if (!inheritsFrom.isEmpty())
252
processParent(data.name, inheritsFrom);
256
// comments have locale attributes. We want the default, English one
257
QString locale = atts.value(QLatin1String(localeAttributeC)).toString();
258
const QString comment = reader.readElementText();
259
if (locale.isEmpty())
260
locale = QString::fromLatin1("en_US");
261
data.localeComments.insert(locale, comment);
265
const QString alias = atts.value(QLatin1String(mimeTypeAttributeC)).toString();
266
if (!alias.isEmpty())
267
processAlias(alias, data.name);
272
const QString priorityS = atts.value(QLatin1String(priorityAttributeC)).toString();
273
if (!priorityS.isEmpty()) {
274
if (!parseNumber(priorityS, &priority, errorMessage))
278
currentRules.clear();
279
//qDebug() << "MAGIC start for mimetype" << data.name;
282
case ParseMagicMatchRule: {
283
QMimeMagicRule *rule = 0;
284
if (!createMagicMatchRule(atts, errorMessage, rule))
286
QList<QMimeMagicRule> *ruleList;
287
if (currentRules.isEmpty())
289
else // nest this rule into the proper parent
290
ruleList = ¤tRules.top()->m_subMatches;
291
ruleList->append(*rule);
292
//qDebug() << " MATCH added. Stack size was" << currentRules.size();
293
currentRules.push(&ruleList->last());
298
reader.raiseError(QString::fromLatin1("Unexpected element <%1>").
299
arg(reader.name().toString()));
305
// continue switch QXmlStreamReader::Token...
306
case QXmlStreamReader::EndElement: // Finished element
308
const QStringRef elementName = reader.name();
309
if (elementName == QLatin1String(mimeTypeTagC)) {
310
if (!process(QMimeType(data), errorMessage))
313
} else if (elementName == QLatin1String(matchTagC)) {
314
// Closing a <match> tag, pop stack
316
//qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
317
} else if (elementName == QLatin1String(magicTagC)) {
318
//qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
319
// Finished a <magic> sequence
320
QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
321
ruleMatcher.addRules(rules);
322
processMagicMatcher(ruleMatcher);
332
if (reader.hasError()) {
334
*errorMessage = QString::fromLatin1("An error has been encountered at line %1 of %2: %3:").arg(reader.lineNumber()).arg(fileName, reader.errorString());