1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the QtCore module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:LGPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU Lesser General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU Lesser
19** General Public License version 3 as published by the Free Software
20** Foundation and appearing in the file LICENSE.LGPL3 included in the
21** packaging of this file. Please review the following information to
22** ensure the GNU Lesser General Public License version 3 requirements
23** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24**
25** GNU General Public License Usage
26** Alternatively, this file may be used under the terms of the GNU
27** General Public License version 2.0 or (at your option) the GNU General
28** Public license version 3 or any later version approved by the KDE Free
29** Qt Foundation. The licenses are as published by the Free Software
30** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31** included in the packaging of this file. Please review the following
32** information to ensure the GNU General Public License requirements will
33** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34** https://www.gnu.org/licenses/gpl-3.0.html.
35**
36** $QT_END_LICENSE$
37**
38****************************************************************************/
39
40#define QT_NO_CAST_FROM_ASCII
41
42#include "qmimetypeparser_p.h"
43
44#include "qmimetype_p.h"
45#include "qmimemagicrulematcher_p.h"
46
47#include <QtCore/QCoreApplication>
48#include <QtCore/QDebug>
49#include <QtCore/QDir>
50#include <QtCore/QXmlStreamReader>
51#include <QtCore/QXmlStreamWriter>
52#include <QtCore/QStack>
53
54QT_BEGIN_NAMESPACE
55
56// XML tags in MIME files
57static const char mimeInfoTagC[] = "mime-info";
58static const char mimeTypeTagC[] = "mime-type";
59static const char mimeTypeAttributeC[] = "type";
60static const char subClassTagC[] = "sub-class-of";
61static const char commentTagC[] = "comment";
62static const char genericIconTagC[] = "generic-icon";
63static const char iconTagC[] = "icon";
64static const char nameAttributeC[] = "name";
65static const char globTagC[] = "glob";
66static const char globDeleteAllTagC[] = "glob-deleteall";
67static const char aliasTagC[] = "alias";
68static const char patternAttributeC[] = "pattern";
69static const char weightAttributeC[] = "weight";
70static const char caseSensitiveAttributeC[] = "case-sensitive";
71static const char localeAttributeC[] = "xml:lang";
72
73static const char magicTagC[] = "magic";
74static const char priorityAttributeC[] = "priority";
75
76static const char matchTagC[] = "match";
77static const char matchValueAttributeC[] = "value";
78static const char matchTypeAttributeC[] = "type";
79static const char matchOffsetAttributeC[] = "offset";
80static const char matchMaskAttributeC[] = "mask";
81
82/*!
83 \class QMimeTypeParser
84 \inmodule QtCore
85 \internal
86 \brief The QMimeTypeParser class parses MIME types, and builds a MIME database hierarchy by adding to QMimeDatabase.
87
88 Populates QMimeDataBase
89
90 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
91 \sa QMimeTypeParser
92*/
93
94/*!
95 \class QMimeTypeParserBase
96 \inmodule QtCore
97 \internal
98 \brief The QMimeTypeParserBase class parses for a sequence of <mime-type> in a generic way.
99
100 Calls abstract handler function process for QMimeType it finds.
101
102 \sa QMimeDatabase, QMimeMagicRuleMatcher, MagicRule, MagicStringRule, MagicByteRule, GlobPattern
103 \sa QMimeTypeParser
104*/
105
106/*!
107 \fn virtual bool QMimeTypeParserBase::process(const QMimeType &t, QString *errorMessage) = 0;
108 Overwrite to process the sequence of parsed data
109*/
110
111QMimeTypeParserBase::ParseState QMimeTypeParserBase::nextState(ParseState currentState, const QStringRef &startElement)
112{
113 switch (currentState) {
114 case ParseBeginning:
115 if (startElement == QLatin1String(mimeInfoTagC))
116 return ParseMimeInfo;
117 if (startElement == QLatin1String(mimeTypeTagC))
118 return ParseMimeType;
119 return ParseError;
120 case ParseMimeInfo:
121 return startElement == QLatin1String(mimeTypeTagC) ? ParseMimeType : ParseError;
122 case ParseMimeType:
123 case ParseComment:
124 case ParseGenericIcon:
125 case ParseIcon:
126 case ParseGlobPattern:
127 case ParseGlobDeleteAll:
128 case ParseSubClass:
129 case ParseAlias:
130 case ParseOtherMimeTypeSubTag:
131 case ParseMagicMatchRule:
132 if (startElement == QLatin1String(mimeTypeTagC)) // Sequence of <mime-type>
133 return ParseMimeType;
134 if (startElement == QLatin1String(commentTagC ))
135 return ParseComment;
136 if (startElement == QLatin1String(genericIconTagC))
137 return ParseGenericIcon;
138 if (startElement == QLatin1String(iconTagC))
139 return ParseIcon;
140 if (startElement == QLatin1String(globTagC))
141 return ParseGlobPattern;
142 if (startElement == QLatin1String(globDeleteAllTagC))
143 return ParseGlobDeleteAll;
144 if (startElement == QLatin1String(subClassTagC))
145 return ParseSubClass;
146 if (startElement == QLatin1String(aliasTagC))
147 return ParseAlias;
148 if (startElement == QLatin1String(magicTagC))
149 return ParseMagic;
150 if (startElement == QLatin1String(matchTagC))
151 return ParseMagicMatchRule;
152 return ParseOtherMimeTypeSubTag;
153 case ParseMagic:
154 if (startElement == QLatin1String(matchTagC))
155 return ParseMagicMatchRule;
156 break;
157 case ParseError:
158 break;
159 }
160 return ParseError;
161}
162
163// Parse int number from an (attribute) string
164bool QMimeTypeParserBase::parseNumber(const QStringRef &n, int *target, QString *errorMessage)
165{
166 bool ok;
167 *target = n.toInt(ok: &ok);
168 if (Q_UNLIKELY(!ok)) {
169 if (errorMessage)
170 *errorMessage = QLatin1String("Not a number '") + n + QLatin1String("'.");
171 return false;
172 }
173 return true;
174}
175
176#ifndef QT_NO_XMLSTREAMREADER
177struct CreateMagicMatchRuleResult {
178 QString errorMessage; // must be first
179 QMimeMagicRule rule;
180
181 CreateMagicMatchRuleResult(const QStringRef &type, const QStringRef &value, const QStringRef &offsets, const QStringRef &mask)
182 : errorMessage(), rule(type.toString(), value.toUtf8(), offsets.toString(), mask.toLatin1(), &errorMessage)
183 {
184
185 }
186};
187
188static CreateMagicMatchRuleResult createMagicMatchRule(const QXmlStreamAttributes &atts)
189{
190 const QStringRef type = atts.value(qualifiedName: QLatin1String(matchTypeAttributeC));
191 const QStringRef value = atts.value(qualifiedName: QLatin1String(matchValueAttributeC));
192 const QStringRef offsets = atts.value(qualifiedName: QLatin1String(matchOffsetAttributeC));
193 const QStringRef mask = atts.value(qualifiedName: QLatin1String(matchMaskAttributeC));
194 return CreateMagicMatchRuleResult(type, value, offsets, mask);
195}
196#endif
197
198bool QMimeTypeParserBase::parse(QIODevice *dev, const QString &fileName, QString *errorMessage)
199{
200#ifdef QT_NO_XMLSTREAMREADER
201 Q_UNUSED(dev);
202 if (errorMessage)
203 *errorMessage = QString::fromLatin1("QXmlStreamReader is not available, cannot parse '%1'.").arg(fileName);
204 return false;
205#else
206 QMimeTypePrivate data;
207 data.loaded = true;
208 int priority = 50;
209 QStack<QMimeMagicRule *> currentRules; // stack for the nesting of rules
210 QList<QMimeMagicRule> rules; // toplevel rules
211 QXmlStreamReader reader(dev);
212 ParseState ps = ParseBeginning;
213 while (!reader.atEnd()) {
214 switch (reader.readNext()) {
215 case QXmlStreamReader::StartElement: {
216 ps = nextState(currentState: ps, startElement: reader.name());
217 const QXmlStreamAttributes atts = reader.attributes();
218 switch (ps) {
219 case ParseMimeType: { // start parsing a MIME type name
220 const QString name = atts.value(qualifiedName: QLatin1String(mimeTypeAttributeC)).toString();
221 if (name.isEmpty()) {
222 reader.raiseError(QStringLiteral("Missing 'type'-attribute"));
223 } else {
224 data.name = name;
225 }
226 }
227 break;
228 case ParseGenericIcon:
229 data.genericIconName = atts.value(qualifiedName: QLatin1String(nameAttributeC)).toString();
230 break;
231 case ParseIcon:
232 data.iconName = atts.value(qualifiedName: QLatin1String(nameAttributeC)).toString();
233 break;
234 case ParseGlobPattern: {
235 const QString pattern = atts.value(qualifiedName: QLatin1String(patternAttributeC)).toString();
236 unsigned weight = atts.value(qualifiedName: QLatin1String(weightAttributeC)).toInt();
237 const bool caseSensitive = atts.value(qualifiedName: QLatin1String(caseSensitiveAttributeC)) == QLatin1String("true");
238
239 if (weight == 0)
240 weight = QMimeGlobPattern::DefaultWeight;
241
242 Q_ASSERT(!data.name.isEmpty());
243 const QMimeGlobPattern glob(pattern, data.name, weight, caseSensitive ? Qt::CaseSensitive : Qt::CaseInsensitive);
244 if (!process(t: glob, errorMessage)) // for actual glob matching
245 return false;
246 data.addGlobPattern(pattern); // just for QMimeType::globPatterns()
247 }
248 break;
249 case ParseGlobDeleteAll:
250 data.globPatterns.clear();
251 break;
252 case ParseSubClass: {
253 const QString inheritsFrom = atts.value(qualifiedName: QLatin1String(mimeTypeAttributeC)).toString();
254 if (!inheritsFrom.isEmpty())
255 processParent(child: data.name, parent: inheritsFrom);
256 }
257 break;
258 case ParseComment: {
259 // comments have locale attributes.
260 QString locale = atts.value(qualifiedName: QLatin1String(localeAttributeC)).toString();
261 const QString comment = reader.readElementText();
262 if (locale.isEmpty())
263 locale = QString::fromLatin1(str: "default");
264 data.localeComments.insert(akey: locale, avalue: comment);
265 }
266 break;
267 case ParseAlias: {
268 const QString alias = atts.value(qualifiedName: QLatin1String(mimeTypeAttributeC)).toString();
269 if (!alias.isEmpty())
270 processAlias(alias, name: data.name);
271 }
272 break;
273 case ParseMagic: {
274 priority = 50;
275 const QStringRef priorityS = atts.value(qualifiedName: QLatin1String(priorityAttributeC));
276 if (!priorityS.isEmpty()) {
277 if (!parseNumber(n: priorityS, target: &priority, errorMessage))
278 return false;
279
280 }
281 currentRules.clear();
282 //qDebug() << "MAGIC start for mimetype" << data.name;
283 }
284 break;
285 case ParseMagicMatchRule: {
286 auto result = createMagicMatchRule(atts);
287 if (Q_UNLIKELY(!result.rule.isValid()))
288 qWarning(msg: "QMimeDatabase: Error parsing %ls\n%ls",
289 qUtf16Printable(fileName), qUtf16Printable(result.errorMessage));
290 QList<QMimeMagicRule> *ruleList;
291 if (currentRules.isEmpty())
292 ruleList = &rules;
293 else // nest this rule into the proper parent
294 ruleList = &currentRules.top()->m_subMatches;
295 ruleList->append(t: std::move(result.rule));
296 //qDebug() << " MATCH added. Stack size was" << currentRules.size();
297 currentRules.push(t: &ruleList->last());
298 break;
299 }
300 case ParseError:
301 reader.raiseError(message: QLatin1String("Unexpected element <") + reader.name() + QLatin1Char('>'));
302 break;
303 default:
304 break;
305 }
306 }
307 break;
308 // continue switch QXmlStreamReader::Token...
309 case QXmlStreamReader::EndElement: // Finished element
310 {
311 const QStringRef elementName = reader.name();
312 if (elementName == QLatin1String(mimeTypeTagC)) {
313 if (!process(t: QMimeType(data), errorMessage))
314 return false;
315 data.clear();
316 } else if (elementName == QLatin1String(matchTagC)) {
317 // Closing a <match> tag, pop stack
318 currentRules.pop();
319 //qDebug() << " MATCH closed. Stack size is now" << currentRules.size();
320 } else if (elementName == QLatin1String(magicTagC)) {
321 //qDebug() << "MAGIC ended, we got" << rules.count() << "rules, with prio" << priority;
322 // Finished a <magic> sequence
323 QMimeMagicRuleMatcher ruleMatcher(data.name, priority);
324 ruleMatcher.addRules(rules);
325 processMagicMatcher(matcher: ruleMatcher);
326 rules.clear();
327 }
328 break;
329 }
330 default:
331 break;
332 }
333 }
334
335 if (Q_UNLIKELY(reader.hasError())) {
336 if (errorMessage) {
337 *errorMessage = QString::asprintf(format: "An error has been encountered at line %lld of %ls: %ls:",
338 reader.lineNumber(),
339 qUtf16Printable(fileName),
340 qUtf16Printable(reader.errorString()));
341 }
342 return false;
343 }
344
345 return true;
346#endif //QT_NO_XMLSTREAMREADER
347}
348
349QT_END_NAMESPACE
350

source code of qtbase/src/corelib/mimetypes/qmimetypeparser.cpp