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