/**************************************************************************** ** ** This file is part of the KD Soap project. ** ** SPDX-FileCopyrightText: 2010 Klarälvdalens Datakonsult AB, a KDAB Group company ** ** SPDX-License-Identifier: MIT ** ****************************************************************************/ #include "KDDateTime.h" #include "KDSoapMessageReader_p.h" #include "KDSoapNamespaceManager.h" #include "KDSoapNamespacePrefixes_p.h" #include #include #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) #define QStringView QStringRef #endif static QStringView namespaceForPrefix(const QXmlStreamNamespaceDeclarations &decls, const QString &prefix) { for (const QXmlStreamNamespaceDeclaration &decl : std::as_const(decls)) { if (decl.prefix() == prefix) { return decl.namespaceUri(); } } return QStringView(); } static int xmlTypeToMetaType(const QString &xmlType) { // Reverse operation from variantToXmlType in KDSoapClientInterface, keep in sync static const struct { const char *xml; // xsd: prefix assumed const int metaTypeId; } s_types[] = {{"string", QVariant::String}, // or QUrl {"base64Binary", QVariant::ByteArray}, {"int", QVariant::Int}, // or long, or uint, or longlong {"unsignedInt", QVariant::ULongLong}, {"boolean", QVariant::Bool}, {"float", QMetaType::Float}, {"double", QVariant::Double}, {"time", QVariant::Time}, {"date", QVariant::Date}}; // Speed: could be sorted and then we could use qBinaryFind for (const auto &type : s_types) { if (xmlType == QLatin1String(type.xml)) { return type.metaTypeId; } } if (xmlType == QLatin1String("dateTime")) { return qMetaTypeId(); } // This will happen with any custom type, don't bother the user // qDebug() << QString::fromLatin1("xmlTypeToMetaType: XML type %1 is not supported in " // "KDSoap, see the documentation").arg(xmlType); return -1; } static KDSoapValue parseElement(QXmlStreamReader &reader, const QXmlStreamNamespaceDeclarations &envNsDecls) { const QXmlStreamNamespaceDeclarations combinedNamespaceDeclarations = envNsDecls + reader.namespaceDeclarations(); const QString name = reader.name().toString(); KDSoapValue val(name, QVariant()); val.setNamespaceUri(reader.namespaceUri().toString()); val.setNamespaceDeclarations(reader.namespaceDeclarations()); val.setEnvironmentNamespaceDeclarations(combinedNamespaceDeclarations); // qDebug() << "parsing" << name; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) QVariant::Type metaTypeId = QVariant::Invalid; #else QMetaType::Type metaTypeId = QMetaType::UnknownType; #endif const auto invalidType = metaTypeId; const QXmlStreamAttributes attributes = reader.attributes(); for (const QXmlStreamAttribute &attribute : attributes) { const QStringView name = attribute.name(); const QStringView ns = attribute.namespaceUri(); const QStringView attrValue = attribute.value(); // Parse xsi:type and soap-enc:arrayType // and ignore anything else from the xsi or soap-enc namespaces until someone needs it... if (ns == KDSoapNamespaceManager::xmlSchemaInstance1999() || ns == KDSoapNamespaceManager::xmlSchemaInstance2001()) { if (name == QLatin1String("type")) { // The type can be like xsd:float, resolve that const QString type = attrValue.toString(); const int pos = type.indexOf(QLatin1Char(':')); const QString dataType = type.mid(pos + 1); val.setType(namespaceForPrefix(combinedNamespaceDeclarations, type.left(pos)).toString(), dataType); metaTypeId = static_cast(xmlTypeToMetaType(dataType)); } continue; } else if (ns == KDSoapNamespaceManager::soapEncoding() || ns == KDSoapNamespaceManager::soapEncoding200305() || ns == KDSoapNamespaceManager::soapEnvelope() || ns == KDSoapNamespaceManager::soapEnvelope200305()) { continue; } // qDebug() << "Got attribute:" << name << ns << "=" << attrValue; val.childValues().attributes().append(KDSoapValue(name.toString(), attrValue.toString())); } QString text; while (reader.readNext() != QXmlStreamReader::Invalid) { if (reader.isEndElement()) { break; } if (reader.isCharacters()) { text = reader.text().toString(); // qDebug() << "text=" << text; } else if (reader.isStartElement()) { const KDSoapValue subVal = parseElement(reader, combinedNamespaceDeclarations); // recurse val.childValues().append(subVal); } } if (!text.isEmpty()) { QVariant variant(text); // qDebug() << text << variant << metaTypeId; // With use=encoded, we have type info, we can convert the variant here // Otherwise, for servers, we do it later, once we know the method's parameter types. if (metaTypeId != invalidType) { QVariant copy = variant; #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) if (!variant.convert(metaTypeId)) { #else if (!variant.convert(QMetaType(metaTypeId))) { #endif variant = std::move(copy); } } val.setValue(variant); } return val; } KDSoapMessageReader::KDSoapMessageReader() { } static bool isInvalidCharRef(const QByteArray &charRef) { bool ok = true; int symbol = charRef.indexOf('x'); int end = charRef.indexOf(';'); if (symbol == -1 || end == -1) { return false; } uint val = charRef.mid(symbol + 1, end - symbol - 1).toInt(&ok, 16); if (!ok) { return false; } if (val != 0x9 && val != 0xa && val != 0xd && (val <= 0x20)) { return true; } return false; } static QByteArray handleNotWellFormedError(const QByteArray &data, qint64 offset) { qint64 i = offset - 1; // offset is the char following the failing one QByteArray dataCleanedUp; QByteArray originalSequence; while (i >= 0 && data.at(i) != '&') { if (data.at(i) == '<') { // InvalidXML but not invalid characters related return dataCleanedUp; } originalSequence.prepend(data.at(i)); i--; } if (isInvalidCharRef(originalSequence)) { qWarning() << "found an invalid character sequence to remove:" << QLatin1String(originalSequence.prepend('&').constData()); dataCleanedUp = data; dataCleanedUp = dataCleanedUp.replace(originalSequence, "?"); } return dataCleanedUp; } KDSoapMessageReader::XmlError KDSoapMessageReader::xmlToMessage(const QByteArray &data, KDSoapMessage *pMsg, QString *pMessageNamespace, KDSoapHeaders *pRequestHeaders, KDSoap::SoapVersion soapVersion) const { Q_ASSERT(pMsg); QXmlStreamReader reader(data); if (reader.readNextStartElement()) { if (reader.name() == QLatin1String("Envelope") && (reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope() || reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope200305())) { const QXmlStreamNamespaceDeclarations envNsDecls = reader.namespaceDeclarations(); if (reader.readNextStartElement()) { if (reader.name() == QLatin1String("Header") && (reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope() || reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope200305())) { KDSoapMessageAddressingProperties messageAddressingProperties; while (reader.readNextStartElement()) { if (KDSoapMessageAddressingProperties::isWSAddressingNamespace(reader.namespaceUri().toString())) { KDSoapValue value = parseElement(reader, envNsDecls); messageAddressingProperties.readMessageAddressingProperty(value); } else { KDSoapMessage header; static_cast(header) = parseElement(reader, envNsDecls); pRequestHeaders->append(header); } } pMsg->setMessageAddressingProperties(messageAddressingProperties); reader.readNextStartElement(); // read } if (reader.name() == QLatin1String("Body") && (reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope() || reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope200305())) { if (reader.readNextStartElement()) { *pMsg = parseElement(reader, envNsDecls); if (pMessageNamespace) { *pMessageNamespace = pMsg->namespaceUri(); } if (pMsg->name() == QLatin1String("Fault") && (reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope() || reader.namespaceUri() == KDSoapNamespaceManager::soapEnvelope200305())) { pMsg->setFault(true); } } } else { reader.raiseError(QObject::tr("Invalid SOAP Message, Body expected")); } } else { reader.raiseError(QObject::tr("Invalid SOAP Message, empty Envelope")); } } else { reader.raiseError(QObject::tr("Invalid SOAP Message, Envelope expected")); } } if (reader.hasError()) { if (reader.error() == QXmlStreamReader::NotWellFormedError) { qWarning() << "Handling a Not well Formed Error"; QByteArray dataCleanedUp = handleNotWellFormedError(data, reader.characterOffset()); if (!dataCleanedUp.isEmpty()) { return xmlToMessage(dataCleanedUp, pMsg, pMessageNamespace, pRequestHeaders, soapVersion); } } QString faultText = QString::fromLatin1("XML error: [%1:%2] %3") .arg(QString::number(reader.lineNumber()), QString::number(reader.columnNumber()), reader.errorString()); pMsg->createFaultMessage(QString::number(reader.error()), faultText, soapVersion); return reader.error() == QXmlStreamReader::PrematureEndOfDocumentError ? PrematureEndOfDocumentError : ParseError; } return NoError; }