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

Provided by KDAB

Privacy Policy
Learn Advanced QML with KDAB
Find out more

source code of qtbase/src/dbus/qdbusutil.cpp