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
51QT_BEGIN_NAMESPACE
52
53Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg)
54
55#define qDBusParserError(...) qCDebug(dbusParser, ##__VA_ARGS__)
56
57static 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
84static 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
105static 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
171static 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
228static 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
279static 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
328static 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
344QDBusXmlParser::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
404QT_END_NAMESPACE
405
406#endif // QT_NO_DBUS
407