1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtDBus module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include <QtCore/qmetaobject.h>
41 #include <QtCore/qstringlist.h>
42 #include <QtCore/qdebug.h>
43 
44 #include "qdbusinterface_p.h"   // for ANNOTATION_NO_WAIT
45 #include "qdbusabstractadaptor_p.h" // for QCLASSINFO_DBUS_*
46 #include "qdbusconnection_p.h"  // for the flags
47 #include "qdbusmetatype_p.h"
48 #include "qdbusmetatype.h"
49 #include "qdbusutil_p.h"
50 
51 #ifndef QT_NO_DBUS
52 
53 QT_BEGIN_NAMESPACE
54 
55 extern Q_DBUS_EXPORT QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
56                                                        const QMetaObject *base, int flags);
57 
typeNameToXml(const char * typeName)58 static inline QString typeNameToXml(const char *typeName)
59 {
60     // ### copied from qtextdocument.cpp
61     // ### move this into Qt Core at some point
62     const QLatin1String plain(typeName);
63     QString rich;
64     rich.reserve(int(plain.size() * 1.1));
65     for (int i = 0; i < plain.size(); ++i) {
66         if (plain.at(i) == QLatin1Char('<'))
67             rich += QLatin1String("&lt;");
68         else if (plain.at(i) == QLatin1Char('>'))
69             rich += QLatin1String("&gt;");
70         else if (plain.at(i) == QLatin1Char('&'))
71             rich += QLatin1String("&amp;");
72         else
73             rich += plain.at(i);
74     }
75     return rich;
76 }
77 
accessAsString(bool read,bool write)78 static inline QLatin1String accessAsString(bool read, bool write)
79 {
80     if (read)
81         return write ? QLatin1String("readwrite") : QLatin1String("read") ;
82     else
83         return write ? QLatin1String("write") : QLatin1String("") ;
84 }
85 
86 // implement the D-Bus org.freedesktop.DBus.Introspectable interface
87 // we do that by analysing the metaObject of all the adaptor interfaces
88 
generateInterfaceXml(const QMetaObject * mo,int flags,int methodOffset,int propOffset)89 static QString generateInterfaceXml(const QMetaObject *mo, int flags, int methodOffset, int propOffset)
90 {
91     QString retval;
92 
93     // start with properties:
94     if (flags & (QDBusConnection::ExportScriptableProperties |
95                  QDBusConnection::ExportNonScriptableProperties)) {
96         for (int i = propOffset; i < mo->propertyCount(); ++i) {
97 
98             QMetaProperty mp = mo->property(i);
99 
100             if (!((mp.isScriptable() && (flags & QDBusConnection::ExportScriptableProperties)) ||
101                   (!mp.isScriptable() && (flags & QDBusConnection::ExportNonScriptableProperties))))
102                 continue;
103 
104             int typeId = mp.userType();
105             if (!typeId)
106                 continue;
107             const char *signature = QDBusMetaType::typeToSignature(typeId);
108             if (!signature)
109                 continue;
110 
111             retval += QLatin1String("    <property name=\"%1\" type=\"%2\" access=\"%3\"")
112                       .arg(QLatin1String(mp.name()),
113                            QLatin1String(signature),
114                            accessAsString(mp.isReadable(), mp.isWritable()));
115 
116             if (QDBusMetaType::signatureToType(signature) == QMetaType::UnknownType) {
117                 const char *typeName = QMetaType::typeName(typeId);
118                 retval += QLatin1String(">\n      <annotation name=\"org.qtproject.QtDBus.QtTypeName\" value=\"%3\"/>\n    </property>\n")
119                           .arg(typeNameToXml(typeName));
120             } else {
121                 retval += QLatin1String("/>\n");
122             }
123         }
124     }
125 
126     // now add methods:
127     for (int i = methodOffset; i < mo->methodCount(); ++i) {
128         QMetaMethod mm = mo->method(i);
129 
130         bool isSignal;
131         if (mm.methodType() == QMetaMethod::Signal)
132             // adding a signal
133             isSignal = true;
134         else if (mm.access() == QMetaMethod::Public && (mm.methodType() == QMetaMethod::Slot || mm.methodType() == QMetaMethod::Method))
135             isSignal = false;
136         else
137             continue;           // neither signal nor public slot
138 
139         if (isSignal && !(flags & (QDBusConnection::ExportScriptableSignals |
140                                    QDBusConnection::ExportNonScriptableSignals)))
141             continue;           // we're not exporting any signals
142         if (!isSignal && (!(flags & (QDBusConnection::ExportScriptableSlots | QDBusConnection::ExportNonScriptableSlots)) &&
143                           !(flags & (QDBusConnection::ExportScriptableInvokables | QDBusConnection::ExportNonScriptableInvokables))))
144             continue;           // we're not exporting any slots or invokables
145 
146         // we want to skip non-scriptable stuff as early as possible to avoid bogus warning
147         // for methods that are not being exported at all
148         bool isScriptable = mm.attributes() & QMetaMethod::Scriptable;
149         if (!isScriptable && !(flags & (isSignal ? QDBusConnection::ExportNonScriptableSignals : QDBusConnection::ExportNonScriptableInvokables | QDBusConnection::ExportNonScriptableSlots)))
150             continue;
151 
152         QString xml = QString::asprintf("    <%s name=\"%s\">\n",
153                                         isSignal ? "signal" : "method", mm.name().constData());
154 
155         // check the return type first
156         int typeId = mm.returnType();
157         if (typeId != QMetaType::UnknownType && typeId != QMetaType::Void) {
158             const char *typeName = QDBusMetaType::typeToSignature(typeId);
159             if (typeName) {
160                 xml += QLatin1String("      <arg type=\"%1\" direction=\"out\"/>\n")
161                        .arg(typeNameToXml(typeName));
162 
163                 // do we need to describe this argument?
164                 if (QDBusMetaType::signatureToType(typeName) == QMetaType::UnknownType)
165                     xml += QLatin1String("      <annotation name=\"org.qtproject.QtDBus.QtTypeName.Out0\" value=\"%1\"/>\n")
166                         .arg(typeNameToXml(QMetaType::typeName(typeId)));
167             } else {
168                 qWarning() << "Unsupported return type" << typeId << QMetaType::typeName(typeId) << "in method" << mm.name();
169                 continue;
170             }
171         }
172         else if (typeId == QMetaType::UnknownType) {
173             qWarning() << "Invalid return type in method" << mm.name();
174             continue;           // wasn't a valid type
175         }
176 
177         QList<QByteArray> names = mm.parameterNames();
178         QVector<int> types;
179         QString errorMsg;
180         int inputCount = qDBusParametersForMethod(mm, types, errorMsg);
181         if (inputCount == -1) {
182             qWarning() << "Skipped method" << mm.name() << ":" << qPrintable(errorMsg);
183             continue;           // invalid form
184         }
185         if (isSignal && inputCount + 1 != types.count())
186             continue;           // signal with output arguments?
187         if (isSignal && types.at(inputCount) == QDBusMetaTypeId::message())
188             continue;           // signal with QDBusMessage argument?
189         if (isSignal && mm.attributes() & QMetaMethod::Cloned)
190             continue;           // cloned signal?
191 
192         int j;
193         for (j = 1; j < types.count(); ++j) {
194             // input parameter for a slot or output for a signal
195             if (types.at(j) == QDBusMetaTypeId::message()) {
196                 isScriptable = true;
197                 continue;
198             }
199 
200             QString name;
201             if (!names.at(j - 1).isEmpty())
202                 name = QLatin1String("name=\"%1\" ").arg(QLatin1String(names.at(j - 1)));
203 
204             bool isOutput = isSignal || j > inputCount;
205 
206             const char *signature = QDBusMetaType::typeToSignature(types.at(j));
207             xml += QString::asprintf("      <arg %lstype=\"%s\" direction=\"%s\"/>\n",
208                                      qUtf16Printable(name), signature, isOutput ? "out" : "in");
209 
210             // do we need to describe this argument?
211             if (QDBusMetaType::signatureToType(signature) == QMetaType::UnknownType) {
212                 const char *typeName = QMetaType::typeName(types.at(j));
213                 xml += QString::fromLatin1("      <annotation name=\"org.qtproject.QtDBus.QtTypeName.%1%2\" value=\"%3\"/>\n")
214                        .arg(isOutput ? QLatin1String("Out") : QLatin1String("In"))
215                        .arg(isOutput && !isSignal ? j - inputCount : j - 1)
216                        .arg(typeNameToXml(typeName));
217             }
218         }
219 
220         int wantedMask;
221         if (isScriptable)
222             wantedMask = isSignal ? QDBusConnection::ExportScriptableSignals
223                                   : QDBusConnection::ExportScriptableSlots;
224         else
225             wantedMask = isSignal ? QDBusConnection::ExportNonScriptableSignals
226                                   : QDBusConnection::ExportNonScriptableSlots;
227         if ((flags & wantedMask) != wantedMask)
228             continue;
229 
230         if (qDBusCheckAsyncTag(mm.tag()))
231             // add the no-reply annotation
232             xml += QLatin1String("      <annotation name=\"" ANNOTATION_NO_WAIT "\""
233                                  " value=\"true\"/>\n");
234 
235         retval += xml;
236         retval += QLatin1String("    </%1>\n")
237                   .arg(isSignal ? QLatin1String("signal") : QLatin1String("method"));
238     }
239 
240     return retval;
241 }
242 
qDBusGenerateMetaObjectXml(QString interface,const QMetaObject * mo,const QMetaObject * base,int flags)243 QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo,
244                                    const QMetaObject *base, int flags)
245 {
246     if (interface.isEmpty())
247         // generate the interface name from the meta object
248         interface = qDBusInterfaceFromMetaObject(mo);
249 
250     QString xml;
251     int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
252     if (idx >= mo->classInfoOffset())
253         return QString::fromUtf8(mo->classInfo(idx).value());
254     else
255         xml = generateInterfaceXml(mo, flags, base->methodCount(), base->propertyCount());
256 
257     if (xml.isEmpty())
258         return QString();       // don't add an empty interface
259     return QLatin1String("  <interface name=\"%1\">\n%2  </interface>\n")
260         .arg(interface, xml);
261 }
262 #if 0
263 QString qDBusGenerateMetaObjectXml(QString interface, const QMetaObject *mo, const QMetaObject *base,
264                                    int flags)
265 {
266     if (interface.isEmpty()) {
267         // generate the interface name from the meta object
268         int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTERFACE);
269         if (idx >= mo->classInfoOffset()) {
270             interface = QLatin1String(mo->classInfo(idx).value());
271         } else {
272             interface = QLatin1String(mo->className());
273             interface.replace(QLatin1String("::"), QLatin1String("."));
274 
275             if (interface.startsWith(QLatin1String("QDBus"))) {
276                 interface.prepend(QLatin1String("org.qtproject.QtDBus."));
277             } else if (interface.startsWith(QLatin1Char('Q')) &&
278                        interface.length() >= 2 && interface.at(1).isUpper()) {
279                 // assume it's Qt
280                 interface.prepend(QLatin1String("org.qtproject.Qt."));
281             } else if (!QCoreApplication::instance()||
282                        QCoreApplication::instance()->applicationName().isEmpty()) {
283                 interface.prepend(QLatin1String("local."));
284             } else {
285                 interface.prepend(QLatin1Char('.')).prepend(QCoreApplication::instance()->applicationName());
286                 QStringList domainName =
287                     QCoreApplication::instance()->organizationDomain().split(QLatin1Char('.'),
288                                                                              Qt::SkipEmptyParts);
289                 if (domainName.isEmpty())
290                     interface.prepend(QLatin1String("local."));
291                 else
292                     for (int i = 0; i < domainName.count(); ++i)
293                         interface.prepend(QLatin1Char('.')).prepend(domainName.at(i));
294             }
295         }
296     }
297 
298     QString xml;
299     int idx = mo->indexOfClassInfo(QCLASSINFO_DBUS_INTROSPECTION);
300     if (idx >= mo->classInfoOffset())
301         return QString::fromUtf8(mo->classInfo(idx).value());
302     else
303         xml = generateInterfaceXml(mo, flags, base->methodCount(), base->propertyCount());
304 
305     if (xml.isEmpty())
306         return QString();       // don't add an empty interface
307     return QString::fromLatin1("  <interface name=\"%1\">\n%2  </interface>\n")
308         .arg(interface, xml);
309 }
310 
311 #endif
312 
313 QT_END_NAMESPACE
314 
315 #endif // QT_NO_DBUS
316