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 auto result = createMagicMatchRule(atts);
256 if (Q_UNLIKELY(!result.rule.isValid()))
257 qWarning("QMimeDatabase: Error parsing %ls\n%ls",
258 qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
259 QList<QMimeMagicRule> *ruleList;
260 if (currentRules.isEmpty())
261 ruleList = &rules;
262 else // nest this rule into the proper parent
263 ruleList = &currentRules.top()->m_subMatches;
264 ruleList->append(std::move(result.rule));
265 //qDebug() << " MATCH added. Stack size was" << currentRules.size();
266 currentRules.push(&ruleList->last());
267 break;
268 }
269 case ParseError:
270 reader.raiseError("Unexpected element <"_L1 + reader.name() + u'>');
271 break;
272 default:
273 break;
274 }
275 }
276 break;
277 // continue switch QXmlStreamReader::Token...
278 case QXmlStreamReader::EndElement: // Finished element
279 {
280 const auto elementName = reader.name();
281 if (elementName == QLatin1StringView(mimeTypeTagC)) {
282 if (!process(data, errorMessage))
283 return false;
284 data.clear();
285 } else if (elementName == QLatin1StringView(matchTagC)) {
286 // Closing a <match> tag, pop stack
287 currentRules.pop();
288 //qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
289 } else if (elementName == QLatin1StringView(magicTagC)) {
290 //qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
291 // Finished a <magic> sequence
292 QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
293 ruleMatcher.addRules(rules);
294 processMagicMatcher(ruleMatcher);
295 rules.clear();
296 ps = ParseOtherMimeTypeSubTag; // in case of an empty glob tag
297 }
298 break;
299 }
300 default:
301 break;
302 }
303 }
304
305 if (Q_UNLIKELY(reader.hasError())) {
306 if (errorMessage) {
307 *errorMessage = QString::asprintf("An error has been encountered at line %lld of %ls: %ls:",
308 reader.lineNumber(),
309 qUtf16Printable(fileName),
310 qUtf16Printable(reader.errorString()));
311 }
312 return false;
313 }
314
315 return true;
316#else
317 Q_UNUSED(dev);
318 if (errorMessage)
319 *errorMessage = "QXmlStreamReader is not available, cannot parse '%1'."_L1.arg(fileName);
320 return false;
321#endif // feature xmlstreamreader
322}
323
325{
326 hasGlobDeleteAll = false;
327 name.clear();
328 localeComments.clear();
329 genericIconName.clear();
330 iconName.clear();
331 globPatterns.clear();
332}
333
334void QMimeTypeXMLData::addGlobPattern(const QString &pattern)
335{
336 globPatterns.append(pattern);
337}
338
339QT_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[]