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