]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Expand searching in m_httpd_stats, add global handling of GET parameters (#1566)
[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 /// $CompilerFlags: -Ivendor_directory("http_parser")
25
26
27 #include "inspircd.h"
28 #include "iohook.h"
29 #include "modules/httpd.h"
30
31 // Fix warnings about the use of commas at end of enumerator lists on C++03.
32 #if defined __clang__
33 # pragma clang diagnostic ignored "-Wc++11-extensions"
34 #elif defined __GNUC__
35 # if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8))
36 #  pragma GCC diagnostic ignored "-Wpedantic"
37 # else
38 #  pragma GCC diagnostic ignored "-pedantic"
39 # endif
40 #endif
41
42 // Fix warnings about shadowing in http_parser.
43 #pragma GCC diagnostic ignored "-Wshadow"
44
45 #include <http_parser.c>
46
47 class ModuleHttpServer;
48
49 static ModuleHttpServer* HttpModule;
50 static insp::intrusive_list<HttpServerSocket> sockets;
51 static Events::ModuleEventProvider* aclevprov;
52 static Events::ModuleEventProvider* reqevprov;
53 static http_parser_settings parser_settings;
54
55 /** A socket used for HTTP transport
56  */
57 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
58 {
59         friend ModuleHttpServer;
60
61         http_parser parser;
62         http_parser_url url;
63         std::string ip;
64         std::string uri;
65         HTTPHeaders headers;
66         std::string body;
67         size_t total_buffers;
68         int status_code;
69
70         /** True if this object is in the cull list
71          */
72         bool waitingcull;
73
74         bool Tick(time_t currtime) CXX11_OVERRIDE
75         {
76                 AddToCull();
77                 return false;
78         }
79
80         template<int (HttpServerSocket::*f)()>
81         static int Callback(http_parser* p)
82         {
83                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
84                 return (sock->*f)();
85         }
86
87         template<int (HttpServerSocket::*f)(const char*, size_t)>
88         static int DataCallback(http_parser* p, const char* buf, size_t len)
89         {
90                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
91                 return (sock->*f)(buf, len);
92         }
93
94         static void ConfigureParser()
95         {
96                 http_parser_settings_init(&parser_settings);
97                 parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>;
98                 parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>;
99                 parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>;
100                 parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>;
101                 parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>;
102         }
103
104         int OnMessageBegin()
105         {
106                 uri.clear();
107                 header_state = HEADER_NONE;
108                 body.clear();
109                 total_buffers = 0;
110                 return 0;
111         }
112
113         bool AcceptData(size_t len)
114         {
115                 total_buffers += len;
116                 return total_buffers < 8192;
117         }
118
119         int OnUrl(const char* buf, size_t len)
120         {
121                 if (!AcceptData(len))
122                 {
123                         status_code = HTTP_STATUS_URI_TOO_LONG;
124                         return -1;
125                 }
126                 uri.append(buf, len);
127                 return 0;
128         }
129
130         enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
131         std::string header_field;
132         std::string header_value;
133
134         void OnHeaderComplete()
135         {
136                 headers.SetHeader(header_field, header_value);
137                 header_field.clear();
138                 header_value.clear();
139         }
140
141         int OnHeaderField(const char* buf, size_t len)
142         {
143                 if (header_state == HEADER_VALUE)
144                         OnHeaderComplete();
145                 header_state = HEADER_FIELD;
146                 if (!AcceptData(len))
147                 {
148                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
149                         return -1;
150                 }
151                 header_field.append(buf, len);
152                 return 0;
153         }
154
155         int OnHeaderValue(const char* buf, size_t len)
156         {
157                 header_state = HEADER_VALUE;
158                 if (!AcceptData(len))
159                 {
160                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
161                         return -1;
162                 }
163                 header_value.append(buf, len);
164                 return 0;
165         }
166
167         int OnHeadersComplete()
168         {
169                 if (header_state != HEADER_NONE)
170                         OnHeaderComplete();
171                 return 0;
172         }
173
174         int OnBody(const char* buf, size_t len)
175         {
176                 if (!AcceptData(len))
177                 {
178                         status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
179                         return -1;
180                 }
181                 body.append(buf, len);
182                 return 0;
183         }
184
185         int OnMessageComplete()
186         {
187                 ServeData();
188                 return 0;
189         }
190
191  public:
192         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
193                 : BufferedSocket(newfd)
194                 , Timer(timeoutsec)
195                 , ip(IP)
196                 , status_code(0)
197                 , waitingcull(false)
198         {
199                 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
200                 {
201                         via->iohookprovs.back()->OnAccept(this, client, server);
202                         // IOHook may have errored
203                         if (!getError().empty())
204                         {
205                                 AddToCull();
206                                 return;
207                         }
208                 }
209
210                 parser.data = this;
211                 http_parser_init(&parser, HTTP_REQUEST);
212                 ServerInstance->Timers.AddTimer(this);
213         }
214
215         ~HttpServerSocket()
216         {
217                 sockets.erase(this);
218         }
219
220         void OnError(BufferedSocketError) CXX11_OVERRIDE
221         {
222                 AddToCull();
223         }
224
225         const char* Response(unsigned int response)
226         {
227                 switch (response)
228                 {
229 #define HTTP_STATUS_CASE(n, m, s) case n: return #s;
230                         HTTP_STATUS_MAP(HTTP_STATUS_CASE)
231                         default:
232                                 return "WTF";
233                         break;
234                 }
235         }
236
237         void SendHTTPError(unsigned int response)
238         {
239                 HTTPHeaders empty;
240                 std::string data = InspIRCd::Format(
241                         "<html><head></head><body>Server error %u: %s<br>"
242                         "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>", response, Response(response));
243
244                 SendHeaders(data.length(), response, empty);
245                 WriteData(data);
246                 Close();
247         }
248
249         void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders)
250         {
251                 WriteData(InspIRCd::Format("HTTP/%u.%u %u %s\r\n", parser.http_major ? parser.http_major : 1, parser.http_major ? parser.http_minor : 1, response, Response(response)));
252
253                 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
254                 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
255                 rheaders.SetHeader("Content-Length", ConvToStr(size));
256
257                 if (size)
258                         rheaders.CreateHeader("Content-Type", "text/html");
259                 else
260                         rheaders.RemoveHeader("Content-Type");
261
262                 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
263                  * so remove it, its not essential for what we need.
264                  */
265                 rheaders.SetHeader("Connection", "Close");
266
267                 WriteData(rheaders.GetFormattedHeaders());
268                 WriteData("\r\n");
269         }
270
271         void OnDataReady() CXX11_OVERRIDE
272         {
273                 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
274                         return;
275                 http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size());
276                 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
277                         SendHTTPError(status_code ? status_code : 400);
278         }
279
280         void ServeData()
281         {
282                 ModResult MOD_RESULT;
283                 std::string method = http_method_str(static_cast<http_method>(parser.method));
284                 HTTPRequestURI parsed;
285                 ParseURI(uri, parsed);
286                 HTTPRequest acl(method, parsed, &headers, this, ip, body);
287                 FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
288                 if (MOD_RESULT != MOD_RES_DENY)
289                 {
290                         HTTPRequest url(method, parsed, &headers, this, ip, body);
291                         FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
292                         if (MOD_RESULT == MOD_RES_PASSTHRU)
293                         {
294                                 SendHTTPError(404);
295                         }
296                 }
297         }
298
299         void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
300         {
301                 SendHeaders(n->str().length(), response, *hheaders);
302                 WriteData(n->str());
303                 Close();
304         }
305
306         void AddToCull()
307         {
308                 if (waitingcull)
309                         return;
310
311                 waitingcull = true;
312                 Close();
313                 ServerInstance->GlobalCulls.AddItem(this);
314         }
315
316         bool ParseURI(const std::string& uri, HTTPRequestURI& out)
317         {
318                 http_parser_url_init(&url);
319                 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &url) != 0)
320                         return false;
321
322                 if (url.field_set & (1 << UF_PATH))
323                         out.path = uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len);
324
325                 if (url.field_set & (1 << UF_FRAGMENT))
326                         out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
327
328                 std::string param_str;
329                 if (url.field_set & (1 << UF_QUERY))
330                         param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
331
332                 irc::sepstream param_stream(param_str, '&');
333                 std::string token;
334                 std::string::size_type eq_pos;
335                 while (param_stream.GetToken(token))
336                 {
337                         eq_pos = token.find('=');
338                         if (eq_pos == std::string::npos)
339                         {
340                                 out.query_params.insert(std::make_pair(token, ""));
341                         }
342                         else
343                         {
344                                 out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
345                         }
346                 }
347                 return true;
348         }
349 };
350
351 class HTTPdAPIImpl : public HTTPdAPIBase
352 {
353  public:
354         HTTPdAPIImpl(Module* parent)
355                 : HTTPdAPIBase(parent)
356         {
357         }
358
359         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
360         {
361                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
362         }
363 };
364
365 class ModuleHttpServer : public Module
366 {
367         HTTPdAPIImpl APIImpl;
368         unsigned int timeoutsec;
369         Events::ModuleEventProvider acleventprov;
370         Events::ModuleEventProvider reqeventprov;
371
372  public:
373         ModuleHttpServer()
374                 : APIImpl(this)
375                 , acleventprov(this, "event/http-acl")
376                 , reqeventprov(this, "event/http-request")
377         {
378                 aclevprov = &acleventprov;
379                 reqevprov = &reqeventprov;
380                 HttpServerSocket::ConfigureParser();
381         }
382
383         void init() CXX11_OVERRIDE
384         {
385                 HttpModule = this;
386         }
387
388         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
389         {
390                 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
391                 timeoutsec = tag->getDuration("timeout", 10, 1);
392         }
393
394         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
395         {
396                 if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
397                         return MOD_RES_PASSTHRU;
398
399                 sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
400                 return MOD_RES_ALLOW;
401         }
402
403         void OnUnloadModule(Module* mod) CXX11_OVERRIDE
404         {
405                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
406                 {
407                         HttpServerSocket* sock = *i;
408                         ++i;
409                         if (sock->GetModHook(mod))
410                         {
411                                 sock->cull();
412                                 delete sock;
413                         }
414                 }
415         }
416
417         CullResult cull() CXX11_OVERRIDE
418         {
419                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
420                 {
421                         HttpServerSocket* sock = *i;
422                         sock->AddToCull();
423                 }
424                 return Module::cull();
425         }
426
427         Version GetVersion() CXX11_OVERRIDE
428         {
429                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
430         }
431 };
432
433 MODULE_INIT(ModuleHttpServer)