Coverage Report

Created: 2026-01-25 07:18

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/qtbase/src/dbus/qdbusutil.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 "qdbusutil_p.h"
6
7
#include "qdbus_symbols_p.h"
8
9
#include <QtCore/qlist.h>
10
#include <QtCore/qstringlist.h>
11
#include <private/qtools_p.h>
12
13
#include "qdbusargument.h"
14
#include "qdbusunixfiledescriptor.h"
15
16
#ifndef QT_NO_DBUS
17
18
QT_BEGIN_NAMESPACE
19
20
using namespace Qt::StringLiterals;
21
using namespace QtMiscUtils;
22
23
static inline bool isValidCharacterNoDash(QChar c)
24
0
{
25
0
    ushort u = c.unicode();
26
0
    return isAsciiLetterOrNumber(u) || (u == '_');
27
0
}
28
29
static inline bool isValidCharacter(QChar c)
30
0
{
31
0
    ushort u = c.unicode();
32
0
    return isAsciiLetterOrNumber(u)
33
0
            || (u == '_') || (u == '-');
34
0
}
35
36
static inline bool isValidNumber(QChar c)
37
0
{
38
0
    return (isAsciiDigit(c.toLatin1()));
39
0
}
40
41
#ifndef QT_BOOTSTRAPPED
42
static bool argToString(const QDBusArgument &arg, QString &out);
43
44
static bool variantToString(const QVariant &arg, QString &out)
45
0
{
46
0
    int argType = arg.metaType().id();
47
48
0
    if (argType == QMetaType::QStringList) {
49
0
        out += u'{';
50
0
        const QStringList list = arg.toStringList();
51
0
        for (const QString &item : list)
52
0
            out += u'\"' + item + "\", "_L1;
53
0
        if (!list.isEmpty())
54
0
            out.chop(2);
55
0
        out += u'}';
56
0
    } else if (argType == QMetaType::QByteArray) {
57
0
        out += u'{';
58
0
        QByteArray list = arg.toByteArray();
59
0
        for (int i = 0; i < list.size(); ++i) {
60
0
            out += QString::number(list.at(i));
61
0
            out += ", "_L1;
62
0
        }
63
0
        if (!list.isEmpty())
64
0
            out.chop(2);
65
0
        out += u'}';
66
0
    } else if (argType == QMetaType::QVariantList) {
67
0
        out += u'{';
68
0
        const QList<QVariant> list = arg.toList();
69
0
        for (const QVariant &item : list) {
70
0
            if (!variantToString(item, out))
71
0
                return false;
72
0
            out += ", "_L1;
73
0
        }
74
0
        if (!list.isEmpty())
75
0
            out.chop(2);
76
0
        out += u'}';
77
0
    } else if (argType == QMetaType::Char || argType == QMetaType::Short || argType == QMetaType::Int
78
0
               || argType == QMetaType::Long || argType == QMetaType::LongLong) {
79
0
        out += QString::number(arg.toLongLong());
80
0
    } else if (argType == QMetaType::UChar || argType == QMetaType::UShort || argType == QMetaType::UInt
81
0
               || argType == QMetaType::ULong || argType == QMetaType::ULongLong) {
82
0
        out += QString::number(arg.toULongLong());
83
0
    } else if (argType == QMetaType::Double) {
84
0
        out += QString::number(arg.toDouble());
85
0
    } else if (argType == QMetaType::Bool) {
86
0
        out += arg.toBool() ? "true"_L1 : "false"_L1;
87
0
    } else if (argType == qMetaTypeId<QDBusArgument>()) {
88
0
        argToString(qvariant_cast<QDBusArgument>(arg), out);
89
0
    } else if (argType == qMetaTypeId<QDBusObjectPath>()) {
90
0
        const QString path = qvariant_cast<QDBusObjectPath>(arg).path();
91
0
        out += "[ObjectPath: "_L1;
92
0
        out += path;
93
0
        out += u']';
94
0
    } else if (argType == qMetaTypeId<QDBusSignature>()) {
95
0
        out += "[Signature: "_L1 + qvariant_cast<QDBusSignature>(arg).signature();
96
0
        out += u']';
97
0
    } else if (argType == qMetaTypeId<QDBusUnixFileDescriptor>()) {
98
0
        out += "[Unix FD: "_L1;
99
0
        out += qvariant_cast<QDBusUnixFileDescriptor>(arg).isValid() ? "valid"_L1 : "not valid"_L1;
100
0
        out += u']';
101
0
    } else if (argType == qMetaTypeId<QDBusVariant>()) {
102
0
        const QVariant v = qvariant_cast<QDBusVariant>(arg).variant();
103
0
        out += "[Variant"_L1;
104
0
        QMetaType vUserType = v.metaType();
105
0
        if (vUserType != QMetaType::fromType<QDBusVariant>()
106
0
                && vUserType != QMetaType::fromType<QDBusSignature>()
107
0
                && vUserType != QMetaType::fromType<QDBusObjectPath>()
108
0
                && vUserType != QMetaType::fromType<QDBusArgument>())
109
0
            out += u'(' + QLatin1StringView(v.typeName()) + u')';
110
0
        out += ": "_L1;
111
0
        if (!variantToString(v, out))
112
0
            return false;
113
0
        out += u']';
114
0
    } else if (arg.canConvert<QString>()) {
115
0
        out += u'\"' + arg.toString() + u'\"';
116
0
    } else {
117
0
        out += u'[';
118
0
        out += QLatin1StringView(arg.typeName());
119
0
        out += u']';
120
0
    }
121
122
0
    return true;
123
0
}
124
125
bool argToString(const QDBusArgument &busArg, QString &out)
126
0
{
127
0
    QString busSig = busArg.currentSignature();
128
0
    bool doIterate = false;
129
0
    QDBusArgument::ElementType elementType = busArg.currentType();
130
131
0
    if (elementType != QDBusArgument::BasicType && elementType != QDBusArgument::VariantType
132
0
            && elementType != QDBusArgument::MapEntryType)
133
0
        out += "[Argument: "_L1 + busSig + u' ';
134
135
0
    switch (elementType) {
136
0
        case QDBusArgument::BasicType:
137
0
        case QDBusArgument::VariantType:
138
0
            if (!variantToString(busArg.asVariant(), out))
139
0
                return false;
140
0
            break;
141
0
        case QDBusArgument::StructureType:
142
0
            busArg.beginStructure();
143
0
            doIterate = true;
144
0
            break;
145
0
        case QDBusArgument::ArrayType:
146
0
            busArg.beginArray();
147
0
            out += u'{';
148
0
            doIterate = true;
149
0
            break;
150
0
        case QDBusArgument::MapType:
151
0
            busArg.beginMap();
152
0
            out += u'{';
153
0
            doIterate = true;
154
0
            break;
155
0
        case QDBusArgument::MapEntryType:
156
0
            busArg.beginMapEntry();
157
0
            if (!variantToString(busArg.asVariant(), out))
158
0
                return false;
159
0
            out += " = "_L1;
160
0
            if (!argToString(busArg, out))
161
0
                return false;
162
0
            busArg.endMapEntry();
163
0
            break;
164
0
        case QDBusArgument::UnknownType:
165
0
        default:
166
0
            out += "<ERROR - Unknown Type>"_L1;
167
0
            return false;
168
0
    }
169
0
    if (doIterate && !busArg.atEnd()) {
170
0
        while (!busArg.atEnd()) {
171
0
            if (!argToString(busArg, out))
172
0
                return false;
173
0
            out += ", "_L1;
174
0
        }
175
0
        out.chop(2);
176
0
    }
177
0
    switch (elementType) {
178
0
        case QDBusArgument::BasicType:
179
0
        case QDBusArgument::VariantType:
180
0
        case QDBusArgument::UnknownType:
181
0
        case QDBusArgument::MapEntryType:
182
            // nothing to do
183
0
            break;
184
0
        case QDBusArgument::StructureType:
185
0
            busArg.endStructure();
186
0
            break;
187
0
        case QDBusArgument::ArrayType:
188
0
            out += u'}';
189
0
            busArg.endArray();
190
0
            break;
191
0
        case QDBusArgument::MapType:
192
0
            out += u'}';
193
0
            busArg.endMap();
194
0
            break;
195
0
    }
196
197
0
    if (elementType != QDBusArgument::BasicType && elementType != QDBusArgument::VariantType
198
0
            && elementType != QDBusArgument::MapEntryType)
199
0
        out += u']';
200
201
0
    return true;
202
0
}
203
#endif
204
205
//------- D-Bus Types --------
206
static const char oneLetterTypes[] = "vsogybnqiuxtdh";
207
static const char basicTypes[] =      "sogybnqiuxtdh";
208
static const char fixedTypes[] =         "ybnqiuxtdh";
209
210
/*
211
    D-Bus signature grammar (in ABNF), as of 0.42 (2023-08-21):
212
    https://dbus.freedesktop.org/doc/dbus-specification.html#type-system
213
214
    <signature>            = *<single-complete-type>
215
    <single-complete-type> = <basic-type> / <variant> / <struct> / <array>
216
    <fixed-type>           = "y" / "b" / "n" / "q" / "i" / "u" / "x" / "t" / "d" / "h"
217
    <variable-length-type> = "s" / "o" / "g"
218
    <basic-type>           = <variable-length-type> / <fixed-type>
219
    <variant>              = "v"
220
    <struct>               = "(" 1*<single-complete-type> ")"
221
    <array>                = "a" ( <single-complete-type> / <dict-entry> )
222
    <dict-entry>           = "{" <basic-type> <single-complete-type> "}"
223
*/
224
225
static bool isBasicType(int c)
226
0
{
227
0
    return c != DBUS_TYPE_INVALID && strchr(basicTypes, c) != nullptr;
228
0
}
229
230
static bool isFixedType(int c)
231
0
{
232
0
    return c != DBUS_TYPE_INVALID && strchr(fixedTypes, c) != nullptr;
233
0
}
234
235
// Returns a pointer to one-past-end of this type if it's valid;
236
// returns NULL if it isn't valid.
237
static const char *validateSingleType(const char *signature)
238
0
{
239
0
    char c = *signature;
240
0
    if (c == DBUS_TYPE_INVALID)
241
0
        return nullptr;
242
243
    // is it one of the one-letter types?
244
0
    if (strchr(oneLetterTypes, c) != nullptr)
245
0
        return signature + 1;
246
247
    // is it an array?
248
0
    if (c == DBUS_TYPE_ARRAY) {
249
        // then it's valid if the next type is valid
250
        // or if it's a dict-entry
251
0
        c = *++signature;
252
0
        if (c == DBUS_DICT_ENTRY_BEGIN_CHAR) {
253
            // beginning of a dictionary entry
254
            // a dictionary entry has a key which is of basic types
255
            // and a free value
256
0
            c = *++signature;
257
0
            if (!isBasicType(c))
258
0
                return nullptr;
259
0
            signature = validateSingleType(signature + 1);
260
0
            return signature && *signature == DBUS_DICT_ENTRY_END_CHAR ? signature + 1 : nullptr;
261
0
        }
262
263
0
        return validateSingleType(signature);
264
0
    }
265
266
0
    if (c == DBUS_STRUCT_BEGIN_CHAR) {
267
        // beginning of a struct
268
0
        ++signature;
269
0
        while (true) {
270
0
            signature = validateSingleType(signature);
271
0
            if (!signature)
272
0
                return nullptr;
273
0
            if (*signature == DBUS_STRUCT_END_CHAR)
274
0
                return signature + 1;
275
0
        }
276
0
    }
277
278
    // invalid/unknown type
279
0
    return nullptr;
280
0
}
281
282
/*!
283
    \namespace QDBusUtil
284
    \inmodule QtDBus
285
    \internal
286
287
    \brief The QDBusUtil namespace contains a few functions that are of general use when
288
    dealing with D-Bus strings.
289
*/
290
namespace QDBusUtil
291
{
292
    /*!
293
        \internal
294
        \since 4.5
295
        Dumps the contents of a Qt D-Bus argument from \a arg into a string.
296
    */
297
    QString argumentToString(const QVariant &arg)
298
0
    {
299
0
        QString out;
300
301
0
#ifndef QT_BOOTSTRAPPED
302
0
        variantToString(arg, out);
303
#else
304
        Q_UNUSED(arg);
305
#endif
306
307
0
        return out;
308
0
    }
309
310
    /*!
311
        \internal
312
        \fn bool isValidPartOfObjectPath(QStringView part)
313
        See isValidObjectPath
314
    */
315
    bool isValidPartOfObjectPath(QStringView part)
316
0
    {
317
0
        if (part.isEmpty())
318
0
            return false;       // can't be valid if it's empty
319
320
0
        const QChar *c = part.data();
321
0
        for (int i = 0; i < part.size(); ++i)
322
0
            if (!isValidCharacterNoDash(c[i]))
323
0
                return false;
324
325
0
        return true;
326
0
    }
327
328
    /*!
329
        \fn bool isValidInterfaceName(const QString &ifaceName)
330
        Returns \c true if this is \a ifaceName is a valid interface name.
331
332
        Valid interface names must:
333
        \list
334
          \li not be empty
335
          \li not exceed 255 characters in length
336
          \li be composed of dot-separated string components that contain only ASCII letters, digits
337
             and the underscore ("_") character
338
          \li contain at least two such components
339
        \endlist
340
    */
341
    bool isValidInterfaceName(const QString& ifaceName)
342
0
    {
343
0
        if (ifaceName.isEmpty() || ifaceName.size() > DBUS_MAXIMUM_NAME_LENGTH)
344
0
            return false;
345
346
0
        const auto parts = QStringView{ifaceName}.split(u'.');
347
0
        if (parts.size() < 2)
348
0
            return false;           // at least two parts
349
350
0
        for (auto part : parts)
351
0
            if (!isValidMemberName(part))
352
0
                return false;
353
354
0
        return true;
355
0
    }
356
357
    /*!
358
        \fn bool isValidUniqueConnectionName(QStringView connName)
359
        Returns \c true if \a connName is a valid unique connection name.
360
361
        Unique connection names start with a colon (":") and are followed by a list of dot-separated
362
        components composed of ASCII letters, digits, the hyphen or the underscore ("_") character.
363
    */
364
    bool isValidUniqueConnectionName(QStringView connName)
365
0
    {
366
0
        if (connName.isEmpty() || connName.size() > DBUS_MAXIMUM_NAME_LENGTH ||
367
0
            !connName.startsWith(u':'))
368
0
            return false;
369
370
0
        const auto parts = connName.mid(1).split(u'.');
371
0
        if (parts.size() < 1)
372
0
            return false;
373
374
0
        for (QStringView part : parts) {
375
0
            if (part.isEmpty())
376
0
                 return false;
377
378
0
            const QChar* c = part.data();
379
0
            for (int j = 0; j < part.size(); ++j)
380
0
                if (!isValidCharacter(c[j]))
381
0
                    return false;
382
0
        }
383
384
0
        return true;
385
0
    }
386
387
    /*!
388
        \fn bool isValidBusName(const QString &busName)
389
        Returns \c true if \a busName is a valid bus name.
390
391
        A valid bus name is either a valid unique connection name or follows the rules:
392
        \list
393
          \li is not empty
394
          \li does not exceed 255 characters in length
395
          \li be composed of dot-separated string components that contain only ASCII letters, digits,
396
             hyphens or underscores ("_"), but don't start with a digit
397
          \li contains at least two such elements
398
        \endlist
399
400
        \sa isValidUniqueConnectionName()
401
    */
402
    bool isValidBusName(const QString &busName)
403
0
    {
404
0
        if (busName.isEmpty() || busName.size() > DBUS_MAXIMUM_NAME_LENGTH)
405
0
            return false;
406
407
0
        if (busName.startsWith(u':'))
408
0
            return isValidUniqueConnectionName(busName);
409
410
0
        const auto parts = QStringView{busName}.split(u'.');
411
0
        if (parts.size() < 2)
412
0
            return false;
413
0
        for (QStringView part : parts) {
414
0
            if (part.isEmpty())
415
0
                return false;
416
417
0
            const QChar *c = part.data();
418
0
            if (isValidNumber(c[0]))
419
0
                return false;
420
0
            for (int j = 0; j < part.size(); ++j)
421
0
                if (!isValidCharacter(c[j]))
422
0
                    return false;
423
0
        }
424
425
0
        return true;
426
0
    }
427
428
    /*!
429
        \fn bool isValidMemberName(QStringView memberName)
430
        Returns \c true if \a memberName is a valid member name. A valid member name does not exceed
431
        255 characters in length, is not empty, is composed only of ASCII letters, digits and
432
        underscores, but does not start with a digit.
433
    */
434
    bool isValidMemberName(QStringView memberName)
435
0
    {
436
0
        if (memberName.isEmpty() || memberName.size() > DBUS_MAXIMUM_NAME_LENGTH)
437
0
            return false;
438
439
0
        const QChar* c = memberName.data();
440
0
        if (isValidNumber(c[0]))
441
0
            return false;
442
0
        for (int j = 0; j < memberName.size(); ++j)
443
0
            if (!isValidCharacterNoDash(c[j]))
444
0
                return false;
445
0
        return true;
446
0
    }
447
448
    /*!
449
        \fn bool isValidErrorName(const QString &errorName)
450
        Returns \c true if \a errorName is a valid error name. Valid error names are valid interface
451
        names and vice-versa, so this function is actually an alias for isValidInterfaceName.
452
    */
453
    bool isValidErrorName(const QString &errorName)
454
0
    {
455
0
        return isValidInterfaceName(errorName);
456
0
    }
457
458
    /*!
459
        \fn bool isValidObjectPath(const QString &path)
460
        Returns \c true if \a path is valid object path.
461
462
        Valid object paths follow the rules:
463
        \list
464
          \li start with the slash character ("/")
465
          \li do not end in a slash, unless the path is just the initial slash
466
          \li contain slash-separated parts, each of which is not empty, and composed
467
              only of ASCII letters, digits and underscores ("_").
468
        \endlist
469
    */
470
    bool isValidObjectPath(const QString &path)
471
0
    {
472
0
        if (path == "/"_L1)
473
0
            return true;
474
475
0
        if (!path.startsWith(u'/') || path.indexOf("//"_L1) != -1 ||
476
0
            path.endsWith(u'/'))
477
0
            return false;
478
479
        // it starts with /, so we skip the empty first part
480
0
        const auto parts = QStringView{path}.mid(1).split(u'/');
481
0
        for (QStringView part : parts)
482
0
            if (!isValidPartOfObjectPath(part))
483
0
                return false;
484
485
0
        return true;
486
0
    }
487
488
    /*!
489
        \fn bool isValidBasicType(int type)
490
        Returns \c true if \a c is a valid, basic D-Bus type.
491
     */
492
    bool isValidBasicType(int c)
493
0
    {
494
0
        return isBasicType(c);
495
0
    }
496
497
    /*!
498
        \fn bool isValidFixedType(int type)
499
        Returns \c true if \a c is a valid, fixed D-Bus type.
500
     */
501
    bool isValidFixedType(int c)
502
0
    {
503
0
        return isFixedType(c);
504
0
    }
505
506
507
    /*!
508
        \fn bool isValidSignature(const QString &signature)
509
        Returns \c true if \a signature is a valid D-Bus type signature for one or more types.
510
        This function returns \c true if it can all of \a signature into valid, individual types and no
511
        characters remain in \a signature.
512
513
        \sa isValidSingleSignature()
514
    */
515
    bool isValidSignature(const QString &signature)
516
0
    {
517
0
        QByteArray ba = signature.toLatin1();
518
0
        const char *data = ba.constBegin();
519
0
        const char *end = ba.constEnd();
520
0
        while (data != end) {
521
0
            data = validateSingleType(data);
522
0
            if (!data)
523
0
                return false;
524
0
        }
525
0
        return true;
526
0
    }
527
528
    /*!
529
        \fn bool isValidSingleSignature(const QString &signature)
530
        Returns \c true if \a signature is a valid D-Bus type signature for exactly one full type. This
531
        function tries to convert the type signature into a D-Bus type and, if it succeeds and no
532
        characters remain in the signature, it returns \c true.
533
    */
534
    bool isValidSingleSignature(const QString &signature)
535
0
    {
536
0
        QByteArray ba = signature.toLatin1();
537
0
        const char *data = validateSingleType(ba.constData());
538
0
        return data && *data == '\0';
539
0
    }
540
541
} // namespace QDBusUtil
542
543
QT_END_NAMESPACE
544
545
#endif // QT_NO_DBUS