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/qxmlstream.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 qDBusParserError(...) qCDebug(dbusParser, ##__VA_ARGS__) |
22 | |
23 | static bool parseArg(const QXmlStreamAttributes &attributes, QDBusIntrospection::Argument &argData, |
24 | QDBusIntrospection::Interface *ifaceData) |
25 | { |
26 | const QString argType = attributes.value(qualifiedName: "type"_L1 ).toString(); |
27 | |
28 | bool ok = QDBusUtil::isValidSingleSignature(signature: argType); |
29 | if (!ok) { |
30 | qDBusParserError("Invalid D-BUS type signature '%s' found while parsing introspection" , |
31 | qPrintable(argType)); |
32 | } |
33 | |
34 | argData.name = attributes.value(qualifiedName: "name"_L1 ).toString(); |
35 | argData.type = argType; |
36 | |
37 | ifaceData->introspection += " <arg"_L1 ; |
38 | if (attributes.hasAttribute(qualifiedName: "direction"_L1 )) { |
39 | const QString direction = attributes.value(qualifiedName: "direction"_L1 ).toString(); |
40 | ifaceData->introspection += " direction=\""_L1 + direction + u'"'; |
41 | } |
42 | ifaceData->introspection += " type=\""_L1 + argData.type + u'"'; |
43 | if (!argData.name.isEmpty()) |
44 | ifaceData->introspection += " name=\""_L1 + argData.name + u'"'; |
45 | ifaceData->introspection += "/>\n"_L1 ; |
46 | |
47 | return ok; |
48 | } |
49 | |
50 | static bool parseAnnotation(const QXmlStreamReader &xml, QDBusIntrospection::Annotations &annotations, |
51 | QDBusIntrospection::Interface *ifaceData, bool interfaceAnnotation = false) |
52 | { |
53 | Q_ASSERT(xml.isStartElement() && xml.name() == "annotation"_L1 ); |
54 | |
55 | const QXmlStreamAttributes attributes = xml.attributes(); |
56 | const QString name = attributes.value(qualifiedName: "name"_L1 ).toString(); |
57 | |
58 | if (!QDBusUtil::isValidInterfaceName(ifaceName: name)) { |
59 | qDBusParserError("Invalid D-BUS annotation '%s' found while parsing introspection" , |
60 | qPrintable(name)); |
61 | return false; |
62 | } |
63 | const QString value = attributes.value(qualifiedName: "value"_L1 ).toString(); |
64 | annotations.insert(key: name, value); |
65 | if (!interfaceAnnotation) |
66 | ifaceData->introspection += " "_L1 ; |
67 | ifaceData->introspection += " <annotation value=\""_L1 + value.toHtmlEscaped() + "\" name=\""_L1 + name + "\"/>\n"_L1 ; |
68 | return true; |
69 | } |
70 | |
71 | static bool parseProperty(QXmlStreamReader &xml, QDBusIntrospection::Property &propertyData, |
72 | QDBusIntrospection::Interface *ifaceData) |
73 | { |
74 | Q_ASSERT(xml.isStartElement() && xml.name() == "property"_L1 ); |
75 | |
76 | QXmlStreamAttributes attributes = xml.attributes(); |
77 | const QString propertyName = attributes.value(qualifiedName: "name"_L1 ).toString(); |
78 | if (!QDBusUtil::isValidMemberName(memberName: propertyName)) { |
79 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
80 | qPrintable(propertyName), qPrintable(ifaceData->name)); |
81 | xml.skipCurrentElement(); |
82 | return false; |
83 | } |
84 | |
85 | // parse data |
86 | propertyData.name = propertyName; |
87 | propertyData.type = attributes.value(qualifiedName: "type"_L1 ).toString(); |
88 | |
89 | if (!QDBusUtil::isValidSingleSignature(signature: propertyData.type)) { |
90 | // cannot be! |
91 | qDBusParserError("Invalid D-BUS type signature '%s' found in property '%s.%s' while parsing introspection" , |
92 | qPrintable(propertyData.type), qPrintable(ifaceData->name), |
93 | qPrintable(propertyName)); |
94 | } |
95 | |
96 | const QString access = attributes.value(qualifiedName: "access"_L1 ).toString(); |
97 | if (access == "read"_L1 ) |
98 | propertyData.access = QDBusIntrospection::Property::Read; |
99 | else if (access == "write"_L1 ) |
100 | propertyData.access = QDBusIntrospection::Property::Write; |
101 | else if (access == "readwrite"_L1 ) |
102 | propertyData.access = QDBusIntrospection::Property::ReadWrite; |
103 | else { |
104 | qDBusParserError("Invalid D-BUS property access '%s' found in property '%s.%s' while parsing introspection" , |
105 | qPrintable(access), qPrintable(ifaceData->name), |
106 | qPrintable(propertyName)); |
107 | return false; // invalid one! |
108 | } |
109 | |
110 | ifaceData->introspection += " <property access=\""_L1 + access + "\" type=\""_L1 + propertyData.type + "\" name=\""_L1 + propertyName + u'"'; |
111 | |
112 | if (!xml.readNextStartElement()) { |
113 | ifaceData->introspection += "/>\n"_L1 ; |
114 | } else { |
115 | ifaceData->introspection += ">\n"_L1 ; |
116 | |
117 | do { |
118 | if (xml.name() == "annotation"_L1 ) { |
119 | parseAnnotation(xml, annotations&: propertyData.annotations, ifaceData); |
120 | } else if (xml.prefix().isEmpty()) { |
121 | qDBusParserError() << "Unknown element" << xml.name() << "while checking for annotations" ; |
122 | } |
123 | xml.skipCurrentElement(); |
124 | } while (xml.readNextStartElement()); |
125 | |
126 | ifaceData->introspection += " </property>\n"_L1 ; |
127 | } |
128 | |
129 | if (!xml.isEndElement() || xml.name() != "property"_L1 ) { |
130 | qDBusParserError() << "Invalid property specification" << xml.tokenString() << xml.name(); |
131 | return false; |
132 | } |
133 | |
134 | return true; |
135 | } |
136 | |
137 | static bool parseMethod(QXmlStreamReader &xml, QDBusIntrospection::Method &methodData, |
138 | QDBusIntrospection::Interface *ifaceData) |
139 | { |
140 | Q_ASSERT(xml.isStartElement() && xml.name() == "method"_L1 ); |
141 | |
142 | const QXmlStreamAttributes attributes = xml.attributes(); |
143 | const QString methodName = attributes.value(qualifiedName: "name"_L1 ).toString(); |
144 | if (!QDBusUtil::isValidMemberName(memberName: methodName)) { |
145 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
146 | qPrintable(methodName), qPrintable(ifaceData->name)); |
147 | return false; |
148 | } |
149 | |
150 | methodData.name = methodName; |
151 | ifaceData->introspection += " <method name=\""_L1 + methodName + u'"'; |
152 | |
153 | QDBusIntrospection::Arguments outArguments; |
154 | QDBusIntrospection::Arguments inArguments; |
155 | QDBusIntrospection::Annotations annotations; |
156 | |
157 | if (!xml.readNextStartElement()) { |
158 | ifaceData->introspection += "/>\n"_L1 ; |
159 | } else { |
160 | ifaceData->introspection += ">\n"_L1 ; |
161 | |
162 | do { |
163 | if (xml.name() == "annotation"_L1 ) { |
164 | parseAnnotation(xml, annotations, ifaceData); |
165 | } else if (xml.name() == "arg"_L1 ) { |
166 | const QXmlStreamAttributes attributes = xml.attributes(); |
167 | const QString direction = attributes.value(qualifiedName: "direction"_L1 ).toString(); |
168 | QDBusIntrospection::Argument argument; |
169 | if (!attributes.hasAttribute(qualifiedName: "direction"_L1 ) || direction == "in"_L1 ) { |
170 | parseArg(attributes, argData&: argument, ifaceData); |
171 | inArguments << argument; |
172 | } else if (direction == "out"_L1 ) { |
173 | parseArg(attributes, argData&: argument, ifaceData); |
174 | outArguments << argument; |
175 | } |
176 | } else if (xml.prefix().isEmpty()) { |
177 | qDBusParserError() << "Unknown element" << xml.name() << "while checking for method arguments" ; |
178 | } |
179 | xml.skipCurrentElement(); |
180 | } while (xml.readNextStartElement()); |
181 | |
182 | ifaceData->introspection += " </method>\n"_L1 ; |
183 | } |
184 | |
185 | methodData.inputArgs = inArguments; |
186 | methodData.outputArgs = outArguments; |
187 | methodData.annotations = annotations; |
188 | |
189 | return true; |
190 | } |
191 | |
192 | |
193 | static bool parseSignal(QXmlStreamReader &xml, QDBusIntrospection::Signal &signalData, |
194 | QDBusIntrospection::Interface *ifaceData) |
195 | { |
196 | Q_ASSERT(xml.isStartElement() && xml.name() == "signal"_L1 ); |
197 | |
198 | const QXmlStreamAttributes attributes = xml.attributes(); |
199 | const QString signalName = attributes.value(qualifiedName: "name"_L1 ).toString(); |
200 | |
201 | if (!QDBusUtil::isValidMemberName(memberName: signalName)) { |
202 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
203 | qPrintable(signalName), qPrintable(ifaceData->name)); |
204 | return false; |
205 | } |
206 | |
207 | signalData.name = signalName; |
208 | ifaceData->introspection += " <signal name=\""_L1 + signalName + u'"'; |
209 | |
210 | QDBusIntrospection::Arguments arguments; |
211 | QDBusIntrospection::Annotations annotations; |
212 | |
213 | if (!xml.readNextStartElement()) { |
214 | ifaceData->introspection += "/>\n"_L1 ; |
215 | } else { |
216 | ifaceData->introspection += ">\n"_L1 ; |
217 | |
218 | do { |
219 | if (xml.name() == "annotation"_L1 ) { |
220 | parseAnnotation(xml, annotations, ifaceData); |
221 | } else if (xml.name() == "arg"_L1 ) { |
222 | const QXmlStreamAttributes attributes = xml.attributes(); |
223 | QDBusIntrospection::Argument argument; |
224 | if (!attributes.hasAttribute(qualifiedName: "direction"_L1 ) || |
225 | attributes.value(qualifiedName: "direction"_L1 ) == "out"_L1 ) { |
226 | parseArg(attributes, argData&: argument, ifaceData); |
227 | arguments << argument; |
228 | } |
229 | } else { |
230 | qDBusParserError() << "Unknown element" << xml.name() << "while checking for signal arguments" ; |
231 | } |
232 | xml.skipCurrentElement(); |
233 | } while (xml.readNextStartElement()); |
234 | |
235 | ifaceData->introspection += " </signal>\n"_L1 ; |
236 | } |
237 | |
238 | signalData.outputArgs = arguments; |
239 | signalData.annotations = annotations; |
240 | |
241 | return true; |
242 | } |
243 | |
244 | static void readInterface(QXmlStreamReader &xml, QDBusIntrospection::Object *objData, |
245 | QDBusIntrospection::Interfaces *interfaces) |
246 | { |
247 | const QString ifaceName = xml.attributes().value(qualifiedName: "name"_L1 ).toString(); |
248 | if (!QDBusUtil::isValidInterfaceName(ifaceName)) { |
249 | qDBusParserError("Invalid D-BUS interface name '%s' found while parsing introspection" , |
250 | qPrintable(ifaceName)); |
251 | return; |
252 | } |
253 | |
254 | objData->interfaces.append(t: ifaceName); |
255 | |
256 | QDBusIntrospection::Interface *ifaceData = new QDBusIntrospection::Interface; |
257 | ifaceData->name = ifaceName; |
258 | ifaceData->introspection += " <interface name=\""_L1 + ifaceName + "\">\n"_L1 ; |
259 | |
260 | while (xml.readNextStartElement()) { |
261 | if (xml.name() == "method"_L1 ) { |
262 | QDBusIntrospection::Method methodData; |
263 | if (parseMethod(xml, methodData, ifaceData)) |
264 | ifaceData->methods.insert(key: methodData.name, value: methodData); |
265 | } else if (xml.name() == "signal"_L1 ) { |
266 | QDBusIntrospection::Signal signalData; |
267 | if (parseSignal(xml, signalData, ifaceData)) |
268 | ifaceData->signals_.insert(key: signalData.name, value: signalData); |
269 | } else if (xml.name() == "property"_L1 ) { |
270 | QDBusIntrospection::Property propertyData; |
271 | if (parseProperty(xml, propertyData, ifaceData)) |
272 | ifaceData->properties.insert(key: propertyData.name, value: propertyData); |
273 | } else if (xml.name() == "annotation"_L1 ) { |
274 | parseAnnotation(xml, annotations&: ifaceData->annotations, ifaceData, interfaceAnnotation: true); |
275 | xml.skipCurrentElement(); // skip over annotation object |
276 | } else { |
277 | if (xml.prefix().isEmpty()) { |
278 | qDBusParserError() << "Unknown element while parsing interface" << xml.name(); |
279 | } |
280 | xml.skipCurrentElement(); |
281 | } |
282 | } |
283 | |
284 | ifaceData->introspection += " </interface>"_L1 ; |
285 | |
286 | interfaces->insert(key: ifaceName, value: QSharedDataPointer<QDBusIntrospection::Interface>(ifaceData)); |
287 | |
288 | if (!xml.isEndElement() || xml.name() != "interface"_L1 ) { |
289 | qDBusParserError() << "Invalid Interface specification" ; |
290 | } |
291 | } |
292 | |
293 | static void readNode(const QXmlStreamReader &xml, QDBusIntrospection::Object *objData, int nodeLevel) |
294 | { |
295 | const QString objName = xml.attributes().value(qualifiedName: "name"_L1 ).toString(); |
296 | const QString fullName = objData->path.endsWith(c: u'/') |
297 | ? (objData->path + objName) |
298 | : QString(objData->path + u'/' + objName); |
299 | if (!QDBusUtil::isValidObjectPath(path: fullName)) { |
300 | qDBusParserError("Invalid D-BUS object path '%s' found while parsing introspection" , |
301 | qPrintable(fullName)); |
302 | return; |
303 | } |
304 | |
305 | if (nodeLevel > 0) |
306 | objData->childObjects.append(t: objName); |
307 | } |
308 | |
309 | QDBusXmlParser::QDBusXmlParser(const QString& service, const QString& path, |
310 | const QString& xmlData) |
311 | : m_service(service), m_path(path), m_object(new QDBusIntrospection::Object) |
312 | { |
313 | // qDBusParserError() << "parsing" << xmlData; |
314 | |
315 | m_object->service = m_service; |
316 | m_object->path = m_path; |
317 | |
318 | QXmlStreamReader xml(xmlData); |
319 | |
320 | int nodeLevel = -1; |
321 | |
322 | while (!xml.atEnd()) { |
323 | xml.readNext(); |
324 | |
325 | switch (xml.tokenType()) { |
326 | case QXmlStreamReader::StartElement: |
327 | if (xml.name() == "node"_L1 ) { |
328 | readNode(xml, objData: m_object, nodeLevel: ++nodeLevel); |
329 | } else if (xml.name() == "interface"_L1 ) { |
330 | readInterface(xml, objData: m_object, interfaces: &m_interfaces); |
331 | } else { |
332 | if (xml.prefix().isEmpty()) { |
333 | qDBusParserError() << "skipping unknown element" << xml.name(); |
334 | } |
335 | xml.skipCurrentElement(); |
336 | } |
337 | break; |
338 | case QXmlStreamReader::EndElement: |
339 | if (xml.name() == "node"_L1 ) { |
340 | --nodeLevel; |
341 | } else { |
342 | qDBusParserError() << "Invalid Node declaration" << xml.name(); |
343 | } |
344 | break; |
345 | case QXmlStreamReader::StartDocument: |
346 | case QXmlStreamReader::EndDocument: |
347 | case QXmlStreamReader::DTD: |
348 | // not interested |
349 | break; |
350 | case QXmlStreamReader::Comment: |
351 | // ignore comments and processing instructions |
352 | break; |
353 | case QXmlStreamReader::Characters: |
354 | // ignore whitespace |
355 | if (xml.isWhitespace()) |
356 | break; |
357 | Q_FALLTHROUGH(); |
358 | default: |
359 | qDBusParserError() << "unknown token" << xml.name() << xml.tokenString(); |
360 | break; |
361 | } |
362 | } |
363 | |
364 | if (xml.hasError()) { |
365 | qDBusParserError() << "xml error" << xml.errorString() << "doc" << xmlData; |
366 | } |
367 | } |
368 | |
369 | QT_END_NAMESPACE |
370 | |
371 | #endif // QT_NO_DBUS |
372 | |