/**************************************************************************** ** ** 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 #include #include #include #include #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(" 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(" \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(" 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(" \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(" 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(" \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(" 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(" \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(" \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(" "); interfaces->insert(ifaceName, QSharedDataPointer(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