//============================================================================= // // File : Service.cpp // Creation date : Fri Aug 08 18:00:00 2008 GMT by Fabio Bas // // This file is part of the KVIrc IRC client distribution // Copyright (C) 2008 Fabio Bas (ctrlaltca at gmail dot com) // // This program is FREE software. You can redistribute it and/or // modify it under the terms of the GNU General Public License // as published by the Free Software Foundation; either version 2 // of the License, or (at your option) any later version. // // This program is distributed in the HOPE that it will be USEFUL, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. // See the GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program. If not, write to the Free Software Foundation, // Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. // // Original Copyright follows: //============================================================================= /*************************************************************************** Service.cpp - description ------------------- begin : Sun Jul 24 2005 copyright : (C) 2005 by Diederik van der Boor email : vdboor --at-- codingdomain.com ***************************************************************************/ #include "Service.h" #include "XmlFunctions.h" #include #include #include #include "KviNetworkAccessManager.h" // This implementation was created with the help of the following documentation: // http://www.upnp.org/standardizeddcps/documents/UPnP_IGD_1.0.zip // http://zacbowling.com/upnp/ // http://www.knoxscape.com/Upnp/NAT.htm // http://www.artima.com/spontaneous/upnp_digihome2.html namespace UPnP { // The constructor for information services Service::Service(QString hostname, int port, const QString & informationUrl) : m_iPendingRequests(0) , m_szBaseXmlPrefix("s") , m_szHostname(std::move(hostname)) , m_iPort(port) { m_szInformationUrl = informationUrl; qDebug() << "UPnP::Service: created information service url='" << m_szInformationUrl << "'." << Qt::endl; } // The constructor for action services Service::Service(const ServiceParameters & params) : m_szControlUrl(params.controlUrl), m_szInformationUrl(params.scpdUrl), m_iPendingRequests(0), m_szServiceId(params.serviceId), m_szServiceType(params.serviceType), m_szBaseXmlPrefix("s"), m_szHostname(params.hostname), m_iPort(params.port) { qDebug() << "CREATED UPnP::Service: url='" << m_szControlUrl << "' id='" << m_szServiceId << "'." << Qt::endl; } // The destructor Service::~Service() { qDebug() << "DESTROYED UPnP::Service [url=" << m_szControlUrl << ", id=" << m_szServiceId << "]" << Qt::endl; } // Makes a UPnP action request // TODO: rename to callMethod / callSoapMethod int Service::callAction(const QString & actionName, const QString & prefix) { return callActionInternal(actionName, nullptr, prefix); } // Makes a UPnP action request int Service::callAction(const QString & actionName, const QMap & arguments, const QString & prefix) { return callActionInternal(actionName, &arguments, prefix); } // Makes a UPnP action request (keeps pointers from the external interface) int Service::callActionInternal(const QString & actionName, const QMap * arguments, const QString & prefix) { qDebug() << "UPnP::Service: calling remote procedure '" << actionName << "'." << Qt::endl; // Create the data message //NOTE: we shouldm use serviceId_ instead of serviceType_, but it seems that my router // (and maybe others) are reporting wrong Ids, while they're gonna accepting only the // correct ones. This is a decoded reply from my Zyxel: // Service : // servicetype = urn:schemas-upnp-org:service:Layer3Forwarding:1 // controlurl = /UD/act?0 // eventsuburl = /?0 // scpdurl = /L3Fwd.xml // serviceid = urn:upnp-org:serviceId:L3Forwarding1 // this router wants us to use servicetype in the following requests QString soapMessage = "\r\n" "<" + m_szBaseXmlPrefix + ":Envelope xmlns:" + m_szBaseXmlPrefix + "=\"http://schemas.xmlsoap.org/soap/envelope/\"" " " + m_szBaseXmlPrefix + ":encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">\n" " <" + m_szBaseXmlPrefix + ":Body>\n" " <" + prefix + ":" + actionName + " xmlns:" + prefix + "=\"" + m_szServiceType + "\">\n"; // Do we have any arguments? if(arguments != nullptr) { // Add the arguments QMap::const_iterator it; for(it = arguments->begin(); it != arguments->end(); ++it) { QString argumentName = it.key(); soapMessage += "<" + argumentName + ">" + it.value() + ""; } } // Add the closing tags soapMessage += " \n \n\n"; // Get an utf8 encoding string QByteArray content = soapMessage.toUtf8().data(); // Create the HTTP header QNetworkRequest request; request.setHeader(QNetworkRequest::ContentTypeHeader, "text/xml"); request.setHeader(QNetworkRequest::ContentLengthHeader, content.size()); request.setRawHeader("SOAPAction", QString("\"%1#%2\"").arg(m_szServiceType, actionName).toUtf8()); QString port; port.setNum(m_iPort); request.setRawHeader("HOST", QString("%1:%2").arg(m_szHostname, port).toUtf8()); QUrl url; url.setHost(m_szHostname); url.setPort(m_iPort); request.setUrl(url); // Send the POST request m_iPendingRequests++; QByteArray dummy; QNetworkReply * pReply = KviNetworkAccessManager::getInstance()->post(request, dummy); connect(pReply, SIGNAL(finished()), this, SLOT(slotRequestFinished())); return 0; } // Makes a UPnP service request // TODO: rename to downloadFile() int Service::callInformationUrl() { qDebug() << "UPnP::Service: requesting file '" << m_szInformationUrl << "'." << Qt::endl; // Send the GET request // TODO: User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) m_iPendingRequests++; QNetworkRequest request; QByteArray dummy; QUrl url; url.setHost(m_szHostname); url.setPort(m_iPort); url.setPath(m_szInformationUrl); request.setUrl(url); QNetworkReply * pReply = KviNetworkAccessManager::getInstance()->post(request, dummy); connect(pReply, SIGNAL(finished()), this, SLOT(slotRequestFinished())); return 0; } // Get the number of pending requests int Service::getPendingRequests() const { return m_iPendingRequests; } // The control point received an action failure indication void Service::gotActionErrorResponse(const QDomNode & response) { QString faultString = XmlFunctions::getNodeValue(response, "/faultstring"); QString errorCode = XmlFunctions::getNodeValue(response, "/detail/" + faultString + "/errorCode"); QString errorDescription = XmlFunctions::getNodeValue(response, "/detail/" + faultString + "/errorDescription"); qWarning() << "UPnP::Service - Action failed: " << errorCode << " " << errorDescription << Qt::endl; } // The control point received a response to callAction() void Service::gotActionResponse(const QString & responseType, const QMap & /*resultValues*/) { qWarning() << "UPnP::Service - Action response '" << responseType << "' is not handled." << Qt::endl; } // The control point received a response to callInformationUrl() void Service::gotInformationResponse(const QDomNode & response) { QString rootTagName = response.nodeName(); qWarning() << "UPnP::Service - Service response (with root '" << rootTagName << "') is not handled." << Qt::endl; } // The QHttp object retrieved data. void Service::slotRequestFinished() { QNetworkReply * reply = qobject_cast(sender()); qDebug() << "UPnP::Service: received HTTP response for request " << Qt::endl; if(!reply) { qWarning() << "UPnP::Service - HTTP Request failed: " << reply->errorString() << Qt::endl; m_iPendingRequests--; emit queryFinished(true); return; } if(reply->error() != QNetworkReply::NoError) { qWarning() << "UPnP::Service - HTTP Request failed: " << reply->errorString() << Qt::endl; m_iPendingRequests--; emit queryFinished(true); reply->deleteLater(); return; } // Get the XML content QByteArray response = reply->readAll(); QDomDocument xml; qDebug() << "Response:\n" << response << "\n---\n"; // Parse the XML QString errorMessage; #if (QT_VERSION < QT_VERSION_CHECK(6, 9, 0)) bool error = !xml.setContent(response, false, &errorMessage); #else auto result = xml.setContent(response, QDomDocument::ParseOption::Default); bool error = !(bool)result; if(error) { errorMessage = result.errorMessage; } #endif if(!error) { QString baseNamespace = xml.documentElement().tagName(); if(baseNamespace.length() > 0) { int cutAt = baseNamespace.indexOf(':'); if(cutAt > -1) { baseNamespace.truncate(cutAt); qDebug() << "Device is using " << baseNamespace << " as XML namespace" << Qt::endl; m_szBaseXmlPrefix = baseNamespace; } } // Determine how to process the data if(xml.namedItem(m_szBaseXmlPrefix + ":Envelope").isNull()) { qDebug() << "UPnP::Service: plain XML detected, calling gotInformationResponse()." << Qt::endl; // No SOAP envelope found, this is a normal response to callService() gotInformationResponse(xml.lastChild()); } else { qDebug() << xml.toString() << Qt::endl; // Got a SOAP message response to callAction() QDomNode resultNode = XmlFunctions::getNode(xml, "/" + m_szBaseXmlPrefix + ":Envelope/" + m_szBaseXmlPrefix + ":Body").firstChild(); error = (resultNode.nodeName() == m_szBaseXmlPrefix + ":Fault"); if(!error) { if(resultNode.nodeName().startsWith("m:") || resultNode.nodeName().startsWith("u:")) { qDebug() << "UPnP::Service: SOAP envelope detected, calling gotActionResponse()." << Qt::endl; // Action success, return SOAP body QMap resultValues; // Parse all parameters // It's possible to pass the entire QDomNode object to the gotActionResponse() // function, but this is somewhat nicer, and reduces code boat in the subclasses QDomNodeList children = resultNode.childNodes(); for(int i = 0; i < children.count(); i++) { QString key = children.item(i).nodeName(); resultValues[key] = children.item(i).toElement().text(); } // Call the gotActionResponse() gotActionResponse(resultNode.nodeName().mid(2), resultValues); } } else { qDebug() << "UPnP::Service: SOAP error detected, calling gotActionResponse()." << Qt::endl; // Action failed gotActionErrorResponse(resultNode); } } } else { qWarning() << "UPnP::Service - XML parsing failed: " << errorMessage << Qt::endl; } // Only emit when bytes>0 m_iPendingRequests--; emit queryFinished(error); } } // end of namespace