1// Copyright (C) 2016 The Qt Company Ltd.
2// Copyright (C) 2016 Intel Corporation.
3// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
4
5#include "qdbusconnection_p.h"
6
7#include "qdbus_symbols_p.h"
8#include <QtCore/qcoreapplication.h>
9#include <QtCore/qmetaobject.h>
10#include <QtCore/qstringlist.h>
11#include <QtCore/qthread.h>
12
13#include "qdbusabstractadaptor.h"
14#include "qdbusabstractadaptor_p.h"
15#include "qdbusconnection.h"
16#include "qdbusextratypes.h"
17#include "qdbusmessage.h"
18#include "qdbusmetatype.h"
19#include "qdbusmetatype_p.h"
20#include "qdbusmessage_p.h"
21#include "qdbusutil_p.h"
22#include "qdbusvirtualobject.h"
23
24#include <algorithm>
25
26#ifndef QT_NO_DBUS
27
28QT_BEGIN_NAMESPACE
29
30using namespace Qt::StringLiterals;
31
32// defined in qdbusxmlgenerator.cpp
33extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
34 const QMetaObject *base, int flags);
35
36static const char introspectableInterfaceXml[] =
37 " <interface name=\"org.freedesktop.DBus.Introspectable\">\n"
38 " <method name=\"Introspect\">\n"
39 " <arg name=\"xml_data\" type=\"s\" direction=\"out\"/>\n"
40 " </method>\n"
41 " </interface>\n";
42
43static const char propertiesInterfaceXml[] =
44 " <interface name=\"org.freedesktop.DBus.Properties\">\n"
45 " <method name=\"Get\">\n"
46 " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
47 " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
48 " <arg name=\"value\" type=\"v\" direction=\"out\"/>\n"
49 " </method>\n"
50 " <method name=\"Set\">\n"
51 " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
52 " <arg name=\"property_name\" type=\"s\" direction=\"in\"/>\n"
53 " <arg name=\"value\" type=\"v\" direction=\"in\"/>\n"
54 " </method>\n"
55 " <method name=\"GetAll\">\n"
56 " <arg name=\"interface_name\" type=\"s\" direction=\"in\"/>\n"
57 " <arg name=\"values\" type=\"a{sv}\" direction=\"out\"/>\n"
58 " <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"QVariantMap\"/>\n"
59 " </method>\n"
60 " <signal name=\"PropertiesChanged\">\n"
61 " <arg name=\"interface_name\" type=\"s\" direction=\"out\"/>\n"
62 " <arg name=\"changed_properties\" type=\"a{sv}\" direction=\"out\"/>\n"
63 " <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out1\" value=\"QVariantMap\"/>\n"
64 " <arg name=\"invalidated_properties\" type=\"as\" direction=\"out\"/>\n"
65 " </signal>\n"
66 " </interface>\n";
67
68static const char peerInterfaceXml[] =
69 " <interface name=\"org.freedesktop.DBus.Peer\">\n"
70 " <method name=\"Ping\"/>\n"
71 " <method name=\"GetMachineId\">\n"
72 " <arg name=\"machine_uuid\" type=\"s\" direction=\"out\"/>\n"
73 " </method>\n"
74 " </interface>\n";
75
76static QString generateSubObjectXml(QObject *object)
77{
78 QString retval;
79 for (const QObject *child : object->children()) {
80 QString name = child->objectName();
81 if (!name.isEmpty() && QDBusUtil::isValidPartOfObjectPath(path: name))
82 retval += " <node name=\""_L1 + name + "\"/>\n"_L1;
83 }
84 return retval;
85}
86
87// declared as extern in qdbusconnection_p.h
88
89QString qDBusIntrospectObject(const QDBusConnectionPrivate::ObjectTreeNode &node, const QString &path)
90{
91 // object may be null
92
93 QString xml_data(DBUS_INTROSPECT_1_0_XML_DOCTYPE_DECL_NODE ""_L1);
94 xml_data += "<node>\n"_L1;
95
96 if (node.obj) {
97 Q_ASSERT_X(QThread::currentThread() == node.obj->thread(),
98 "QDBusConnection: internal threading error",
99 "function called for an object that is in another thread!!");
100
101 if (node.flags & (QDBusConnection::ExportScriptableContents
102 | QDBusConnection::ExportNonScriptableContents)) {
103 // create XML for the object itself
104 const QMetaObject *mo = node.obj->metaObject();
105 for ( ; mo != &QObject::staticMetaObject; mo = mo->superClass())
106 xml_data += qDBusGenerateMetaObjectXml(interface: node.interfaceName, mo, base: mo->superClass(),
107 flags: node.flags);
108 }
109
110 // does this object have adaptors?
111 QDBusAdaptorConnector *connector;
112 if (node.flags & QDBusConnection::ExportAdaptors &&
113 (connector = qDBusFindAdaptorConnector(object: node.obj))) {
114
115 // trasverse every adaptor in this object
116 for (const QDBusAdaptorConnector::AdaptorData &adaptorData :
117 std::as_const(t&: connector->adaptors)) {
118 // add the interface:
119 QString ifaceXml =
120 QDBusAbstractAdaptorPrivate::retrieveIntrospectionXml(adaptor: adaptorData.adaptor);
121 if (ifaceXml.isEmpty()) {
122 // add the interface's contents:
123 ifaceXml += qDBusGenerateMetaObjectXml(
124 interface: QString::fromLatin1(ba: adaptorData.interface),
125 mo: adaptorData.adaptor->metaObject(),
126 base: &QDBusAbstractAdaptor::staticMetaObject,
127 flags: QDBusConnection::ExportScriptableContents
128 | QDBusConnection::ExportNonScriptableContents);
129
130 QDBusAbstractAdaptorPrivate::saveIntrospectionXml(adaptor: adaptorData.adaptor,
131 xml: ifaceXml);
132 }
133
134 xml_data += ifaceXml;
135 }
136 }
137
138 // is it a virtual node that handles introspection itself?
139 if (node.flags & QDBusConnectionPrivate::VirtualObject) {
140 xml_data += node.treeNode->introspect(path);
141 }
142
143 xml_data += QLatin1StringView(propertiesInterfaceXml);
144 }
145
146 xml_data += QLatin1StringView(introspectableInterfaceXml);
147 xml_data += QLatin1StringView(peerInterfaceXml);
148
149 if (node.flags & QDBusConnection::ExportChildObjects) {
150 xml_data += generateSubObjectXml(object: node.obj);
151 } else {
152 // generate from the object tree
153 for (const QDBusConnectionPrivate::ObjectTreeNode &node : node.children) {
154 if (node.obj || !node.children.isEmpty())
155 xml_data += " <node name=\""_L1 + node.name + "\"/>\n"_L1;
156 }
157 }
158
159 xml_data += "</node>\n"_L1;
160 return xml_data;
161}
162
163// implement the D-Bus interface org.freedesktop.DBus.Properties
164
165static inline QDBusMessage interfaceNotFoundError(const QDBusMessage &msg, const QString &interface_name)
166{
167 return msg.createErrorReply(type: QDBusError::UnknownInterface,
168 msg: "Interface %1 was not found in object %2"_L1
169 .arg(args: interface_name, args: msg.path()));
170}
171
172static inline QDBusMessage
173propertyNotFoundError(const QDBusMessage &msg, const QString &interface_name, const QByteArray &property_name)
174{
175 return msg.createErrorReply(type: QDBusError::UnknownProperty,
176 msg: "Property %1%2%3 was not found in object %4"_L1
177 .arg(args: interface_name,
178 args: interface_name.isEmpty() ? ""_L1 : "."_L1,
179 args: QLatin1StringView(property_name),
180 args: msg.path()));
181}
182
183QDBusMessage qDBusPropertyGet(const QDBusConnectionPrivate::ObjectTreeNode &node,
184 const QDBusMessage &msg)
185{
186 Q_ASSERT(msg.arguments().size() == 2);
187 Q_ASSERT_X(!node.obj || QThread::currentThread() == node.obj->thread(),
188 "QDBusConnection: internal threading error",
189 "function called for an object that is in another thread!!");
190
191 QString interface_name = msg.arguments().at(i: 0).toString();
192 QByteArray property_name = msg.arguments().at(i: 1).toString().toUtf8();
193
194 const QDBusAdaptorConnector *connector;
195 QVariant value;
196 bool interfaceFound = false;
197 if (node.flags & QDBusConnection::ExportAdaptors &&
198 (connector = qDBusFindAdaptorConnector(object: node.obj))) {
199
200 // find the class that implements interface_name or try until we've found the property
201 // in case of an empty interface
202 if (interface_name.isEmpty()) {
203 for (const QDBusAdaptorConnector::AdaptorData &adaptorData : connector->adaptors) {
204 const QMetaObject *mo = adaptorData.adaptor->metaObject();
205 int pidx = mo->indexOfProperty(name: property_name);
206 if (pidx != -1) {
207 value = mo->property(index: pidx).read(obj: adaptorData.adaptor);
208 break;
209 }
210 }
211 } else {
212 QDBusAdaptorConnector::AdaptorMap::ConstIterator it;
213 it = std::lower_bound(first: connector->adaptors.constBegin(), last: connector->adaptors.constEnd(),
214 val: interface_name);
215 if (it != connector->adaptors.constEnd() && interface_name == QLatin1StringView(it->interface)) {
216 interfaceFound = true;
217 value = it->adaptor->property(name: property_name);
218 }
219 }
220 }
221
222 if (!interfaceFound && !value.isValid()
223 && node.flags & (QDBusConnection::ExportAllProperties |
224 QDBusConnection::ExportNonScriptableProperties)) {
225 // try the object itself
226 if (!interface_name.isEmpty())
227 interfaceFound = qDBusInterfaceInObject(obj: node.obj, interface_name);
228
229 if (interfaceFound) {
230 int pidx = node.obj->metaObject()->indexOfProperty(name: property_name);
231 if (pidx != -1) {
232 QMetaProperty mp = node.obj->metaObject()->property(index: pidx);
233 if ((mp.isScriptable() && (node.flags & QDBusConnection::ExportScriptableProperties)) ||
234 (!mp.isScriptable() && (node.flags & QDBusConnection::ExportNonScriptableProperties)))
235 value = mp.read(obj: node.obj);
236 }
237 }
238 }
239
240 if (!value.isValid()) {
241 // the property was not found
242 if (!interfaceFound)
243 return interfaceNotFoundError(msg, interface_name);
244 return propertyNotFoundError(msg, interface_name, property_name);
245 }
246
247 return msg.createReply(argument: QVariant::fromValue(value: QDBusVariant(value)));
248}
249
250enum PropertyWriteResult {
251 PropertyWriteSuccess = 0,
252 PropertyNotFound,
253 PropertyTypeMismatch,
254 PropertyReadOnly,
255 PropertyWriteFailed
256};
257
258static QDBusMessage propertyWriteReply(const QDBusMessage &msg, const QString &interface_name,
259 const QByteArray &property_name, int status)
260{
261 switch (status) {
262 case PropertyNotFound:
263 return propertyNotFoundError(msg, interface_name, property_name);
264 case PropertyTypeMismatch:
265 return msg.createErrorReply(type: QDBusError::InvalidArgs,
266 msg: "Invalid arguments for writing to property %1%2%3"_L1
267 .arg(args: interface_name,
268 args: interface_name.isEmpty() ? ""_L1 : "."_L1,
269 args: QLatin1StringView(property_name)));
270 case PropertyReadOnly:
271 return msg.createErrorReply(type: QDBusError::PropertyReadOnly,
272 msg: "Property %1%2%3 is read-only"_L1
273 .arg(args: interface_name,
274 args: interface_name.isEmpty() ? ""_L1 : "."_L1,
275 args: QLatin1StringView(property_name)));
276 case PropertyWriteFailed:
277 return msg.createErrorReply(type: QDBusError::InternalError,
278 msg: QString::fromLatin1(ba: "Internal error"));
279
280 case PropertyWriteSuccess:
281 return msg.createReply();
282 }
283 Q_ASSERT_X(false, "", "Should not be reached");
284 return QDBusMessage();
285}
286
287static int writeProperty(QObject *obj, const QByteArray &property_name, QVariant value,
288 int propFlags = QDBusConnection::ExportAllProperties)
289{
290 const QMetaObject *mo = obj->metaObject();
291 int pidx = mo->indexOfProperty(name: property_name);
292 if (pidx == -1) {
293 // this object has no property by that name
294 return PropertyNotFound;
295 }
296
297 QMetaProperty mp = mo->property(index: pidx);
298
299 // check if this property is writable
300 if (!mp.isWritable())
301 return PropertyReadOnly;
302
303 // check if this property is exported
304 bool isScriptable = mp.isScriptable();
305 if (!(propFlags & QDBusConnection::ExportScriptableProperties) && isScriptable)
306 return PropertyNotFound;
307 if (!(propFlags & QDBusConnection::ExportNonScriptableProperties) && !isScriptable)
308 return PropertyNotFound;
309
310 // we found our property
311 // do we have the right type?
312 QMetaType id = mp.metaType();
313 if (!id.isValid()){
314 // type not registered or invalid / void?
315 qWarning(msg: "QDBusConnection: Unable to handle unregistered datatype '%s' for property '%s::%s'",
316 mp.typeName(), mo->className(), property_name.constData());
317 return PropertyWriteFailed;
318 }
319
320 if (id.id() != QMetaType::QVariant && value.metaType() == QDBusMetaTypeId::argument()) {
321 // we have to demarshall before writing
322 QVariant other{QMetaType(id)};
323 if (!QDBusMetaType::demarshall(qvariant_cast<QDBusArgument>(v: value), id: other.metaType(), data: other.data())) {
324 qWarning(msg: "QDBusConnection: type '%s' (%d) is not registered with QtDBus. "
325 "Use qDBusRegisterMetaType to register it",
326 mp.typeName(), id.id());
327 return PropertyWriteFailed;
328 }
329
330 value = std::move(other);
331 }
332
333 if (mp.metaType() == QMetaType::fromType<QDBusVariant>())
334 value = QVariant::fromValue(value: QDBusVariant(value));
335
336 // the property type here should match
337 return mp.write(obj, value: std::move(value)) ? PropertyWriteSuccess : PropertyWriteFailed;
338}
339
340QDBusMessage qDBusPropertySet(const QDBusConnectionPrivate::ObjectTreeNode &node,
341 const QDBusMessage &msg)
342{
343 Q_ASSERT(msg.arguments().size() == 3);
344 Q_ASSERT_X(!node.obj || QThread::currentThread() == node.obj->thread(),
345 "QDBusConnection: internal threading error",
346 "function called for an object that is in another thread!!");
347
348 QString interface_name = msg.arguments().at(i: 0).toString();
349 QByteArray property_name = msg.arguments().at(i: 1).toString().toUtf8();
350 QVariant value = qvariant_cast<QDBusVariant>(v: msg.arguments().at(i: 2)).variant();
351
352 QDBusAdaptorConnector *connector;
353 if (node.flags & QDBusConnection::ExportAdaptors &&
354 (connector = qDBusFindAdaptorConnector(object: node.obj))) {
355
356 // find the class that implements interface_name or try until we've found the property
357 // in case of an empty interface
358 if (interface_name.isEmpty()) {
359 for (const QDBusAdaptorConnector::AdaptorData &adaptorData :
360 std::as_const(t&: connector->adaptors)) {
361 int status = writeProperty(obj: adaptorData.adaptor, property_name, value);
362 if (status == PropertyNotFound)
363 continue;
364 return propertyWriteReply(msg, interface_name, property_name, status);
365 }
366 } else {
367 QDBusAdaptorConnector::AdaptorMap::ConstIterator it;
368 it = std::lower_bound(first: connector->adaptors.constBegin(), last: connector->adaptors.constEnd(),
369 val: interface_name);
370 if (it != connector->adaptors.cend() && interface_name == QLatin1StringView(it->interface)) {
371 return propertyWriteReply(msg, interface_name, property_name,
372 status: writeProperty(obj: it->adaptor, property_name, value));
373 }
374 }
375 }
376
377 if (node.flags & (QDBusConnection::ExportScriptableProperties |
378 QDBusConnection::ExportNonScriptableProperties)) {
379 // try the object itself
380 bool interfaceFound = true;
381 if (!interface_name.isEmpty())
382 interfaceFound = qDBusInterfaceInObject(obj: node.obj, interface_name);
383
384 if (interfaceFound) {
385 return propertyWriteReply(msg, interface_name, property_name,
386 status: writeProperty(obj: node.obj, property_name, value, propFlags: node.flags));
387 }
388 }
389
390 // the property was not found
391 if (!interface_name.isEmpty())
392 return interfaceNotFoundError(msg, interface_name);
393 return propertyWriteReply(msg, interface_name, property_name, status: PropertyNotFound);
394}
395
396// unite two QVariantMaps, but don't generate duplicate keys
397static QVariantMap &operator+=(QVariantMap &lhs, const QVariantMap &rhs)
398{
399 for (const auto &[key, value] : rhs.asKeyValueRange())
400 lhs.insert(key, value);
401 return lhs;
402}
403
404static QVariantMap readAllProperties(QObject *object, int flags)
405{
406 QVariantMap result;
407 const QMetaObject *mo = object->metaObject();
408
409 // QObject has properties, so don't start from 0
410 for (int i = QObject::staticMetaObject.propertyCount(); i < mo->propertyCount(); ++i) {
411 QMetaProperty mp = mo->property(index: i);
412
413 // is it readable?
414 if (!mp.isReadable())
415 continue;
416
417 // is it a registered property?
418 QMetaType type = mp.metaType();
419 if (!type.isValid())
420 continue;
421 const char *signature = QDBusMetaType::typeToSignature(type);
422 if (!signature)
423 continue;
424
425 // is this property visible from the outside?
426 if ((mp.isScriptable() && flags & QDBusConnection::ExportScriptableProperties) ||
427 (!mp.isScriptable() && flags & QDBusConnection::ExportNonScriptableProperties)) {
428 // yes, it's visible
429 QVariant value = mp.read(obj: object);
430 if (value.isValid())
431 result.insert(key: QString::fromLatin1(ba: mp.name()), value);
432 }
433 }
434
435 return result;
436}
437
438QDBusMessage qDBusPropertyGetAll(const QDBusConnectionPrivate::ObjectTreeNode &node,
439 const QDBusMessage &msg)
440{
441 Q_ASSERT(msg.arguments().size() == 1);
442 Q_ASSERT_X(!node.obj || QThread::currentThread() == node.obj->thread(),
443 "QDBusConnection: internal threading error",
444 "function called for an object that is in another thread!!");
445
446 QString interface_name = msg.arguments().at(i: 0).toString();
447
448 bool interfaceFound = false;
449 QVariantMap result;
450
451 QDBusAdaptorConnector *connector;
452 if (node.flags & QDBusConnection::ExportAdaptors &&
453 (connector = qDBusFindAdaptorConnector(object: node.obj))) {
454
455 if (interface_name.isEmpty()) {
456 // iterate over all interfaces
457 for (const QDBusAdaptorConnector::AdaptorData &adaptorData :
458 std::as_const(t&: connector->adaptors)) {
459 result += readAllProperties(object: adaptorData.adaptor,
460 flags: QDBusConnection::ExportAllProperties);
461 }
462 } else {
463 // find the class that implements interface_name
464 QDBusAdaptorConnector::AdaptorMap::ConstIterator it;
465 it = std::lower_bound(first: connector->adaptors.constBegin(), last: connector->adaptors.constEnd(),
466 val: interface_name);
467 if (it != connector->adaptors.constEnd() && interface_name == QLatin1StringView(it->interface)) {
468 interfaceFound = true;
469 result = readAllProperties(object: it->adaptor, flags: QDBusConnection::ExportAllProperties);
470 }
471 }
472 }
473
474 if (node.flags & QDBusConnection::ExportAllProperties &&
475 (!interfaceFound || interface_name.isEmpty())) {
476 // try the object itself
477 result += readAllProperties(object: node.obj, flags: node.flags);
478 interfaceFound = true;
479 }
480
481 if (!interfaceFound && !interface_name.isEmpty()) {
482 // the interface was not found
483 return interfaceNotFoundError(msg, interface_name);
484 }
485
486 return msg.createReply(argument: QVariant::fromValue(value: result));
487}
488
489QT_END_NAMESPACE
490
491#endif // QT_NO_DBUS
492

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