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