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