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 
parseArg(const QXmlStreamAttributes & attributes,QDBusIntrospection::Argument & argData,QDBusIntrospection::Interface * ifaceData)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 
parseAnnotation(const QXmlStreamReader & xml,QDBusIntrospection::Annotations & annotations,QDBusIntrospection::Interface * ifaceData,bool interfaceAnnotation=false)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 
parseProperty(QXmlStreamReader & xml,QDBusIntrospection::Property & propertyData,QDBusIntrospection::Interface * ifaceData)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 
parseMethod(QXmlStreamReader & xml,QDBusIntrospection::Method & methodData,QDBusIntrospection::Interface * ifaceData)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 
parseSignal(QXmlStreamReader & xml,QDBusIntrospection::Signal & signalData,QDBusIntrospection::Interface * ifaceData)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 
readInterface(QXmlStreamReader & xml,QDBusIntrospection::Object * objData,QDBusIntrospection::Interfaces * interfaces)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 
readNode(const QXmlStreamReader & xml,QDBusIntrospection::Object * objData,int nodeLevel)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 
QDBusXmlParser(const QString & service,const QString & path,const QString & xmlData)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