File indexing completed on 2025-01-05 04:37:28

0001 /*
0002     SPDX-FileCopyrightText: 2005-2007 Joris Guisson <joris.guisson@gmail.com>
0003 
0004     SPDX-License-Identifier: GPL-2.0-or-later
0005 */
0006 
0007 #include "upnpdescriptionparser.h"
0008 
0009 #include <QFile>
0010 #include <QStack>
0011 #include <QXmlStreamReader>
0012 
0013 #include "upnprouter.h"
0014 #include <util/fileops.h>
0015 #include <util/log.h>
0016 
0017 using StringView = QStringView;
0018 
0019 using namespace bt;
0020 
0021 namespace bt
0022 {
0023 class XMLContentHandler
0024 {
0025     enum Status {
0026         TOPLEVEL,
0027         ROOT,
0028         DEVICE,
0029         SERVICE,
0030         FIELD,
0031         OTHER,
0032     };
0033 
0034     QString tmp;
0035     UPnPRouter *router;
0036     UPnPService curr_service;
0037     QStack<Status> status_stack;
0038 
0039 public:
0040     XMLContentHandler(UPnPRouter *router);
0041     ~XMLContentHandler();
0042 
0043     bool parse(const QByteArray &data);
0044 
0045     bool startDocument();
0046     bool endDocument();
0047     bool startElement(const StringView &namespaceUri, const StringView &localName, const StringView &qName, const QXmlStreamAttributes &atts);
0048     bool endElement(const StringView &namespaceUri, const StringView &localName, const StringView &qName);
0049     bool characters(const StringView &chars);
0050 
0051     bool interestingDeviceField(const StringView &name);
0052     bool interestingServiceField(const StringView &name);
0053 };
0054 
0055 UPnPDescriptionParser::UPnPDescriptionParser()
0056 {
0057 }
0058 
0059 UPnPDescriptionParser::~UPnPDescriptionParser()
0060 {
0061 }
0062 
0063 bool UPnPDescriptionParser::parse(const QString &file, UPnPRouter *router)
0064 {
0065     QFile fptr(file);
0066     if (!fptr.open(QIODevice::ReadOnly))
0067         return false;
0068 
0069     QByteArray data = fptr.readAll();
0070     XMLContentHandler chandler(router);
0071 
0072     const bool ret = chandler.parse(data);
0073 
0074     if (!ret) {
0075         Out(SYS_PNP | LOG_IMPORTANT) << "Error parsing XML" << endl;
0076         return false;
0077     }
0078     return true;
0079 }
0080 
0081 bool UPnPDescriptionParser::parse(const QByteArray &data, UPnPRouter *router)
0082 {
0083     XMLContentHandler chandler(router);
0084 
0085     const bool ret = chandler.parse(data);
0086 
0087     if (!ret) {
0088         Out(SYS_PNP | LOG_IMPORTANT) << "Error parsing XML" << endl;
0089         return false;
0090     }
0091     return true;
0092 }
0093 
0094 /////////////////////////////////////////////////////////////////////////////////
0095 
0096 XMLContentHandler::XMLContentHandler(UPnPRouter *router)
0097     : router(router)
0098 {
0099 }
0100 
0101 XMLContentHandler::~XMLContentHandler()
0102 {
0103 }
0104 
0105 bool XMLContentHandler::parse(const QByteArray &data)
0106 {
0107     QXmlStreamReader reader(data);
0108 
0109     while (!reader.atEnd()) {
0110         reader.readNext();
0111         if (reader.hasError())
0112             return false;
0113 
0114         switch (reader.tokenType()) {
0115         case QXmlStreamReader::StartDocument:
0116             if (!startDocument()) {
0117                 return false;
0118             }
0119             break;
0120         case QXmlStreamReader::EndDocument:
0121             if (!endDocument()) {
0122                 return false;
0123             }
0124             break;
0125         case QXmlStreamReader::StartElement:
0126             if (!startElement(reader.namespaceUri(), reader.name(), reader.qualifiedName(), reader.attributes())) {
0127                 return false;
0128             }
0129             break;
0130         case QXmlStreamReader::EndElement:
0131             if (!endElement(reader.namespaceUri(), reader.name(), reader.qualifiedName())) {
0132                 return false;
0133             }
0134             break;
0135         case QXmlStreamReader::Characters:
0136             if (!reader.isWhitespace() && !reader.text().trimmed().isEmpty()) {
0137                 if (!characters(reader.text()))
0138                     return false;
0139             }
0140             break;
0141         default:
0142             break;
0143         }
0144     }
0145 
0146     if (!reader.isEndDocument())
0147         return false;
0148 
0149     return true;
0150 }
0151 
0152 bool XMLContentHandler::startDocument()
0153 {
0154     status_stack.push(TOPLEVEL);
0155     return true;
0156 }
0157 
0158 bool XMLContentHandler::endDocument()
0159 {
0160     status_stack.pop();
0161     return true;
0162 }
0163 
0164 bool XMLContentHandler::interestingDeviceField(const StringView &name)
0165 {
0166     return name == QLatin1String("friendlyName") || name == QLatin1String("manufacturer") || name == QLatin1String("modelDescription")
0167         || name == QLatin1String("modelName") || name == QLatin1String("modelNumber");
0168 }
0169 
0170 bool XMLContentHandler::interestingServiceField(const StringView &name)
0171 {
0172     return name == QLatin1String("serviceType") || name == QLatin1String("serviceId") || name == QLatin1String("SCPDURL") || name == QLatin1String("controlURL")
0173         || name == QLatin1String("eventSubURL");
0174 }
0175 
0176 bool XMLContentHandler::startElement(const StringView &namespaceUri, const StringView &localName, const StringView &qName, const QXmlStreamAttributes &atts)
0177 {
0178     Q_UNUSED(namespaceUri)
0179     Q_UNUSED(qName)
0180     Q_UNUSED(atts)
0181 
0182     tmp = "";
0183     switch (status_stack.top()) {
0184     case TOPLEVEL:
0185         // from toplevel we can only go to root
0186         if (localName == QLatin1String("root"))
0187             status_stack.push(ROOT);
0188         else
0189             return false;
0190         break;
0191     case ROOT:
0192         // from the root we can go to device or specVersion
0193         // we are not interested in the specVersion
0194         if (localName == QLatin1String("device"))
0195             status_stack.push(DEVICE);
0196         else
0197             status_stack.push(OTHER);
0198         break;
0199     case DEVICE:
0200         // see if it is a field we are interested in
0201         if (interestingDeviceField(localName))
0202             status_stack.push(FIELD);
0203         else
0204             status_stack.push(OTHER);
0205         break;
0206     case SERVICE:
0207         if (interestingServiceField(localName))
0208             status_stack.push(FIELD);
0209         else
0210             status_stack.push(OTHER);
0211         break;
0212     case OTHER:
0213         if (localName == QLatin1String("service"))
0214             status_stack.push(SERVICE);
0215         else if (localName == QLatin1String("device"))
0216             status_stack.push(DEVICE);
0217         else
0218             status_stack.push(OTHER);
0219         break;
0220     case FIELD:
0221         break;
0222     }
0223     return true;
0224 }
0225 
0226 bool XMLContentHandler::endElement(const StringView &namespaceUri, const StringView &localName, const StringView &qName)
0227 {
0228     Q_UNUSED(namespaceUri)
0229     Q_UNUSED(qName)
0230 
0231     switch (status_stack.top()) {
0232     case FIELD:
0233         // we have a field so set it
0234         status_stack.pop();
0235         if (status_stack.top() == DEVICE) {
0236             // if we are in a device
0237             router->getDescription().setProperty(localName.toString(), tmp);
0238         } else if (status_stack.top() == SERVICE) {
0239             // set a property of a service
0240             curr_service.setProperty(localName.toString(), tmp);
0241         }
0242         break;
0243     case SERVICE:
0244         // add the service
0245         router->addService(curr_service);
0246         curr_service.clear();
0247         // pop the stack
0248         status_stack.pop();
0249         break;
0250     default:
0251         status_stack.pop();
0252         break;
0253     }
0254 
0255     // reset tmp
0256     tmp = "";
0257     return true;
0258 }
0259 
0260 bool XMLContentHandler::characters(const StringView &chars)
0261 {
0262     tmp.append(chars);
0263     return true;
0264 }
0265 
0266 }