]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Keep multiple IOHookProvider references in class ListenSocket
[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 "iohook.h"
27 #include "modules/httpd.h"
28
29 class ModuleHttpServer;
30
31 static ModuleHttpServer* HttpModule;
32 static insp::intrusive_list<HttpServerSocket> sockets;
33 static Events::ModuleEventProvider* aclevprov;
34 static Events::ModuleEventProvider* reqevprov;
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, public Timer, public insp::intrusive_list_node<HttpServerSocket>
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         /** True if this object is in the cull list
61          */
62         bool waitingcull;
63
64         bool Tick(time_t currtime) CXX11_OVERRIDE
65         {
66                 AddToCull();
67                 return false;
68         }
69
70  public:
71         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
72                 : BufferedSocket(newfd)
73                 , Timer(timeoutsec)
74                 , InternalState(HTTP_SERVE_WAIT_REQUEST)
75                 , ip(IP)
76                 , postsize(0)
77                 , waitingcull(false)
78         {
79                 ServerInstance->Timers.AddTimer(this);
80
81                 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
82                         via->iohookprovs.back()->OnAccept(this, client, server);
83         }
84
85         ~HttpServerSocket()
86         {
87                 sockets.erase(this);
88         }
89
90         void OnError(BufferedSocketError) CXX11_OVERRIDE
91         {
92                 AddToCull();
93         }
94
95         std::string Response(int response)
96         {
97                 switch (response)
98                 {
99                         case 100:
100                                 return "CONTINUE";
101                         case 101:
102                                 return "SWITCHING PROTOCOLS";
103                         case 200:
104                                 return "OK";
105                         case 201:
106                                 return "CREATED";
107                         case 202:
108                                 return "ACCEPTED";
109                         case 203:
110                                 return "NON-AUTHORITATIVE INFORMATION";
111                         case 204:
112                                 return "NO CONTENT";
113                         case 205:
114                                 return "RESET CONTENT";
115                         case 206:
116                                 return "PARTIAL CONTENT";
117                         case 300:
118                                 return "MULTIPLE CHOICES";
119                         case 301:
120                                 return "MOVED PERMANENTLY";
121                         case 302:
122                                 return "FOUND";
123                         case 303:
124                                 return "SEE OTHER";
125                         case 304:
126                                 return "NOT MODIFIED";
127                         case 305:
128                                 return "USE PROXY";
129                         case 307:
130                                 return "TEMPORARY REDIRECT";
131                         case 400:
132                                 return "BAD REQUEST";
133                         case 401:
134                                 return "UNAUTHORIZED";
135                         case 402:
136                                 return "PAYMENT REQUIRED";
137                         case 403:
138                                 return "FORBIDDEN";
139                         case 404:
140                                 return "NOT FOUND";
141                         case 405:
142                                 return "METHOD NOT ALLOWED";
143                         case 406:
144                                 return "NOT ACCEPTABLE";
145                         case 407:
146                                 return "PROXY AUTHENTICATION REQUIRED";
147                         case 408:
148                                 return "REQUEST TIMEOUT";
149                         case 409:
150                                 return "CONFLICT";
151                         case 410:
152                                 return "GONE";
153                         case 411:
154                                 return "LENGTH REQUIRED";
155                         case 412:
156                                 return "PRECONDITION FAILED";
157                         case 413:
158                                 return "REQUEST ENTITY TOO LARGE";
159                         case 414:
160                                 return "REQUEST-URI TOO LONG";
161                         case 415:
162                                 return "UNSUPPORTED MEDIA TYPE";
163                         case 416:
164                                 return "REQUESTED RANGE NOT SATISFIABLE";
165                         case 417:
166                                 return "EXPECTATION FAILED";
167                         case 500:
168                                 return "INTERNAL SERVER ERROR";
169                         case 501:
170                                 return "NOT IMPLEMENTED";
171                         case 502:
172                                 return "BAD GATEWAY";
173                         case 503:
174                                 return "SERVICE UNAVAILABLE";
175                         case 504:
176                                 return "GATEWAY TIMEOUT";
177                         case 505:
178                                 return "HTTP VERSION NOT SUPPORTED";
179                         default:
180                                 return "WTF";
181                         break;
182
183                 }
184         }
185
186         void SendHTTPError(int response)
187         {
188                 HTTPHeaders empty;
189                 std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
190                                    "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
191
192                 SendHeaders(data.length(), response, empty);
193                 WriteData(data);
194         }
195
196         void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
197         {
198
199                 WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
200
201                 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
202                 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
203                 rheaders.SetHeader("Content-Length", ConvToStr(size));
204
205                 if (size)
206                         rheaders.CreateHeader("Content-Type", "text/html");
207                 else
208                         rheaders.RemoveHeader("Content-Type");
209
210                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
211                  * so remove it, its not essential for what we need.
212                  */
213                 rheaders.SetHeader("Connection", "Close");
214
215                 WriteData(rheaders.GetFormattedHeaders());
216                 WriteData("\r\n");
217         }
218
219         void OnDataReady()
220         {
221                 if (InternalState == HTTP_SERVE_RECV_POSTDATA)
222                 {
223                         postdata.append(recvq);
224                         if (postdata.length() >= postsize)
225                                 ServeData();
226                 }
227                 else
228                 {
229                         reqbuffer.append(recvq);
230
231                         if (reqbuffer.length() >= 8192)
232                         {
233                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "m_httpd dropped connection due to an oversized request buffer");
234                                 reqbuffer.clear();
235                                 SetError("Buffer");
236                         }
237
238                         if (InternalState == HTTP_SERVE_WAIT_REQUEST)
239                                 CheckRequestBuffer();
240                 }
241         }
242
243         void CheckRequestBuffer()
244         {
245                 std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
246                 if (reqend == std::string::npos)
247                         return;
248
249                 // We have the headers; parse them all
250                 std::string::size_type hbegin = 0, hend;
251                 while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
252                 {
253                         if (hbegin == hend)
254                                 break;
255
256                         if (request_type.empty())
257                         {
258                                 std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
259                                 cheader >> request_type;
260                                 cheader >> uri;
261                                 cheader >> http_version;
262
263                                 if (request_type.empty() || uri.empty() || http_version.empty())
264                                 {
265                                         SendHTTPError(400);
266                                         return;
267                                 }
268
269                                 hbegin = hend + 2;
270                                 continue;
271                         }
272
273                         std::string cheader(reqbuffer, hbegin, hend - hbegin);
274
275                         std::string::size_type fieldsep = cheader.find(':');
276                         if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
277                         {
278                                 SendHTTPError(400);
279                                 return;
280                         }
281
282                         headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
283
284                         hbegin = hend + 2;
285                 }
286
287                 reqbuffer.erase(0, reqend + 4);
288
289                 std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
290                 std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
291
292                 if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
293                 {
294                         SendHTTPError(505);
295                         return;
296                 }
297
298                 if (headers.IsSet("Content-Length") && (postsize = ConvToInt(headers.GetHeader("Content-Length"))) > 0)
299                 {
300                         InternalState = HTTP_SERVE_RECV_POSTDATA;
301
302                         if (reqbuffer.length() >= postsize)
303                         {
304                                 postdata.assign(reqbuffer, 0, postsize);
305                                 reqbuffer.erase(0, postsize);
306                         }
307                         else if (!reqbuffer.empty())
308                         {
309                                 postdata = reqbuffer;
310                                 reqbuffer.clear();
311                         }
312
313                         if (postdata.length() >= postsize)
314                                 ServeData();
315
316                         return;
317                 }
318
319                 ServeData();
320         }
321
322         void ServeData()
323         {
324                 InternalState = HTTP_SERVE_SEND_DATA;
325
326                 ModResult MOD_RESULT;
327                 HTTPRequest acl(request_type, uri, &headers, this, ip, postdata);
328                 FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
329                 if (MOD_RESULT != MOD_RES_DENY)
330                 {
331                         HTTPRequest url(request_type, uri, &headers, this, ip, postdata);
332                         FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
333                         if (MOD_RESULT == MOD_RES_PASSTHRU)
334                         {
335                                 SendHTTPError(404);
336                         }
337                 }
338         }
339
340         void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
341         {
342                 SendHeaders(n->str().length(), response, *hheaders);
343                 WriteData(n->str());
344         }
345
346         void AddToCull()
347         {
348                 if (waitingcull)
349                         return;
350
351                 waitingcull = true;
352                 Close();
353                 ServerInstance->GlobalCulls.AddItem(this);
354         }
355 };
356
357 class HTTPdAPIImpl : public HTTPdAPIBase
358 {
359  public:
360         HTTPdAPIImpl(Module* parent)
361                 : HTTPdAPIBase(parent)
362         {
363         }
364
365         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
366         {
367                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
368         }
369 };
370
371 class ModuleHttpServer : public Module
372 {
373         HTTPdAPIImpl APIImpl;
374         unsigned int timeoutsec;
375         Events::ModuleEventProvider acleventprov;
376         Events::ModuleEventProvider reqeventprov;
377
378  public:
379         ModuleHttpServer()
380                 : APIImpl(this)
381                 , acleventprov(this, "event/http-acl")
382                 , reqeventprov(this, "event/http-request")
383         {
384                 aclevprov = &acleventprov;
385                 reqevprov = &reqeventprov;
386         }
387
388         void init() CXX11_OVERRIDE
389         {
390                 HttpModule = this;
391         }
392
393         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
394         {
395                 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
396                 timeoutsec = tag->getInt("timeout", 10, 1);
397         }
398
399         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
400         {
401                 if (from->bind_tag->getString("type") != "httpd")
402                         return MOD_RES_PASSTHRU;
403                 int port;
404                 std::string incomingip;
405                 irc::sockets::satoap(*client, incomingip, port);
406                 sockets.push_front(new HttpServerSocket(nfd, incomingip, from, client, server, timeoutsec));
407                 return MOD_RES_ALLOW;
408         }
409
410         void OnUnloadModule(Module* mod)
411         {
412                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
413                 {
414                         HttpServerSocket* sock = *i;
415                         ++i;
416                         if (sock->GetModHook(mod))
417                         {
418                                 sock->cull();
419                                 delete sock;
420                         }
421                 }
422         }
423
424         CullResult cull() CXX11_OVERRIDE
425         {
426                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
427                 {
428                         HttpServerSocket* sock = *i;
429                         sock->AddToCull();
430                 }
431                 return Module::cull();
432         }
433
434         Version GetVersion() CXX11_OVERRIDE
435         {
436                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
437         }
438 };
439
440 MODULE_INIT(ModuleHttpServer)