]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_http_client.cpp
3f9875caf2d8da94193547af4a52e1bfc641ff2a
[user/henk/code/inspircd.git] / src / modules / m_http_client.cpp
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