]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Use a better server name in m_httpd.
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
5  *   Copyright (C) 2007-2008 Robin Burchell <robin+git@viroteck.net>
6  *   Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
7  *   Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
8  *   Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net>
9  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
10  *
11  * This file is part of InspIRCd.  InspIRCd is free software: you can
12  * redistribute it and/or modify it under the terms of the GNU General Public
13  * License as published by the Free Software Foundation, version 2.
14  *
15  * This program is distributed in the hope that it will be useful, but WITHOUT
16  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
17  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
18  * details.
19  *
20  * You should have received a copy of the GNU General Public License
21  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
22  */
23
24
25 #include "inspircd.h"
26 #include "httpd.h"
27
28 /* $ModDesc: Provides HTTP serving facilities to modules */
29 /* $ModDep: httpd.h */
30
31 class ModuleHttpServer;
32
33 static ModuleHttpServer* HttpModule;
34 static bool claimed;
35
36 /** HTTP socket states
37  */
38 enum HttpState
39 {
40         HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
41         HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
42         HTTP_SERVE_SEND_DATA = 2 /* Sending response */
43 };
44
45 /** A socket used for HTTP transport
46  */
47 class HttpServerSocket : public BufferedSocket
48 {
49         HttpState InternalState;
50         std::string ip;
51
52         HTTPHeaders headers;
53         std::string reqbuffer;
54         std::string postdata;
55         unsigned int postsize;
56         std::string request_type;
57         std::string uri;
58         std::string http_version;
59
60  public:
61
62         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
63                 : BufferedSocket(newfd), ip(IP), postsize(0)
64         {
65                 InternalState = HTTP_SERVE_WAIT_REQUEST;
66
67                 FOREACH_MOD(I_OnHookIO, OnHookIO(this, via));
68                 if (GetIOHook())
69                         GetIOHook()->OnStreamSocketAccept(this, client, server);
70         }
71
72         virtual void OnError(BufferedSocketError)
73         {
74                 ServerInstance->GlobalCulls.AddItem(this);
75         }
76
77         std::string Response(int response)
78         {
79                 switch (response)
80                 {
81                         case 100:
82                                 return "CONTINUE";
83                         case 101:
84                                 return "SWITCHING PROTOCOLS";
85                         case 200:
86                                 return "OK";
87                         case 201:
88                                 return "CREATED";
89                         case 202:
90                                 return "ACCEPTED";
91                         case 203:
92                                 return "NON-AUTHORITATIVE INFORMATION";
93                         case 204:
94                                 return "NO CONTENT";
95                         case 205:
96                                 return "RESET CONTENT";
97                         case 206:
98                                 return "PARTIAL CONTENT";
99                         case 300:
100                                 return "MULTIPLE CHOICES";
101                         case 301:
102                                 return "MOVED PERMENANTLY";
103                         case 302:
104                                 return "FOUND";
105                         case 303:
106                                 return "SEE OTHER";
107                         case 304:
108                                 return "NOT MODIFIED";
109                         case 305:
110                                 return "USE PROXY";
111                         case 307:
112                                 return "TEMPORARY REDIRECT";
113                         case 400:
114                                 return "BAD REQUEST";
115                         case 401:
116                                 return "UNAUTHORIZED";
117                         case 402:
118                                 return "PAYMENT REQUIRED";
119                         case 403:
120                                 return "FORBIDDEN";
121                         case 404:
122                                 return "NOT FOUND";
123                         case 405:
124                                 return "METHOD NOT ALLOWED";
125                         case 406:
126                                 return "NOT ACCEPTABLE";
127                         case 407:
128                                 return "PROXY AUTHENTICATION REQUIRED";
129                         case 408:
130                                 return "REQUEST TIMEOUT";
131                         case 409:
132                                 return "CONFLICT";
133                         case 410:
134                                 return "GONE";
135                         case 411:
136                                 return "LENGTH REQUIRED";
137                         case 412:
138                                 return "PRECONDITION FAILED";
139                         case 413:
140                                 return "REQUEST ENTITY TOO LARGE";
141                         case 414:
142                                 return "REQUEST-URI TOO LONG";
143                         case 415:
144                                 return "UNSUPPORTED MEDIA TYPE";
145                         case 416:
146                                 return "REQUESTED RANGE NOT SATISFIABLE";
147                         case 417:
148                                 return "EXPECTATION FAILED";
149                         case 500:
150                                 return "INTERNAL SERVER ERROR";
151                         case 501:
152                                 return "NOT IMPLEMENTED";
153                         case 502:
154                                 return "BAD GATEWAY";
155                         case 503:
156                                 return "SERVICE UNAVAILABLE";
157                         case 504:
158                                 return "GATEWAY TIMEOUT";
159                         case 505:
160                                 return "HTTP VERSION NOT SUPPORTED";
161                         default:
162                                 return "WTF";
163                         break;
164
165                 }
166         }
167
168         void SendHTTPError(int response)
169         {
170                 HTTPHeaders empty;
171                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
172                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
173
174                 SendHeaders(data.length(), response, empty);
175                 WriteData(data);
176         }
177
178         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
179         {
180
181                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
182
183                 time_t local = ServerInstance->Time();
184                 struct tm *timeinfo = gmtime(&local);
185                 char *date = asctime(timeinfo);
186                 date[strlen(date) - 1] = '\0';
187                 rheaders.CreateHeader("Date", date);
188
189                 rheaders.CreateHeader("Server", BRANCH);
190                 rheaders.SetHeader("Content-Length", ConvToStr(size));
191
192                 if (size)
193                         rheaders.CreateHeader("Content-Type", "text/html");
194                 else
195                         rheaders.RemoveHeader("Content-Type");
196
197                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
198                  * so remove it, its not essential for what we need.
199                  */
200                 rheaders.SetHeader("Connection", "Close");
201
202                 WriteData(rheaders.GetFormattedHeaders());
203                 WriteData("\r\n");
204         }
205
206         void OnDataReady()
207         {
208                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
209                 {
210                         postdata.append(recvq);
211                         if (postdata.length() >= postsize)
212                                 ServeData();
213                 }
214                 else
215                 {
216                         reqbuffer.append(recvq);
217
218                         if (reqbuffer.length() >= 8192)
219                         {
220                                 ServerInstance->Logs->Log("m_httpd",DEBUG, "m_httpd dropped connection due to an oversized request buffer");
221                                 reqbuffer.clear();
222                                 SetError("Buffer");
223                         }
224
225                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
226                                 CheckRequestBuffer();
227                 }
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 = ConvToInt(headers.GetHeader("Content-Length"))) > 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                 claimed = false;
314                 HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata);
315                 acl.Send();
316                 if (!claimed)
317                 {
318                         HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata);
319                         url.Send();
320                         if (!claimed)
321                         {
322                                 SendHTTPError(404);
323                         }
324                 }
325         }
326
327         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
328         {
329                 SendHeaders(n->str().length(), response, *hheaders);
330                 WriteData(n->str());
331         }
332 };
333
334 class ModuleHttpServer : public Module
335 {
336         std::vector<HttpServerSocket *> httpsocks;
337  public:
338
339         void init()
340         {
341                 HttpModule = this;
342                 ServerInstance->Modules->Attach(I_OnAcceptConnection, this);
343         }
344
345         void OnRequest(Request& request)
346         {
347                 if (strcmp(request.id, "HTTP-DOC") != 0)
348                         return;
349                 HTTPDocumentResponse& resp = static_cast<HTTPDocumentResponse&>(request);
350                 claimed = true;
351                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
352         }
353
354         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
355         {
356                 if (from->bind_tag->getString("type") != "httpd")
357                         return MOD_RES_PASSTHRU;
358                 int port;
359                 std::string incomingip;
360                 irc::sockets::satoap(*client, incomingip, port);
361                 new HttpServerSocket(nfd, incomingip, from, client, server);
362                 return MOD_RES_ALLOW;
363         }
364
365
366         virtual ~ModuleHttpServer()
367         {
368                 for (size_t i = 0; i < httpsocks.size(); i++)
369                 {
370                         httpsocks[i]->cull();
371                         delete httpsocks[i];
372                 }
373         }
374
375         virtual Version GetVersion()
376         {
377                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
378         }
379 };
380
381 MODULE_INIT(ModuleHttpServer)