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