Coverage Report

Created: 2026-04-29 07:00

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