From e513c4c3f99154e2a98f7564600183323e5dffaf Mon Sep 17 00:00:00 2001 From: special Date: Sun, 7 Jan 2007 03:50:18 +0000 Subject: Added m_http_client - this is incomplete and won't work at all, don't try to use it git-svn-id: http://svn.inspircd.org/repository/trunk/inspircd@6243 e03df62e-2008-0410-955e-edbf42e46eb7 --- src/modules/httpclient.h | 121 +++++++++++++++ src/modules/m_http_client.cpp | 342 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 463 insertions(+) create mode 100644 src/modules/httpclient.h create mode 100644 src/modules/m_http_client.cpp diff --git a/src/modules/httpclient.h b/src/modules/httpclient.h new file mode 100644 index 000000000..8e1a314dd --- /dev/null +++ b/src/modules/httpclient.h @@ -0,0 +1,121 @@ +#include "base.h" + +#ifndef HTTPCLIENT_H__ +#define HTTPCLIENT_H__ + +#include +#include + +typedef std::map HeaderMap; + +/** This class represents an outgoing HTTP request + */ +class HTTPClientRequest : public classbase +{ + protected: + std::string url; + InspIRCd *Instance; + Module *src; + HeaderMap Headers; + public: + HTTPClientRequest(InspIRCd *Instance, Module *src, const std::string &url) + : url(url), Instance(Instance), src(src) + { + Headers["User-Agent"] = "InspIRCd (m_http_client.so)"; + Headers["Connection"] = "Close"; + Headers["Accept"] = "*/*"; + } + + const std::string &GetURL() + { + return url; + } + + Module *GetSrc() + { + return src; + } + + void AddHeader(std::string &header, std::string &data) + { + Headers[header] = data; + } + + void DeleteHeader(std::string &header) + { + Headers.erase(header); + } + + HeaderMap GetHeaders() + { + return Headers; + } + + void SendRequest() + { + Module *HTTPModule = Instance->FindModule("m_http_client.so"); + if (!HTTPModule) + { + Instance->Log(DEFAULT, "HTTP module not loaded!"); + return; + } + + Request req((char *)this, src, HTTPModule); + req.Send(); + } +}; + +class HTTPClientResponse : public classbase +{ + protected: + friend class HTTPSocket; + + std::string url; + std::string data; + int response; + std::string responsestr; + HeaderMap Headers; + public: + HTTPClientResponse(std::string &url, int response, std::string responsestr) + : url(url), response(response), responsestr(responsestr) + { + } + + void SetData(const std::string &ndata) + { + data = ndata; + } + + void AddHeader(const std::string &header, const std::string &data) + { + Headers[header] = data; + } + + const std::string &GetURL() + { + return url; + } + + const std::string &GetData() + { + return data; + } + + int GetResponse(std::string &str) + { + str = responsestr; + return response; + } + + std::string GetHeader(const std::string &header) + { + HeaderMap::iterator i = Headers.find(header); + + if (i != Headers.end()) + return i->second; + else + return ""; + } +}; + +#endif diff --git a/src/modules/m_http_client.cpp b/src/modules/m_http_client.cpp new file mode 100644 index 000000000..7e5791965 --- /dev/null +++ b/src/modules/m_http_client.cpp @@ -0,0 +1,342 @@ +/* +------------------------------------+ + * | Inspire Internet Relay Chat Daemon | + * +------------------------------------+ + * + * InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev. + * E-mail: + * + * + * + * Written by Craig Edwards, Craig McLure, and others. + * This program is free but copyrighted software; see + * the file COPYING for details. + * + * --------------------------------------------------- + */ + +/* Written by Special (john@yarbbles.com) */ + +#include "inspircd.h" +#include "http.h" + +/* $ModDesc: HTTP client service provider */ + +class URL +{ + public: + std::string url; + std::string protocol, username, password, domain, request; + int port; +}; + +class HTTPSocket : public InspSocket +{ + private: + InspIRCd *Server; + class ModuleHTTPClient *Mod; + HTTPClientRequest *req; + HTTPClientResponse *response; + URL url; + enum { HTTP_CLOSED, HTTP_REQSENT, HTTP_HEADERS, HTTP_DATA } status; + std::string data; + + public: + HTTPSocket(InspIRCd *Instance, class ModuleHTTPClient *Mod); + virtual ~HTTPSocket(); + virtual bool DoRequest(HTTPClientRequest *req); + virtual bool ParseURL(const std::string &url); + virtual void Connect(const std::string &ip); + virtual bool OnConnected(); + virtual bool OnDataReady(); + virtual void OnClose(); +}; + +class HTTPResolver : public Resolver +{ + private: + HTTPSocket *socket; + public: + HTTPResolver(HTTPSocket *socket, InspIRCd *Instance, const string &hostname) : Resolver(Instance, hostname, DNS_QUERY_FORWARD), socket(socket) + { + } + + void OnLookupComplete(const string &result) + { + socket->Connect(result); + } + + void OnError(ResolverError e, const string &errmsg) + { + delete socket; + } +}; + +typedef vector HTTPList; + +class ModuleHTTPClient : public Module +{ + public: + HTTPList sockets; + + ModuleHTTPClient(InspIRCd *Me) + : Module::Module(Me) + { + } + + virtual ~ModuleHTTPClient() + { + for (HTTPList::iterator i = sockets.begin(); i != sockets.end(); i++) + delete *i; + } + + virtual Version GetVersion() + { + return Version(1, 0, 0, 0, VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION); + } + + void Implements(char* List) + { + List[I_OnRequest] = 1; + } + + char *OnRequest(Request *req) + { + HTTPClientRequest *httpreq = (HTTPClientRequest *) req->GetData(); + HTTPSocket *sock = new HTTPSocket(ServerInstance, this); + sock->DoRequest(httpreq); + // No return value + return NULL; + } + + void SendReply(Module *to, HTTPClientResponse *data) + { + Request req((char *) data, this, to); + req.Send(); + } +}; + +HTTPSocket::HTTPSocket(InspIRCd *Instance, ModuleHTTPClient *Mod) + : InspSocket(Instance), Server(Instance), Mod(Mod), status(HTTP_CLOSED) +{ + this->ClosePending = false; + this->port = 80; +} + +HTTPSocket::~HTTPSocket() +{ + Close(); + for (HTTPList::iterator i = Mod->sockets.begin(); i != Mod->sockets.end(); i++) + { + if (*i == this) + { + Mod->sockets.erase(i); + break; + } + } +} + +bool HTTPSocket::DoRequest(HTTPClientRequest *req) +{ + this->req = req; + + if (!ParseURL(req->GetURL())) + return false; + + this->port = url.port; + strlcpy(this->host, url.domain.c_str(), MAXBUF); + + if (!inet_aton(this->host, &this->addy)) + { + new HTTPResolver(this, Server, url.domain); + return true; + } + else + { + this->Connect(url.domain); + } + + return true; +} + +bool HTTPSocket::ParseURL(const std::string &iurl) +{ + url.url = iurl; + url.port = 80; + + // Tokenize by slashes (protocol:, blank, domain, request..) + int pos = 0, pstart = 0, pend = 0; + + for (; ; pend = url.url.find('/', pstart)) + { + string part = url.url.substr(pstart, pend); + + switch (pos) + { + case 0: + // Protocol + if (part[part.length()-1] != ':') + return false; + url.protocol = part.substr(0, part.length() - 1); + break; + case 1: + // Empty, skip + break; + case 2: + // User and password (user:pass@) + string::size_type aend = part.find('@', 0); + if (aend != string::npos) + { + // Technically, it is valid to not have a password (username@domain) + string::size_type usrend = part.find(':', 0); + + if ((usrend != string::npos) && (usrend < aend)) + url.password = part.substr(usrend + 1, aend); + else + usrend = aend; + + url.username = part.substr(0, usrend); + } + else + aend = 0; + + // Port (:port) + string::size_type dend = part.find(':', aend); + if (dend != string::npos) + url.port = atoi(part.substr(dend + 1).c_str()); + + // Domain + url.domain = part.substr(aend + 1, dend); + + // The rest of the string is the request + url.request = url.url.substr(pend); + break; + } + + if (pos++ == 2) + break; + + pstart = pend + 1; + } + + return true; +} + +void HTTPSocket::Connect(const string &ip) +{ + strlcpy(this->IP, ip.c_str(), MAXBUF); + + if (!this->DoConnect()) + { + Server->Log(DEBUG, "Unable to connect HTTPSocket to %s", this->host); + delete this; + } +} + +bool HTTPSocket::OnConnected() +{ + std::string request = "GET " + url.request + " HTTP/1.1\r\n"; + + // Dump headers into the request + HeaderMap headers = req->GetHeaders(); + + for (HeaderMap::iterator i = headers.begin(); i != headers.end(); i++) + request += i->first + ": " + i->second + "\r\n"; + + // The Host header is required for HTTP 1.1 and isn't known when the request is created; if they didn't overload it + // manually, add it here + if (headers.find("Host") == headers.end()) + request += "Host: " + url.domain + "\r\n"; + + request += "\r\n"; + + this->status = HTTP_REQSENT; + + return this->Write(request); +} + +bool HTTPSocket::OnDataReady() +{ + char *data = this->Read(); + + if (!data) + { + this->Close(); + return false; + } + + // Needs buffering for incomplete reads.. + char *lend; + + if (this->status < HTTP_DATA) + { + while ((lend = strstr(data, "\r\n")) != NULL) + { + if (strncmp(data, "\r\n", 2) == 0) + { + this->status = HTTP_DATA; + break; + } + + *lend = '\0'; + + if (this->status == HTTP_REQSENT) + { + // HTTP reply (HTTP/1.1 200 msg) + data += 9; + response = new HTTPClientResponse(url.url, atoi(data), data + 4); + this->status = HTTP_HEADERS; + continue; + } + + char *hdata = strchr(data, ':'); + + if (!hdata) + continue; + + *hdata = '\0'; + + response->AddHeader(data, hdata + 2); + + data = lend + 2; + } + } + + this->data += data; + return true; +} + +void HTTPSocket::OnClose() +{ + if (!data.length()) + { + Server->Log(DEBUG, "HTTP socket closed unexpectedly (no content recieved)"); + return; + } + Server->Log(DEBUG, "Got file from HTTP successfully"); + response->data = data; + Mod->SendReply(req->GetSrc(), response); +} + +class ModuleHTTPClientFactory : public ModuleFactory +{ + public: + ModuleHTTPClientFactory() + { + } + + ~ModuleHTTPClientFactory() + { + } + + Module *CreateModule(InspIRCd* Me) + { + return new ModuleHTTPClient(Me); + } +}; + +extern "C" void *init_module(void) +{ + return new ModuleHTTPClientFactory; +} + + -- cgit v1.2.3