Qt
Internal/Contributor docs for the Qt SDK. <b>Note:</b> These are NOT official API docs; those are found <a href='https://doc.qt.io/'>here</a>.
Loading...
Searching...
No Matches
qdbusxmlparser.cpp
Go to the documentation of this file.
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/qdebug.h>
11
12#ifndef QT_NO_DBUS
13
15
16using namespace Qt::StringLiterals;
17
18Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg)
19
20#define qDBusParserWarning(format, ...) \
21 do { \
22 if (m_reporter) \
23 m_reporter->warning(m_currentLocation, format "\n", ##__VA_ARGS__); \
24 else \
25 qCDebug(dbusParser, "Warning: " format, ##__VA_ARGS__); \
26 } while (0)
27
28#define qDBusParserError(format, ...) \
29 do { \
30 if (m_reporter) \
31 m_reporter->error(m_currentLocation, format "\n", ##__VA_ARGS__); \
32 else \
33 qCDebug(dbusParser, "Error: " format, ##__VA_ARGS__); \
34 } while (0)
35
36bool QDBusXmlParser::parseArg(const QXmlStreamAttributes &attributes,
38{
39 Q_ASSERT(m_currentInterface);
40
41 const QString argType = attributes.value("type"_L1).toString();
42
44 if (!ok) {
45 qDBusParserError("Invalid D-Bus type signature '%s' found while parsing introspection",
46 qPrintable(argType));
47 }
48
49 argData.name = attributes.value("name"_L1).toString();
50 argData.type = argType;
51
52 m_currentInterface->introspection += " <arg"_L1;
53 if (attributes.hasAttribute("direction"_L1)) {
54 const QString direction = attributes.value("direction"_L1).toString();
55 m_currentInterface->introspection += " direction=\""_L1 + direction + u'"';
56 }
57 m_currentInterface->introspection += " type=\""_L1 + argData.type + u'"';
58 if (!argData.name.isEmpty())
59 m_currentInterface->introspection += " name=\""_L1 + argData.name + u'"';
60 m_currentInterface->introspection += "/>\n"_L1;
61
62 return ok;
63}
64
65bool QDBusXmlParser::parseAnnotation(QDBusIntrospection::Annotations &annotations,
66 bool interfaceAnnotation)
67{
68 Q_ASSERT(m_currentInterface);
69 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "annotation"_L1);
70
72 annotation.location = m_currentLocation;
73
74 const QXmlStreamAttributes attributes = m_xml.attributes();
75 annotation.name = attributes.value("name"_L1).toString();
76
77 if (!QDBusUtil::isValidInterfaceName(annotation.name)) {
78 qDBusParserError("Invalid D-Bus annotation '%s' found while parsing introspection",
79 qPrintable(annotation.name));
80 return false;
81 }
82 annotation.value = attributes.value("value"_L1).toString();
83 annotations.insert(annotation.name, annotation);
84 if (!interfaceAnnotation)
85 m_currentInterface->introspection += " "_L1;
86 m_currentInterface->introspection += " <annotation value=\""_L1
87 + annotation.value.toHtmlEscaped() + "\" name=\""_L1 + annotation.name + "\"/>\n"_L1;
88 return true;
89}
90
91bool QDBusXmlParser::parseProperty(QDBusIntrospection::Property &propertyData)
92{
93 Q_ASSERT(m_currentInterface);
94 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "property"_L1);
95
96 QXmlStreamAttributes attributes = m_xml.attributes();
97 const QString propertyName = attributes.value("name"_L1).toString();
98 if (!QDBusUtil::isValidMemberName(propertyName)) {
99 qDBusParserWarning("Invalid D-Bus member name '%s' found in interface '%s' while parsing "
100 "introspection",
101 qPrintable(propertyName), qPrintable(m_currentInterface->name));
102 m_xml.skipCurrentElement();
103 return false;
104 }
105
106 // parse data
107 propertyData.name = propertyName;
108 propertyData.type = attributes.value("type"_L1).toString();
109
110 if (!QDBusUtil::isValidSingleSignature(propertyData.type)) {
111 // cannot be!
112 qDBusParserError("Invalid D-Bus type signature '%s' found in property '%s.%s' while "
113 "parsing introspection",
114 qPrintable(propertyData.type), qPrintable(m_currentInterface->name),
115 qPrintable(propertyName));
116 }
117
118 const QString access = attributes.value("access"_L1).toString();
119 if (access == "read"_L1)
121 else if (access == "write"_L1)
123 else if (access == "readwrite"_L1)
125 else {
126 qDBusParserError("Invalid D-Bus property access '%s' found in property '%s.%s' while "
127 "parsing introspection",
128 qPrintable(access), qPrintable(m_currentInterface->name),
129 qPrintable(propertyName));
130 return false; // invalid one!
131 }
132
133 m_currentInterface->introspection += " <property access=\""_L1 + access + "\" type=\""_L1 + propertyData.type + "\" name=\""_L1 + propertyName + u'"';
134
135 if (!readNextStartElement()) {
136 m_currentInterface->introspection += "/>\n"_L1;
137 } else {
138 m_currentInterface->introspection += ">\n"_L1;
139
140 do {
141 if (m_xml.name() == "annotation"_L1) {
142 parseAnnotation(propertyData.annotations);
143 } else if (m_xml.prefix().isEmpty()) {
144 qDBusParserWarning("Unknown element '%s' while checking for annotations",
145 qPrintable(m_xml.name().toString()));
146 }
147 m_xml.skipCurrentElement();
148 } while (readNextStartElement());
149
150 m_currentInterface->introspection += " </property>\n"_L1;
151 }
152
153 if (!m_xml.isEndElement() || m_xml.name() != "property"_L1) {
154 qDBusParserError("Invalid property specification: '%s'", qPrintable(m_xml.tokenString()));
155 return false;
156 }
157
158 return true;
159}
160
161bool QDBusXmlParser::parseMethod(QDBusIntrospection::Method &methodData)
162{
163 Q_ASSERT(m_currentInterface);
164 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "method"_L1);
165
166 const QXmlStreamAttributes attributes = m_xml.attributes();
167 const QString methodName = attributes.value("name"_L1).toString();
169 qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing "
170 "introspection",
171 qPrintable(methodName), qPrintable(m_currentInterface->name));
172 return false;
173 }
174
175 methodData.name = methodName;
176 m_currentInterface->introspection += " <method name=\""_L1 + methodName + u'"';
177
181
182 if (!readNextStartElement()) {
183 m_currentInterface->introspection += "/>\n"_L1;
184 } else {
185 m_currentInterface->introspection += ">\n"_L1;
186
187 do {
188 if (m_xml.name() == "annotation"_L1) {
189 parseAnnotation(annotations);
190 } else if (m_xml.name() == "arg"_L1) {
191 const QXmlStreamAttributes attributes = m_xml.attributes();
192 const QString direction = attributes.value("direction"_L1).toString();
194 argument.location = m_currentLocation;
195 if (!attributes.hasAttribute("direction"_L1) || direction == "in"_L1) {
196 parseArg(attributes, argument);
197 inArguments << argument;
198 } else if (direction == "out"_L1) {
199 parseArg(attributes, argument);
200 outArguments << argument;
201 }
202 } else if (m_xml.prefix().isEmpty()) {
203 qDBusParserWarning("Unknown element '%s' while checking for method arguments",
204 qPrintable(m_xml.name().toString()));
205 }
206 m_xml.skipCurrentElement();
207 } while (readNextStartElement());
208
209 m_currentInterface->introspection += " </method>\n"_L1;
210 }
211
212 methodData.inputArgs = inArguments;
213 methodData.outputArgs = outArguments;
214 methodData.annotations = annotations;
215
216 return true;
217}
218
219bool QDBusXmlParser::parseSignal(QDBusIntrospection::Signal &signalData)
220{
221 Q_ASSERT(m_currentInterface);
222 Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "signal"_L1);
223
224 const QXmlStreamAttributes attributes = m_xml.attributes();
225 const QString signalName = attributes.value("name"_L1).toString();
226
227 if (!QDBusUtil::isValidMemberName(signalName)) {
228 qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing "
229 "introspection",
230 qPrintable(signalName), qPrintable(m_currentInterface->name));
231 return false;
232 }
233
234 signalData.name = signalName;
235 m_currentInterface->introspection += " <signal name=\""_L1 + signalName + u'"';
236
239
240 if (!readNextStartElement()) {
241 m_currentInterface->introspection += "/>\n"_L1;
242 } else {
243 m_currentInterface->introspection += ">\n"_L1;
244
245 do {
246 if (m_xml.name() == "annotation"_L1) {
247 parseAnnotation(annotations);
248 } else if (m_xml.name() == "arg"_L1) {
249 const QXmlStreamAttributes attributes = m_xml.attributes();
251 argument.location = m_currentLocation;
252 if (!attributes.hasAttribute("direction"_L1) ||
253 attributes.value("direction"_L1) == "out"_L1) {
254 parseArg(attributes, argument);
256 }
257 } else {
258 qDBusParserWarning("Unknown element '%s' while checking for signal arguments",
259 qPrintable(m_xml.name().toString()));
260 }
261 m_xml.skipCurrentElement();
262 } while (readNextStartElement());
263
264 m_currentInterface->introspection += " </signal>\n"_L1;
265 }
266
267 signalData.outputArgs = arguments;
268 signalData.annotations = annotations;
269
270 return true;
271}
272
273void QDBusXmlParser::readInterface()
274{
275 Q_ASSERT(!m_currentInterface);
276
277 const QString ifaceName = m_xml.attributes().value("name"_L1).toString();
278 if (!QDBusUtil::isValidInterfaceName(ifaceName)) {
279 qDBusParserError("Invalid D-Bus interface name '%s' found while parsing introspection",
280 qPrintable(ifaceName));
281 return;
282 }
283
284 m_object->interfaces.append(ifaceName);
285
286 m_currentInterface = std::make_unique<QDBusIntrospection::Interface>();
287 m_currentInterface->location = m_currentLocation;
288 m_currentInterface->name = ifaceName;
289 m_currentInterface->introspection += " <interface name=\""_L1 + ifaceName + "\">\n"_L1;
290
291 while (readNextStartElement()) {
292 if (m_xml.name() == "method"_L1) {
294 methodData.location = m_currentLocation;
295 if (parseMethod(methodData))
296 m_currentInterface->methods.insert(methodData.name, methodData);
297 } else if (m_xml.name() == "signal"_L1) {
299 signalData.location = m_currentLocation;
300 if (parseSignal(signalData))
301 m_currentInterface->signals_.insert(signalData.name, signalData);
302 } else if (m_xml.name() == "property"_L1) {
303 QDBusIntrospection::Property propertyData;
304 propertyData.location = m_currentLocation;
305 if (parseProperty(propertyData))
306 m_currentInterface->properties.insert(propertyData.name, propertyData);
307 } else if (m_xml.name() == "annotation"_L1) {
308 parseAnnotation(m_currentInterface->annotations, true);
309 m_xml.skipCurrentElement(); // skip over annotation object
310 } else {
311 if (m_xml.prefix().isEmpty()) {
312 qDBusParserWarning("Unknown element '%s' while parsing interface",
313 qPrintable(m_xml.name().toString()));
314 }
315 m_xml.skipCurrentElement();
316 }
317 }
318
319 m_currentInterface->introspection += " </interface>"_L1;
320
321 m_interfaces.insert(
322 ifaceName,
323 QSharedDataPointer<QDBusIntrospection::Interface>(m_currentInterface.release()));
324
325 if (!m_xml.isEndElement() || m_xml.name() != "interface"_L1) {
326 qDBusParserError("Invalid Interface specification");
327 }
328}
329
330void QDBusXmlParser::readNode(int nodeLevel)
331{
332 const QString objName = m_xml.attributes().value("name"_L1).toString();
333 QString fullName = m_object->path;
334 if (!(fullName.endsWith(u'/') || (nodeLevel == 0 && objName.startsWith(u'/'))))
335 fullName.append(u'/');
336 fullName += objName;
337
339 qDBusParserError("Invalid D-Bus object path '%s' found while parsing introspection",
341 return;
342 }
343
344 if (nodeLevel > 0)
345 m_object->childObjects.append(objName);
346 else
347 m_object->location = m_currentLocation;
348}
349
350void QDBusXmlParser::updateCurrentLocation()
351{
352 m_currentLocation =
353 QDBusIntrospection::SourceLocation{ m_xml.lineNumber(), m_xml.columnNumber() };
354}
355
356// Similar to m_xml.readNextElement() but sets current location to point
357// to the start element.
358bool QDBusXmlParser::readNextStartElement()
359{
360 updateCurrentLocation();
361
362 while (m_xml.readNext() != QXmlStreamReader::Invalid) {
363 if (m_xml.isEndElement())
364 return false;
365 else if (m_xml.isStartElement())
366 return true;
367 updateCurrentLocation();
368 }
369 return false;
370}
371
372QDBusXmlParser::QDBusXmlParser(const QString &service, const QString &path, const QString &xmlData,
374 : m_service(service),
375 m_path(path),
376 m_object(new QDBusIntrospection::Object),
377 m_xml(xmlData),
378 m_reporter(reporter)
379{
380 m_object->service = m_service;
381 m_object->path = m_path;
382
383 int nodeLevel = -1;
384
385 while (!m_xml.atEnd()) {
386 updateCurrentLocation();
387 m_xml.readNext();
388
389 switch (m_xml.tokenType()) {
390 case QXmlStreamReader::StartElement:
391 if (m_xml.name() == "node"_L1) {
392 readNode(++nodeLevel);
393 } else if (m_xml.name() == "interface"_L1) {
394 readInterface();
395 } else {
396 if (m_xml.prefix().isEmpty()) {
397 qDBusParserWarning("Skipping unknown element '%s'",
398 qPrintable(m_xml.name().toString()));
399 }
400 m_xml.skipCurrentElement();
401 }
402 break;
403 case QXmlStreamReader::EndElement:
404 if (m_xml.name() == "node"_L1) {
405 --nodeLevel;
406 } else {
407 qDBusParserError("Invalid node declaration '%s'",
408 qPrintable(m_xml.name().toString()));
409 }
410 break;
411 case QXmlStreamReader::StartDocument:
412 case QXmlStreamReader::EndDocument:
413 case QXmlStreamReader::DTD:
414 // not interested
415 break;
416 case QXmlStreamReader::Comment:
417 // ignore comments and processing instructions
418 break;
419 case QXmlStreamReader::Characters:
420 // ignore whitespace
421 if (m_xml.isWhitespace())
422 break;
424 default:
425 qDBusParserError("Unknown token: '%s'", qPrintable(m_xml.tokenString()));
426 break;
427 }
428 }
429
430 if (m_xml.hasError())
431 qDBusParserError("XML error: %s", qPrintable(m_xml.errorString()));
432}
433
435
436#endif // QT_NO_DBUS
Definition main.cpp:8
QDBusXmlParser(const QString &service, const QString &path, const QString &xmlData, QDBusIntrospection::DiagnosticsReporter *reporter=nullptr)
iterator insert(const Key &key, const T &value)
Definition qmap.h:688
\macro QT_RESTRICTED_CAST_FROM_ASCII
Definition qstring.h:129
QString toHtmlEscaped() const
QList< QVariant > arguments
direction
bool isValidMemberName(QStringView memberName)
Returns true if memberName is a valid member name.
bool isValidInterfaceName(const QString &ifaceName)
Returns true if this is ifaceName is a valid interface name.
bool isValidObjectPath(const QString &path)
Returns true if path is valid object path.
bool isValidSingleSignature(const QString &signature)
Returns true if signature is a valid D-Bus type signature for exactly one full type.
Combined button and popup list for selecting options.
#define Q_FALLTHROUGH()
static QString methodName(const QDBusIntrospection::Method &method)
#define qDBusParserError(format,...)
#define qDBusParserWarning(format,...)
@ QtWarningMsg
Definition qlogging.h:31
#define Q_LOGGING_CATEGORY(name,...)
GLenum access
GLsizei const GLchar *const * path
#define Q_ASSERT(cond)
Definition qrandom.cpp:47
#define qPrintable(string)
Definition qstring.h:1531
QDBusArgument argument
QJSValue fullName