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 }