/*************************************************************************** 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 "../../utils/xmlfunctions.h" #include "../../kmessdebug.h" #include <QHttp> #include <QByteArray> #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, quint16 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 kDebug() << "Created information service url='" << informationUrl_ << "'."; #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 kDebug() << "CREATED url='" << controlUrl_ << "' id='" << serviceId_ << "'."; #endif } // The destructor Service::~Service() { #ifdef KMESSDEBUG_UPNP_GENERAL kDebug() << "DESTROYED. [url=" << controlUrl_ << ", id=" << serviceId_ << "]"; #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 KMESS_ASSERT( ! controlUrl_ .isEmpty() ); KMESS_ASSERT( ! serviceId_ .isEmpty() ); KMESS_ASSERT( ! actionName .isEmpty() ); #endif #ifdef KMESSDEBUG_UPNP_GENERAL kDebug() << "calling remote procedure '" << actionName << "'."; #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 QMap<QString,QString>::ConstIterator it; for(it = arguments->begin(); it != arguments->end(); ++it) { QString argumentName( it.key() ); soapMessage += "<" + argumentName + ">" + it.value() + "</" + argumentName + ">"; } } // Add the closing tags soapMessage += "</u:" + actionName + "></s:Body></s:Envelope>"; // Get an utf8 encoding string QByteArray content = soapMessage.toUtf8(); // 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 KMESS_ASSERT( ! informationUrl_.isEmpty() ); #endif #ifdef KMESSDEBUG_UPNP_GENERAL kDebug() << "requesting file '" << informationUrl_ << "'."; #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 KMESS_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" ) ); kWarning() << "Action failed: " << errorCode << " " << errorDescription; } // The control point received a response to callAction() void Service::gotActionResponse(const QString &responseType, const QMap<QString,QString> &/*resultValues*/) { kWarning() << "Action response '" << responseType << "' is not handled."; } // The control point received a response to callInformationUrl() void Service::gotInformationResponse(const QDomNode &response) { QString rootTagName( response.nodeName() ); kWarning() << "Service response (with root '" << rootTagName << "') is not handled."; } // The QHttp object retrieved data. void Service::slotRequestFinished(int /*id*/, bool error) { #ifdef KMESSDEBUG_UPNP_GENERAL kDebug() << "Got HTTP response."; #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 kDebug() << "Plain XML detected, calling gotInformationResponse()."; #endif // No SOAP envelope found, this is a normal response to callService() gotInformationResponse( xml.lastChild() ); } else { #ifdef KMESSDEBUG_UPNP_SOAP_RESPONSE kDebug() << xml.toString(); #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 kDebug() << "SOAP Envelope detected, calling gotActionResponse()."; #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( 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 { #ifdef KMESSDEBUG_UPNP_GENERAL kDebug() << "SOAP Error detected, calling gotActionResponse()."; #endif // Action failed gotActionErrorResponse(resultNode); } } } else { kWarning() << "XML Parsing failed: " << errorMessage; } // Only emit when bytes>0 pendingRequests_--; emit queryFinished(error); } } else { kWarning() << "HTTP Request failed: " << http_->errorString(); pendingRequests_--; emit queryFinished(error); } #ifdef KMESSTEST KMESS_ASSERT(pendingRequests_ >= 0); #endif } } // end of namespace #include "service.moc"