diff options
author | Slávek Banko <[email protected]> | 2013-07-27 16:34:45 +0200 |
---|---|---|
committer | Slávek Banko <[email protected]> | 2013-07-27 16:34:45 +0200 |
commit | d76ff81b7c1beffef0b84e570914c8f2d47834e6 (patch) | |
tree | 284b80ce7c5456fbb041f7979ac2c0baeead8902 /src/upnp | |
download | tork-d76ff81b7c1beffef0b84e570914c8f2d47834e6.tar.gz tork-d76ff81b7c1beffef0b84e570914c8f2d47834e6.zip |
Initial import of tork 0.33
Diffstat (limited to 'src/upnp')
-rw-r--r-- | src/upnp/Makefile.am | 14 | ||||
-rw-r--r-- | src/upnp/exitoperation.cpp | 47 | ||||
-rw-r--r-- | src/upnp/exitoperation.h | 67 | ||||
-rw-r--r-- | src/upnp/forwardportlist.cpp | 83 | ||||
-rw-r--r-- | src/upnp/forwardportlist.h | 101 | ||||
-rw-r--r-- | src/upnp/httprequest.cpp | 123 | ||||
-rw-r--r-- | src/upnp/httprequest.h | 98 | ||||
-rw-r--r-- | src/upnp/portlist.cpp | 73 | ||||
-rw-r--r-- | src/upnp/portlist.h | 103 | ||||
-rw-r--r-- | src/upnp/soap.cpp | 53 | ||||
-rw-r--r-- | src/upnp/soap.h | 62 | ||||
-rw-r--r-- | src/upnp/upnpdescriptionparser.cpp | 220 | ||||
-rw-r--r-- | src/upnp/upnpdescriptionparser.h | 49 | ||||
-rw-r--r-- | src/upnp/upnpmcastsocket.cpp | 313 | ||||
-rw-r--r-- | src/upnp/upnpmcastsocket.h | 91 | ||||
-rw-r--r-- | src/upnp/upnprouter.cpp | 531 | ||||
-rw-r--r-- | src/upnp/upnprouter.h | 303 |
17 files changed, 2331 insertions, 0 deletions
diff --git a/src/upnp/Makefile.am b/src/upnp/Makefile.am new file mode 100644 index 0000000..97a2708 --- /dev/null +++ b/src/upnp/Makefile.am @@ -0,0 +1,14 @@ +METASOURCES = AUTO +INCLUDES = $(all_includes) + +libktupnp_la_LDFLAGS = $(all_libraries) +noinst_LTLIBRARIES = libktupnp.la +libktupnp_la_SOURCES = soap.cpp upnpdescriptionparser.cpp upnpmcastsocket.cpp \ + upnprouter.cpp portlist.cpp httprequest.cpp exitoperation.cpp \ + forwardportlist.cpp + +noinst_HEADERS = upnpmcastsocket.h upnprouter.h \ + upnpdescriptionparser.h soap.h + + +KDE_CXXFLAGS = $(USE_EXCEPTIONS) $(USE_RTTI) diff --git a/src/upnp/exitoperation.cpp b/src/upnp/exitoperation.cpp new file mode 100644 index 0000000..8eedb7a --- /dev/null +++ b/src/upnp/exitoperation.cpp @@ -0,0 +1,47 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include "exitoperation.h" + +namespace kt +{ + + ExitOperation::ExitOperation() + {} + + + ExitOperation::~ExitOperation() + {} + + ExitJobOperation::ExitJobOperation(KIO::Job* j) + { + connect(j,SIGNAL(result(KIO::Job*)),this,SLOT(onResult( KIO::Job* ))); + } + + ExitJobOperation::~ExitJobOperation() + { + } + + void ExitJobOperation::onResult(KIO::Job* ) + { + operationFinished(this); + } + +} +#include "exitoperation.moc" diff --git a/src/upnp/exitoperation.h b/src/upnp/exitoperation.h new file mode 100644 index 0000000..edaa2fa --- /dev/null +++ b/src/upnp/exitoperation.h @@ -0,0 +1,67 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef KTEXITOPERATION_H +#define KTEXITOPERATION_H + +#include <qobject.h> +#include <kio/job.h> + +namespace kt +{ + + /** + * @author Joris Guisson <[email protected]> + * + * Object to derive from for operations which need to be performed at exit. + * The operation should emit the operationFinished signal when they are done. + * + * ExitOperation's can be used in combination with a WaitJob, to wait for a certain amount of time + * to give serveral ExitOperation's the time time to finish up. + */ + class ExitOperation : public QObject + { + Q_OBJECT + public: + ExitOperation(); + virtual ~ExitOperation(); + + /// wether or not we can do a deleteLater on the job after it has finished. + virtual bool deleteAllowed() const {return true;} + signals: + void operationFinished(kt::ExitOperation* opt); + }; + + /** + * Exit operation which waits for a KIO::Job + */ + class ExitJobOperation : public ExitOperation + { + Q_OBJECT + public: + ExitJobOperation(KIO::Job* j); + virtual ~ExitJobOperation(); + + virtual bool deleteAllowed() const {return false;} + private slots: + virtual void onResult(KIO::Job* j); + }; +} + +#endif diff --git a/src/upnp/forwardportlist.cpp b/src/upnp/forwardportlist.cpp new file mode 100644 index 0000000..906bde3 --- /dev/null +++ b/src/upnp/forwardportlist.cpp @@ -0,0 +1,83 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include "forwardportlist.h" +#include <kdebug.h> +namespace net +{ + ForwardPort::ForwardPort() : extnumber(0),intnumber(0),proto(TCP),forward(false) + { + } + + ForwardPort::ForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto,bool forward) + : extnumber(extnumber),intnumber(intnumber),proto(proto),forward(forward) + { + } + + ForwardPort::ForwardPort(const ForwardPort & p) : extnumber(p.extnumber), + intnumber(p.intnumber),proto(p.proto),forward(p.forward) + { + } + + + bool ForwardPort::operator == (const ForwardPort & p) const + { + return extnumber == p.extnumber && intnumber == p.intnumber && proto == p.proto; + } + + ForwardPortList::ForwardPortList() : lst(0) + {} + + + ForwardPortList::~ForwardPortList() + {} + + + void ForwardPortList::addNewForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto, + bool forward) + { + kdDebug() << "adding forward port" << endl; + + ForwardPort p = ForwardPort(extnumber,intnumber,proto,forward); + append(p); + if (lst) + lst->portAdded(p); + kdDebug() << "added forward port" << endl; + + } + + + void ForwardPortList::removeForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto) + { + kdDebug() << "removing forward port" << endl; + ForwardPortList::iterator itr = find(ForwardPort(extnumber,intnumber,proto,false)); + if (itr == end()) + return; + + if (lst) + lst->portRemoved(*itr); + + erase(itr); + kdDebug() << "removed forward port" << endl; + + } + + + +} diff --git a/src/upnp/forwardportlist.h b/src/upnp/forwardportlist.h new file mode 100644 index 0000000..2a27f59 --- /dev/null +++ b/src/upnp/forwardportlist.h @@ -0,0 +1,101 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef FORWARDPORTLIST_H +#define FORWARDPORTLIST_H + +#include <qvaluelist.h> +#include "../constants.h" +#include "portlist.h" + +namespace net +{ + + + struct ForwardPort + { + bt::Uint16 extnumber; + bt::Uint16 intnumber; + Protocol proto; + bool forward; + + ForwardPort(); + ForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto,bool forward); + ForwardPort(const ForwardPort & p); + + bool operator == (const ForwardPort & p) const; + }; + + /** + * Listener class for the ForwardPortList. + */ + class ForwardPortListener + { + public: + /** + * A port has been added. + * @param port The port + */ + virtual void portAdded(const ForwardPort & port) = 0; + + /** + * A port has been removed + * @param port The port + */ + virtual void portRemoved(const ForwardPort & port) = 0; + }; + + /** + * @author Joris Guisson <[email protected]> + * + * List of ports which are currently being used. + * + */ + class ForwardPortList : public QValueList<ForwardPort> + { + ForwardPortListener* lst; + public: + ForwardPortList(); + virtual ~ForwardPortList(); + + /** + * When a port is in use, this function needs to be called. + * @param number ForwardPort number + * @param proto Protocol + * @param forward Wether or not it needs to be forwarded + */ + void addNewForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto,bool forward); + + /** + * Needs to be called when a port is not being using anymore. + * @param number ForwardPort number + * @param proto Protocol + */ + void removeForwardPort(bt::Uint16 extnumber,bt::Uint16 intnumber,Protocol proto); + + /** + * Set the port listener. + * @param pl ForwardPort listener + */ + void setListener(ForwardPortListener* pl) {lst = pl;} + }; + +} + +#endif diff --git a/src/upnp/httprequest.cpp b/src/upnp/httprequest.cpp new file mode 100644 index 0000000..5a612b0 --- /dev/null +++ b/src/upnp/httprequest.cpp @@ -0,0 +1,123 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include <qstringlist.h> +#include "httprequest.h" +#include "../functions.h" +#include <kdebug.h> +#include <ksocks.h> + + +namespace bt +{ + + HTTPRequest::HTTPRequest(const QString & hdr,const QString & payload,const QString & host,Uint16 port,bool verbose, bool fwd) : hdr(hdr),payload(payload),verbose(verbose),fwd(fwd) + { + KSocks::self()->disableSocks(); + sock = new KNetwork::KStreamSocket(host,QString::number(port),this,0); + sock->enableRead(true); + sock->enableWrite(true); + sock->setTimeout(30000); + sock->setBlocking(false); + connect(sock,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + connect(sock,SIGNAL(gotError(int)),this,SLOT(onError(int ))); + connect(sock,SIGNAL(timedOut()),this,SLOT(onTimeout())); + connect(sock,SIGNAL(connected(const KResolverEntry&)), + this, SLOT(onConnect( const KResolverEntry& ))); + } + + + HTTPRequest::~HTTPRequest() + { + sock->close(); + delete sock; + } + + void HTTPRequest::start() + { + sock->connect(); + } + + void HTTPRequest::onConnect(const KResolverEntry&) + { + payload = payload.replace("$LOCAL_IP",sock->localAddress().nodeName()); + hdr = hdr.replace("$CONTENT_LENGTH",QString::number(payload.length())); + + QString req = hdr + payload; +/* if (verbose) + { + KdDebug() << "Sending " << endl; + KdDebug() << hdr << payload << endl; + }*/ + sock->writeBlock(req.ascii(),req.length()); + } + + void HTTPRequest::onReadyRead() + { + Uint32 ba = sock->bytesAvailable(); + if (ba == 0) + { + error(this,false); + sock->close(); + return; + } + + Array<char> data(ba); + ba = sock->readBlock(data,ba); + QString strdata((const char*)data); + QStringList sl = QStringList::split("\r\n",strdata,false); + +/* if (verbose) + { + KdDebug() << "Got reply : " << endl; + KdDebug() << strdata << endl; + }*/ + + if (sl.first().contains("HTTP") && sl.first().contains("200")) + { + // emit reply OK + replyOK(this,sl.last(),fwd); + } + else + { + // emit reply error + replyError(this,sl.last(),fwd); + } + operationFinished(this); + } + + void HTTPRequest::onError(int) + { + kdDebug() << "HTTPRequest error : " << sock->errorString() << endl; + error(this,false); + sock->close(); + operationFinished(this); + } + + void HTTPRequest::onTimeout() + { + kdDebug() << "HTTPRequest timeout" << endl; + error(this,true); + sock->close(); + operationFinished(this); + } + + +} +#include "httprequest.moc" diff --git a/src/upnp/httprequest.h b/src/upnp/httprequest.h new file mode 100644 index 0000000..9832da8 --- /dev/null +++ b/src/upnp/httprequest.h @@ -0,0 +1,98 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef BTHTTPREQUEST_H +#define BTHTTPREQUEST_H + +#include <qobject.h> +#include <kurl.h> +#include <kstreamsocket.h> +#include "exitoperation.h" +#include "../constants.h" + +using KNetwork::KResolverEntry; + +namespace bt +{ + + /** + * @author Joris Guisson + * + * Just create one, fill in the fields, + * connect to the right signals and forget about it. After the reply has been received or + * an error occurred, the appropriate signal will be emitted. + */ + class HTTPRequest : public kt::ExitOperation + { + Q_OBJECT + public: + /** + * Constructor, set the url and the request header. + * @param hdr The http request header + * @param payload The payload + * @param host The host + * @param port THe port + * @param verbose Print traffic to the log + */ + HTTPRequest(const QString & hdr,const QString & payload,const QString & host, + Uint16 port,bool verbose, bool fwd); + virtual ~HTTPRequest(); + + /** + * Open a connetion and send the request. + */ + void start(); + QString showPayload(){return payload;}; + signals: + /** + * An OK reply was sent. + * @param r The sender of the request + * @param data The data of the reply + */ + void replyOK(bt::HTTPRequest* r,const QString & data, bool fwd); + + /** + * Anything else but an 200 OK was sent. + * @param r The sender of the request + * @param data The data of the reply + */ + void replyError(bt::HTTPRequest* r,const QString & data, bool fwd); + + /** + * No reply was sent and an error or timeout occurred. + * @param r The sender of the request + * @param timeout Wether or not a timeout occurred + */ + void error(bt::HTTPRequest* r,bool timeout); + + private slots: + void onReadyRead(); + void onError(int); + void onTimeout(); + void onConnect(const KResolverEntry&); + + private: + KNetwork::KStreamSocket* sock; + QString hdr,payload; + bool verbose,fwd; + }; + +} + +#endif diff --git a/src/upnp/portlist.cpp b/src/upnp/portlist.cpp new file mode 100644 index 0000000..56076ed --- /dev/null +++ b/src/upnp/portlist.cpp @@ -0,0 +1,73 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include "portlist.h" + +namespace net +{ + Port::Port() : number(0),proto(TCP),forward(false) + { + } + + Port::Port(bt::Uint16 number,Protocol proto,bool forward) + : number(number),proto(proto),forward(forward) + { + } + + Port::Port(const Port & p) : number(p.number),proto(p.proto),forward(p.forward) + { + } + + bool Port::operator == (const Port & p) const + { + return number == p.number && proto == p.proto; + } + + PortList::PortList() : lst(0) + {} + + + PortList::~PortList() + {} + + + void PortList::addNewPort(bt::Uint16 number,Protocol proto,bool forward) + { + Port p = Port(number,proto,forward); + append(p); + if (lst) + lst->portAdded(p); + } + + + void PortList::removePort(bt::Uint16 number,Protocol proto) + { + PortList::iterator itr = find(Port(number,proto,false)); + if (itr == end()) + return; + + if (lst) + lst->portRemoved(*itr); + + erase(itr); + } + + + +} diff --git a/src/upnp/portlist.h b/src/upnp/portlist.h new file mode 100644 index 0000000..5063b78 --- /dev/null +++ b/src/upnp/portlist.h @@ -0,0 +1,103 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef NETPORTLIST_H +#define NETPORTLIST_H + +#include <qvaluelist.h> +#include "../constants.h" + +namespace net +{ + enum Protocol + { + TCP, + UDP + }; + + struct Port + { + bt::Uint16 number; + Protocol proto; + bool forward; + + Port(); + Port(bt::Uint16 number,Protocol proto,bool forward); + Port(const Port & p); + + bool operator == (const Port & p) const; + }; + + /** + * Listener class for the PortList. + */ + class PortListener + { + public: + /** + * A port has been added. + * @param port The port + */ + virtual void portAdded(const Port & port) = 0; + + /** + * A port has been removed + * @param port The port + */ + virtual void portRemoved(const Port & port) = 0; + }; + + /** + * @author Joris Guisson <[email protected]> + * + * List of ports which are currently being used. + * + */ + class PortList : public QValueList<Port> + { + PortListener* lst; + public: + PortList(); + virtual ~PortList(); + + /** + * When a port is in use, this function needs to be called. + * @param number Port number + * @param proto Protocol + * @param forward Wether or not it needs to be forwarded + */ + void addNewPort(bt::Uint16 number,Protocol proto,bool forward); + + /** + * Needs to be called when a port is not being using anymore. + * @param number Port number + * @param proto Protocol + */ + void removePort(bt::Uint16 number,Protocol proto); + + /** + * Set the port listener. + * @param pl Port listener + */ + void setListener(PortListener* pl) {lst = pl;} + }; + +} + +#endif diff --git a/src/upnp/soap.cpp b/src/upnp/soap.cpp new file mode 100644 index 0000000..b155b55 --- /dev/null +++ b/src/upnp/soap.cpp @@ -0,0 +1,53 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include "soap.h" + +namespace kt +{ + + QString SOAP::createCommand(const QString & action,const QString & service) + { + QString comm = QString("<?xml version=\"1.0\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\"SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:%1 xmlns:m=\"%2\"/>" + "</SOAP-ENV:Body></SOAP-ENV:Envelope>" + "\r\n").arg(action).arg(service); + + return comm; + } + + QString SOAP::createCommand(const QString & action,const QString & service,const QValueList<Arg> & args) + { + QString comm = QString("<?xml version=\"1.0\"?>\r\n" + "<SOAP-ENV:Envelope xmlns:SOAP-ENV=\"http://schemas.xmlsoap.org/soap/envelope/\" SOAP-ENV:encodingStyle=\"http://schemas.xmlsoap.org/soap/encoding/\">" + "<SOAP-ENV:Body>" + "<m:%1 xmlns:m=\"%2\">").arg(action).arg(service); + + for (QValueList<Arg>::const_iterator i = args.begin();i != args.end();i++) + { + const Arg & a = *i; + comm += "<" + a.element + ">" + a.value + "</" + a.element + ">"; + } + + comm += QString("</m:%1></SOAP-ENV:Body></SOAP-ENV:Envelope>\r\n").arg(action); + return comm; + } +} diff --git a/src/upnp/soap.h b/src/upnp/soap.h new file mode 100644 index 0000000..c11e2ed --- /dev/null +++ b/src/upnp/soap.h @@ -0,0 +1,62 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef KTSOAP_H +#define KTSOAP_H + +#include <qvaluelist.h> +#include <qstring.h> + +namespace kt +{ + + /** + @author Joris Guisson + */ + class SOAP + { + public: + + /** + * Create a simple UPnP SOAP command without parameters. + * @param action The name of the action + * @param service The name of the service + * @return The command + */ + static QString createCommand(const QString & action,const QString & service); + + struct Arg + { + QString element; + QString value; + }; + + /** + * Create a UPnP SOAP command with parameters. + * @param action The name of the action + * @param service The name of the service + * @param args Arguments for command + * @return The command + */ + static QString createCommand(const QString & action,const QString & service,const QValueList<Arg> & args); + }; + +} + +#endif diff --git a/src/upnp/upnpdescriptionparser.cpp b/src/upnp/upnpdescriptionparser.cpp new file mode 100644 index 0000000..f27b6cd --- /dev/null +++ b/src/upnp/upnpdescriptionparser.cpp @@ -0,0 +1,220 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include <qxml.h> +#include <qvaluestack.h> +// #include <util/fileops.h> +#include <kdebug.h> +// #include <torrent/globals.h> +#include "upnprouter.h" +#include "upnpdescriptionparser.h" + +using namespace bt; + +namespace kt +{ + + class XMLContentHandler : public QXmlDefaultHandler + { + enum Status + { + TOPLEVEL,ROOT,DEVICE,SERVICE,FIELD,OTHER + }; + + QString tmp; + UPnPRouter* router; + UPnPService curr_service; + QValueStack<Status> status_stack; + public: + XMLContentHandler(UPnPRouter* router); + virtual ~XMLContentHandler(); + + + bool startDocument(); + bool endDocument(); + bool startElement(const QString &, const QString & localName, const QString &, + const QXmlAttributes & atts); + bool endElement(const QString & , const QString & localName, const QString & ); + bool characters(const QString & ch); + + bool interestingDeviceField(const QString & name); + bool interestingServiceField(const QString & name); + }; + + + UPnPDescriptionParser::UPnPDescriptionParser() + {} + + + UPnPDescriptionParser::~UPnPDescriptionParser() + {} + + bool UPnPDescriptionParser::parse(const QString & file,UPnPRouter* router) + { + bool ret = true; + { + QFile fptr(file); + if (!fptr.open(IO_ReadOnly)) + return false; + + QXmlInputSource input(&fptr); + XMLContentHandler chandler(router); + QXmlSimpleReader reader; + + reader.setContentHandler(&chandler); + ret = reader.parse(&input,false); + } + + if (!ret) + { + kdDebug() << "Error parsing XML" << endl; + return false; + } + return true; + } + + ///////////////////////////////////////////////////////////////////////////////// + + + XMLContentHandler::XMLContentHandler(UPnPRouter* router) : router(router) + {} + + XMLContentHandler::~XMLContentHandler() + {} + + + bool XMLContentHandler::startDocument() + { + status_stack.push(TOPLEVEL); + return true; + } + + bool XMLContentHandler::endDocument() + { + status_stack.pop(); + return true; + } + + bool XMLContentHandler::interestingDeviceField(const QString & name) + { + return name == "friendlyName" || name == "manufacturer" || name == "modelDescription" || + name == "modelName" || name == "modelNumber"; + } + + + bool XMLContentHandler::interestingServiceField(const QString & name) + { + return name == "serviceType" || name == "serviceId" || name == "SCPDURL" || + name == "controlURL" || name == "eventSubURL"; + } + + bool XMLContentHandler::startElement(const QString &, const QString & localName, const QString &, + const QXmlAttributes & ) + { + tmp = ""; + switch (status_stack.top()) + { + case TOPLEVEL: + // from toplevel we can only go to root + if (localName == "root") + status_stack.push(ROOT); + else + return false; + break; + case ROOT: + // from the root we can go to device or specVersion + // we are not interested in the specVersion + if (localName == "device") + status_stack.push(DEVICE); + else + status_stack.push(OTHER); + break; + case DEVICE: + // see if it is a field we are interested in + if (interestingDeviceField(localName)) + status_stack.push(FIELD); + else + status_stack.push(OTHER); + break; + case SERVICE: + if (interestingServiceField(localName)) + status_stack.push(FIELD); + else + status_stack.push(OTHER); + break; + case OTHER: + if (localName == "service") + status_stack.push(SERVICE); + else if (localName == "device") + status_stack.push(DEVICE); + else + status_stack.push(OTHER); + break; + case FIELD: + break; + } + return true; + } + + bool XMLContentHandler::endElement(const QString & , const QString & localName, const QString & ) + { + switch (status_stack.top()) + { + case FIELD: + // we have a field so set it + status_stack.pop(); + if (status_stack.top() == DEVICE) + { + // if we are in a device + router->getDescription().setProperty(localName,tmp); + } + else if (status_stack.top() == SERVICE) + { + // set a property of a service + curr_service.setProperty(localName,tmp); + } + break; + case SERVICE: + // add the service + router->addService(curr_service); + curr_service.clear(); + // pop the stack + status_stack.pop(); + break; + default: + status_stack.pop(); + break; + } + + // reset tmp + tmp = ""; + return true; + } + + + bool XMLContentHandler::characters(const QString & ch) + { + if (ch.length() > 0) + { + tmp += ch; + } + return true; + } + +} diff --git a/src/upnp/upnpdescriptionparser.h b/src/upnp/upnpdescriptionparser.h new file mode 100644 index 0000000..5d4bf1e --- /dev/null +++ b/src/upnp/upnpdescriptionparser.h @@ -0,0 +1,49 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef KTUPNPDESCRIPTIONPARSER_H +#define KTUPNPDESCRIPTIONPARSER_H + +namespace kt +{ + class UPnPRouter; + + /** + * @author Joris Guisson + * + * Parses the xml description of a router. + */ + class UPnPDescriptionParser + { + public: + UPnPDescriptionParser(); + virtual ~UPnPDescriptionParser(); + + /** + * Parse the xml description. + * @param file File it is located in + * @param router The router off the xml description + * @return true upon success + */ + bool parse(const QString & file,UPnPRouter* router); + }; + +} + +#endif diff --git a/src/upnp/upnpmcastsocket.cpp b/src/upnp/upnpmcastsocket.cpp new file mode 100644 index 0000000..48159c2 --- /dev/null +++ b/src/upnp/upnpmcastsocket.cpp @@ -0,0 +1,313 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ + +#include <kurl.h> +#include <kdebug.h> +#include <unistd.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netinet/in_systm.h> +#include <netinet/ip.h> +#include <arpa/inet.h> +#include <netinet/ip.h> +#include <qstringlist.h> +#include <ksocketdevice.h> +#include <ksocketaddress.h> +/*#include <util/log.h> +#include <torrent/globals.h>*/ +#include <qfile.h> +#include <qtextstream.h> +#include "upnpmcastsocket.h" + + + +using namespace KNetwork; +using namespace bt; + +namespace kt +{ + + UPnPMCastSocket::UPnPMCastSocket(bool verbose) : verbose(verbose) + { + routers.setAutoDelete(true); + QObject::connect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + QObject::connect(this,SIGNAL(gotError(int)),this,SLOT(onError(int))); + setAddressReuseable(true); + setFamily(KNetwork::KResolver::IPv4Family); + setBlocking(true); + for (Uint32 i = 0;i < 10;i++) + { + if (!bind(QString::null,QString::number(1900 + i))) + kdDebug() << "Cannot bind to UDP port 1900" << endl; + else + break; + } + setBlocking(false); + joinUPnPMCastGroup(); + } + + + UPnPMCastSocket::~UPnPMCastSocket() + { + leaveUPnPMCastGroup(); + QObject::disconnect(this,SIGNAL(readyRead()),this,SLOT(onReadyRead())); + QObject::disconnect(this,SIGNAL(gotError(int)),this,SLOT(onError(int))); + } + + void UPnPMCastSocket::discover() + { + kdDebug() << "Trying to find UPnP devices on the local network" << endl; + + // send a HTTP M-SEARCH message to 239.255.255.250:1900 + const char* data = "M-SEARCH * HTTP/1.1\r\n" + "HOST: 239.255.255.250:1900\r\n" + "ST:urn:schemas-upnp-org:device:InternetGatewayDevice:1\r\n" + "MAN:\"ssdp:discover\"\r\n" + "MX:3\r\n" + "\r\n\0"; + + if (verbose) + { + kdDebug() << "Sending : " << endl; + kdDebug() << data << endl; + } + + KDatagramSocket::send(KNetwork::KDatagramPacket(data,strlen(data),KInetSocketAddress("239.255.255.250",1900))); + } + + void UPnPMCastSocket::onXmlFileDownloaded(UPnPRouter* r,bool success) + { + if (!success) + { + // we couldn't download and parse the XML file so + // get rid of it + r->deleteLater(); + } + else + { + // add it to the list and emit the signal + if (!routers.contains(r->getServer())) + { + routers.insert(r->getServer(),r); + discovered(r); + } + else + { + r->deleteLater(); + } + } + } + + void UPnPMCastSocket::onReadyRead() + { + if (bytesAvailable() == 0) + { + kdDebug() << "0 byte UDP packet " << endl; + // KDatagramSocket wrongly handles UDP packets with no payload + // so we need to deal with it oursleves + int fd = socketDevice()->socket(); + char tmp; + read(fd,&tmp,1); + return; + } + + KNetwork::KDatagramPacket p = KDatagramSocket::receive(); + if (p.isNull()) + return; + + if (verbose) + { + kdDebug() << "Received : " << endl; + kdDebug() << QString(p.data()) << endl; + } + + // try to make a router of it + UPnPRouter* r = parseResponse(p.data()); + if (r) + { + QObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )), + this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool ))); + + // download it's xml file + r->downloadXMLFile(); + } + } + + UPnPRouter* UPnPMCastSocket::parseResponse(const QByteArray & arr) + { + QStringList lines = QStringList::split("\r\n",QString(arr),false); + QString server; + KURL location; + + + kdDebug() << "Received : " << endl; + for (Uint32 idx = 0;idx < lines.count(); idx++) + kdDebug() << lines[idx] << endl; + + + // first read first line and see if contains a HTTP 200 OK message + QString line = lines.first(); + if (!line.contains("HTTP")) + { + // it is either a 200 OK or a NOTIFY + if (!line.contains("NOTIFY") && !line.contains("200")) + return 0; + } + else if (line.contains("M-SEARCH")) // ignore M-SEARCH + return 0; + + // quick check that the response being parsed is valid + bool validDevice = false; + for (Uint32 idx = 0;idx < lines.count() && !validDevice; idx++) + { + line = lines[idx]; + if ((line.contains("ST:") || line.contains("NT:")) && line.contains("InternetGatewayDevice")) + { + validDevice = true; + } + } + if (!validDevice) + { + kdDebug() << "Not a valid Internet Gateway Device" << endl; + return 0; + } + + // read all lines and try to find the server and location fields + for (Uint32 i = 1;i < lines.count();i++) + { + line = lines[i]; + if (line.startsWith("Location") || line.startsWith("LOCATION") || line.startsWith("location")) + { + location = line.mid(line.find(':') + 1).stripWhiteSpace(); + if (!location.isValid()) + return 0; + } + else if (line.startsWith("Server") || line.startsWith("server") || line.startsWith("SERVER")) + { + server = line.mid(line.find(':') + 1).stripWhiteSpace(); + if (server.length() == 0) + return 0; + + } + } + + if (routers.contains(server)) + { + return 0; + } + else + { + kdDebug() << "Detected IGD " << server << endl; + // everything OK, make a new UPnPRouter + return new UPnPRouter(server,location,verbose); + } + } + + void UPnPMCastSocket::onError(int) + { + kdDebug() << "UPnPMCastSocket Error : " << errorString() << endl; + } + + void UPnPMCastSocket::saveRouters(const QString & file) + { + QFile fptr(file); + if (!fptr.open(IO_WriteOnly)) + { + kdDebug() << "Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + // file format is simple : 2 lines per router, + // one containing the server, the other the location + QTextStream fout(&fptr); + bt::PtrMap<QString,UPnPRouter>::iterator i = routers.begin(); + while (i != routers.end()) + { + UPnPRouter* r = i->second; + fout << r->getServer() << endl; + fout << r->getLocation().prettyURL() << endl; + i++; + } + } + + void UPnPMCastSocket::loadRouters(const QString & file) + { + QFile fptr(file); + if (!fptr.open(IO_ReadOnly)) + { + kdDebug() << "Cannot open file " << file << " : " << fptr.errorString() << endl; + return; + } + + // file format is simple : 2 lines per router, + // one containing the server, the other the location + QTextStream fin(&fptr); + + while (!fin.atEnd()) + { + QString server, location; + server = fin.readLine(); + location = fin.readLine(); + if (!routers.contains(server)) + { + UPnPRouter* r = new UPnPRouter(server,location); + // download it's xml file + QObject::connect(r,SIGNAL(xmlFileDownloaded( UPnPRouter*, bool )),this,SLOT(onXmlFileDownloaded( UPnPRouter*, bool ))); + r->downloadXMLFile(); + } + } + } + + void UPnPMCastSocket::joinUPnPMCastGroup() + { + int fd = socketDevice()->socket(); + struct ip_mreq mreq; + + memset(&mreq,0,sizeof(struct ip_mreq)); + + inet_aton("239.255.255.250",&mreq.imr_multiaddr); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(fd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0) + { + kdDebug() << "Failed to join multicast group 239.255.255.250" << endl; + } + } + + void UPnPMCastSocket::leaveUPnPMCastGroup() + { + int fd = socketDevice()->socket(); + struct ip_mreq mreq; + + memset(&mreq,0,sizeof(struct ip_mreq)); + + inet_aton("239.255.255.250",&mreq.imr_multiaddr); + mreq.imr_interface.s_addr = htonl(INADDR_ANY); + + if (setsockopt(fd,IPPROTO_IP,IP_DROP_MEMBERSHIP,&mreq,sizeof(struct ip_mreq)) < 0) + { + kdDebug() << "Failed to leave multicast group 239.255.255.250" << endl; + } + } +} + + + +#include "upnpmcastsocket.moc" diff --git a/src/upnp/upnpmcastsocket.h b/src/upnp/upnpmcastsocket.h new file mode 100644 index 0000000..be75f4b --- /dev/null +++ b/src/upnp/upnpmcastsocket.h @@ -0,0 +1,91 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef KTUPNPMCASTSOCKET_H +#define KTUPNPMCASTSOCKET_H + +#include <kdatagramsocket.h> +#include "../constants.h" +#include "../functions.h" +#include "upnprouter.h" + +using bt::Uint32; + +namespace kt +{ + class UPnPRouter; + + /** + * @author Joris Guisson + * + * Socket used to discover UPnP devices. This class will keep track + * of all discovered devices. + */ + class UPnPMCastSocket : public KNetwork::KDatagramSocket + { + Q_OBJECT + public: + UPnPMCastSocket(bool verbose = false); + virtual ~UPnPMCastSocket(); + + /// Get the number of routers discovered + Uint32 getNumDevicesDiscovered() const {return routers.count();} + + /// Find a router using it's server name + UPnPRouter* findDevice(const QString & name) {return routers.find(name);} + + /// Save all routers to a file (for convenience at startup) + void saveRouters(const QString & file); + + /// Load all routers from a file + void loadRouters(const QString & file); + + public slots: + /** + * Try to discover a UPnP device on the network. + * A signal will be emitted when a device is found. + */ + void discover(); + + private slots: + void onReadyRead(); + void onError(int); + void onXmlFileDownloaded(UPnPRouter* r,bool success); + + signals: + /** + * Emitted when a router or internet gateway device is detected. + * @param router The router + */ + void discovered(kt::UPnPRouter* router); + + public: + UPnPRouter* parseResponse(const QByteArray & arr); + + private: + void joinUPnPMCastGroup(); + void leaveUPnPMCastGroup(); + + private: + bt::PtrMap<QString,UPnPRouter> routers; + bool verbose; + }; +} + +#endif diff --git a/src/upnp/upnprouter.cpp b/src/upnp/upnprouter.cpp new file mode 100644 index 0000000..4d8f5ad --- /dev/null +++ b/src/upnp/upnprouter.cpp @@ -0,0 +1,531 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#include <stdlib.h> +#include <klocale.h> +#include <kdebug.h> +#include <kglobal.h> +#include <kstandarddirs.h> +#include <qstringlist.h> +#include <kio/netaccess.h> +#include <kio/job.h> +#include "httprequest.h" +#include "upnprouter.h" +#include "upnpdescriptionparser.h" +#include "soap.h" +#include "../functions.h" +#include <fcntl.h> +#include <qdir.h> +#include <qfile.h> + +using namespace bt; +using namespace net; + +namespace kt +{ + UPnPService::UPnPService() + { + } + + UPnPService::UPnPService(const UPnPService & s) + { + this->servicetype = s.servicetype; + this->controlurl = s.controlurl; + this->eventsuburl = s.eventsuburl; + this->serviceid = s.serviceid; + this->scpdurl = s.scpdurl; + } + + void UPnPService::setProperty(const QString & name,const QString & value) + { + if (name == "serviceType") + servicetype = value; + else if (name == "controlURL") + controlurl = value; + else if (name == "eventSubURL") + eventsuburl = value; + else if (name == "SCPDURL") + scpdurl = value; + else if (name == "serviceId") + serviceid = value; + } + + void UPnPService::clear() + { + servicetype = controlurl = eventsuburl = scpdurl = serviceid = ""; + } + + void UPnPService::debugPrintData() + { + kdDebug() << " servicetype = " << servicetype << endl; + kdDebug() << " controlurl = " << controlurl << endl; + kdDebug() << " eventsuburl = " << eventsuburl << endl; + kdDebug() << " scpdurl = " << scpdurl << endl; + kdDebug() << " serviceid = " << serviceid << endl; + } + + UPnPService & UPnPService::operator = (const UPnPService & s) + { + this->servicetype = s.servicetype; + this->controlurl = s.controlurl; + this->eventsuburl = s.eventsuburl; + this->serviceid = s.serviceid; + this->scpdurl = s.scpdurl; + return *this; + } + + /////////////////////////////////////// + + void UPnPDeviceDescription::setProperty(const QString & name,const QString & value) + { + if (name == "friendlyName") + friendlyName = value; + else if (name == "manufacturer") + manufacturer = value; + else if (name == "modelDescription") + modelDescription = value; + else if (name == "modelName") + modelName = value; + else if (name == "modelNumber") + modelNumber == value; + } + + /////////////////////////////////////// + + UPnPRouter::UPnPRouter(const QString & server,const KURL & location,bool verbose) : server(server),location(location),verbose(verbose) + { + forwardedPortList = new ForwardPortList(); + // make the tmp_file unique, current time * a random number should be enough + tmp_file = QString("/tmp/tork_upnp_description-%1.xml").arg(bt::GetCurrentTime() * rand()); + } + + + UPnPRouter::~UPnPRouter() + { + QValueList<HTTPRequest*>::iterator i = active_reqs.begin(); + while (i != active_reqs.end()) + { + (*i)->deleteLater(); + i++; + } + } + + void UPnPRouter::addService(const UPnPService & s) + { + QValueList<UPnPService>::iterator i = services.begin(); + while (i != services.end()) + { + UPnPService & os = *i; + if (s.servicetype == os.servicetype) + return; + i++; + } + services.append(s); + } + + void UPnPRouter::downloadFinished(KIO::Job* j) + { + if (j->error()) + { + kdDebug() << "Failed to download " << location << " : " << j->errorString() << endl; + return; + } + + QString target = tmp_file; + // load in the file (target is always local) + UPnPDescriptionParser desc_parse; + bool ret = desc_parse.parse(target,this); + if (!ret) + { + kdDebug() << "Error parsing router description !" << endl; + QString dest = KGlobal::dirs()->saveLocation("data","tork") + "upnp_failure"; + KIO::file_copy(target,dest,-1,true,false,false); + } + else + { + if (verbose) + debugPrintData(); + } + xmlFileDownloaded(this,ret); + remove(QFile::encodeName(target)); + } + + void UPnPRouter::downloadXMLFile() + { + // downlaod XML description into a temporary file in /tmp + KIO::Job* job = KIO::file_copy(location,tmp_file,-1,true,false,false); + connect(job,SIGNAL(result(KIO::Job *)),this,SLOT(downloadFinished( KIO::Job* ))); + } + + void UPnPRouter::debugPrintData() + { + kdDebug() << "UPnPRouter : " << endl; + kdDebug() << "Friendly name = " << desc.friendlyName << endl; + kdDebug() << "Manufacterer = " << desc.manufacturer << endl; + kdDebug() << "Model description = " << desc.modelDescription << endl; + kdDebug() << "Model name = " << desc.modelName << endl; + kdDebug() << "Model number = " << desc.modelNumber << endl; + for (QValueList<UPnPService>::iterator i = services.begin();i != services.end();i++) + { + UPnPService & s = *i; + kdDebug() << "Service : " << endl; + s.debugPrintData(); + kdDebug() << "Done" << endl; + } + kdDebug() << "Done" << endl; + } + + + void UPnPRouter::forward(UPnPService* srv,const net::Port & externalport, + const net::Port & internalport) + { + + + // add all the arguments for the command + QValueList<SOAP::Arg> args; + SOAP::Arg a; + a.element = "NewRemoteHost"; + args.append(a); + + // the external port + a.element = "NewExternalPort"; + a.value = QString::number(externalport.number); + args.append(a); + + // the protocol + a.element = "NewProtocol"; + a.value = externalport.proto == TCP ? "TCP" : "UDP"; + args.append(a); + + // the local port + a.element = "NewInternalPort"; + if (internalport.number) + a.value = QString::number(internalport.number); + else + a.value = QString::number(externalport.number); + args.append(a); + + // the local IP address + a.element = "NewInternalClient"; + a.value = "$LOCAL_IP";// will be replaced by our local ip in bt::HTTPRequest + args.append(a); + + a.element = "NewEnabled"; + a.value = "1"; + args.append(a); + + a.element = "NewPortMappingDescription"; + static Uint32 cnt = 0; + a.value = QString("TorK UPNP %1").arg(cnt++); // TODO: change this + args.append(a); + + a.element = "NewLeaseDuration"; + a.value = "0"; + args.append(a); + + QString action = "AddPortMapping"; + QString comm = SOAP::createCommand(action,srv->servicetype,args); + + Forwarding fw = {externalport,internalport,0,srv}; + // erase old forwarding if one exists + QValueList<Forwarding>::iterator itr = fwds.begin(); + while (itr != fwds.end()) + { + Forwarding & fwo = *itr; + if (fwo.extport == externalport && fwo.intport == internalport && fwo.service == srv) + itr = fwds.erase(itr); + else + itr++; + } + + fw.pending_req = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl, true); + fwds.append(fw); + + //Track the forwarding request so we can know whether it was successful or not + //and keep a map of successfully forwarded ports in forwardedPorts + ForwardingRequest fwreq = {externalport,internalport,fw.pending_req}; + QValueList<ForwardingRequest>::iterator itrq = fwdreqs.begin(); + while (itrq != fwdreqs.end()) + { + ForwardingRequest & fwo = *itrq; + if (fwo.extport == externalport && fwo.intport == internalport) + itrq = fwdreqs.erase(itrq); + else + itrq++; + } + fwdreqs.append(fwreq); + + } + + void UPnPRouter::forward(const net::Port & externalport, + const net::Port & internalport, + bool force) + { + + if ((forwardedPortList->contains(net::ForwardPort(externalport.number, + internalport.number, + net::TCP,false))) + && (!force)) + return; + + kdDebug() << "Forwarding port " << externalport.number << " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl; + // first find the right service + QValueList<UPnPService>::iterator i = services.begin(); + while (i != services.end()) + { + UPnPService & s = *i; + if (s.servicetype == "urn:schemas-upnp-org:service:WANIPConnection:1" || + s.servicetype == "urn:schemas-upnp-org:service:WANPPPConnection:1") + { + if (internalport.number) + forward(&s,externalport,internalport); + else + forward(&s,externalport); + } + i++; + } + + } + + void UPnPRouter::undoForward(UPnPService* srv,const net::Port & extport, + const net::Port & intport,bt::WaitJob* waitjob) + { + // add all the arguments for the command + QValueList<SOAP::Arg> args; + SOAP::Arg a; + a.element = "NewRemoteHost"; + args.append(a); + + // the external port + a.element = "NewExternalPort"; + a.value = QString::number(extport.number); + args.append(a); + + // the protocol + a.element = "NewProtocol"; + a.value = extport.proto == TCP ? "TCP" : "UDP"; + args.append(a); + + + QString action = "DeletePortMapping"; + QString comm = SOAP::createCommand(action,srv->servicetype,args); + bt::HTTPRequest* r = sendSoapQuery(comm,srv->servicetype + "#" + action,srv->controlurl,waitjob != 0,false); + + ForwardingRequest fwreq = {extport,intport,r}; + QValueList<ForwardingRequest>::iterator itrq = fwdreqs.begin(); + while (itrq != fwdreqs.end()) + { + ForwardingRequest & fwo = *itrq; + if (fwo.extport == extport && fwo.intport == intport) + itrq = fwdreqs.erase(itrq); + else + itrq++; + } + fwdreqs.append(fwreq); + + + if (waitjob) + waitjob->addExitOperation(r); + + updateGUI(); + } + + + void UPnPRouter::undoForward(const net::Port & externalport, + const net::Port & ,bt::WaitJob* waitjob) + { + kdDebug() << "Undoing forward of port " << externalport.number + << " (" << (externalport.proto == UDP ? "UDP" : "TCP") << ")" << endl; + + QValueList<Forwarding>::iterator itr = fwds.begin(); + while (itr != fwds.end()) + { + Forwarding & wd = *itr; + if (wd.extport == externalport) + { + undoForward(wd.service,wd.extport,wd.intport,waitjob); + itr = fwds.erase(itr); + } + else + { + itr++; + } + } + } + + bt::HTTPRequest* UPnPRouter::sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl, bool fwd,bool at_exit) + { + // if port is not set, 0 will be returned + // thanks to Diego R. Brogna for spotting this bug + if (location.port()==0) + location.setPort(80); + + QString http_hdr = QString( + "POST %1 HTTP/1.1\r\n" + "HOST: %2:%3\r\n" + "Content-length: $CONTENT_LENGTH\r\n" + "Content-Type: text/xml\r\n" + "SOAPAction: \"%4\"\r\n" + "\r\n").arg(controlurl).arg(location.host()).arg(location.port()).arg(soapact); + + + HTTPRequest* r = new HTTPRequest(http_hdr,query,location.host(),location.port(),verbose, fwd); + connect(r,SIGNAL(replyError(bt::HTTPRequest* ,const QString& ,bool)), + this,SLOT(onReplyError(bt::HTTPRequest* ,const QString& ,bool))); + connect(r,SIGNAL(replyOK(bt::HTTPRequest* ,const QString& ,bool)), + this,SLOT(onReplyOK(bt::HTTPRequest* ,const QString& ,bool))); + connect(r,SIGNAL(error(bt::HTTPRequest*, bool )), + this,SLOT(onError(bt::HTTPRequest*, bool ))); + r->start(); + if (!at_exit) + active_reqs.append(r); + return r; + } + + void UPnPRouter::httpRequestDone(bt::HTTPRequest* r,bool erase_fwd) + { + QValueList<Forwarding>::iterator i = fwds.begin(); + while (i != fwds.end()) + { + Forwarding & fw = *i; + if (fw.pending_req == r) + { + fw.pending_req = 0; + if (erase_fwd) + fwds.erase(i); + break; + } + i++; + } + + updateGUI(); + active_reqs.remove(r); + r->deleteLater(); + } + + void UPnPRouter::onReplyOK(bt::HTTPRequest* r,const QString & s,bool fwd) + { + kdDebug() << "UPnPRouter : OK" << endl; + kdDebug() << "FWD : " << fwd << endl; + QValueList<ForwardingRequest>::iterator i = fwdreqs.begin(); + while (i != fwdreqs.end()) + { + ForwardingRequest & fw = *i; + if (fw.pending_req == r) + { + if (fwd) + forwardedPortList->addNewForwardPort(fw.extport.number, + fw.intport.number,net::TCP,false); + else + forwardedPortList->removeForwardPort(fw.extport.number, + fw.intport.number,net::TCP); + break; + } + i++; + } + + emit replyOK(this,r,s,fwd); + httpRequestDone(r,false); + } + + void UPnPRouter::onReplyError(bt::HTTPRequest* r,const QString & s,bool fwd) + { + if (verbose) + kdDebug() << "UPnPRouter : Error" << endl; + kdDebug() << r->showPayload() << endl; + httpRequestDone(r,true); + emit replyError(this,r,s,fwd); + } + + void UPnPRouter::onError(bt::HTTPRequest* r,bool) + { + httpRequestDone(r,true); + } + + +} + + +namespace bt +{ + + WaitJob::WaitJob(Uint32 millis) : KIO::Job(false) + { + connect(&timer,SIGNAL(timeout()),this,SLOT(timerDone())); + timer.start(millis,true); + } + + + WaitJob::~WaitJob() + {} + + void WaitJob::kill(bool) + { + m_error = 0; + emitResult(); + } + + void WaitJob::timerDone() + { + // set the error to null and emit the result + m_error = 0; + emitResult(); + } + + void WaitJob::addExitOperation(kt::ExitOperation* op) + { + exit_ops.append(op); + connect(op,SIGNAL(operationFinished( kt::ExitOperation* )), + this,SLOT(operationFinished( kt::ExitOperation* ))); + } + + void WaitJob::operationFinished(kt::ExitOperation* op) + { + if (exit_ops.count() > 0) + { + exit_ops.remove(op); + if (op->deleteAllowed()) + op->deleteLater(); + + if (exit_ops.count() == 0) + timerDone(); + } + } + + void WaitJob::execute(WaitJob* job) + { + KIO::NetAccess::synchronousRun(job,0); + } + + void SynchronousWait(Uint32 millis) + { + kdDebug() << "SynchronousWait" << endl; + WaitJob* j = new WaitJob(millis); + KIO::NetAccess::synchronousRun(j,0); + } + + +// void UpdateCurrentTime() +// { +// global_time_stamp = Now(); +// } + +} + +#include "upnprouter.moc" diff --git a/src/upnp/upnprouter.h b/src/upnp/upnprouter.h new file mode 100644 index 0000000..d717a49 --- /dev/null +++ b/src/upnp/upnprouter.h @@ -0,0 +1,303 @@ +/*************************************************************************** + * Copyright (C) 2005 by Joris Guisson * + * [email protected] * + * * + * 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. * + ***************************************************************************/ +#ifndef KTUPNPROUTER_H +#define KTUPNPROUTER_H + +#include <qtimer.h> +#include <kio/job.h> +#include <qvaluelist.h> +#include "exitoperation.h" +#include <kurl.h> +#include <qstringlist.h> +#include <kstreamsocket.h> +#include "portlist.h" +#include "forwardportlist.h" + +using bt::Uint16; + +namespace bt +{ + + /** + * @author Joris Guisson <[email protected]> + * + * Job to wait for a certain amount of time or until one or more ExitOperation's have + * finished. + */ + class WaitJob : public KIO::Job + { + Q_OBJECT + public: + WaitJob(Uint32 millis); + virtual ~WaitJob(); + + virtual void kill(bool quietly=true); + + /** + * Add an ExitOperation; + * @param op The operation + */ + void addExitOperation(kt::ExitOperation* op); + + + /** + * Execute a WaitJob + * @param job The Job + */ + static void execute(WaitJob* job); + + /// Are there any ExitOperation's we need to wait for + bool needToWait() const {return exit_ops.count() > 0;} + + private slots: + void timerDone(); + void operationFinished(kt::ExitOperation* op); + + private: + QTimer timer; + QValueList<kt::ExitOperation*> exit_ops; + }; + + void SynchronousWait(Uint32 millis); + + +} + +namespace bt +{ + class HTTPRequest; + class WaitJob; +} + +namespace net +{ + class ForwardPortList; +} +namespace KIO +{ + class Job; +} + +namespace kt +{ + + /** + * Structure describing a UPnP service found in an xml file. + */ + struct UPnPService + { + QString serviceid; + QString servicetype; + QString controlurl; + QString eventsuburl; + QString scpdurl; + + UPnPService(); + UPnPService(const UPnPService & s); + + /** + * Set a property of the service. + * @param name Name of the property (matches to variable names) + * @param value Value of the property + */ + void setProperty(const QString & name,const QString & value); + + /** + * Set all strings to empty. + */ + void clear(); + + /// Print the data of this service + void debugPrintData(); + + /** + * Assignment operator + * @param s The service to copy + * @return *this + */ + UPnPService & operator = (const UPnPService & s); + }; + + /** + * Struct to hold the description of a device + */ + struct UPnPDeviceDescription + { + QString friendlyName; + QString manufacturer; + QString modelDescription; + QString modelName; + QString modelNumber; + + /** + * Set a property of the description + * @param name Name of the property (matches to variable names) + * @param value Value of the property + */ + void setProperty(const QString & name,const QString & value); + }; + + /** + * @author Joris Guisson + * + * Class representing a UPnP enabled router. This class is also used to communicate + * with the router. + */ + class UPnPRouter : public QObject + { + Q_OBJECT + + public: + struct Forwarding + { + net::Port extport; + net::Port intport; + bt::HTTPRequest* pending_req; + UPnPService* service; + }; + + struct ForwardingRequest + { + net::Port extport; + net::Port intport; + bt::HTTPRequest* pending_req; + }; + + private: + QString server; + QString tmp_file; + KURL location; + UPnPDeviceDescription desc; + QValueList<UPnPService> services; + QValueList<Forwarding> fwds; + QValueList<ForwardingRequest> fwdreqs; + QValueList<bt::HTTPRequest*> active_reqs; + net::ForwardPortList* forwardedPortList; + + public: + /** + * Construct a router. + * @param server The name of the router + * @param location The location of it's xml description file + * @param verbose Print lots of debug info + */ + UPnPRouter(const QString & server,const KURL & location,bool verbose = false); + virtual ~UPnPRouter(); + + /// Get the name of the server + QString getServer() const {return server;} + + /// Get the location of it's xml description + KURL getLocation() const {return location;} + + /// Get the device description + UPnPDeviceDescription & getDescription() {return desc;} + + /// Get the device description (const version) + const UPnPDeviceDescription & getDescription() const {return desc;} + + /** + * Download the XML File of the router. + */ + void downloadXMLFile(); + + /** + * Add a service to the router. + * @param s The service + */ + void addService(const UPnPService & s); + +#if 0 + /** + * See if a port is forwarded + * @param port The Port + */ + void isPortForwarded(const net::Port & port); + + /** + * Get the external IP address. + */ + void getExternalIP(); +#endif + + /** + * Forward a local port + * @param port The local port to forward + */ + void forward(const net::Port & externalport, + const net::Port & internalport = net::Port::Port(), + bool force = false); + + /** + * Undo forwarding + * @param port The port + * @param waitjob When this is set the jobs needs to be added to the waitjob, + * so we can wait for their completeion at exit + */ + void undoForward(const net::Port & externalport,const net::Port & internalport, + bt::WaitJob* waitjob = 0); + + void debugPrintData(); + + QValueList<ForwardingRequest>::iterator beginReqMappings() {return fwdreqs.begin();} + QValueList<ForwardingRequest>::iterator endReqMappings() {return fwdreqs.end();} + QValueList<Forwarding>::iterator beginPortMappings() {return fwds.begin();} + QValueList<Forwarding>::iterator endPortMappings() {return fwds.end();} + net::ForwardPortList* forwardedPorts() {return forwardedPortList;} + private slots: + void onReplyOK(bt::HTTPRequest* r,const QString &,bool); + void onReplyError(bt::HTTPRequest* r,const QString &,bool); + void onError(bt::HTTPRequest* r,bool); + void downloadFinished(KIO::Job* j); + + + + signals: + /** + * Tell the GUI that it needs to be updated. + */ + void updateGUI(); + + /** + * Signal which indicates that the XML was downloaded successfully or not. + * @param r The router which emitted the signal + * @param success Wether or not it succeeded + */ + void xmlFileDownloaded(UPnPRouter* r,bool success); + + void replyOK(kt::UPnPRouter*,bt::HTTPRequest* ,const QString &,bool); + void replyError(kt::UPnPRouter*,bt::HTTPRequest* ,const QString &,bool); + + private: + QValueList<UPnPService>::iterator findPortForwardingService(); + + bt::HTTPRequest* sendSoapQuery(const QString & query,const QString & soapact,const QString & controlurl,bool fwd, bool at_exit = false ); + bool verbose; + + void forward(UPnPService* srv,const net::Port & externalport,const net::Port & internalport = net::Port::Port()); + void undoForward(UPnPService* srv,const net::Port & externalport,const net::Port & + internalport,bt::WaitJob* waitjob); + void httpRequestDone(bt::HTTPRequest* r,bool erase_fwd); + }; + +} + + +#endif |