]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_http_client.cpp
0034aa246091368925a8042de3b54f0c3da397d5
[user/henk/code/inspircd.git] / src / modules / m_http_client.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd is copyright (C) 2002-2006 ChatSpike-Dev.
6  *                       E-mail:
7  *                <brain@chatspike.net>
8  *                <Craig@chatspike.net>
9  *     
10  * Written by Craig Edwards, Craig McLure, and others.
11  * This program is free but copyrighted software; see
12  *            the file COPYING for details.
13  *
14  * ---------------------------------------------------
15  */
16
17 /* Written by Special (john@yarbbles.com) */
18
19 #include "inspircd.h"
20 #include "httpclient.h"
21
22 /* $ModDesc: HTTP client service provider */
23
24 class URL
25 {
26  public:
27         std::string url;
28         std::string protocol, username, password, domain, request;
29         int port;
30 };
31
32 class HTTPSocket : public InspSocket
33 {
34  private:
35         InspIRCd *Server;
36         class ModuleHTTPClient *Mod;
37         HTTPClientRequest req;
38         HTTPClientResponse *response;
39         URL url;
40         enum { HTTP_CLOSED, HTTP_REQSENT, HTTP_HEADERS, HTTP_DATA } status;
41         std::string data;
42         std::string buffer;
43
44  public:
45         HTTPSocket(InspIRCd *Instance, class ModuleHTTPClient *Mod);
46         virtual ~HTTPSocket();
47         virtual bool DoRequest(HTTPClientRequest *req);
48         virtual bool ParseURL(const std::string &url);
49         virtual void Connect(const std::string &ip);
50         virtual bool OnConnected();
51         virtual bool OnDataReady();
52         virtual void OnClose();
53 };
54
55 class HTTPResolver : public Resolver
56 {
57  private:
58         HTTPSocket *socket;
59  public:
60         HTTPResolver(HTTPSocket *socket, InspIRCd *Instance, const string &hostname, bool &cached, Module* me) : Resolver(Instance, hostname, DNS_QUERY_FORWARD, cached, me), socket(socket)
61         {
62         }
63         
64         void OnLookupComplete(const string &result, unsigned int ttl, bool cached)
65         {
66                 socket->Connect(result);
67         }
68         
69         void OnError(ResolverError e, const string &errmsg)
70         {
71                 delete socket;
72         }
73 };
74
75 typedef vector<HTTPSocket*> HTTPList;
76
77 class ModuleHTTPClient : public Module
78 {
79  public:
80         HTTPList sockets;
81
82         ModuleHTTPClient(InspIRCd *Me)
83                 : Module::Module(Me)
84         {
85         }
86         
87         virtual ~ModuleHTTPClient()
88         {
89                 for (HTTPList::iterator i = sockets.begin(); i != sockets.end(); i++)
90                         delete *i;
91         }
92         
93         virtual Version GetVersion()
94         {
95                 return Version(1, 0, 0, 0, VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
96         }
97
98         void Implements(char* List)
99         {
100                 List[I_OnRequest] = 1;
101         }
102
103         char* OnRequest(Request *req)
104         {
105                 HTTPClientRequest *httpreq = (HTTPClientRequest *)req;
106                 if (!strcmp(httpreq->GetId(), HTTP_CLIENT_REQUEST))
107                 {
108                         HTTPSocket *sock = new HTTPSocket(ServerInstance, this);
109                         sock->DoRequest(httpreq);
110                         // No return value
111                 }
112                 return NULL;
113         }
114 };
115
116 HTTPSocket::HTTPSocket(InspIRCd *Instance, ModuleHTTPClient *Mod)
117                 : InspSocket(Instance), Server(Instance), Mod(Mod), status(HTTP_CLOSED)
118 {
119         this->ClosePending = false;
120         this->port = 80;
121 }
122
123 HTTPSocket::~HTTPSocket()
124 {
125         Close();
126         for (HTTPList::iterator i = Mod->sockets.begin(); i != Mod->sockets.end(); i++)
127         {
128                 if (*i == this)
129                 {
130                         Mod->sockets.erase(i);
131                         break;
132                 }
133         }
134 }
135
136 bool HTTPSocket::DoRequest(HTTPClientRequest *req)
137 {
138         /* Tweak by brain - we take a copy of this,
139          * so that the caller doesnt need to leave
140          * pointers knocking around, less chance of
141          * a memory leak.
142          */
143         this->req = *req;
144
145         if (!ParseURL(this->req.GetURL()))
146                 return false;
147         
148         this->port = url.port;
149         strlcpy(this->host, url.domain.c_str(), MAXBUF);
150
151         if (!insp_aton(this->host, &this->addy))
152         {
153                 bool cached;
154                 HTTPResolver* r = new HTTPResolver(this, Server, url.domain, cached, (Module*)Mod);
155                 Instance->AddResolver(r, cached);
156                 return true;
157         }
158         else
159         {
160                 this->Connect(url.domain);
161         }
162         
163         return true;
164 }
165
166 bool HTTPSocket::ParseURL(const std::string &iurl)
167 {
168         url.url = iurl;
169         url.port = 80;
170         
171         // Tokenize by slashes (protocol:, blank, domain, request..)
172         int pos = 0, pstart = 0, pend = 0;
173         
174         for (; ; pend = url.url.find('/', pstart))
175         {
176                 string part = url.url.substr(pstart, pend);
177
178                 switch (pos)
179                 {
180                         case 0:
181                                 // Protocol
182                                 if (part[part.length()-1] != ':')
183                                         return false;
184                                 url.protocol = part.substr(0, part.length() - 1);
185                                 break;
186                         case 1:
187                                 // Empty, skip
188                                 break;
189                         case 2:
190                                 // User and password (user:pass@)
191                                 string::size_type aend = part.find('@', 0);
192                                 if (aend != string::npos)
193                                 {
194                                         // Technically, it is valid to not have a password (username@domain)
195                                         string::size_type usrend = part.find(':', 0);
196                                         
197                                         if ((usrend != string::npos) && (usrend < aend))
198                                                 url.password = part.substr(usrend + 1, aend);
199                                         else
200                                                 usrend = aend;
201                                         
202                                         url.username = part.substr(0, usrend);
203                                 }
204                                 else
205                                         aend = 0;
206                                 
207                                 // Port (:port)
208                                 string::size_type dend = part.find(':', aend);
209                                 if (dend != string::npos)
210                                         url.port = atoi(part.substr(dend + 1).c_str());
211
212                                 // Domain
213                                 url.domain = part.substr(aend + 1, dend);
214                                 
215                                 // The rest of the string is the request
216                                 url.request = url.url.substr(pend);
217                                 break;
218                 }
219                 
220                 if (pos++ == 2)
221                         break;
222
223                 pstart = pend + 1;
224         }
225         
226         return true;
227 }
228
229 void HTTPSocket::Connect(const string &ip)
230 {
231         strlcpy(this->IP, ip.c_str(), MAXBUF);
232         
233         if (!this->DoConnect())
234         {
235                 delete this;
236         }
237 }
238
239 bool HTTPSocket::OnConnected()
240 {
241         std::string request = "GET " + url.request + " HTTP/1.1\r\n";
242
243         // Dump headers into the request
244         HeaderMap headers = req.GetHeaders();
245         
246         for (HeaderMap::iterator i = headers.begin(); i != headers.end(); i++)
247                 request += i->first + ": " + i->second + "\r\n";
248
249         // The Host header is required for HTTP 1.1 and isn't known when the request is created; if they didn't overload it
250         // manually, add it here
251         if (headers.find("Host") == headers.end())
252                 request += "Host: " + url.domain + "\r\n"; 
253         
254         request += "\r\n";
255         
256         this->status = HTTP_REQSENT;
257         
258         return this->Write(request);
259 }
260
261 bool HTTPSocket::OnDataReady()
262 {
263         char *data = this->Read();
264
265         if (!data)
266         {
267                 this->Close();
268                 return false;
269         }
270
271         if (this->status < HTTP_DATA)
272         {
273                 std::string line;
274                 std::string::size_type pos;
275
276                 this->buffer += data;
277                 while ((pos = buffer.find("\r\n")) != std::string::npos)
278                 {
279                         line = buffer.substr(0, pos);
280                         buffer = buffer.substr(pos + 2);
281                         if (line.empty())
282                         {
283                                 this->status = HTTP_DATA;
284                                 this->data += this->buffer;
285                                 this->buffer = "";
286                                 break;
287                         }
288 //              while ((line = buffer.sstrstr(data, "\r\n")) != NULL)
289 //              {
290 //                      if (strncmp(data, "\r\n", 2) == 0)
291                         
292                         if (this->status == HTTP_REQSENT)
293                         {
294                                 // HTTP reply (HTTP/1.1 200 msg)
295                                 char const* data = line.c_str();
296                                 data += 9;
297                                 response = new HTTPClientResponse((Module*)Mod, req.GetSource() , url.url, atoi(data), data + 4);
298                                 this->status = HTTP_HEADERS;
299                                 continue;
300                         }
301                         
302                         if ((pos = line.find(':')) != std::string::npos)
303                         {
304
305 //                      char *hdata = strchr(data, ':');
306                         
307 //                      if (!hdata)
308 //                              continue;
309                         
310 //                      *hdata = '\0';
311                         
312 //                      response->AddHeader(data, hdata + 2);
313                                 response->AddHeader(line.substr(0, pos), line.substr(pos + 1));
314                         
315 //                      data = lend + 2;
316                         } else
317                                 continue;
318                 }
319         } else {
320                 this->data += data;
321         }
322         return true;
323 }
324
325 void HTTPSocket::OnClose()
326 {
327         if (data.empty())
328                 return; // notification that request failed?
329
330         response->data = data;
331         response->Send();
332         delete response;
333 }
334
335 class ModuleHTTPClientFactory : public ModuleFactory
336 {
337  public:
338         ModuleHTTPClientFactory()
339         {
340         }
341         
342         ~ModuleHTTPClientFactory()
343         {
344         }
345         
346         Module *CreateModule(InspIRCd* Me)
347         {
348                 return new ModuleHTTPClient(Me);
349         }
350 };
351
352 extern "C" void *init_module(void)
353 {
354         return new ModuleHTTPClientFactory;
355 }