/*************************************************************************** service.cpp - description ------------------- begin : Sun Jul 24 2005 copyright : (C) 2005 by Diederik van der Boor email : vdboor --at-- codingdomain.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. * * * ***************************************************************************/ #include "service.h" #include "../extra/xmlfunctions.h" #include "../../kmessdebug.h" #include <qhttp.h> // QHttp #include <qcstring.h> // QByteArray #include <kdebug.h> // kdDebug() #ifdef KMESSDEBUG_UPNP #define KMESSDEBUG_UPNP_GENERAL // #define KMESSDEBUG_UPNP_SOAP_RESPONSE #endif // 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(const QString &hostname, int port, const QString &informationUrl) : informationUrl_(informationUrl) , pendingRequests_(0) { http_ = new QHttp(hostname, port); connect(http_, SIGNAL( requestFinished(int,bool) ) , this, SLOT( slotRequestFinished(int,bool) ) ); #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: Created information service url='" << informationUrl_ << "'." << endl; #endif } // The constructor for action services Service::Service(const ServiceParameters ¶ms) : controlUrl_(params.controlUrl) , informationUrl_(params.scdpUrl) , pendingRequests_(0) , serviceId_(params.serviceId) { http_ = new QHttp(params.hostname, params.port); connect(http_, SIGNAL( requestFinished(int,bool) ) , this, SLOT( slotRequestFinished(int,bool) ) ); #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "CREATED UPnP::Service: url='" << controlUrl_ << "' id='" << serviceId_ << "'." << endl; #endif } // The destructor Service::~Service() { #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "DESTROYED UPnP::Service: url='" << controlUrl_ << "' id='" << serviceId_ << "'." << endl; #endif delete http_; } // Makes a UPnP action request // TODO: rename to callMethod / callSoapMethod int Service::callAction(const QString &actionName) { return callActionInternal(actionName, 0); } // Makes a UPnP action request int Service::callAction(const QString &actionName, const QMap<QString,QString> &arguments) { return callActionInternal(actionName, &arguments); } // Makes a UPnP action request (keeps pointers from the external interface) int Service::callActionInternal(const QString &actionName, const QMap<QString,QString> *arguments) { #ifdef KMESSTEST ASSERT( ! controlUrl_ .isEmpty() ); ASSERT( ! serviceId_ .isEmpty() ); ASSERT( ! actionName .isEmpty() ); #endif #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: calling remote prodecure '" << actionName << "'." << endl; #endif // Create the data message QString soapMessage = "<s:Envelope xmlns:s=\"http://schemas.xmlsoap.org/soap/envelope/\"" " s:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" "<s:Body>" "<u:" + actionName + " xmlns:u=\"" + serviceId_ + "\">"; // Do we have any arguments? if(arguments != 0) { // Add the arguments QMapConstIterator<QString,QString> it; for(it = arguments->begin(); it != arguments->end(); ++it) { QString argumentName = it.key(); soapMessage += "<" + argumentName + ">" + it.data() + "</" + argumentName + ">"; } } // Add the closing tags soapMessage += "</u:" + actionName + "></s:Body></s:Envelope>"; // Get an utf8 encoding string QCString content = soapMessage.utf8(); // Create the HTTP header QHttpRequestHeader header("POST", controlUrl_); header.setContentType("text/xml; charset=\"utf-8\""); header.setContentLength(content.size()); header.setValue("SoapAction", serviceId_ + "#" + actionName); // Send the POST request pendingRequests_++; return http_->request(header, content); } // Makes a UPnP service request // TODO: rename to downloadFile() int Service::callInformationUrl() { #ifdef KMESSTEST ASSERT( ! informationUrl_.isEmpty() ); #endif #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: requesting file '" << informationUrl_ << "'." << endl; #endif // Send the GET request // TODO: User-Agent: Mozilla/4.0 (compatible; UPnP/1.0; Windows NT/5.1) pendingRequests_++; return http_->get(informationUrl_); } // Get the number of pending requests int Service::getPendingRequests() const { return pendingRequests_; } // The control point received an action failure indication void Service::gotActionErrorResponse(const QDomNode &response) { #ifdef KMESSTEST ASSERT( response.nodeName() == "s:Fault"); #endif QString faultString = XmlFunctions::getNodeValue(response, "/faultstring"); QString errorCode = XmlFunctions::getNodeValue(response, "/detail/" + faultString + "/errorCode"); QString errorDescription = XmlFunctions::getNodeValue(response, "/detail/" + faultString + "/errorDescription"); kdWarning() << "UPnP::Service - Action failed: " << errorCode << " " << errorDescription << endl; } // The control point received a response to callAction() void Service::gotActionResponse(const QString &responseType, const QMap<QString,QString> &/*resultValues*/) { kdWarning() << "UPnP::Service - Action response '" << responseType << "' is not handled." << endl; } // The control point received a response to callInformationUrl() void Service::gotInformationResponse(const QDomNode &response) { QString rootTagName = response.nodeName(); kdWarning() << "UPnP::Service - Service response (with root '" << rootTagName << "') is not handled." << endl; } // The QHttp object retrieved data. void Service::slotRequestFinished(int /*id*/, bool error) { #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: Got HTTP response." << endl; #endif if(! error) { // Not sure why this happens if(http_->bytesAvailable() > 0) { // Get the XML content QByteArray response = http_->readAll(); QDomDocument xml; // TODO: handle 401 Authorisation required messages // Parse the XML QString errorMessage; error = ! xml.setContent(response, false, &errorMessage); if(! error) { // Determine how to process the data if(xml.namedItem("s:Envelope").isNull()) { #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: Plain XML detected, calling gotInformationResponse()." << endl; #endif // No SOAP envelope found, this is a normal response to callService() gotInformationResponse( xml.lastChild() ); } else { #ifdef KMESSDEBUG_UPNP_SOAP_RESPONSE kdDebug() << xml.toString() << endl; #endif // Got a SOAP message response to callAction() QDomNode resultNode = XmlFunctions::getNode(xml, "/s:Envelope/s:Body").firstChild(); error = (resultNode.nodeName() == "s:Fault"); if(! error) { if(resultNode.nodeName().startsWith("m:")) { #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: SOAP Envelope detected, calling gotActionResponse()." << endl; #endif // Action success, return SOAP body QMap<QString,QString> 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(uint 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 { #ifdef KMESSDEBUG_UPNP_GENERAL kdDebug() << "UPnP::Service: SOAP Error detected, calling gotActionResponse()." << endl; #endif // Action failed gotActionErrorResponse(resultNode); } } } else { kdWarning() << "UPnP::Service - XML Parsing failed: " << errorMessage << endl; } // Only emit when bytes>0 pendingRequests_--; emit queryFinished(error); } } else { kdWarning() << "UPnP::Service - HTTP Request failed: " << http_->errorString() << endl; pendingRequests_--; emit queryFinished(error); } #ifdef KMESSTEST ASSERT(pendingRequests_ >= 0); #endif } } // end of namespace #include "service.moc"