/src/qtbase/src/dbus/qdbusxmlparser.cpp
Line | Count | Source |
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:critical reason:data-parser |
4 | | |
5 | | #include "qdbusxmlparser_p.h" |
6 | | #include "qdbusutil_p.h" |
7 | | |
8 | | #include <QtCore/qmap.h> |
9 | | #include <QtCore/qvariant.h> |
10 | | #include <QtCore/qtextstream.h> |
11 | | #include <QtCore/qdebug.h> |
12 | | |
13 | | #ifndef QT_NO_DBUS |
14 | | |
15 | | QT_BEGIN_NAMESPACE |
16 | | |
17 | | using namespace Qt::StringLiterals; |
18 | | |
19 | | Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg) |
20 | | |
21 | | #define qDBusParserWarning(format, ...) \ |
22 | 0 | do { \ |
23 | 0 | if (m_reporter) \ |
24 | 0 | m_reporter->warning(m_currentLocation, format "\n", ##__VA_ARGS__); \ |
25 | 0 | else \ |
26 | 0 | qCDebug(dbusParser, "Warning: " format, ##__VA_ARGS__); \ |
27 | 0 | } while (0) |
28 | | |
29 | | #define qDBusParserError(format, ...) \ |
30 | 0 | do { \ |
31 | 0 | if (m_reporter) \ |
32 | 0 | m_reporter->error(m_currentLocation, format "\n", ##__VA_ARGS__); \ |
33 | 0 | else \ |
34 | 0 | qCDebug(dbusParser, "Error: " format, ##__VA_ARGS__); \ |
35 | 0 | } while (0) |
36 | | |
37 | | bool QDBusXmlParser::parseArg(const QXmlStreamAttributes &attributes, |
38 | | QDBusIntrospection::Argument &argData) |
39 | 0 | { |
40 | 0 | Q_ASSERT(m_currentInterface); |
41 | |
|
42 | 0 | const QString argType = attributes.value("type"_L1).toString(); |
43 | |
|
44 | 0 | bool ok = QDBusUtil::isValidSingleSignature(argType); |
45 | 0 | if (!ok) { |
46 | 0 | qDBusParserError("Invalid D-Bus type signature '%s' found while parsing introspection", |
47 | 0 | qPrintable(argType)); |
48 | 0 | } |
49 | |
|
50 | 0 | argData.name = attributes.value("name"_L1).toString(); |
51 | 0 | argData.type = argType; |
52 | |
|
53 | 0 | m_currentInterface->introspection += " <arg"_L1; |
54 | 0 | if (attributes.hasAttribute("direction"_L1)) { |
55 | 0 | const QString direction = attributes.value("direction"_L1).toString(); |
56 | 0 | m_currentInterface->introspection += " direction=\""_L1 + direction + u'"'; |
57 | 0 | } |
58 | 0 | m_currentInterface->introspection += " type=\""_L1 + argData.type + u'"'; |
59 | 0 | if (!argData.name.isEmpty()) |
60 | 0 | m_currentInterface->introspection += " name=\""_L1 + argData.name + u'"'; |
61 | 0 | m_currentInterface->introspection += "/>\n"_L1; |
62 | |
|
63 | 0 | return ok; |
64 | 0 | } |
65 | | |
66 | | bool QDBusXmlParser::parseAnnotation(QDBusIntrospection::Annotations &annotations, |
67 | | bool interfaceAnnotation) |
68 | 0 | { |
69 | 0 | Q_ASSERT(m_currentInterface); |
70 | 0 | Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "annotation"_L1); |
71 | |
|
72 | 0 | QDBusIntrospection::Annotation annotation; |
73 | 0 | annotation.location = m_currentLocation; |
74 | |
|
75 | 0 | const QXmlStreamAttributes attributes = m_xml.attributes(); |
76 | 0 | annotation.name = attributes.value("name"_L1).toString(); |
77 | |
|
78 | 0 | if (!QDBusUtil::isValidInterfaceName(annotation.name)) { |
79 | 0 | qDBusParserError("Invalid D-Bus annotation '%s' found while parsing introspection", |
80 | 0 | qPrintable(annotation.name)); |
81 | 0 | return false; |
82 | 0 | } |
83 | 0 | annotation.value = attributes.value("value"_L1).toString(); |
84 | 0 | annotations.insert(annotation.name, annotation); |
85 | 0 | if (!interfaceAnnotation) |
86 | 0 | m_currentInterface->introspection += " "_L1; |
87 | 0 | m_currentInterface->introspection += " <annotation value=\""_L1 |
88 | 0 | + annotation.value.toHtmlEscaped() + "\" name=\""_L1 + annotation.name + "\"/>\n"_L1; |
89 | 0 | return true; |
90 | 0 | } |
91 | | |
92 | | bool QDBusXmlParser::parseProperty(QDBusIntrospection::Property &propertyData) |
93 | 0 | { |
94 | 0 | Q_ASSERT(m_currentInterface); |
95 | 0 | Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "property"_L1); |
96 | |
|
97 | 0 | QXmlStreamAttributes attributes = m_xml.attributes(); |
98 | 0 | const QString propertyName = attributes.value("name"_L1).toString(); |
99 | 0 | if (!QDBusUtil::isValidMemberName(propertyName)) { |
100 | 0 | qDBusParserWarning("Invalid D-Bus member name '%s' found in interface '%s' while parsing " |
101 | 0 | "introspection", |
102 | 0 | qPrintable(propertyName), qPrintable(m_currentInterface->name)); |
103 | 0 | m_xml.skipCurrentElement(); |
104 | 0 | return false; |
105 | 0 | } |
106 | | |
107 | | // parse data |
108 | 0 | propertyData.name = propertyName; |
109 | 0 | propertyData.type = attributes.value("type"_L1).toString(); |
110 | |
|
111 | 0 | if (!QDBusUtil::isValidSingleSignature(propertyData.type)) { |
112 | | // cannot be! |
113 | 0 | qDBusParserError("Invalid D-Bus type signature '%s' found in property '%s.%s' while " |
114 | 0 | "parsing introspection", |
115 | 0 | qPrintable(propertyData.type), qPrintable(m_currentInterface->name), |
116 | 0 | qPrintable(propertyName)); |
117 | 0 | } |
118 | |
|
119 | 0 | const QString access = attributes.value("access"_L1).toString(); |
120 | 0 | if (access == "read"_L1) |
121 | 0 | propertyData.access = QDBusIntrospection::Property::Read; |
122 | 0 | else if (access == "write"_L1) |
123 | 0 | propertyData.access = QDBusIntrospection::Property::Write; |
124 | 0 | else if (access == "readwrite"_L1) |
125 | 0 | propertyData.access = QDBusIntrospection::Property::ReadWrite; |
126 | 0 | else { |
127 | 0 | qDBusParserError("Invalid D-Bus property access '%s' found in property '%s.%s' while " |
128 | 0 | "parsing introspection", |
129 | 0 | qPrintable(access), qPrintable(m_currentInterface->name), |
130 | 0 | qPrintable(propertyName)); |
131 | 0 | return false; // invalid one! |
132 | 0 | } |
133 | | |
134 | 0 | m_currentInterface->introspection += " <property access=\""_L1 + access + "\" type=\""_L1 + propertyData.type + "\" name=\""_L1 + propertyName + u'"'; |
135 | |
|
136 | 0 | if (!readNextStartElement()) { |
137 | 0 | m_currentInterface->introspection += "/>\n"_L1; |
138 | 0 | } else { |
139 | 0 | m_currentInterface->introspection += ">\n"_L1; |
140 | |
|
141 | 0 | do { |
142 | 0 | if (m_xml.name() == "annotation"_L1) { |
143 | 0 | parseAnnotation(propertyData.annotations); |
144 | 0 | } else if (m_xml.prefix().isEmpty()) { |
145 | 0 | qDBusParserWarning("Unknown element '%s' while checking for annotations", |
146 | 0 | qPrintable(m_xml.name().toString())); |
147 | 0 | } |
148 | 0 | m_xml.skipCurrentElement(); |
149 | 0 | } while (readNextStartElement()); |
150 | |
|
151 | 0 | m_currentInterface->introspection += " </property>\n"_L1; |
152 | 0 | } |
153 | |
|
154 | 0 | if (!m_xml.isEndElement() || m_xml.name() != "property"_L1) { |
155 | 0 | qDBusParserError("Invalid property specification: '%s'", qPrintable(m_xml.tokenString())); |
156 | 0 | return false; |
157 | 0 | } |
158 | | |
159 | 0 | return true; |
160 | 0 | } |
161 | | |
162 | | bool QDBusXmlParser::parseMethod(QDBusIntrospection::Method &methodData) |
163 | 0 | { |
164 | 0 | Q_ASSERT(m_currentInterface); |
165 | 0 | Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "method"_L1); |
166 | |
|
167 | 0 | const QXmlStreamAttributes attributes = m_xml.attributes(); |
168 | 0 | const QString methodName = attributes.value("name"_L1).toString(); |
169 | 0 | if (!QDBusUtil::isValidMemberName(methodName)) { |
170 | 0 | qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing " |
171 | 0 | "introspection", |
172 | 0 | qPrintable(methodName), qPrintable(m_currentInterface->name)); |
173 | 0 | return false; |
174 | 0 | } |
175 | | |
176 | 0 | methodData.name = methodName; |
177 | 0 | m_currentInterface->introspection += " <method name=\""_L1 + methodName + u'"'; |
178 | |
|
179 | 0 | QDBusIntrospection::Arguments outArguments; |
180 | 0 | QDBusIntrospection::Arguments inArguments; |
181 | 0 | QDBusIntrospection::Annotations annotations; |
182 | |
|
183 | 0 | if (!readNextStartElement()) { |
184 | 0 | m_currentInterface->introspection += "/>\n"_L1; |
185 | 0 | } else { |
186 | 0 | m_currentInterface->introspection += ">\n"_L1; |
187 | |
|
188 | 0 | do { |
189 | 0 | if (m_xml.name() == "annotation"_L1) { |
190 | 0 | parseAnnotation(annotations); |
191 | 0 | } else if (m_xml.name() == "arg"_L1) { |
192 | 0 | const QXmlStreamAttributes attributes = m_xml.attributes(); |
193 | 0 | const QString direction = attributes.value("direction"_L1).toString(); |
194 | 0 | QDBusIntrospection::Argument argument; |
195 | 0 | argument.location = m_currentLocation; |
196 | 0 | if (!attributes.hasAttribute("direction"_L1) || direction == "in"_L1) { |
197 | 0 | parseArg(attributes, argument); |
198 | 0 | inArguments << argument; |
199 | 0 | } else if (direction == "out"_L1) { |
200 | 0 | parseArg(attributes, argument); |
201 | 0 | outArguments << argument; |
202 | 0 | } |
203 | 0 | } else if (m_xml.prefix().isEmpty()) { |
204 | 0 | qDBusParserWarning("Unknown element '%s' while checking for method arguments", |
205 | 0 | qPrintable(m_xml.name().toString())); |
206 | 0 | } |
207 | 0 | m_xml.skipCurrentElement(); |
208 | 0 | } while (readNextStartElement()); |
209 | |
|
210 | 0 | m_currentInterface->introspection += " </method>\n"_L1; |
211 | 0 | } |
212 | |
|
213 | 0 | methodData.inputArgs = inArguments; |
214 | 0 | methodData.outputArgs = outArguments; |
215 | 0 | methodData.annotations = annotations; |
216 | |
|
217 | 0 | return true; |
218 | 0 | } |
219 | | |
220 | | bool QDBusXmlParser::parseSignal(QDBusIntrospection::Signal &signalData) |
221 | 0 | { |
222 | 0 | Q_ASSERT(m_currentInterface); |
223 | 0 | Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "signal"_L1); |
224 | |
|
225 | 0 | const QXmlStreamAttributes attributes = m_xml.attributes(); |
226 | 0 | const QString signalName = attributes.value("name"_L1).toString(); |
227 | |
|
228 | 0 | if (!QDBusUtil::isValidMemberName(signalName)) { |
229 | 0 | qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing " |
230 | 0 | "introspection", |
231 | 0 | qPrintable(signalName), qPrintable(m_currentInterface->name)); |
232 | 0 | return false; |
233 | 0 | } |
234 | | |
235 | 0 | signalData.name = signalName; |
236 | 0 | m_currentInterface->introspection += " <signal name=\""_L1 + signalName + u'"'; |
237 | |
|
238 | 0 | QDBusIntrospection::Arguments arguments; |
239 | 0 | QDBusIntrospection::Annotations annotations; |
240 | |
|
241 | 0 | if (!readNextStartElement()) { |
242 | 0 | m_currentInterface->introspection += "/>\n"_L1; |
243 | 0 | } else { |
244 | 0 | m_currentInterface->introspection += ">\n"_L1; |
245 | |
|
246 | 0 | do { |
247 | 0 | if (m_xml.name() == "annotation"_L1) { |
248 | 0 | parseAnnotation(annotations); |
249 | 0 | } else if (m_xml.name() == "arg"_L1) { |
250 | 0 | const QXmlStreamAttributes attributes = m_xml.attributes(); |
251 | 0 | QDBusIntrospection::Argument argument; |
252 | 0 | argument.location = m_currentLocation; |
253 | 0 | if (!attributes.hasAttribute("direction"_L1) || |
254 | 0 | attributes.value("direction"_L1) == "out"_L1) { |
255 | 0 | parseArg(attributes, argument); |
256 | 0 | arguments << argument; |
257 | 0 | } |
258 | 0 | } else { |
259 | 0 | qDBusParserWarning("Unknown element '%s' while checking for signal arguments", |
260 | 0 | qPrintable(m_xml.name().toString())); |
261 | 0 | } |
262 | 0 | m_xml.skipCurrentElement(); |
263 | 0 | } while (readNextStartElement()); |
264 | |
|
265 | 0 | m_currentInterface->introspection += " </signal>\n"_L1; |
266 | 0 | } |
267 | |
|
268 | 0 | signalData.outputArgs = arguments; |
269 | 0 | signalData.annotations = annotations; |
270 | |
|
271 | 0 | return true; |
272 | 0 | } |
273 | | |
274 | | void QDBusXmlParser::readInterface() |
275 | 0 | { |
276 | 0 | Q_ASSERT(!m_currentInterface); |
277 | |
|
278 | 0 | const QString ifaceName = m_xml.attributes().value("name"_L1).toString(); |
279 | 0 | if (!QDBusUtil::isValidInterfaceName(ifaceName)) { |
280 | 0 | qDBusParserError("Invalid D-Bus interface name '%s' found while parsing introspection", |
281 | 0 | qPrintable(ifaceName)); |
282 | 0 | return; |
283 | 0 | } |
284 | | |
285 | 0 | m_object->interfaces.append(ifaceName); |
286 | |
|
287 | 0 | m_currentInterface = std::make_unique<QDBusIntrospection::Interface>(); |
288 | 0 | m_currentInterface->location = m_currentLocation; |
289 | 0 | m_currentInterface->name = ifaceName; |
290 | 0 | m_currentInterface->introspection += " <interface name=\""_L1 + ifaceName + "\">\n"_L1; |
291 | |
|
292 | 0 | while (readNextStartElement()) { |
293 | 0 | if (m_xml.name() == "method"_L1) { |
294 | 0 | QDBusIntrospection::Method methodData; |
295 | 0 | methodData.location = m_currentLocation; |
296 | 0 | if (parseMethod(methodData)) |
297 | 0 | m_currentInterface->methods.insert(methodData.name, methodData); |
298 | 0 | } else if (m_xml.name() == "signal"_L1) { |
299 | 0 | QDBusIntrospection::Signal signalData; |
300 | 0 | signalData.location = m_currentLocation; |
301 | 0 | if (parseSignal(signalData)) |
302 | 0 | m_currentInterface->signals_.insert(signalData.name, signalData); |
303 | 0 | } else if (m_xml.name() == "property"_L1) { |
304 | 0 | QDBusIntrospection::Property propertyData; |
305 | 0 | propertyData.location = m_currentLocation; |
306 | 0 | if (parseProperty(propertyData)) |
307 | 0 | m_currentInterface->properties.insert(propertyData.name, propertyData); |
308 | 0 | } else if (m_xml.name() == "annotation"_L1) { |
309 | 0 | parseAnnotation(m_currentInterface->annotations, true); |
310 | 0 | m_xml.skipCurrentElement(); // skip over annotation object |
311 | 0 | } else { |
312 | 0 | if (m_xml.prefix().isEmpty()) { |
313 | 0 | qDBusParserWarning("Unknown element '%s' while parsing interface", |
314 | 0 | qPrintable(m_xml.name().toString())); |
315 | 0 | } |
316 | 0 | m_xml.skipCurrentElement(); |
317 | 0 | } |
318 | 0 | } |
319 | |
|
320 | 0 | m_currentInterface->introspection += " </interface>"_L1; |
321 | |
|
322 | 0 | m_interfaces.insert( |
323 | 0 | ifaceName, |
324 | 0 | QSharedDataPointer<QDBusIntrospection::Interface>(m_currentInterface.release())); |
325 | |
|
326 | 0 | if (!m_xml.isEndElement() || m_xml.name() != "interface"_L1) { |
327 | 0 | qDBusParserError("Invalid Interface specification"); |
328 | 0 | } |
329 | 0 | } |
330 | | |
331 | | void QDBusXmlParser::readNode(int nodeLevel) |
332 | 0 | { |
333 | 0 | const QString objName = m_xml.attributes().value("name"_L1).toString(); |
334 | 0 | QString fullName = m_object->path; |
335 | 0 | if (!(fullName.endsWith(u'/') || (nodeLevel == 0 && objName.startsWith(u'/')))) |
336 | 0 | fullName.append(u'/'); |
337 | 0 | fullName += objName; |
338 | |
|
339 | 0 | if (!QDBusUtil::isValidObjectPath(fullName)) { |
340 | 0 | qDBusParserError("Invalid D-Bus object path '%s' found while parsing introspection", |
341 | 0 | qPrintable(fullName)); |
342 | 0 | return; |
343 | 0 | } |
344 | | |
345 | 0 | if (nodeLevel > 0) |
346 | 0 | m_object->childObjects.append(objName); |
347 | 0 | else |
348 | 0 | m_object->location = m_currentLocation; |
349 | 0 | } |
350 | | |
351 | | void QDBusXmlParser::updateCurrentLocation() |
352 | 0 | { |
353 | 0 | m_currentLocation = |
354 | 0 | QDBusIntrospection::SourceLocation{ m_xml.lineNumber(), m_xml.columnNumber() }; |
355 | 0 | } |
356 | | |
357 | | // Similar to m_xml.readNextElement() but sets current location to point |
358 | | // to the start element. |
359 | | bool QDBusXmlParser::readNextStartElement() |
360 | 0 | { |
361 | 0 | updateCurrentLocation(); |
362 | |
|
363 | 0 | while (m_xml.readNext() != QXmlStreamReader::Invalid) { |
364 | 0 | if (m_xml.isEndElement()) |
365 | 0 | return false; |
366 | 0 | else if (m_xml.isStartElement()) |
367 | 0 | return true; |
368 | 0 | updateCurrentLocation(); |
369 | 0 | } |
370 | 0 | return false; |
371 | 0 | } |
372 | | |
373 | | QDBusXmlParser::QDBusXmlParser(const QString &service, const QString &path, const QString &xmlData, |
374 | | QDBusIntrospection::DiagnosticsReporter *reporter) |
375 | 0 | : m_service(service), |
376 | 0 | m_path(path), |
377 | 0 | m_object(new QDBusIntrospection::Object), |
378 | 0 | m_xml(xmlData), |
379 | 0 | m_reporter(reporter) |
380 | 0 | { |
381 | 0 | m_object->service = m_service; |
382 | 0 | m_object->path = m_path; |
383 | |
|
384 | 0 | int nodeLevel = -1; |
385 | |
|
386 | 0 | while (!m_xml.atEnd()) { |
387 | 0 | updateCurrentLocation(); |
388 | 0 | m_xml.readNext(); |
389 | |
|
390 | 0 | switch (m_xml.tokenType()) { |
391 | 0 | case QXmlStreamReader::StartElement: |
392 | 0 | if (m_xml.name() == "node"_L1) { |
393 | 0 | readNode(++nodeLevel); |
394 | 0 | } else if (m_xml.name() == "interface"_L1) { |
395 | 0 | readInterface(); |
396 | 0 | } else { |
397 | 0 | if (m_xml.prefix().isEmpty()) { |
398 | 0 | qDBusParserWarning("Skipping unknown element '%s'", |
399 | 0 | qPrintable(m_xml.name().toString())); |
400 | 0 | } |
401 | 0 | m_xml.skipCurrentElement(); |
402 | 0 | } |
403 | 0 | break; |
404 | 0 | case QXmlStreamReader::EndElement: |
405 | 0 | if (m_xml.name() == "node"_L1) { |
406 | 0 | --nodeLevel; |
407 | 0 | } else { |
408 | 0 | qDBusParserError("Invalid node declaration '%s'", |
409 | 0 | qPrintable(m_xml.name().toString())); |
410 | 0 | } |
411 | 0 | break; |
412 | 0 | case QXmlStreamReader::StartDocument: |
413 | 0 | case QXmlStreamReader::EndDocument: |
414 | 0 | case QXmlStreamReader::DTD: |
415 | | // not interested |
416 | 0 | break; |
417 | 0 | case QXmlStreamReader::Comment: |
418 | | // ignore comments and processing instructions |
419 | 0 | break; |
420 | 0 | case QXmlStreamReader::Characters: |
421 | | // ignore whitespace |
422 | 0 | if (m_xml.isWhitespace()) |
423 | 0 | break; |
424 | 0 | Q_FALLTHROUGH(); |
425 | 0 | default: |
426 | 0 | qDBusParserError("Unknown token: '%s'", qPrintable(m_xml.tokenString())); |
427 | 0 | break; |
428 | 0 | } |
429 | 0 | } |
430 | | |
431 | 0 | if (m_xml.hasError()) |
432 | 0 | qDBusParserError("XML error: %s", qPrintable(m_xml.errorString())); |
433 | 0 | } |
434 | | |
435 | | QT_END_NAMESPACE |
436 | | |
437 | | #endif // QT_NO_DBUS |