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