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