| /**************************************************************************** |
| ** |
| ** Copyright (C) 2016 The Qt Company Ltd. |
| ** Contact: https://www.qt.io/licensing/ |
| ** |
| ** This file is part of the QtDBus module of the Qt Toolkit. |
| ** |
| ** $QT_BEGIN_LICENSE:LGPL$ |
| ** Commercial License Usage |
| ** Licensees holding valid commercial Qt licenses may use this file in |
| ** accordance with the commercial license agreement provided with the |
| ** Software or, alternatively, in accordance with the terms contained in |
| ** a written agreement between you and The Qt Company. For licensing terms |
| ** and conditions see https://www.qt.io/terms-conditions. For further |
| ** information use the contact form at https://www.qt.io/contact-us. |
| ** |
| ** GNU Lesser General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU Lesser |
| ** General Public License version 3 as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.LGPL3 included in the |
| ** packaging of this file. Please review the following information to |
| ** ensure the GNU Lesser General Public License version 3 requirements |
| ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. |
| ** |
| ** GNU General Public License Usage |
| ** Alternatively, this file may be used under the terms of the GNU |
| ** General Public License version 2.0 or (at your option) the GNU General |
| ** Public license version 3 or any later version approved by the KDE Free |
| ** Qt Foundation. The licenses are as published by the Free Software |
| ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 |
| ** included in the packaging of this file. Please review the following |
| ** information to ensure the GNU General Public License requirements will |
| ** be met: https://www.gnu.org/licenses/gpl-2.0.html and |
| ** https://www.gnu.org/licenses/gpl-3.0.html. |
| ** |
| ** $QT_END_LICENSE$ |
| ** |
| ****************************************************************************/ |
| |
| #include "qdbusxmlparser_p.h" |
| #include "qdbusutil_p.h" |
| |
| #include <QtCore/qmap.h> |
| #include <QtCore/qvariant.h> |
| #include <QtCore/qtextstream.h> |
| #include <QtCore/qxmlstream.h> |
| #include <QtCore/qdebug.h> |
| |
| #ifndef QT_NO_DBUS |
| |
| QT_BEGIN_NAMESPACE |
| |
| Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg) |
| |
| #define qDBusParserError(...) qCDebug(dbusParser, ##__VA_ARGS__) |
| |
| static bool parseArg(const QXmlStreamAttributes &attributes, QDBusIntrospection::Argument &argData, |
| QDBusIntrospection::Interface *ifaceData) |
| { |
| const QString argType = attributes.value(QLatin1String("type")).toString(); |
| |
| bool ok = QDBusUtil::isValidSingleSignature(argType); |
| if (!ok) { |
| qDBusParserError("Invalid D-BUS type signature '%s' found while parsing introspection", |
| qPrintable(argType)); |
| } |
| |
| argData.name = attributes.value(QLatin1String("name")).toString(); |
| argData.type = argType; |
| |
| ifaceData->introspection += QLatin1String(" <arg"); |
| if (attributes.hasAttribute(QLatin1String("direction"))) { |
| const QString direction = attributes.value(QLatin1String("direction")).toString(); |
| ifaceData->introspection += QLatin1String(" direction=\"") + direction + QLatin1String("\""); |
| } |
| ifaceData->introspection += QLatin1String(" type=\"") + argData.type + QLatin1String("\""); |
| if (!argData.name.isEmpty()) |
| ifaceData->introspection += QLatin1String(" name=\"") + argData.name + QLatin1String("\""); |
| ifaceData->introspection += QLatin1String("/>\n"); |
| |
| return ok; |
| } |
| |
| static bool parseAnnotation(const QXmlStreamReader &xml, QDBusIntrospection::Annotations &annotations, |
| QDBusIntrospection::Interface *ifaceData, bool interfaceAnnotation = false) |
| { |
| Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("annotation")); |
| |
| const QXmlStreamAttributes attributes = xml.attributes(); |
| const QString name = attributes.value(QLatin1String("name")).toString(); |
| |
| if (!QDBusUtil::isValidInterfaceName(name)) { |
| qDBusParserError("Invalid D-BUS annotation '%s' found while parsing introspection", |
| qPrintable(name)); |
| return false; |
| } |
| const QString value = attributes.value(QLatin1String("value")).toString(); |
| annotations.insert(name, value); |
| if (!interfaceAnnotation) |
| ifaceData->introspection += QLatin1String(" "); |
| ifaceData->introspection += QLatin1String(" <annotation value=\"") + value.toHtmlEscaped() + QLatin1String("\" name=\"") + name + QLatin1String("\"/>\n"); |
| return true; |
| } |
| |
| static bool parseProperty(QXmlStreamReader &xml, QDBusIntrospection::Property &propertyData, |
| QDBusIntrospection::Interface *ifaceData) |
| { |
| Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("property")); |
| |
| QXmlStreamAttributes attributes = xml.attributes(); |
| const QString propertyName = attributes.value(QLatin1String("name")).toString(); |
| if (!QDBusUtil::isValidMemberName(propertyName)) { |
| qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection", |
| qPrintable(propertyName), qPrintable(ifaceData->name)); |
| xml.skipCurrentElement(); |
| return false; |
| } |
| |
| // parse data |
| propertyData.name = propertyName; |
| propertyData.type = attributes.value(QLatin1String("type")).toString(); |
| |
| if (!QDBusUtil::isValidSingleSignature(propertyData.type)) { |
| // cannot be! |
| qDBusParserError("Invalid D-BUS type signature '%s' found in property '%s.%s' while parsing introspection", |
| qPrintable(propertyData.type), qPrintable(ifaceData->name), |
| qPrintable(propertyName)); |
| } |
| |
| const QString access = attributes.value(QLatin1String("access")).toString(); |
| if (access == QLatin1String("read")) |
| propertyData.access = QDBusIntrospection::Property::Read; |
| else if (access == QLatin1String("write")) |
| propertyData.access = QDBusIntrospection::Property::Write; |
| else if (access == QLatin1String("readwrite")) |
| propertyData.access = QDBusIntrospection::Property::ReadWrite; |
| else { |
| qDBusParserError("Invalid D-BUS property access '%s' found in property '%s.%s' while parsing introspection", |
| qPrintable(access), qPrintable(ifaceData->name), |
| qPrintable(propertyName)); |
| return false; // invalid one! |
| } |
| |
| ifaceData->introspection += QLatin1String(" <property access=\"") + access + QLatin1String("\" type=\"") + propertyData.type + QLatin1String("\" name=\"") + propertyName + QLatin1String("\""); |
| |
| if (!xml.readNextStartElement()) { |
| ifaceData->introspection += QLatin1String("/>\n"); |
| } else { |
| ifaceData->introspection += QLatin1String(">\n"); |
| |
| do { |
| if (xml.name() == QLatin1String("annotation")) { |
| parseAnnotation(xml, propertyData.annotations, ifaceData); |
| } else if (xml.prefix().isEmpty()) { |
| qDBusParserError() << "Unknown element" << xml.name() << "while checking for annotations"; |
| } |
| xml.skipCurrentElement(); |
| } while (xml.readNextStartElement()); |
| |
| ifaceData->introspection += QLatin1String(" </property>\n"); |
| } |
| |
| if (!xml.isEndElement() || xml.name() != QLatin1String("property")) { |
| qDBusParserError() << "Invalid property specification" << xml.tokenString() << xml.name(); |
| return false; |
| } |
| |
| return true; |
| } |
| |
| static bool parseMethod(QXmlStreamReader &xml, QDBusIntrospection::Method &methodData, |
| QDBusIntrospection::Interface *ifaceData) |
| { |
| Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("method")); |
| |
| const QXmlStreamAttributes attributes = xml.attributes(); |
| const QString methodName = attributes.value(QLatin1String("name")).toString(); |
| if (!QDBusUtil::isValidMemberName(methodName)) { |
| qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection", |
| qPrintable(methodName), qPrintable(ifaceData->name)); |
| return false; |
| } |
| |
| methodData.name = methodName; |
| ifaceData->introspection += QLatin1String(" <method name=\"") + methodName + QLatin1String("\""); |
| |
| QDBusIntrospection::Arguments outArguments; |
| QDBusIntrospection::Arguments inArguments; |
| QDBusIntrospection::Annotations annotations; |
| |
| if (!xml.readNextStartElement()) { |
| ifaceData->introspection += QLatin1String("/>\n"); |
| } else { |
| ifaceData->introspection += QLatin1String(">\n"); |
| |
| do { |
| if (xml.name() == QLatin1String("annotation")) { |
| parseAnnotation(xml, annotations, ifaceData); |
| } else if (xml.name() == QLatin1String("arg")) { |
| const QXmlStreamAttributes attributes = xml.attributes(); |
| const QString direction = attributes.value(QLatin1String("direction")).toString(); |
| QDBusIntrospection::Argument argument; |
| if (!attributes.hasAttribute(QLatin1String("direction")) |
| || direction == QLatin1String("in")) { |
| parseArg(attributes, argument, ifaceData); |
| inArguments << argument; |
| } else if (direction == QLatin1String("out")) { |
| parseArg(attributes, argument, ifaceData); |
| outArguments << argument; |
| } |
| } else if (xml.prefix().isEmpty()) { |
| qDBusParserError() << "Unknown element" << xml.name() << "while checking for method arguments"; |
| } |
| xml.skipCurrentElement(); |
| } while (xml.readNextStartElement()); |
| |
| ifaceData->introspection += QLatin1String(" </method>\n"); |
| } |
| |
| methodData.inputArgs = inArguments; |
| methodData.outputArgs = outArguments; |
| methodData.annotations = annotations; |
| |
| return true; |
| } |
| |
| |
| static bool parseSignal(QXmlStreamReader &xml, QDBusIntrospection::Signal &signalData, |
| QDBusIntrospection::Interface *ifaceData) |
| { |
| Q_ASSERT(xml.isStartElement() && xml.name() == QLatin1String("signal")); |
| |
| const QXmlStreamAttributes attributes = xml.attributes(); |
| const QString signalName = attributes.value(QLatin1String("name")).toString(); |
| |
| if (!QDBusUtil::isValidMemberName(signalName)) { |
| qDBusParserError("Invalid D-BUS member name '%s' found in interface '%s' while parsing introspection", |
| qPrintable(signalName), qPrintable(ifaceData->name)); |
| return false; |
| } |
| |
| signalData.name = signalName; |
| ifaceData->introspection += QLatin1String(" <signal name=\"") + signalName + QLatin1String("\""); |
| |
| QDBusIntrospection::Arguments arguments; |
| QDBusIntrospection::Annotations annotations; |
| |
| if (!xml.readNextStartElement()) { |
| ifaceData->introspection += QLatin1String("/>\n"); |
| } else { |
| ifaceData->introspection += QLatin1String(">\n"); |
| |
| do { |
| if (xml.name() == QLatin1String("annotation")) { |
| parseAnnotation(xml, annotations, ifaceData); |
| } else if (xml.name() == QLatin1String("arg")) { |
| const QXmlStreamAttributes attributes = xml.attributes(); |
| QDBusIntrospection::Argument argument; |
| if (!attributes.hasAttribute(QLatin1String("direction")) || |
| attributes.value(QLatin1String("direction")) == QLatin1String("out")) { |
| parseArg(attributes, argument, ifaceData); |
| arguments << argument; |
| } |
| } else { |
| qDBusParserError() << "Unknown element" << xml.name() << "while checking for signal arguments"; |
| } |
| xml.skipCurrentElement(); |
| } while (xml.readNextStartElement()); |
| |
| ifaceData->introspection += QLatin1String(" </signal>\n"); |
| } |
| |
| signalData.outputArgs = arguments; |
| signalData.annotations = annotations; |
| |
| return true; |
| } |
| |
| static void readInterface(QXmlStreamReader &xml, QDBusIntrospection::Object *objData, |
| QDBusIntrospection::Interfaces *interfaces) |
| { |
| const QString ifaceName = xml.attributes().value(QLatin1String("name")).toString(); |
| if (!QDBusUtil::isValidInterfaceName(ifaceName)) { |
| qDBusParserError("Invalid D-BUS interface name '%s' found while parsing introspection", |
| qPrintable(ifaceName)); |
| return; |
| } |
| |
| objData->interfaces.append(ifaceName); |
| |
| QDBusIntrospection::Interface *ifaceData = new QDBusIntrospection::Interface; |
| ifaceData->name = ifaceName; |
| ifaceData->introspection += QLatin1String(" <interface name=\"") + ifaceName + QLatin1String("\">\n"); |
| |
| while (xml.readNextStartElement()) { |
| if (xml.name() == QLatin1String("method")) { |
| QDBusIntrospection::Method methodData; |
| if (parseMethod(xml, methodData, ifaceData)) |
| ifaceData->methods.insert(methodData.name, methodData); |
| } else if (xml.name() == QLatin1String("signal")) { |
| QDBusIntrospection::Signal signalData; |
| if (parseSignal(xml, signalData, ifaceData)) |
| ifaceData->signals_.insert(signalData.name, signalData); |
| } else if (xml.name() == QLatin1String("property")) { |
| QDBusIntrospection::Property propertyData; |
| if (parseProperty(xml, propertyData, ifaceData)) |
| ifaceData->properties.insert(propertyData.name, propertyData); |
| } else if (xml.name() == QLatin1String("annotation")) { |
| parseAnnotation(xml, ifaceData->annotations, ifaceData, true); |
| xml.skipCurrentElement(); // skip over annotation object |
| } else { |
| if (xml.prefix().isEmpty()) { |
| qDBusParserError() << "Unknown element while parsing interface" << xml.name(); |
| } |
| xml.skipCurrentElement(); |
| } |
| } |
| |
| ifaceData->introspection += QLatin1String(" </interface>"); |
| |
| interfaces->insert(ifaceName, QSharedDataPointer<QDBusIntrospection::Interface>(ifaceData)); |
| |
| if (!xml.isEndElement() || xml.name() != QLatin1String("interface")) { |
| qDBusParserError() << "Invalid Interface specification"; |
| } |
| } |
| |
| static void readNode(const QXmlStreamReader &xml, QDBusIntrospection::Object *objData, int nodeLevel) |
| { |
| const QString objName = xml.attributes().value(QLatin1String("name")).toString(); |
| const QString fullName = objData->path.endsWith(QLatin1Char('/')) |
| ? (objData->path + objName) |
| : QString(objData->path + QLatin1Char('/') + objName); |
| if (!QDBusUtil::isValidObjectPath(fullName)) { |
| qDBusParserError("Invalid D-BUS object path '%s' found while parsing introspection", |
| qPrintable(fullName)); |
| return; |
| } |
| |
| if (nodeLevel > 0) |
| objData->childObjects.append(objName); |
| } |
| |
| QDBusXmlParser::QDBusXmlParser(const QString& service, const QString& path, |
| const QString& xmlData) |
| : m_service(service), m_path(path), m_object(new QDBusIntrospection::Object) |
| { |
| // qDBusParserError() << "parsing" << xmlData; |
| |
| m_object->service = m_service; |
| m_object->path = m_path; |
| |
| QXmlStreamReader xml(xmlData); |
| |
| int nodeLevel = -1; |
| |
| while (!xml.atEnd()) { |
| xml.readNext(); |
| |
| switch (xml.tokenType()) { |
| case QXmlStreamReader::StartElement: |
| if (xml.name() == QLatin1String("node")) { |
| readNode(xml, m_object, ++nodeLevel); |
| } else if (xml.name() == QLatin1String("interface")) { |
| readInterface(xml, m_object, &m_interfaces); |
| } else { |
| if (xml.prefix().isEmpty()) { |
| qDBusParserError() << "skipping unknown element" << xml.name(); |
| } |
| xml.skipCurrentElement(); |
| } |
| break; |
| case QXmlStreamReader::EndElement: |
| if (xml.name() == QLatin1String("node")) { |
| --nodeLevel; |
| } else { |
| qDBusParserError() << "Invalid Node declaration" << xml.name(); |
| } |
| break; |
| case QXmlStreamReader::StartDocument: |
| case QXmlStreamReader::EndDocument: |
| case QXmlStreamReader::DTD: |
| // not interested |
| break; |
| case QXmlStreamReader::Comment: |
| // ignore comments and processing instructions |
| break; |
| case QXmlStreamReader::Characters: |
| // ignore whitespace |
| if (xml.isWhitespace()) |
| break; |
| Q_FALLTHROUGH(); |
| default: |
| qDBusParserError() << "unknown token" << xml.name() << xml.tokenString(); |
| break; |
| } |
| } |
| |
| if (xml.hasError()) { |
| qDBusParserError() << "xml error" << xml.errorString() << "doc" << xmlData; |
| } |
| } |
| |
| QT_END_NAMESPACE |
| |
| #endif // QT_NO_DBUS |