Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qmimetypeparser.cpp
Go to the documentation of this file.
1// Copyright (C) 2016 The Qt Company Ltd.
2// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
3// Qt-Security score:significant reason:trusted-data-only
4
5#define QT_NO_CAST_FROM_ASCII
6
8
9#include "qmimetype_p.h"
11
12#include <QtCore/QCoreApplication>
13#include <QtCore/QDebug>
14#include <QtCore/QDir>
15#include <QtCore/QXmlStreamReader>
16#include <QtCore/QXmlStreamWriter>
17#include <QtCore/QStack>
18
19QT_BEGIN_NAMESPACE
20
21using namespace Qt::StringLiterals;
22
23// XML tags in MIME files
24static const char mimeInfoTagC[] = "mime-info";
25static const char mimeTypeTagC[] = "mime-type";
26static const char mimeTypeAttributeC[] = "type";
27static const char subClassTagC[] = "sub-class-of";
28static const char commentTagC[] = "comment";
29static const char genericIconTagC[] = "generic-icon";
30static const char iconTagC[] = "icon";
31static const char nameAttributeC[] = "name";
32static const char globTagC[] = "glob";
33static const char globDeleteAllTagC[] = "glob-deleteall";
34static const char aliasTagC[] = "alias";
35static const char patternAttributeC[] = "pattern";
36static const char weightAttributeC[] = "weight";
37static const char caseSensitiveAttributeC[] = "case-sensitive";
38static const char localeAttributeC[] = "xml:lang";
39
40static const char magicTagC[] = "magic";
41static const char priorityAttributeC[] = "priority";
42
43static const char matchTagC[] = "match";
44static const char matchValueAttributeC[] = "value";
45static const char matchTypeAttributeC[] = "type";
46static const char matchOffsetAttributeC[] = "offset";
47static const char matchMaskAttributeC[] = "mask";
48
49/*!
50 \class QMimeTypeParser
51 \inmodule QtCore
52 \internal
53 \brief The QMimeTypeParser class parses MIME types, and builds a MIME database hierarchy by adding to QMimeDatabase.
54
55 Populates QMimeDataBase
56
57 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
58 \sa QMimeTypeParser
59*/
60
62 = default;
63
64/*!
65 \class QMimeTypeParserBase
66 \inmodule QtCore
67 \internal
68 \brief The QMimeTypeParserBase class parses for a sequence of <mime-type> in a generic way.
69
70 Calls abstract handler function process for QMimeType it finds.
71
72 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
73 \sa QMimeTypeParser
74*/
75
76QMimeTypeParserBase::~QMimeTypeParserBase()
77 = default;
78
79/*!
80 \fn virtual bool QMimeTypeParserBase::process(const QMimeType &t, QString *errorMessage) = 0;
81 Overwrite to process the sequence of parsed data
82*/
83
84QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState currentState, QStringView startElement)
85{
86 switch (currentState) {
87 case ParseBeginning:
88 if (startElement == QLatin1StringView(mimeInfoTagC))
89 return ParseMimeInfo;
90 if (startElement == QLatin1StringView(mimeTypeTagC))
91 return ParseMimeType;
92 return ParseError;
93 case ParseMimeInfo:
94 return startElement == QLatin1StringView(mimeTypeTagC) ? ParseMimeType : ParseError;
95 case ParseMimeType:
96 case ParseComment:
97 case ParseGenericIcon:
98 case ParseIcon:
99 case ParseGlobPattern:
100 case ParseGlobDeleteAll:
101 case ParseSubClass:
102 case ParseAlias:
103 case ParseOtherMimeTypeSubTag:
104 case ParseMagicMatchRule:
105 if (startElement == QLatin1StringView(mimeTypeTagC)) // Sequence of <mime-type>
106 return ParseMimeType;
107 if (startElement == QLatin1StringView(commentTagC))
108 return ParseComment;
109 if (startElement == QLatin1StringView(genericIconTagC))
110 return ParseGenericIcon;
111 if (startElement == QLatin1StringView(iconTagC))
112 return ParseIcon;
113 if (startElement == QLatin1StringView(globTagC))
114 return ParseGlobPattern;
115 if (startElement == QLatin1StringView(globDeleteAllTagC))
116 return ParseGlobDeleteAll;
117 if (startElement == QLatin1StringView(subClassTagC))
118 return ParseSubClass;
119 if (startElement == QLatin1StringView(aliasTagC))
120 return ParseAlias;
121 if (startElement == QLatin1StringView(magicTagC))
122 return ParseMagic;
123 if (startElement == QLatin1StringView(matchTagC))
124 return ParseMagicMatchRule;
125 return ParseOtherMimeTypeSubTag;
126 case ParseMagic:
127 if (startElement == QLatin1StringView(matchTagC))
128 return ParseMagicMatchRule;
129 break;
130 case ParseError:
131 break;
132 }
133 return ParseError;
134}
135
136// Parse int number from an (attribute) string
137bool QMimeTypeParserBase::parseNumber(QStringView n, int *target, QString *errorMessage)
138{
139 bool ok;
140 *target = n.toInt(&ok);
141 if (Q_UNLIKELY(!ok)) {
142 if (errorMessage)
143 *errorMessage = "Not a number '"_L1 + n + "'."_L1;
144 return false;
145 }
146 return true;
147}
148
149#if QT_CONFIG(xmlstreamreader)
150struct CreateMagicMatchRuleResult
151{
152 QString errorMessage; // must be first
153 QMimeMagicRule rule;
154
155 CreateMagicMatchRuleResult(QStringView type, QStringView value, QStringView offsets, QStringView mask,
156 QMimeMagicRule::RegexRules regexRules)
157 : errorMessage(), rule(type.toString(), value.toUtf8(), offsets.toString(), mask.toLatin1(),
158 regexRules, &errorMessage)
159 {
160
161 }
162};
163
164static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttributes &atts,
165 QMimeMagicRule::RegexRules regexRules)
166{
167 const auto type = atts.value(QLatin1StringView(matchTypeAttributeC));
168 const auto value = atts.value(QLatin1StringView(matchValueAttributeC));
169 const auto offsets = atts.value(QLatin1StringView(matchOffsetAttributeC));
170 const auto mask = atts.value(QLatin1StringView(matchMaskAttributeC));
171 return CreateMagicMatchRuleResult(type, value, offsets, mask, regexRules);
172}
173#endif // feature xmlstreamreader
174
175bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
176{
177#if QT_CONFIG(xmlstreamreader)
178 QMimeTypeXMLData data;
179 int priority = 50;
180 QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
181 QList<QMimeMagicRule> rules; // toplevel rules
182 QXmlStreamReader reader(dev);
183 ParseState ps = ParseBeginning;
184 while (!reader.atEnd()) {
185 switch (reader.readNext()) {
186 case QXmlStreamReader::StartElement: {
187 ps = nextState(ps, reader.name());
188 const QXmlStreamAttributes atts = reader.attributes();
189 switch (ps) {
190 case ParseMimeType: { // start parsing a MIME type name
191 const QString name = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
192 if (name.isEmpty()) {
193 reader.raiseError(QStringLiteral("Missing 'type'-attribute"));
194 } else {
195 data.name = name;
196 }
197 }
198 break;
199 case ParseGenericIcon:
200 data.genericIconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
201 break;
202 case ParseIcon:
203 data.iconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
204 break;
205 case ParseGlobPattern: {
206 const QString pattern = atts.value(QLatin1StringView(patternAttributeC)).toString();
207 unsigned weight = atts.value(QLatin1StringView(weightAttributeC)).toInt();
208 const bool caseSensitive = atts.value(QLatin1StringView(caseSensitiveAttributeC)) == "true"_L1;
209
210 if (weight == 0)
211 weight = QMimeGlobPattern::DefaultWeight;
212
213 Q_ASSERT(!data.name.isEmpty());
214 const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
215 if (!process(glob, errorMessage)) // for actual glob matching
216 return false;
217 data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
218 }
219 break;
220 case ParseGlobDeleteAll:
221 data.globPatterns.clear();
222 data.hasGlobDeleteAll = true;
223 break;
224 case ParseSubClass: {
225 const QString inheritsFrom = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
226 if (!inheritsFrom.isEmpty())
227 processParent(data.name, inheritsFrom);
228 }
229 break;
230 case ParseComment: {
231 // comments have locale attributes.
232 QString locale = atts.value(QLatin1StringView(localeAttributeC)).toString();
233 const QString comment = reader.readElementText();
234 if (locale.isEmpty())
235 locale = QString::fromLatin1("default");
236 else
237 locale.replace(u'-', u'_');
238 data.localeComments.insert(locale, comment);
239 }
240 break;
241 case ParseAlias: {
242 const QString alias = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
243 if (!alias.isEmpty())
244 processAlias(alias, data.name);
245 }
246 break;
247 case ParseMagic: {
248 priority = 50;
249 const auto priorityS = atts.value(QLatin1StringView(priorityAttributeC));
250 if (!priorityS.isEmpty()) {
251 if (!parseNumber(priorityS, &priority, errorMessage))
252 return false;
253
254 }
255 currentRules.clear();
256 //qDebug() << "MAGIC start for mimetype" << data.name;
257 }
258 break;
259 case ParseMagicMatchRule: {
260 // Rules are nested arbitrarily deeply in the XML, while matching
261 // and destroying them is recursive, so drop what is too deep to
262 // be a genuine rule.
263 if (currentRules.size() >= QMimeMagicRule::MaxRecursionDepth) {
264 reader.raiseError("<match> elements nested more than "_L1
265 + QString::number(QMimeMagicRule::MaxRecursionDepth)
266 + " levels deep"_L1);
267 break;
268 }
269 auto result = createMagicMatchRule(atts, m_regexRules);
270 if (Q_UNLIKELY(!result.rule.isValid()))
271 qWarning("QMimeDatabase: Error parsing %ls\n%ls",
272 qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
273 QList<QMimeMagicRule> *ruleList;
274 if (currentRules.isEmpty())
275 ruleList = &rules;
276 else // nest this rule into the proper parent
277 ruleList = &currentRules.top()->m_subMatches;
278 ruleList->append(std::move(result.rule));
279 //qDebug() << " MATCH added. Stack size was" << currentRules.size();
280 currentRules.push(&ruleList->last());
281 break;
282 }
283 case ParseError:
284 reader.raiseError("Unexpected element <"_L1 + reader.name() + u'>');
285 break;
286 default:
287 break;
288 }
289 }
290 break;
291 // continue switch QXmlStreamReader::Token...
292 case QXmlStreamReader::EndElement: // Finished element
293 {
294 const auto elementName = reader.name();
295 if (elementName == QLatin1StringView(mimeTypeTagC)) {
296 if (!process(data, errorMessage))
297 return false;
298 data.clear();
299 } else if (elementName == QLatin1StringView(matchTagC)) {
300 // Closing a <match> tag, pop stack
301 currentRules.pop();
302 //qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
303 } else if (elementName == QLatin1StringView(magicTagC)) {
304 //qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
305 // Finished a <magic> sequence
306 QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
307 ruleMatcher.addRules(rules);
308 processMagicMatcher(ruleMatcher);
309 rules.clear();
310 ps = ParseOtherMimeTypeSubTag; // in case of an empty glob tag
311 }
312 break;
313 }
314 default:
315 break;
316 }
317 }
318
319 if (Q_UNLIKELY(reader.hasError())) {
320 if (errorMessage) {
321 *errorMessage = QString::asprintf("An error has been encountered at line %lld of %ls: %ls:",
322 reader.lineNumber(),
323 qUtf16Printable(fileName),
324 qUtf16Printable(reader.errorString()));
325 }
326 return false;
327 }
328
329 return true;
330#else
331 Q_UNUSED(dev);
332 if (errorMessage)
333 *errorMessage = "QXmlStreamReader is not available, cannot parse '%1'."_L1.arg(fileName);
334 return false;
335#endif // feature xmlstreamreader
336}
337
339{
340 hasGlobDeleteAll = false;
341 name.clear();
342 localeComments.clear();
343 genericIconName.clear();
344 iconName.clear();
345 globPatterns.clear();
346}
347
348void QMimeTypeXMLData::addGlobPattern(const QString &pattern)
349{
350 globPatterns.append(pattern);
351}
352
353QT_END_NAMESPACE
bool parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
\inmodule QtCore
~QMimeTypeParser() override
void addGlobPattern(const QString &pattern)
static const char matchMaskAttributeC[]
static const char matchValueAttributeC[]
static const char localeAttributeC[]
static const char iconTagC[]
static const char nameAttributeC[]
static const char caseSensitiveAttributeC[]
static const char globDeleteAllTagC[]
static const char mimeTypeTagC[]
static const char globTagC[]
static const char subClassTagC[]
static const char mimeTypeAttributeC[]
static const char patternAttributeC[]
static const char commentTagC[]
static const char aliasTagC[]
static const char matchTagC[]
static const char mimeInfoTagC[]
static const char matchTypeAttributeC[]
static const char matchOffsetAttributeC[]
static const char priorityAttributeC[]
static const char magicTagC[]
static const char weightAttributeC[]
static const char genericIconTagC[]