1 /* +------------------------------------+
\r * | Inspire Internet Relay Chat Daemon |
\r * +------------------------------------+
\r *
\r * InspIRCd: (C) 2002-2007 InspIRCd Development Team
\r * See: http://www.inspircd.org/wiki/index.php/Credits
\r *
\r * This program is free but copyrighted software; see
\r * the file COPYING for details.
\r *
\r * ---------------------------------------------------
\r */
\r\r#include "inspircd.h"
\r#include "httpclient.h"
\r\r/* $ModDesc: HTTP client service provider */
\r\rclass URL
\r{
\r public:
\r std::string url;
\r std::string protocol, username, password, domain, request;
\r int port;
\r};
\r\rclass HTTPSocket : public InspSocket
\r{
\r private:
\r InspIRCd *Server;
\r class ModuleHTTPClient *Mod;
\r HTTPClientRequest req;
\r HTTPClientResponse *response;
\r URL url;
\r enum { HTTP_CLOSED, HTTP_REQSENT, HTTP_HEADERS, HTTP_DATA } status;
\r std::string data;
\r std::string buffer;
\r\r public:
\r HTTPSocket(InspIRCd *Instance, class ModuleHTTPClient *Mod);
\r virtual ~HTTPSocket();
\r virtual bool DoRequest(HTTPClientRequest *req);
\r virtual bool ParseURL(const std::string &url);
\r virtual void Connect(const std::string &ip);
\r virtual bool OnConnected();
\r virtual bool OnDataReady();
\r virtual void OnClose();
\r};
\r\rclass HTTPResolver : public Resolver
\r{
\r private:
\r HTTPSocket *socket;
\r public:
\r HTTPResolver(HTTPSocket *socket, InspIRCd *Instance, const string &hostname, bool &cached, Module* me) : Resolver(Instance, hostname, DNS_QUERY_FORWARD, cached, me), socket(socket)
\r {
\r }
\r \r void OnLookupComplete(const string &result, unsigned int ttl, bool cached)
\r {
\r socket->Connect(result);
\r }
\r \r void OnError(ResolverError e, const string &errmsg)
\r {
\r delete socket;
\r }
\r};
\r\rtypedef vector<HTTPSocket*> HTTPList;
\r\rclass ModuleHTTPClient : public Module
\r{
\r public:
\r HTTPList sockets;
\r\r ModuleHTTPClient(InspIRCd *Me)
\r : Module(Me)
\r {
\r }
\r \r virtual ~ModuleHTTPClient()
\r {
\r for (HTTPList::iterator i = sockets.begin(); i != sockets.end(); i++)
\r delete *i;
\r }
\r \r virtual Version GetVersion()
\r {
\r return Version(1, 0, 0, 0, VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
\r }
\r\r void Implements(char* List)
\r {
\r List[I_OnRequest] = 1;
\r }
\r\r char* OnRequest(Request *req)
\r {
\r HTTPClientRequest *httpreq = (HTTPClientRequest *)req;
\r if (!strcmp(httpreq->GetId(), HTTP_CLIENT_REQUEST))
\r {
\r HTTPSocket *sock = new HTTPSocket(ServerInstance, this);
\r sock->DoRequest(httpreq);
\r // No return value
\r }
\r return NULL;
\r }
\r};
\r\rHTTPSocket::HTTPSocket(InspIRCd *Instance, ModuleHTTPClient *Mod)
\r : InspSocket(Instance), Server(Instance), Mod(Mod), status(HTTP_CLOSED)
\r{
\r this->ClosePending = false;
\r this->port = 80;
\r}
\r\rHTTPSocket::~HTTPSocket()
\r{
\r Close();
\r for (HTTPList::iterator i = Mod->sockets.begin(); i != Mod->sockets.end(); i++)
\r {
\r if (*i == this)
\r {
\r Mod->sockets.erase(i);
\r break;
\r }
\r }
\r}
\r\rbool HTTPSocket::DoRequest(HTTPClientRequest *req)
\r{
\r /* Tweak by brain - we take a copy of this,
\r * so that the caller doesnt need to leave
\r * pointers knocking around, less chance of
\r * a memory leak.
\r */
\r this->req = *req;
\r\r if (!ParseURL(this->req.GetURL()))
\r return false;
\r \r this->port = url.port;
\r strlcpy(this->host, url.domain.c_str(), MAXBUF);
\r\r in_addr addy1;
\r#ifdef IPV6
\r in6_addr addy2;
\r if ((inet_aton(this->host, &addy1) > 0) || (inet_pton(AF_INET6, this->host, &addy2) > 0))
\r#else
\r if (inet_aton(this->host, &addy1) > 0)
\r#endif
\r {
\r bool cached;
\r HTTPResolver* r = new HTTPResolver(this, Server, url.domain, cached, (Module*)Mod);
\r Instance->AddResolver(r, cached);
\r return true;
\r }
\r else
\r {
\r this->Connect(url.domain);
\r }
\r \r return true;
\r}
\r\rbool HTTPSocket::ParseURL(const std::string &iurl)
\r{
\r url.url = iurl;
\r url.port = 80;
\r url.protocol = "http";
\r\r irc::sepstream tokenizer(iurl, '/');
\r \r for (int p = 0;; p++)
\r {
\r std::string part = tokenizer.GetToken();
\r if (part.empty() && tokenizer.StreamEnd())
\r break;
\r \r if ((p == 0) && (part[part.length() - 1] == ':'))
\r {
\r // Protocol ('http:')
\r url.protocol = part.substr(0, part.length() - 1);
\r }
\r else if ((p == 1) && (part.empty()))
\r {
\r continue;
\r }
\r else if (url.domain.empty())
\r {
\r // Domain part: [user[:pass]@]domain[:port]
\r std::string::size_type usrpos = part.find('@');
\r if (usrpos != std::string::npos)
\r {
\r // Have a user (and possibly password) part
\r std::string::size_type ppos = part.find(':');
\r if ((ppos != std::string::npos) && (ppos < usrpos))
\r {
\r // Have password too
\r url.password = part.substr(ppos + 1, usrpos - ppos - 1);
\r url.username = part.substr(0, ppos);
\r }
\r else
\r {
\r url.username = part.substr(0, usrpos);
\r }
\r \r part = part.substr(usrpos + 1);
\r }
\r \r std::string::size_type popos = part.rfind(':');
\r if (popos != std::string::npos)
\r {
\r url.port = atoi(part.substr(popos + 1).c_str());
\r url.domain = part.substr(0, popos);
\r }
\r else
\r {
\r url.domain = part;
\r }
\r }
\r else
\r {
\r // Request (part of it)..
\r url.request.append("/");
\r url.request.append(part);
\r }
\r }
\r \r if (url.request.empty())
\r url.request = "/";
\r\r if ((url.domain.empty()) || (!url.port) || (url.protocol.empty()))
\r {
\r Instance->Log(DEFAULT, "Invalid URL (%s): Missing required value", iurl.c_str());
\r return false;
\r }
\r \r if (url.protocol != "http")
\r {
\r Instance->Log(DEFAULT, "Invalid URL (%s): Unsupported protocol '%s'", iurl.c_str(), url.protocol.c_str());
\r return false;
\r }
\r \r return true;
\r}
\r\rvoid HTTPSocket::Connect(const string &ip)
\r{
\r strlcpy(this->IP, ip.c_str(), MAXBUF);
\r \r if (!this->DoConnect())
\r {
\r delete this;
\r }
\r}
\r\rbool HTTPSocket::OnConnected()
\r{
\r std::string request = "GET " + url.request + " HTTP/1.1\r\n";
\r\r // Dump headers into the request
\r HeaderMap headers = req.GetHeaders();
\r \r for (HeaderMap::iterator i = headers.begin(); i != headers.end(); i++)
\r request += i->first + ": " + i->second + "\r\n";
\r\r // The Host header is required for HTTP 1.1 and isn't known when the request is created; if they didn't overload it
\r // manually, add it here
\r if (headers.find("Host") == headers.end())
\r request += "Host: " + url.domain + "\r\n";
\r \r request += "\r\n";
\r \r this->status = HTTP_REQSENT;
\r \r return this->Write(request);
\r}
\r\rbool HTTPSocket::OnDataReady()
\r{
\r char *data = this->Read();
\r\r if (!data)
\r {
\r this->Close();
\r return false;
\r }
\r\r if (this->status < HTTP_DATA)
\r {
\r std::string line;
\r std::string::size_type pos;
\r\r this->buffer += data;
\r while ((pos = buffer.find("\r\n")) != std::string::npos)
\r {
\r line = buffer.substr(0, pos);
\r buffer = buffer.substr(pos + 2);
\r if (line.empty())
\r {
\r this->status = HTTP_DATA;
\r this->data += this->buffer;
\r this->buffer.clear();
\r break;
\r }
\r\r if (this->status == HTTP_REQSENT)
\r {
\r // HTTP reply (HTTP/1.1 200 msg)
\r char const* data = line.c_str();
\r data += 9;
\r response = new HTTPClientResponse((Module*)Mod, req.GetSource() , url.url, atoi(data), data + 4);
\r this->status = HTTP_HEADERS;
\r continue;
\r }
\r \r if ((pos = line.find(':')) != std::string::npos)
\r {
\r response->AddHeader(line.substr(0, pos), line.substr(pos + 1));
\r }
\r else
\r {
\r continue;
\r }
\r }
\r }
\r else
\r {
\r this->data += data;
\r }
\r return true;
\r}
\r\rvoid HTTPSocket::OnClose()
\r{
\r if (data.empty())
\r return; // notification that request failed?
\r\r response->data = data;
\r response->Send();
\r delete response;
\r}
\r\rMODULE_INIT(ModuleHTTPClient)
\r