// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only #include "qdbusxmlparser_p.h" #include "qdbusutil_p.h" #include #include #include #include #ifndef QT_NO_DBUS QT_BEGIN_NAMESPACE using namespace Qt::StringLiterals; Q_LOGGING_CATEGORY(dbusParser, "dbus.parser", QtWarningMsg) #define qDBusParserWarning(format, ...) \ do { \ if (m_reporter) \ m_reporter->warning(m_currentLocation, format "\n", ##__VA_ARGS__); \ else \ qCDebug(dbusParser, "Warning: " format, ##__VA_ARGS__); \ } while (0) #define qDBusParserError(format, ...) \ do { \ if (m_reporter) \ m_reporter->error(m_currentLocation, format "\n", ##__VA_ARGS__); \ else \ qCDebug(dbusParser, "Error: " format, ##__VA_ARGS__); \ } while (0) bool QDBusXmlParser::parseArg(const QXmlStreamAttributes &attributes, QDBusIntrospection::Argument &argData) { Q_ASSERT(m_currentInterface); const QString argType = attributes.value("type"_L1).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("name"_L1).toString(); argData.type = argType; m_currentInterface->introspection += " introspection += " direction=\""_L1 + direction + u'"'; } m_currentInterface->introspection += " type=\""_L1 + argData.type + u'"'; if (!argData.name.isEmpty()) m_currentInterface->introspection += " name=\""_L1 + argData.name + u'"'; m_currentInterface->introspection += "/>\n"_L1; return ok; } bool QDBusXmlParser::parseAnnotation(QDBusIntrospection::Annotations &annotations, bool interfaceAnnotation) { Q_ASSERT(m_currentInterface); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "annotation"_L1); QDBusIntrospection::Annotation annotation; annotation.location = m_currentLocation; const QXmlStreamAttributes attributes = m_xml.attributes(); annotation.name = attributes.value("name"_L1).toString(); if (!QDBusUtil::isValidInterfaceName(annotation.name)) { qDBusParserError("Invalid D-Bus annotation '%s' found while parsing introspection", qPrintable(annotation.name)); return false; } annotation.value = attributes.value("value"_L1).toString(); annotations.insert(annotation.name, annotation); if (!interfaceAnnotation) m_currentInterface->introspection += " "_L1; m_currentInterface->introspection += " \n"_L1; return true; } bool QDBusXmlParser::parseProperty(QDBusIntrospection::Property &propertyData) { Q_ASSERT(m_currentInterface); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "property"_L1); QXmlStreamAttributes attributes = m_xml.attributes(); const QString propertyName = attributes.value("name"_L1).toString(); if (!QDBusUtil::isValidMemberName(propertyName)) { qDBusParserWarning("Invalid D-Bus member name '%s' found in interface '%s' while parsing " "introspection", qPrintable(propertyName), qPrintable(m_currentInterface->name)); m_xml.skipCurrentElement(); return false; } // parse data propertyData.name = propertyName; propertyData.type = attributes.value("type"_L1).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(m_currentInterface->name), qPrintable(propertyName)); } const QString access = attributes.value("access"_L1).toString(); if (access == "read"_L1) propertyData.access = QDBusIntrospection::Property::Read; else if (access == "write"_L1) propertyData.access = QDBusIntrospection::Property::Write; else if (access == "readwrite"_L1) propertyData.access = QDBusIntrospection::Property::ReadWrite; else { qDBusParserError("Invalid D-Bus property access '%s' found in property '%s.%s' while " "parsing introspection", qPrintable(access), qPrintable(m_currentInterface->name), qPrintable(propertyName)); return false; // invalid one! } m_currentInterface->introspection += " introspection += "/>\n"_L1; } else { m_currentInterface->introspection += ">\n"_L1; do { if (m_xml.name() == "annotation"_L1) { parseAnnotation(propertyData.annotations); } else if (m_xml.prefix().isEmpty()) { qDBusParserWarning("Unknown element '%s' while checking for annotations", qPrintable(m_xml.name().toString())); } m_xml.skipCurrentElement(); } while (readNextStartElement()); m_currentInterface->introspection += " \n"_L1; } if (!m_xml.isEndElement() || m_xml.name() != "property"_L1) { qDBusParserError("Invalid property specification: '%s'", qPrintable(m_xml.tokenString())); return false; } return true; } bool QDBusXmlParser::parseMethod(QDBusIntrospection::Method &methodData) { Q_ASSERT(m_currentInterface); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "method"_L1); const QXmlStreamAttributes attributes = m_xml.attributes(); const QString methodName = attributes.value("name"_L1).toString(); if (!QDBusUtil::isValidMemberName(methodName)) { qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing " "introspection", qPrintable(methodName), qPrintable(m_currentInterface->name)); return false; } methodData.name = methodName; m_currentInterface->introspection += " introspection += "/>\n"_L1; } else { m_currentInterface->introspection += ">\n"_L1; do { if (m_xml.name() == "annotation"_L1) { parseAnnotation(annotations); } else if (m_xml.name() == "arg"_L1) { const QXmlStreamAttributes attributes = m_xml.attributes(); const QString direction = attributes.value("direction"_L1).toString(); QDBusIntrospection::Argument argument; argument.location = m_currentLocation; if (!attributes.hasAttribute("direction"_L1) || direction == "in"_L1) { parseArg(attributes, argument); inArguments << argument; } else if (direction == "out"_L1) { parseArg(attributes, argument); outArguments << argument; } } else if (m_xml.prefix().isEmpty()) { qDBusParserWarning("Unknown element '%s' while checking for method arguments", qPrintable(m_xml.name().toString())); } m_xml.skipCurrentElement(); } while (readNextStartElement()); m_currentInterface->introspection += " \n"_L1; } methodData.inputArgs = inArguments; methodData.outputArgs = outArguments; methodData.annotations = annotations; return true; } bool QDBusXmlParser::parseSignal(QDBusIntrospection::Signal &signalData) { Q_ASSERT(m_currentInterface); Q_ASSERT(m_xml.isStartElement() && m_xml.name() == "signal"_L1); const QXmlStreamAttributes attributes = m_xml.attributes(); const QString signalName = attributes.value("name"_L1).toString(); if (!QDBusUtil::isValidMemberName(signalName)) { qDBusParserError("Invalid D-Bus member name '%s' found in interface '%s' while parsing " "introspection", qPrintable(signalName), qPrintable(m_currentInterface->name)); return false; } signalData.name = signalName; m_currentInterface->introspection += " introspection += "/>\n"_L1; } else { m_currentInterface->introspection += ">\n"_L1; do { if (m_xml.name() == "annotation"_L1) { parseAnnotation(annotations); } else if (m_xml.name() == "arg"_L1) { const QXmlStreamAttributes attributes = m_xml.attributes(); QDBusIntrospection::Argument argument; argument.location = m_currentLocation; if (!attributes.hasAttribute("direction"_L1) || attributes.value("direction"_L1) == "out"_L1) { parseArg(attributes, argument); arguments << argument; } } else { qDBusParserWarning("Unknown element '%s' while checking for signal arguments", qPrintable(m_xml.name().toString())); } m_xml.skipCurrentElement(); } while (readNextStartElement()); m_currentInterface->introspection += " \n"_L1; } signalData.outputArgs = arguments; signalData.annotations = annotations; return true; } void QDBusXmlParser::readInterface() { Q_ASSERT(!m_currentInterface); const QString ifaceName = m_xml.attributes().value("name"_L1).toString(); if (!QDBusUtil::isValidInterfaceName(ifaceName)) { qDBusParserError("Invalid D-Bus interface name '%s' found while parsing introspection", qPrintable(ifaceName)); return; } m_object->interfaces.append(ifaceName); m_currentInterface = std::make_unique(); m_currentInterface->location = m_currentLocation; m_currentInterface->name = ifaceName; m_currentInterface->introspection += " \n"_L1; while (readNextStartElement()) { if (m_xml.name() == "method"_L1) { QDBusIntrospection::Method methodData; methodData.location = m_currentLocation; if (parseMethod(methodData)) m_currentInterface->methods.insert(methodData.name, methodData); } else if (m_xml.name() == "signal"_L1) { QDBusIntrospection::Signal signalData; signalData.location = m_currentLocation; if (parseSignal(signalData)) m_currentInterface->signals_.insert(signalData.name, signalData); } else if (m_xml.name() == "property"_L1) { QDBusIntrospection::Property propertyData; propertyData.location = m_currentLocation; if (parseProperty(propertyData)) m_currentInterface->properties.insert(propertyData.name, propertyData); } else if (m_xml.name() == "annotation"_L1) { parseAnnotation(m_currentInterface->annotations, true); m_xml.skipCurrentElement(); // skip over annotation object } else { if (m_xml.prefix().isEmpty()) { qDBusParserWarning("Unknown element '%s' while parsing interface", qPrintable(m_xml.name().toString())); } m_xml.skipCurrentElement(); } } m_currentInterface->introspection += " "_L1; m_interfaces.insert( ifaceName, QSharedDataPointer(m_currentInterface.release())); if (!m_xml.isEndElement() || m_xml.name() != "interface"_L1) { qDBusParserError("Invalid Interface specification"); } } void QDBusXmlParser::readNode(int nodeLevel) { const QString objName = m_xml.attributes().value("name"_L1).toString(); QString fullName = m_object->path; if (!(fullName.endsWith(u'/') || (nodeLevel == 0 && objName.startsWith(u'/')))) fullName.append(u'/'); fullName += objName; if (!QDBusUtil::isValidObjectPath(fullName)) { qDBusParserError("Invalid D-Bus object path '%s' found while parsing introspection", qPrintable(fullName)); return; } if (nodeLevel > 0) m_object->childObjects.append(objName); else m_object->location = m_currentLocation; } void QDBusXmlParser::updateCurrentLocation() { m_currentLocation = QDBusIntrospection::SourceLocation{ m_xml.lineNumber(), m_xml.columnNumber() }; } // Similar to m_xml.readNextElement() but sets current location to point // to the start element. bool QDBusXmlParser::readNextStartElement() { updateCurrentLocation(); while (m_xml.readNext() != QXmlStreamReader::Invalid) { if (m_xml.isEndElement()) return false; else if (m_xml.isStartElement()) return true; updateCurrentLocation(); } return false; } QDBusXmlParser::QDBusXmlParser(const QString &service, const QString &path, const QString &xmlData, QDBusIntrospection::DiagnosticsReporter *reporter) : m_service(service), m_path(path), m_object(new QDBusIntrospection::Object), m_xml(xmlData), m_reporter(reporter) { m_object->service = m_service; m_object->path = m_path; int nodeLevel = -1; while (!m_xml.atEnd()) { updateCurrentLocation(); m_xml.readNext(); switch (m_xml.tokenType()) { case QXmlStreamReader::StartElement: if (m_xml.name() == "node"_L1) { readNode(++nodeLevel); } else if (m_xml.name() == "interface"_L1) { readInterface(); } else { if (m_xml.prefix().isEmpty()) { qDBusParserWarning("Skipping unknown element '%s'", qPrintable(m_xml.name().toString())); } m_xml.skipCurrentElement(); } break; case QXmlStreamReader::EndElement: if (m_xml.name() == "node"_L1) { --nodeLevel; } else { qDBusParserError("Invalid node declaration '%s'", qPrintable(m_xml.name().toString())); } 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 (m_xml.isWhitespace()) break; Q_FALLTHROUGH(); default: qDBusParserError("Unknown token: '%s'", qPrintable(m_xml.tokenString())); break; } } if (m_xml.hasError()) qDBusParserError("XML error: %s", qPrintable(m_xml.errorString())); } QT_END_NAMESPACE #endif // QT_NO_DBUS