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