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 : errorMessage(), rule(type.toString(), value.toUtf8(), offsets.toString(), mask.toLatin1(), &errorMessage)
157 {
158
159 }
160};
161
162static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttributes &atts)
163{
164 const auto type = atts.value(QLatin1StringView(matchTypeAttributeC));
165 const auto value = atts.value(QLatin1StringView(matchValueAttributeC));
166 const auto offsets = atts.value(QLatin1StringView(matchOffsetAttributeC));
167 const auto mask = atts.value(QLatin1StringView(matchMaskAttributeC));
168 return CreateMagicMatchRuleResult(type, value, offsets, mask);
169}
170#endif // feature xmlstreamreader
171
172bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
173{
174#if QT_CONFIG(xmlstreamreader)
175 QMimeTypeXMLData data;
176 int priority = 50;
177 QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
178 QList<QMimeMagicRule> rules; // toplevel rules
179 QXmlStreamReader reader(dev);
180 ParseState ps = ParseBeginning;
181 while (!reader.atEnd()) {
182 switch (reader.readNext()) {
183 case QXmlStreamReader::StartElement: {
184 ps = nextState(ps, reader.name());
185 const QXmlStreamAttributes atts = reader.attributes();
186 switch (ps) {
187 case ParseMimeType: { // start parsing a MIME type name
188 const QString name = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
189 if (name.isEmpty()) {
190 reader.raiseError(QStringLiteral("Missing 'type'-attribute"));
191 } else {
192 data.name = name;
193 }
194 }
195 break;
196 case ParseGenericIcon:
197 data.genericIconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
198 break;
199 case ParseIcon:
200 data.iconName = atts.value(QLatin1StringView(nameAttributeC)).toString();
201 break;
202 case ParseGlobPattern: {
203 const QString pattern = atts.value(QLatin1StringView(patternAttributeC)).toString();
204 unsigned weight = atts.value(QLatin1StringView(weightAttributeC)).toInt();
205 const bool caseSensitive = atts.value(QLatin1StringView(caseSensitiveAttributeC)) == "true"_L1;
206
207 if (weight == 0)
208 weight = QMimeGlobPattern::DefaultWeight;
209
210 Q_ASSERT(!data.name.isEmpty());
211 const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
212 if (!process(glob, errorMessage)) // for actual glob matching
213 return false;
214 data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
215 }
216 break;
217 case ParseGlobDeleteAll:
218 data.globPatterns.clear();
219 data.hasGlobDeleteAll = true;
220 break;
221 case ParseSubClass: {
222 const QString inheritsFrom = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
223 if (!inheritsFrom.isEmpty())
224 processParent(data.name, inheritsFrom);
225 }
226 break;
227 case ParseComment: {
228 // comments have locale attributes.
229 QString locale = atts.value(QLatin1StringView(localeAttributeC)).toString();
230 const QString comment = reader.readElementText();
231 if (locale.isEmpty())
232 locale = QString::fromLatin1("default");
233 data.localeComments.insert(locale, comment);
234 }
235 break;
236 case ParseAlias: {
237 const QString alias = atts.value(QLatin1StringView(mimeTypeAttributeC)).toString();
238 if (!alias.isEmpty())
239 processAlias(alias, data.name);
240 }
241 break;
242 case ParseMagic: {
243 priority = 50;
244 const auto priorityS = atts.value(QLatin1StringView(priorityAttributeC));
245 if (!priorityS.isEmpty()) {
246 if (!parseNumber(priorityS, &priority, errorMessage))
247 return false;
248
249 }
250 currentRules.clear();
251 //qDebug() << "MAGIC start for mimetype" << data.name;
252 }
253 break;
254 case ParseMagicMatchRule: {
255 // Rules are nested arbitrarily deeply in the XML, while matching
256 // and destroying them is recursive, so drop what is too deep to
257 // be a genuine rule.
258 if (currentRules.size() >= QMimeMagicRule::MaxRecursionDepth) {
259 reader.raiseError("<match> elements nested more than "_L1
260 + QString::number(QMimeMagicRule::MaxRecursionDepth)
261 + " levels deep"_L1);
262 break;
263 }
264 auto result = createMagicMatchRule(atts);
265 if (Q_UNLIKELY(!result.rule.isValid()))
266 qWarning("QMimeDatabase: Error parsing %ls\n%ls",
267 qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
268 QList<QMimeMagicRule> *ruleList;
269 if (currentRules.isEmpty())
270 ruleList = &rules;
271 else // nest this rule into the proper parent
272 ruleList = &currentRules.top()->m_subMatches;
273 ruleList->append(std::move(result.rule));
274 //qDebug() << " MATCH added. Stack size was" << currentRules.size();
275 currentRules.push(&ruleList->last());
276 break;
277 }
278 case ParseError:
279 reader.raiseError("Unexpected element <"_L1 + reader.name() + u'>');
280 break;
281 default:
282 break;
283 }
284 }
285 break;
286 // continue switch QXmlStreamReader::Token...
287 case QXmlStreamReader::EndElement: // Finished element
288 {
289 const auto elementName = reader.name();
290 if (elementName == QLatin1StringView(mimeTypeTagC)) {
291 if (!process(data, errorMessage))
292 return false;
293 data.clear();
294 } else if (elementName == QLatin1StringView(matchTagC)) {
295 // Closing a <match> tag, pop stack
296 currentRules.pop();
297 //qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
298 } else if (elementName == QLatin1StringView(magicTagC)) {
299 //qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
300 // Finished a <magic> sequence
301 QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
302 ruleMatcher.addRules(rules);
303 processMagicMatcher(ruleMatcher);
304 rules.clear();
305 ps = ParseOtherMimeTypeSubTag; // in case of an empty glob tag
306 }
307 break;
308 }
309 default:
310 break;
311 }
312 }
313
314 if (Q_UNLIKELY(reader.hasError())) {
315 if (errorMessage) {
316 *errorMessage = QString::asprintf("An error has been encountered at line %lld of %ls: %ls:",
317 reader.lineNumber(),
318 qUtf16Printable(fileName),
319 qUtf16Printable(reader.errorString()));
320 }
321 return false;
322 }
323
324 return true;
325#else
326 Q_UNUSED(dev);
327 if (errorMessage)
328 *errorMessage = "QXmlStreamReader is not available, cannot parse '%1'."_L1.arg(fileName);
329 return false;
330#endif // feature xmlstreamreader
331}
332
334{
335 hasGlobDeleteAll = false;
336 name.clear();
337 localeComments.clear();
338 genericIconName.clear();
339 iconName.clear();
340 globPatterns.clear();
341}
342
343void QMimeTypeXMLData::addGlobPattern(const QString &pattern)
344{
345 globPatterns.append(pattern);
346}
347
348QT_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[]