]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_http_client.cpp
4cb7b28480a230be515bcbf41c6c8c1691f28058
[user/henk/code/inspircd.git] / src / modules / m_http_client.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2008 InspIRCd Development Team
6  * See: http://www.inspircd.org/wiki/index.php/Credits
7  *
8  * This program is free but copyrighted software; see
9  *            the file COPYING for details.
10  *
11  * ---------------------------------------------------
12  */
13
14 #include "inspircd.h"
15 #include "httpclient.h"
16
17 /* $ModDesc: HTTP client service provider */
18
19 class URL
20 {
21  public:
22         std::string url;
23         std::string protocol, username, password, domain, request;
24         int port;
25 };
26
27 class HTTPSocket : public BufferedSocket
28 {
29  private:
30         InspIRCd *Server;
31         class ModuleHTTPClient *Mod;
32         HTTPClientRequest req;
33         HTTPClientResponse *response;
34         URL url;
35         enum { HTTP_CLOSED, HTTP_REQSENT, HTTP_HEADERS, HTTP_DATA } status;
36         std::string data;
37         std::string buffer;
38         bool closed;
39
40  public:
41         HTTPSocket(InspIRCd *Instance, class ModuleHTTPClient *Mod);
42         virtual ~HTTPSocket();
43         virtual bool DoRequest(HTTPClientRequest *req);
44         virtual bool ParseURL(const std::string &url);
45         virtual void Connect(const std::string &ip);
46         virtual bool OnConnected();
47         virtual bool OnDataReady();
48         virtual void OnClose();
49 };
50
51 class HTTPResolver : public Resolver
52 {
53  private:
54         HTTPSocket *socket;
55         std::string orig;
56  public:
57         HTTPResolver(HTTPSocket *s, InspIRCd *Instance, const std::string &hostname, bool &cached, Module* me) : Resolver(Instance, hostname, DNS_QUERY_FORWARD, cached, me), socket(s)
58         {
59                 ServerInstance->Log(DEBUG,">>>>>>>>>>>>>>>>>> HTTPResolver::HTTPResolver <<<<<<<<<<<<<<<");
60                 orig = hostname;
61         }
62         
63         void OnLookupComplete(const std::string &result, unsigned int ttl, bool cached, int resultnum = 0)
64         {
65                 ServerInstance->Log(DEBUG,"************* HTTPResolver::OnLookupComplete ***************");
66                 if (!resultnum)
67                         socket->Connect(result);
68                 else
69                         socket->OnClose();
70         }
71         
72         void OnError(ResolverError e, const std::string &errmsg)
73         {
74                 ServerInstance->Log(DEBUG,"!!!!!!!!!!!!!!!! HTTPResolver::OnError: %s", errmsg.c_str());
75                 socket->OnClose();
76         }
77 };
78
79 typedef std::vector<HTTPSocket*> HTTPList;
80
81 class ModuleHTTPClient : public Module
82 {
83  public:
84         HTTPList sockets;
85
86         ModuleHTTPClient(InspIRCd *Me)
87                 : Module(Me)
88         {
89                 Implementation eventlist[] = { I_OnRequest };
90                 ServerInstance->Modules->Attach(eventlist, this, 1);
91         }
92         
93         virtual ~ModuleHTTPClient()
94         {
95                 for (HTTPList::iterator i = sockets.begin(); i != sockets.end(); i++)
96                         (*i)->Close();
97                 ServerInstance->BufferedSocketCull();
98         }
99         
100         virtual Version GetVersion()
101         {
102                 return Version(1, 0, 0, 0, VF_SERVICEPROVIDER | VF_VENDOR, API_VERSION);
103         }
104
105
106         virtual const char* OnRequest(Request *req)
107         {
108                 HTTPClientRequest *httpreq = (HTTPClientRequest *)req;
109                 if (!strcmp(httpreq->GetId(), HTTP_CLIENT_REQUEST))
110                 {
111                         HTTPSocket *sock = new HTTPSocket(ServerInstance, this);
112                         sock->DoRequest(httpreq);
113                         // No return value
114                 }
115                 return NULL;
116         }
117 };
118
119 HTTPSocket::HTTPSocket(InspIRCd *SI, ModuleHTTPClient *m)
120                 : BufferedSocket(SI), Server(SI), Mod(m), status(HTTP_CLOSED)
121 {
122         Instance->Log(DEBUG,"HTTPSocket::HTTPSocket");
123         this->port = 80;
124         response = NULL;
125         closed = false;
126         timeout_val = 10;
127 }
128
129 HTTPSocket::~HTTPSocket()
130 {
131         Close();
132         for (HTTPList::iterator i = Mod->sockets.begin(); i != Mod->sockets.end(); i++)
133         {
134                 if (*i == this)
135                 {
136                         Mod->sockets.erase(i);
137                         break;
138                 }
139         }
140 }
141
142 bool HTTPSocket::DoRequest(HTTPClientRequest *request)
143 {
144         Instance->Log(DEBUG,"HTTPSocket::DoRequest");
145         /* Tweak by brain - we take a copy of this,
146          * so that the caller doesnt need to leave
147          * pointers knocking around, less chance of
148          * a memory leak.
149          */
150         this->req = *request;
151
152         if (!ParseURL(this->req.GetURL()))
153                 return false;
154         
155         this->port = url.port;
156         strlcpy(this->host, url.domain.c_str(), MAXBUF);
157
158         Instance->Log(DEBUG,"Doing request for %s", url.url.c_str());
159
160         in6_addr s6;
161         in_addr s4;
162         /* Doesnt look like an ipv4 or an ipv6 address */
163         if ((inet_pton(AF_INET6, url.domain.c_str(), &s6) < 1) && (inet_pton(AF_INET, url.domain.c_str(), &s4) < 1))
164         {
165                 bool cached;
166                 HTTPResolver* r = new HTTPResolver(this, Server, url.domain, cached, (Module*)Mod);
167                 Instance->AddResolver(r, cached);
168                 Instance->Log(DEBUG,"Resolver added, cached=%d", cached);
169         }
170         else
171                 Connect(url.domain);
172         
173         return true;
174 }
175
176 bool HTTPSocket::ParseURL(const std::string &iurl)
177 {
178         Instance->Log(DEBUG,"HTTPSocket::ParseURL %s", iurl.c_str());
179         url.url = iurl;
180         url.port = 80;
181         url.protocol = "http";
182
183         irc::sepstream tokenizer(iurl, '/');
184         
185         for (int p = 0;; p++)
186         {
187                 std::string part;
188                 if (!tokenizer.GetToken(part))
189                         break;
190
191                 if ((p == 0) && (part[part.length() - 1] == ':'))
192                 {
193                         // Protocol ('http:')
194                         url.protocol = part.substr(0, part.length() - 1);
195                 }
196                 else if ((p == 1) && (part.empty()))
197                 {
198                         continue;
199                 }
200                 else if (url.domain.empty())
201                 {
202                         // Domain part: [user[:pass]@]domain[:port]
203                         std::string::size_type usrpos = part.find('@');
204                         if (usrpos != std::string::npos)
205                         {
206                                 // Have a user (and possibly password) part
207                                 std::string::size_type ppos = part.find(':');
208                                 if ((ppos != std::string::npos) && (ppos < usrpos))
209                                 {
210                                         // Have password too
211                                         url.password = part.substr(ppos + 1, usrpos - ppos - 1);
212                                         url.username = part.substr(0, ppos);
213                                 }
214                                 else
215                                 {
216                                         url.username = part.substr(0, usrpos);
217                                 }
218                                 
219                                 part = part.substr(usrpos + 1);
220                         }
221                         
222                         std::string::size_type popos = part.rfind(':');
223                         if (popos != std::string::npos)
224                         {
225                                 url.port = atoi(part.substr(popos + 1).c_str());
226                                 url.domain = part.substr(0, popos);
227                         }
228                         else
229                         {
230                                 url.domain = part;
231                         }
232                 }
233                 else
234                 {
235                         // Request (part of it)..
236                         url.request.append("/");
237                         url.request.append(part);
238                 }
239         }
240         
241         if (url.request.empty())
242                 url.request = "/";
243
244         if ((url.domain.empty()) || (!url.port) || (url.protocol.empty()))
245         {
246                 Instance->Log(DEFAULT, "Invalid URL (%s): Missing required value", iurl.c_str());
247                 return false;
248         }
249         
250         if (url.protocol != "http")
251         {
252                 Instance->Log(DEFAULT, "Invalid URL (%s): Unsupported protocol '%s'", iurl.c_str(), url.protocol.c_str());
253                 return false;
254         }
255         
256         return true;
257 }
258
259 void HTTPSocket::Connect(const std::string &ip)
260 {
261         this->response = new HTTPClientResponse((Module*)Mod, req.GetSource() , url.url, 0, "");
262
263         Instance->Log(DEBUG,"HTTPSocket::Connect(%s) response=%08lx", ip.c_str(), response);
264         strlcpy(this->IP, ip.c_str(), MAXBUF);
265         strlcpy(this->host, ip.c_str(), MAXBUF);
266
267         if (!this->DoConnect())
268         {
269                 Instance->Log(DEBUG,"DoConnect failed, bailing");
270                 this->Close();
271         }
272 }
273
274 bool HTTPSocket::OnConnected()
275 {
276         Instance->Log(DEBUG,"HTTPSocket::OnConnected");
277
278         std::string request = "GET " + url.request + " HTTP/1.1\r\n";
279
280         // Dump headers into the request
281         HeaderMap headers = req.GetHeaders();
282         
283         for (HeaderMap::iterator i = headers.begin(); i != headers.end(); i++)
284                 request += i->first + ": " + i->second + "\r\n";
285
286         // The Host header is required for HTTP 1.1 and isn't known when the request is created; if they didn't overload it
287         // manually, add it here
288         if (headers.find("Host") == headers.end())
289                 request += "Host: " + url.domain + "\r\n"; 
290         
291         request += "\r\n";
292         
293         this->status = HTTP_REQSENT;
294         
295         return this->Write(request);
296 }
297
298 bool HTTPSocket::OnDataReady()
299 {
300         Instance->Log(DEBUG,"HTTPSocket::OnDataReady() for %s", url.url.c_str());
301         const char *sdata = this->Read();
302
303         if (!sdata)
304                 return false;
305
306         if (this->status < HTTP_DATA)
307         {
308                 std::string line;
309                 std::string::size_type pos;
310
311                 this->buffer += sdata;
312                 while ((pos = buffer.find("\r\n")) != std::string::npos)
313                 {
314                         line = buffer.substr(0, pos);
315                         buffer = buffer.substr(pos + 2);
316                         if (line.empty())
317                         {
318                                 this->status = HTTP_DATA;
319                                 this->data += this->buffer;
320                                 this->buffer.clear();
321                                 break;
322                         }
323
324                         if (this->status == HTTP_REQSENT)
325                         {
326                                 // HTTP reply (HTTP/1.1 200 msg)
327                                 char const* sdata2 = line.c_str();
328                                 sdata2 += 9;
329                                 response->SetResponse(sdata2);
330                                 response->SetData(sdata2 + 4);
331                                 this->status = HTTP_HEADERS;
332                                 continue;
333                         }
334                         
335                         if ((pos = line.find(':')) != std::string::npos)
336                         {
337                                 response->AddHeader(line.substr(0, pos), line.substr(pos + 1));
338                         }
339                         else
340                         {
341                                 continue;
342                         }
343                 }
344         }
345         else
346         {
347                 this->data += data;
348         }
349         return true;
350 }
351
352 void HTTPSocket::OnClose()
353 {
354         if (!closed)
355         {
356                 closed = true;
357                 Instance->Log(DEBUG,"HTTPSocket::OnClose response=%08lx", response);
358                 std::string e;
359                 if (data.empty())
360                         {
361                         Instance->Log(DEBUG,"Send error");
362                         HTTPClientError* err = new HTTPClientError((Module*)Mod, req.GetSource(), req.GetURL(), 0);
363                         err->Send();
364                         delete err;
365                         return;
366                 }
367
368                 Instance->Log(DEBUG,"Set data and send, %s", response->GetURL().c_str());
369                 response->SetData(data);
370                 response->Send();
371                 delete response;
372         }
373 }
374
375 MODULE_INIT(ModuleHTTPClient)
376