]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_http_client.cpp
OOPS! We try again, since I'm smoking craq. LF is 0x0a NOT CR.
[user/henk/code/inspircd.git] / src / modules / m_http_client.cpp
index 3f9875caf2d8da94193547af4a52e1bfc641ff2a..35b93b5818c480359e55e47133661ab567727b5d 100644 (file)
@@ -1 +1,346 @@
-/*       +------------------------------------+\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
\ No newline at end of file
+/*       +------------------------------------+
+ *       | Inspire Internet Relay Chat Daemon |
+ *       +------------------------------------+
+ *
+ *  InspIRCd: (C) 2002-2007 InspIRCd Development Team
+ * See: http://www.inspircd.org/wiki/index.php/Credits
+ *
+ * This program is free but copyrighted software; see
+ *            the file COPYING for details.
+ *
+ * ---------------------------------------------------
+ */
+
+#include "inspircd.h"
+#include "httpclient.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;
+       std::string buffer;
+
+ 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, bool &cached, Module* me) : Resolver(Instance, hostname, DNS_QUERY_FORWARD, cached, me), socket(socket)
+       {
+       }
+       
+       void OnLookupComplete(const string &result, unsigned int ttl, bool cached)
+       {
+               socket->Connect(result);
+       }
+       
+       void OnError(ResolverError e, const string &errmsg)
+       {
+               delete socket;
+       }
+};
+
+typedef vector<HTTPSocket*> HTTPList;
+
+class ModuleHTTPClient : public Module
+{
+ public:
+       HTTPList sockets;
+
+       ModuleHTTPClient(InspIRCd *Me)
+               : 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;
+               if (!strcmp(httpreq->GetId(), HTTP_CLIENT_REQUEST))
+               {
+                       HTTPSocket *sock = new HTTPSocket(ServerInstance, this);
+                       sock->DoRequest(httpreq);
+                       // No return value
+               }
+               return NULL;
+       }
+};
+
+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)
+{
+       /* Tweak by brain - we take a copy of this,
+        * so that the caller doesnt need to leave
+        * pointers knocking around, less chance of
+        * a memory leak.
+        */
+       this->req = *req;
+
+       if (!ParseURL(this->req.GetURL()))
+               return false;
+       
+       this->port = url.port;
+       strlcpy(this->host, url.domain.c_str(), MAXBUF);
+
+       in_addr addy1;
+#ifdef IPV6
+       in6_addr addy2;
+       if ((inet_aton(this->host, &addy1) > 0) || (inet_pton(AF_INET6, this->host, &addy2) > 0))
+#else
+       if (inet_aton(this->host, &addy1) > 0)
+#endif
+       {
+               bool cached;
+               HTTPResolver* r = new HTTPResolver(this, Server, url.domain, cached, (Module*)Mod);
+               Instance->AddResolver(r, cached);
+               return true;
+       }
+       else
+       {
+               this->Connect(url.domain);
+       }
+       
+       return true;
+}
+
+bool HTTPSocket::ParseURL(const std::string &iurl)
+{
+       url.url = iurl;
+       url.port = 80;
+       url.protocol = "http";
+
+       irc::sepstream tokenizer(iurl, '/');
+       
+       for (int p = 0;; p++)
+       {
+               std::string part = tokenizer.GetToken();
+               if (part.empty() && tokenizer.StreamEnd())
+                       break;
+               
+               if ((p == 0) && (part[part.length() - 1] == ':'))
+               {
+                       // Protocol ('http:')
+                       url.protocol = part.substr(0, part.length() - 1);
+               }
+               else if ((p == 1) && (part.empty()))
+               {
+                       continue;
+               }
+               else if (url.domain.empty())
+               {
+                       // Domain part: [user[:pass]@]domain[:port]
+                       std::string::size_type usrpos = part.find('@');
+                       if (usrpos != std::string::npos)
+                       {
+                               // Have a user (and possibly password) part
+                               std::string::size_type ppos = part.find(':');
+                               if ((ppos != std::string::npos) && (ppos < usrpos))
+                               {
+                                       // Have password too
+                                       url.password = part.substr(ppos + 1, usrpos - ppos - 1);
+                                       url.username = part.substr(0, ppos);
+                               }
+                               else
+                               {
+                                       url.username = part.substr(0, usrpos);
+                               }
+                               
+                               part = part.substr(usrpos + 1);
+                       }
+                       
+                       std::string::size_type popos = part.rfind(':');
+                       if (popos != std::string::npos)
+                       {
+                               url.port = atoi(part.substr(popos + 1).c_str());
+                               url.domain = part.substr(0, popos);
+                       }
+                       else
+                       {
+                               url.domain = part;
+                       }
+               }
+               else
+               {
+                       // Request (part of it)..
+                       url.request.append("/");
+                       url.request.append(part);
+               }
+       }
+       
+       if (url.request.empty())
+               url.request = "/";
+
+       if ((url.domain.empty()) || (!url.port) || (url.protocol.empty()))
+       {
+               Instance->Log(DEFAULT, "Invalid URL (%s): Missing required value", iurl.c_str());
+               return false;
+       }
+       
+       if (url.protocol != "http")
+       {
+               Instance->Log(DEFAULT, "Invalid URL (%s): Unsupported protocol '%s'", iurl.c_str(), url.protocol.c_str());
+               return false;
+       }
+       
+       return true;
+}
+
+void HTTPSocket::Connect(const string &ip)
+{
+       strlcpy(this->IP, ip.c_str(), MAXBUF);
+       
+       if (!this->DoConnect())
+       {
+               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;
+       }
+
+       if (this->status < HTTP_DATA)
+       {
+               std::string line;
+               std::string::size_type pos;
+
+               this->buffer += data;
+               while ((pos = buffer.find("\r\n")) != std::string::npos)
+               {
+                       line = buffer.substr(0, pos);
+                       buffer = buffer.substr(pos + 2);
+                       if (line.empty())
+                       {
+                               this->status = HTTP_DATA;
+                               this->data += this->buffer;
+                               this->buffer.clear();
+                               break;
+                       }
+
+                       if (this->status == HTTP_REQSENT)
+                       {
+                               // HTTP reply (HTTP/1.1 200 msg)
+                               char const* data = line.c_str();
+                               data += 9;
+                               response = new HTTPClientResponse((Module*)Mod, req.GetSource() , url.url, atoi(data), data + 4);
+                               this->status = HTTP_HEADERS;
+                               continue;
+                       }
+                       
+                       if ((pos = line.find(':')) != std::string::npos)
+                       {
+                               response->AddHeader(line.substr(0, pos), line.substr(pos + 1));
+                       }
+                       else
+                       {
+                               continue;
+                       }
+               }
+       }
+       else
+       {
+               this->data += data;
+       }
+       return true;
+}
+
+void HTTPSocket::OnClose()
+{
+       if (data.empty())
+               return; // notification that request failed?
+
+       response->data = data;
+       response->Send();
+       delete response;
+}
+
+MODULE_INIT(ModuleHTTPClient)