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
15QT_BEGIN_NAMESPACE
16
17using namespace Qt::StringLiterals;
18
19Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg)
20
21#define qDBusParserError(...) qCDebug(dbusParser, ##__VA_ARGS__)
22
23static 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
50static 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
71static 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
137static 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
193static 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
244static 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
293static 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
309QDBusXmlParser::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
369QT_END_NAMESPACE
370
371#endif // QT_NO_DBUS
372

source code of qtbase/src/dbus/qdbusxmlparser.cpp