]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
76e89666ad88c99fe69262c9f08182d6f3157631
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
1 /*       +------------------------------------+
2  *       | Inspire Internet Relay Chat Daemon |
3  *       +------------------------------------+
4  *
5  *  InspIRCd: (C) 2002-2009 InspIRCd Development Team
6  * See: http://wiki.inspircd.org/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 "httpd.h"
16
17 /* $ModDesc: Provides HTTP serving facilities to modules */
18 /* $ModDep: httpd.h */
19
20 class ModuleHttpServer;
21
22 static ModuleHttpServer* HttpModule;
23 static bool claimed;
24
25 /** HTTP socket states
26  */
27 enum HttpState
28 {
29         HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
30         HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
31         HTTP_SERVE_SEND_DATA = 2 /* Sending response */
32 };
33
34 /** A socket used for HTTP transport
35  */
36 class HttpServerSocket : public BufferedSocket
37 {
38         FileReader* index;
39         HttpState InternalState;
40         std::string ip;
41
42         HTTPHeaders headers;
43         std::string reqbuffer;
44         std::string postdata;
45         unsigned int postsize;
46         std::string request_type;
47         std::string uri;
48         std::string http_version;
49
50  public:
51
52         HttpServerSocket(int newfd, const char* IP, FileReader* ind)
53                 : BufferedSocket(newfd), index(ind), ip(IP), postsize(0)
54         {
55                 InternalState = HTTP_SERVE_WAIT_REQUEST;
56         }
57
58         FileReader* GetIndex()
59         {
60                 return index;
61         }
62
63         virtual void OnError(BufferedSocketError)
64         {
65         }
66
67         std::string Response(int response)
68         {
69                 switch (response)
70                 {
71                         case 100:
72                                 return "CONTINUE";
73                         case 101:
74                                 return "SWITCHING PROTOCOLS";
75                         case 200:
76                                 return "OK";
77                         case 201:
78                                 return "CREATED";
79                         case 202:
80                                 return "ACCEPTED";
81                         case 203:
82                                 return "NON-AUTHORITATIVE INFORMATION";
83                         case 204:
84                                 return "NO CONTENT";
85                         case 205:
86                                 return "RESET CONTENT";
87                         case 206:
88                                 return "PARTIAL CONTENT";
89                         case 300:
90                                 return "MULTIPLE CHOICES";
91                         case 301:
92                                 return "MOVED PERMENANTLY";
93                         case 302:
94                                 return "FOUND";
95                         case 303:
96                                 return "SEE OTHER";
97                         case 304:
98                                 return "NOT MODIFIED";
99                         case 305:
100                                 return "USE PROXY";
101                         case 307:
102                                 return "TEMPORARY REDIRECT";
103                         case 400:
104                                 return "BAD REQUEST";
105                         case 401:
106                                 return "UNAUTHORIZED";
107                         case 402:
108                                 return "PAYMENT REQUIRED";
109                         case 403:
110                                 return "FORBIDDEN";
111                         case 404:
112                                 return "NOT FOUND";
113                         case 405:
114                                 return "METHOD NOT ALLOWED";
115                         case 406:
116                                 return "NOT ACCEPTABLE";
117                         case 407:
118                                 return "PROXY AUTHENTICATION REQUIRED";
119                         case 408:
120                                 return "REQUEST TIMEOUT";
121                         case 409:
122                                 return "CONFLICT";
123                         case 410:
124                                 return "GONE";
125                         case 411:
126                                 return "LENGTH REQUIRED";
127                         case 412:
128                                 return "PRECONDITION FAILED";
129                         case 413:
130                                 return "REQUEST ENTITY TOO LARGE";
131                         case 414:
132                                 return "REQUEST-URI TOO LONG";
133                         case 415:
134                                 return "UNSUPPORTED MEDIA TYPE";
135                         case 416:
136                                 return "REQUESTED RANGE NOT SATISFIABLE";
137                         case 417:
138                                 return "EXPECTATION FAILED";
139                         case 500:
140                                 return "INTERNAL SERVER ERROR";
141                         case 501:
142                                 return "NOT IMPLEMENTED";
143                         case 502:
144                                 return "BAD GATEWAY";
145                         case 503:
146                                 return "SERVICE UNAVAILABLE";
147                         case 504:
148                                 return "GATEWAY TIMEOUT";
149                         case 505:
150                                 return "HTTP VERSION NOT SUPPORTED";
151                         default:
152                                 return "WTF";
153                         break;
154
155                 }
156         }
157
158         void SendHTTPError(int response)
159         {
160                 HTTPHeaders empty;
161                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
162                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
163
164                 SendHeaders(data.length(), response, empty);
165                 WriteData(data);
166         }
167
168         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
169         {
170
171                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
172
173                 time_t local = ServerInstance->Time();
174                 struct tm *timeinfo = gmtime(&local);
175                 char *date = asctime(timeinfo);
176                 date[strlen(date) - 1] = '\0';
177                 rheaders.CreateHeader("Date", date);
178
179                 rheaders.CreateHeader("Server", "InspIRCd/m_httpd.so/1.2");
180                 rheaders.SetHeader("Content-Length", ConvToStr(size));
181
182                 if (size)
183                         rheaders.CreateHeader("Content-Type", "text/html");
184                 else
185                         rheaders.RemoveHeader("Content-Type");
186
187                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
188                  * so remove it, its not essential for what we need.
189                  */
190                 rheaders.SetHeader("Connection", "Close");
191
192                 WriteData(rheaders.GetFormattedHeaders());
193                 WriteData("\r\n");
194         }
195
196         void OnDataReady()
197         {
198                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
199                 {
200                         postdata.append(recvq);
201                         if (postdata.length() >= postsize)
202                                 ServeData();
203                 }
204                 else
205                 {
206                         reqbuffer.append(recvq);
207
208                         if (reqbuffer.length() >= 8192)
209                         {
210                                 ServerInstance->Logs->Log("m_httpd",DEBUG, "m_httpd dropped connection due to an oversized request buffer");
211                                 reqbuffer.clear();
212                                 SetError("Buffer");
213                         }
214
215                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
216                                 CheckRequestBuffer();
217                 }
218         }
219
220         void CheckRequestBuffer()
221         {
222                 std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
223                 if (reqend == std::string::npos)
224                         return;
225
226                 // We have the headers; parse them all
227                 std::string::size_type hbegin = 0, hend;
228                 while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
229                 {
230                         if (hbegin == hend)
231                                 break;
232
233                         if (request_type.empty())
234                         {
235                                 std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
236                                 cheader >> request_type;
237                                 cheader >> uri;
238                                 cheader >> http_version;
239
240                                 if (request_type.empty() || uri.empty() || http_version.empty())
241                                 {
242                                         SendHTTPError(400);
243                                         return;
244                                 }
245
246                                 hbegin = hend + 2;
247                                 continue;
248                         }
249
250                         std::string cheader = reqbuffer.substr(hbegin, hend - hbegin);
251
252                         std::string::size_type fieldsep = cheader.find(':');
253                         if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
254                         {
255                                 SendHTTPError(400);
256                                 return;
257                         }
258
259                         headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
260
261                         hbegin = hend + 2;
262                 }
263
264                 reqbuffer.erase(0, reqend + 4);
265
266                 std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
267                 std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
268
269                 if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
270                 {
271                         SendHTTPError(505);
272                         return;
273                 }
274
275                 if (headers.IsSet("Content-Length") && (postsize = atoi(headers.GetHeader("Content-Length").c_str())) != 0)
276                 {
277                         InternalState = HTTP_SERVE_RECV_POSTDATA;
278
279                         if (reqbuffer.length() >= postsize)
280                         {
281                                 postdata = reqbuffer.substr(0, postsize);
282                                 reqbuffer.erase(0, postsize);
283                         }
284                         else if (!reqbuffer.empty())
285                         {
286                                 postdata = reqbuffer;
287                                 reqbuffer.clear();
288                         }
289
290                         if (postdata.length() >= postsize)
291                                 ServeData();
292
293                         return;
294                 }
295
296                 ServeData();
297         }
298
299         void ServeData()
300         {
301                 InternalState = HTTP_SERVE_SEND_DATA;
302
303                 if ((request_type == "GET") && (uri == "/"))
304                 {
305                         HTTPHeaders empty;
306                         SendHeaders(index->ContentSize(), 200, empty);
307                         WriteData(index->Contents());
308                 }
309                 else
310                 {
311                         claimed = false;
312                         HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata);
313                         acl.Send();
314                         if (!claimed)
315                         {
316                                 HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata);
317                                 url.Send();
318                                 if (!claimed)
319                                 {
320                                         SendHTTPError(404);
321                                 }
322                         }
323                 }
324         }
325
326         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
327         {
328                 SendHeaders(n->str().length(), response, *hheaders);
329                 WriteData(n->str());
330         }
331 };
332
333 /** Spawn HTTP sockets from a listener
334  */
335 class HttpListener : public ListenSocketBase
336 {
337         FileReader* index;
338
339  public:
340         HttpListener(FileReader *idx, int port, const std::string &addr)
341                 : ListenSocketBase(port, addr, "httpd", "plaintext")
342         {
343                 this->index = idx;
344         }
345
346         ~HttpListener()
347         {
348                 delete index;
349         }
350
351         virtual void OnAcceptReady(int nfd)
352         {
353                 int port;
354                 std::string incomingip;
355                 irc::sockets::satoap(&client, incomingip, port);
356                 new HttpServerSocket(nfd, incomingip.c_str(), index);
357         }
358 };
359
360 class ModuleHttpServer : public Module
361 {
362         std::vector<HttpServerSocket *> httpsocks;
363         std::vector<HttpListener *> httplisteners;
364  public:
365
366         void ReadConfig()
367         {
368                 int port;
369                 std::string host;
370                 std::string bindip;
371                 std::string indexfile;
372                 FileReader* index;
373                 HttpListener *http;
374                 ConfigReader c;
375
376                 httpsocks.clear(); // XXX this will BREAK if this module is made rehashable
377                 httplisteners.clear();
378
379                 for (int i = 0; i < c.Enumerate("http"); i++)
380                 {
381                         host = c.ReadValue("http", "host", i);
382                         bindip = c.ReadValue("http", "ip", i);
383                         port = c.ReadInteger("http", "port", i, true);
384                         indexfile = c.ReadValue("http", "index", i);
385                         index = new FileReader(indexfile);
386                         if (!index->Exists())
387                         {
388                                 delete index;
389                                 throw ModuleException("Can't read index file: "+indexfile);
390                         }
391                         http = new HttpListener(index, port, bindip);
392                         httplisteners.push_back(http);
393                 }
394         }
395
396         ModuleHttpServer()      {
397                 ReadConfig();
398                 HttpModule = this;
399         }
400
401         void OnRequest(Request& request)
402         {
403                 if (strcmp(request.id, "HTTP-DOC") != 0)
404                         return;
405                 HTTPDocumentResponse& resp = static_cast<HTTPDocumentResponse&>(request);
406                 claimed = true;
407                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
408         }
409
410
411         virtual ~ModuleHttpServer()
412         {
413                 for (size_t i = 0; i < httplisteners.size(); i++)
414                 {
415                         httplisteners[i]->cull();
416                         delete httplisteners[i];
417                 }
418
419                 for (size_t i = 0; i < httpsocks.size(); i++)
420                 {
421                         httpsocks[i]->cull();
422                         delete httpsocks[i];
423                 }
424         }
425
426         virtual Version GetVersion()
427         {
428                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
429         }
430 };
431
432 MODULE_INIT(ModuleHttpServer)