1 | /**************************************************************************** |
2 | ** |
3 | ** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). |
4 | ** Contact: http://www.qt-project.org/legal |
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 Digia. For licensing terms and |
14 | ** conditions see http://qt.digia.com/licensing. For further information |
15 | ** use the contact form at http://qt.digia.com/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 2.1 as published by the Free Software |
20 | ** Foundation and appearing in the file LICENSE.LGPL included in the |
21 | ** packaging of this file. Please review the following information to |
22 | ** ensure the GNU Lesser General Public License version 2.1 requirements |
23 | ** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. |
24 | ** |
25 | ** In addition, as a special exception, Digia gives you certain additional |
26 | ** rights. These rights are described in the Digia Qt LGPL Exception |
27 | ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. |
28 | ** |
29 | ** GNU General Public License Usage |
30 | ** Alternatively, this file may be used under the terms of the GNU |
31 | ** General Public License version 3.0 as published by the Free Software |
32 | ** Foundation and appearing in the file LICENSE.GPL included in the |
33 | ** packaging of this file. Please review the following information to |
34 | ** ensure the GNU General Public License version 3.0 requirements will be |
35 | ** met: http://www.gnu.org/copyleft/gpl.html. |
36 | ** |
37 | ** |
38 | ** $QT_END_LICENSE$ |
39 | ** |
40 | ****************************************************************************/ |
41 | |
42 | #include "qdbusxmlparser_p.h" |
43 | #include "qdbusinterface.h" |
44 | #include "qdbusinterface_p.h" |
45 | #include "qdbusconnection_p.h" |
46 | #include "qdbusutil_p.h" |
47 | |
48 | #include <QtXml/qdom.h> |
49 | #include <QtCore/qmap.h> |
50 | #include <QtCore/qvariant.h> |
51 | #include <QtCore/qtextstream.h> |
52 | |
53 | #ifndef QT_NO_DBUS |
54 | |
55 | //#define QDBUS_PARSER_DEBUG |
56 | #ifdef QDBUS_PARSER_DEBUG |
57 | # define qDBusParserError qWarning |
58 | #else |
59 | # define qDBusParserError if (true) {} else qDebug |
60 | #endif |
61 | |
62 | QT_BEGIN_NAMESPACE |
63 | |
64 | static QDBusIntrospection::Annotations |
65 | parseAnnotations(const QDomElement& elem) |
66 | { |
67 | QDBusIntrospection::Annotations retval; |
68 | QDomNodeList list = elem.elementsByTagName(QLatin1String("annotation" )); |
69 | for (int i = 0; i < list.count(); ++i) |
70 | { |
71 | QDomElement ann = list.item(i).toElement(); |
72 | if (ann.isNull()) |
73 | continue; |
74 | |
75 | QString name = ann.attribute(QLatin1String("name" )), |
76 | value = ann.attribute(QLatin1String("value" )); |
77 | |
78 | if (!QDBusUtil::isValidInterfaceName(name)) { |
79 | qDBusParserError("Invalid D-BUS annotation '%s' found while parsing introspection" , |
80 | qPrintable(name)); |
81 | continue; |
82 | } |
83 | |
84 | retval.insert(name, value); |
85 | } |
86 | |
87 | return retval; |
88 | } |
89 | |
90 | static QDBusIntrospection::Arguments |
91 | parseArgs(const QDomElement& elem, const QLatin1String& direction, bool acceptEmpty) |
92 | { |
93 | QDBusIntrospection::Arguments retval; |
94 | QDomNodeList list = elem.elementsByTagName(QLatin1String("arg" )); |
95 | for (int i = 0; i < list.count(); ++i) |
96 | { |
97 | QDomElement arg = list.item(i).toElement(); |
98 | if (arg.isNull()) |
99 | continue; |
100 | |
101 | if ((acceptEmpty && !arg.hasAttribute(QLatin1String("direction" ))) || |
102 | arg.attribute(QLatin1String("direction" )) == direction) { |
103 | |
104 | QDBusIntrospection::Argument argData; |
105 | if (arg.hasAttribute(QLatin1String("name" ))) |
106 | argData.name = arg.attribute(QLatin1String("name" )); // can be empty |
107 | argData.type = arg.attribute(QLatin1String("type" )); |
108 | if (!QDBusUtil::isValidSingleSignature(argData.type)) { |
109 | qDBusParserError("Invalid D-BUS type signature '%s' found while parsing introspection" , |
110 | qPrintable(argData.type)); |
111 | } |
112 | |
113 | retval << argData; |
114 | } |
115 | } |
116 | return retval; |
117 | } |
118 | |
119 | QDBusXmlParser::QDBusXmlParser(const QString& service, const QString& path, |
120 | const QString& xmlData) |
121 | : m_service(service), m_path(path) |
122 | { |
123 | QDomDocument doc; |
124 | doc.setContent(xmlData); |
125 | m_node = doc.firstChildElement(QLatin1String("node" )); |
126 | } |
127 | |
128 | QDBusXmlParser::QDBusXmlParser(const QString& service, const QString& path, |
129 | const QDomElement& node) |
130 | : m_service(service), m_path(path), m_node(node) |
131 | { |
132 | } |
133 | |
134 | QDBusIntrospection::Interfaces |
135 | QDBusXmlParser::interfaces() const |
136 | { |
137 | QDBusIntrospection::Interfaces retval; |
138 | |
139 | if (m_node.isNull()) |
140 | return retval; |
141 | |
142 | QDomNodeList interfaceList = m_node.elementsByTagName(QLatin1String("interface" )); |
143 | for (int i = 0; i < interfaceList.count(); ++i) |
144 | { |
145 | QDomElement iface = interfaceList.item(i).toElement(); |
146 | QString ifaceName = iface.attribute(QLatin1String("name" )); |
147 | if (iface.isNull()) |
148 | continue; // for whatever reason |
149 | if (!QDBusUtil::isValidInterfaceName(ifaceName)) { |
150 | qDBusParserError("Invalid D-BUS interface name '%s' found while parsing introspection" , |
151 | qPrintable(ifaceName)); |
152 | continue; |
153 | } |
154 | |
155 | QDBusIntrospection::Interface *ifaceData = new QDBusIntrospection::Interface; |
156 | ifaceData->name = ifaceName; |
157 | { |
158 | // save the data |
159 | QTextStream ts(&ifaceData->introspection); |
160 | iface.save(ts,2); |
161 | } |
162 | |
163 | // parse annotations |
164 | ifaceData->annotations = parseAnnotations(iface); |
165 | |
166 | // parse methods |
167 | QDomNodeList list = iface.elementsByTagName(QLatin1String("method" )); |
168 | for (int j = 0; j < list.count(); ++j) |
169 | { |
170 | QDomElement method = list.item(j).toElement(); |
171 | QString methodName = method.attribute(QLatin1String("name" )); |
172 | if (method.isNull()) |
173 | continue; |
174 | if (!QDBusUtil::isValidMemberName(methodName)) { |
175 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
176 | qPrintable(methodName), qPrintable(ifaceName)); |
177 | continue; |
178 | } |
179 | |
180 | QDBusIntrospection::Method methodData; |
181 | methodData.name = methodName; |
182 | |
183 | // parse arguments |
184 | methodData.inputArgs = parseArgs(method, QLatin1String("in" ), true); |
185 | methodData.outputArgs = parseArgs(method, QLatin1String("out" ), false); |
186 | methodData.annotations = parseAnnotations(method); |
187 | |
188 | // add it |
189 | ifaceData->methods.insert(methodName, methodData); |
190 | } |
191 | |
192 | // parse signals |
193 | list = iface.elementsByTagName(QLatin1String("signal" )); |
194 | for (int j = 0; j < list.count(); ++j) |
195 | { |
196 | QDomElement signal = list.item(j).toElement(); |
197 | QString signalName = signal.attribute(QLatin1String("name" )); |
198 | if (signal.isNull()) |
199 | continue; |
200 | if (!QDBusUtil::isValidMemberName(signalName)) { |
201 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
202 | qPrintable(signalName), qPrintable(ifaceName)); |
203 | continue; |
204 | } |
205 | |
206 | QDBusIntrospection::Signal signalData; |
207 | signalData.name = signalName; |
208 | |
209 | // parse data |
210 | signalData.outputArgs = parseArgs(signal, QLatin1String("out" ), true); |
211 | signalData.annotations = parseAnnotations(signal); |
212 | |
213 | // add it |
214 | ifaceData->signals_.insert(signalName, signalData); |
215 | } |
216 | |
217 | // parse properties |
218 | list = iface.elementsByTagName(QLatin1String("property" )); |
219 | for (int j = 0; j < list.count(); ++j) |
220 | { |
221 | QDomElement property = list.item(j).toElement(); |
222 | QString propertyName = property.attribute(QLatin1String("name" )); |
223 | if (property.isNull()) |
224 | continue; |
225 | if (!QDBusUtil::isValidMemberName(propertyName)) { |
226 | qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection" , |
227 | qPrintable(propertyName), qPrintable(ifaceName)); |
228 | continue; |
229 | } |
230 | |
231 | QDBusIntrospection::Property propertyData; |
232 | |
233 | // parse data |
234 | propertyData.name = propertyName; |
235 | propertyData.type = property.attribute(QLatin1String("type" )); |
236 | propertyData.annotations = parseAnnotations(property); |
237 | |
238 | if (!QDBusUtil::isValidSingleSignature(propertyData.type)) { |
239 | // cannot be! |
240 | qDBusParserError("Invalid D-BUS type signature '%s' found in property '%s.%s' while parsing introspection" , |
241 | qPrintable(propertyData.type), qPrintable(ifaceName), |
242 | qPrintable(propertyName)); |
243 | } |
244 | |
245 | QString access = property.attribute(QLatin1String("access" )); |
246 | if (access == QLatin1String("read" )) |
247 | propertyData.access = QDBusIntrospection::Property::Read; |
248 | else if (access == QLatin1String("write" )) |
249 | propertyData.access = QDBusIntrospection::Property::Write; |
250 | else if (access == QLatin1String("readwrite" )) |
251 | propertyData.access = QDBusIntrospection::Property::ReadWrite; |
252 | else { |
253 | qDBusParserError("Invalid D-BUS property access '%s' found in property '%s.%s' while parsing introspection" , |
254 | qPrintable(access), qPrintable(ifaceName), |
255 | qPrintable(propertyName)); |
256 | continue; // invalid one! |
257 | } |
258 | |
259 | // add it |
260 | ifaceData->properties.insert(propertyName, propertyData); |
261 | } |
262 | |
263 | // add it |
264 | retval.insert(ifaceName, QSharedDataPointer<QDBusIntrospection::Interface>(ifaceData)); |
265 | } |
266 | |
267 | return retval; |
268 | } |
269 | |
270 | QSharedDataPointer<QDBusIntrospection::Object> |
271 | QDBusXmlParser::object() const |
272 | { |
273 | if (m_node.isNull()) |
274 | return QSharedDataPointer<QDBusIntrospection::Object>(); |
275 | |
276 | QDBusIntrospection::Object* objData; |
277 | objData = new QDBusIntrospection::Object; |
278 | objData->service = m_service; |
279 | objData->path = m_path; |
280 | |
281 | // check if we have anything to process |
282 | if (objData->introspection.isNull() && !m_node.firstChild().isNull()) { |
283 | // yes, introspect this object |
284 | QTextStream ts(&objData->introspection); |
285 | m_node.save(ts,2); |
286 | |
287 | QDomNodeList objects = m_node.elementsByTagName(QLatin1String("node" )); |
288 | for (int i = 0; i < objects.count(); ++i) { |
289 | QDomElement obj = objects.item(i).toElement(); |
290 | QString objName = obj.attribute(QLatin1String("name" )); |
291 | if (obj.isNull()) |
292 | continue; // for whatever reason |
293 | if (!QDBusUtil::isValidObjectPath(m_path + QLatin1Char('/') + objName)) { |
294 | qDBusParserError("Invalid D-BUS object path '%s/%s' found while parsing introspection" , |
295 | qPrintable(m_path), qPrintable(objName)); |
296 | continue; |
297 | } |
298 | |
299 | objData->childObjects.append(objName); |
300 | } |
301 | |
302 | QDomNodeList interfaceList = m_node.elementsByTagName(QLatin1String("interface" )); |
303 | for (int i = 0; i < interfaceList.count(); ++i) { |
304 | QDomElement iface = interfaceList.item(i).toElement(); |
305 | QString ifaceName = iface.attribute(QLatin1String("name" )); |
306 | if (iface.isNull()) |
307 | continue; |
308 | if (!QDBusUtil::isValidInterfaceName(ifaceName)) { |
309 | qDBusParserError("Invalid D-BUS interface name '%s' found while parsing introspection" , |
310 | qPrintable(ifaceName)); |
311 | continue; |
312 | } |
313 | |
314 | objData->interfaces.append(ifaceName); |
315 | } |
316 | } else { |
317 | objData->introspection = QLatin1String("<node/>\n" ); |
318 | } |
319 | |
320 | QSharedDataPointer<QDBusIntrospection::Object> retval; |
321 | retval = objData; |
322 | return retval; |
323 | } |
324 | |
325 | QSharedDataPointer<QDBusIntrospection::ObjectTree> |
326 | QDBusXmlParser::objectTree() const |
327 | { |
328 | QSharedDataPointer<QDBusIntrospection::ObjectTree> retval; |
329 | |
330 | if (m_node.isNull()) |
331 | return retval; |
332 | |
333 | retval = new QDBusIntrospection::ObjectTree; |
334 | |
335 | retval->service = m_service; |
336 | retval->path = m_path; |
337 | |
338 | QTextStream ts(&retval->introspection); |
339 | m_node.save(ts,2); |
340 | |
341 | // interfaces are easy: |
342 | retval->interfaceData = interfaces(); |
343 | retval->interfaces = retval->interfaceData.keys(); |
344 | |
345 | // sub-objects are slightly more difficult: |
346 | QDomNodeList objects = m_node.elementsByTagName(QLatin1String("node" )); |
347 | for (int i = 0; i < objects.count(); ++i) { |
348 | QDomElement obj = objects.item(i).toElement(); |
349 | QString objName = obj.attribute(QLatin1String("name" )); |
350 | if (obj.isNull() || objName.isEmpty()) |
351 | continue; // for whatever reason |
352 | |
353 | // check if we have anything to process |
354 | if (!obj.firstChild().isNull()) { |
355 | // yes, introspect this object |
356 | QString xml; |
357 | QTextStream ts2(&xml); |
358 | obj.save(ts2,0); |
359 | |
360 | // parse it |
361 | QString objAbsName = m_path; |
362 | if (!objAbsName.endsWith(QLatin1Char('/'))) |
363 | objAbsName.append(QLatin1Char('/')); |
364 | objAbsName += objName; |
365 | |
366 | QDBusXmlParser parser(m_service, objAbsName, obj); |
367 | retval->childObjectData.insert(objName, parser.objectTree()); |
368 | } |
369 | |
370 | retval->childObjects << objName; |
371 | } |
372 | |
373 | return QSharedDataPointer<QDBusIntrospection::ObjectTree>( retval ); |
374 | } |
375 | |
376 | QT_END_NAMESPACE |
377 | |
378 | #endif // QT_NO_DBUS |
379 | |