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
209static bool isBasicType(int c)
210{
211 return c != DBUS_TYPE_INVALID && strchr(s: basicTypes, c: c) != nullptr;
212}
213
214static bool isFixedType(int c)
215{
216 return c != DBUS_TYPE_INVALID && strchr(s: fixedTypes, c: c) != nullptr;
217}
218
219// Returns a pointer to one-past-end of this type if it's valid;
220// returns NULL if it isn't valid.
221static const char *validateSingleType(const char *signature)
222{
223 char c = *signature;
224 if (c == DBUS_TYPE_INVALID)
225 return nullptr;
226
227 // is it one of the one-letter types?
228 if (strchr(s: oneLetterTypes, c: c) != nullptr)
229 return signature + 1;
230
231 // is it an array?
232 if (c == DBUS_TYPE_ARRAY) {
233 // then it's valid if the next type is valid
234 // or if it's a dict-entry
235 c = *++signature;
236 if (c == DBUS_DICT_ENTRY_BEGIN_CHAR) {
237 // beginning of a dictionary entry
238 // a dictionary entry has a key which is of basic types
239 // and a free value
240 c = *++signature;
241 if (!isBasicType(c))
242 return nullptr;
243 signature = validateSingleType(signature: signature + 1);
244 return signature && *signature == DBUS_DICT_ENTRY_END_CHAR ? signature + 1 : nullptr;
245 }
246
247 return validateSingleType(signature);
248 }
249
250 if (c == DBUS_STRUCT_BEGIN_CHAR) {
251 // beginning of a struct
252 ++signature;
253 while (true) {
254 signature = validateSingleType(signature);
255 if (!signature)
256 return nullptr;
257 if (*signature == DBUS_STRUCT_END_CHAR)
258 return signature + 1;
259 }
260 }
261
262 // invalid/unknown type
263 return nullptr;
264}
265
266/*!
267 \namespace QDBusUtil
268 \inmodule QtDBus
269 \internal
270
271 \brief The QDBusUtil namespace contains a few functions that are of general use when
272 dealing with D-Bus strings.
273*/
274namespace QDBusUtil
275{
276 /*!
277 \internal
278 \since 4.5
279 Dumps the contents of a Qt D-Bus argument from \a arg into a string.
280 */
281 QString argumentToString(const QVariant &arg)
282 {
283 QString out;
284
285#ifndef QT_BOOTSTRAPPED
286 variantToString(arg, out);
287#else
288 Q_UNUSED(arg);
289#endif
290
291 return out;
292 }
293
294 /*!
295 \internal
296 \fn bool isValidPartOfObjectPath(QStringView part)
297 See isValidObjectPath
298 */
299 bool isValidPartOfObjectPath(QStringView part)
300 {
301 if (part.isEmpty())
302 return false; // can't be valid if it's empty
303
304 const QChar *c = part.data();
305 for (int i = 0; i < part.size(); ++i)
306 if (!isValidCharacterNoDash(c: c[i]))
307 return false;
308
309 return true;
310 }
311
312 /*!
313 \internal
314 \fn bool isValidPartOfObjectPath(const QString &part)
315
316 \overload
317 */
318
319 /*!
320 \fn bool isValidInterfaceName(const QString &ifaceName)
321 Returns \c true if this is \a ifaceName is a valid interface name.
322
323 Valid interface names must:
324 \list
325 \li not be empty
326 \li not exceed 255 characters in length
327 \li be composed of dot-separated string components that contain only ASCII letters, digits
328 and the underscore ("_") character
329 \li contain at least two such components
330 \endlist
331 */
332 bool isValidInterfaceName(const QString& ifaceName)
333 {
334 if (ifaceName.isEmpty() || ifaceName.size() > DBUS_MAXIMUM_NAME_LENGTH)
335 return false;
336
337 const auto parts = QStringView{ifaceName}.split(sep: u'.');
338 if (parts.size() < 2)
339 return false; // at least two parts
340
341 for (auto part : parts)
342 if (!isValidMemberName(memberName: part))
343 return false;
344
345 return true;
346 }
347
348 /*!
349 \fn bool isValidUniqueConnectionName(QStringView connName)
350 Returns \c true if \a connName is a valid unique connection name.
351
352 Unique connection names start with a colon (":") and are followed by a list of dot-separated
353 components composed of ASCII letters, digits, the hyphen or the underscore ("_") character.
354 */
355 bool isValidUniqueConnectionName(QStringView connName)
356 {
357 if (connName.isEmpty() || connName.size() > DBUS_MAXIMUM_NAME_LENGTH ||
358 !connName.startsWith(c: u':'))
359 return false;
360
361 const auto parts = connName.mid(pos: 1).split(sep: u'.');
362 if (parts.size() < 1)
363 return false;
364
365 for (QStringView part : parts) {
366 if (part.isEmpty())
367 return false;
368
369 const QChar* c = part.data();
370 for (int j = 0; j < part.size(); ++j)
371 if (!isValidCharacter(c: c[j]))
372 return false;
373 }
374
375 return true;
376 }
377
378 /*!
379 \fn bool isValidUniqueConnectionName(const QString &connName)
380
381 \overload
382 */
383
384 /*!
385 \fn bool isValidBusName(const QString &busName)
386 Returns \c true if \a busName is a valid bus name.
387
388 A valid bus name is either a valid unique connection name or follows the rules:
389 \list
390 \li is not empty
391 \li does not exceed 255 characters in length
392 \li be composed of dot-separated string components that contain only ASCII letters, digits,
393 hyphens or underscores ("_"), but don't start with a digit
394 \li contains at least two such elements
395 \endlist
396
397 \sa isValidUniqueConnectionName()
398 */
399 bool isValidBusName(const QString &busName)
400 {
401 if (busName.isEmpty() || busName.size() > DBUS_MAXIMUM_NAME_LENGTH)
402 return false;
403
404 if (busName.startsWith(c: u':'))
405 return isValidUniqueConnectionName(connName: busName);
406
407 const auto parts = QStringView{busName}.split(sep: u'.');
408 if (parts.size() < 1)
409 return false;
410
411 for (QStringView part : parts) {
412 if (part.isEmpty())
413 return false;
414
415 const QChar *c = part.data();
416 if (isValidNumber(c: c[0]))
417 return false;
418 for (int j = 0; j < part.size(); ++j)
419 if (!isValidCharacter(c: c[j]))
420 return false;
421 }
422
423 return true;
424 }
425
426 /*!
427 \fn bool isValidMemberName(QStringView memberName)
428 Returns \c true if \a memberName is a valid member name. A valid member name does not exceed
429 255 characters in length, is not empty, is composed only of ASCII letters, digits and
430 underscores, but does not start with a digit.
431 */
432 bool isValidMemberName(QStringView memberName)
433 {
434 if (memberName.isEmpty() || memberName.size() > DBUS_MAXIMUM_NAME_LENGTH)
435 return false;
436
437 const QChar* c = memberName.data();
438 if (isValidNumber(c: c[0]))
439 return false;
440 for (int j = 0; j < memberName.size(); ++j)
441 if (!isValidCharacterNoDash(c: c[j]))
442 return false;
443 return true;
444 }
445
446 /*!
447 \fn bool isValidMemberName(const QString &memberName)
448
449 \overload
450 */
451
452 /*!
453 \fn bool isValidErrorName(const QString &errorName)
454 Returns \c true if \a errorName is a valid error name. Valid error names are valid interface
455 names and vice-versa, so this function is actually an alias for isValidInterfaceName.
456 */
457 bool isValidErrorName(const QString &errorName)
458 {
459 return isValidInterfaceName(ifaceName: errorName);
460 }
461
462 /*!
463 \fn bool isValidObjectPath(const QString &path)
464 Returns \c true if \a path is valid object path.
465
466 Valid object paths follow the rules:
467 \list
468 \li start with the slash character ("/")
469 \li do not end in a slash, unless the path is just the initial slash
470 \li do not contain any two slashes in sequence
471 \li contain slash-separated parts, each of which is composed of ASCII letters, digits and
472 underscores ("_")
473 \endlist
474 */
475 bool isValidObjectPath(const QString &path)
476 {
477 if (path == "/"_L1)
478 return true;
479
480 if (!path.startsWith(c: u'/') || path.indexOf(s: "//"_L1) != -1 ||
481 path.endsWith(c: u'/'))
482 return false;
483
484 // it starts with /, so we skip the empty first part
485 const auto parts = QStringView{path}.mid(pos: 1).split(sep: u'/');
486 for (QStringView part : parts)
487 if (!isValidPartOfObjectPath(part))
488 return false;
489
490 return true;
491 }
492
493 /*!
494 \fn bool isValidBasicType(int type)
495 Returns \c true if \a c is a valid, basic D-Bus type.
496 */
497 bool isValidBasicType(int c)
498 {
499 return isBasicType(c);
500 }
501
502 /*!
503 \fn bool isValidFixedType(int type)
504 Returns \c true if \a c is a valid, fixed D-Bus type.
505 */
506 bool isValidFixedType(int c)
507 {
508 return isFixedType(c);
509 }
510
511
512 /*!
513 \fn bool isValidSignature(const QString &signature)
514 Returns \c true if \a signature is a valid D-Bus type signature for one or more types.
515 This function returns \c true if it can all of \a signature into valid, individual types and no
516 characters remain in \a signature.
517
518 \sa isValidSingleSignature()
519 */
520 bool isValidSignature(const QString &signature)
521 {
522 QByteArray ba = signature.toLatin1();
523 const char *data = ba.constData();
524 while (true) {
525 data = validateSingleType(signature: data);
526 if (!data)
527 return false;
528 if (*data == '\0')
529 return true;
530 }
531 }
532
533 /*!
534 \fn bool isValidSingleSignature(const QString &signature)
535 Returns \c true if \a signature is a valid D-Bus type signature for exactly one full type. This
536 function tries to convert the type signature into a D-Bus type and, if it succeeds and no
537 characters remain in the signature, it returns \c true.
538 */
539 bool isValidSingleSignature(const QString &signature)
540 {
541 QByteArray ba = signature.toLatin1();
542 const char *data = validateSingleType(signature: ba.constData());
543 return data && *data == '\0';
544 }
545
546} // namespace QDBusUtil
547
548QT_END_NAMESPACE
549
550#endif // QT_NO_DBUS
551

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