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