]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
c4b5dc1f29be996fc2ba658b76b57a27efd31b94
[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 and long long
32 // on C++03.
33 #if defined __clang__
34 # pragma clang diagnostic ignored "-Wc++11-extensions"
35 # pragma clang diagnostic ignored "-Wc++11-long-long"
36 #elif defined __GNUC__
37 # pragma GCC diagnostic ignored "-Wlong-long"
38 # if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8))
39 #  pragma GCC diagnostic ignored "-Wpedantic"
40 # else
41 #  pragma GCC diagnostic ignored "-pedantic"
42 # endif
43 #endif
44
45 // Fix warnings about shadowing in http_parser.
46 #ifdef __GNUC__
47 //# pragma GCC diagnostic ignored "-Wshadow"
48 #endif
49
50 #include <http_parser.c>
51
52 class ModuleHttpServer;
53
54 static ModuleHttpServer* HttpModule;
55 static insp::intrusive_list<HttpServerSocket> sockets;
56 static Events::ModuleEventProvider* aclevprov;
57 static Events::ModuleEventProvider* reqevprov;
58 static http_parser_settings parser_settings;
59
60 /** A socket used for HTTP transport
61  */
62 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
63 {
64  private:
65         friend class ModuleHttpServer;
66
67         http_parser parser;
68         http_parser_url url;
69         std::string ip;
70         std::string uri;
71         HTTPHeaders headers;
72         std::string body;
73         size_t total_buffers;
74         int status_code;
75
76         /** True if this object is in the cull list
77          */
78         bool waitingcull;
79         bool messagecomplete;
80
81         bool Tick(time_t currtime) CXX11_OVERRIDE
82         {
83                 if (!messagecomplete)
84                 {
85                         AddToCull();
86                         return false;
87                 }
88
89                 return true;
90         }
91
92         template<int (HttpServerSocket::*f)()>
93         static int Callback(http_parser* p)
94         {
95                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
96                 return (sock->*f)();
97         }
98
99         template<int (HttpServerSocket::*f)(const char*, size_t)>
100         static int DataCallback(http_parser* p, const char* buf, size_t len)
101         {
102                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
103                 return (sock->*f)(buf, len);
104         }
105
106         static void ConfigureParser()
107         {
108                 http_parser_settings_init(&parser_settings);
109                 parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>;
110                 parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>;
111                 parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>;
112                 parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>;
113                 parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>;
114         }
115
116         int OnMessageBegin()
117         {
118                 uri.clear();
119                 header_state = HEADER_NONE;
120                 body.clear();
121                 total_buffers = 0;
122                 return 0;
123         }
124
125         bool AcceptData(size_t len)
126         {
127                 total_buffers += len;
128                 return total_buffers < 8192;
129         }
130
131         int OnUrl(const char* buf, size_t len)
132         {
133                 if (!AcceptData(len))
134                 {
135                         status_code = HTTP_STATUS_URI_TOO_LONG;
136                         return -1;
137                 }
138                 uri.append(buf, len);
139                 return 0;
140         }
141
142         enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
143         std::string header_field;
144         std::string header_value;
145
146         void OnHeaderComplete()
147         {
148                 headers.SetHeader(header_field, header_value);
149                 header_field.clear();
150                 header_value.clear();
151         }
152
153         int OnHeaderField(const char* buf, size_t len)
154         {
155                 if (header_state == HEADER_VALUE)
156                         OnHeaderComplete();
157                 header_state = HEADER_FIELD;
158                 if (!AcceptData(len))
159                 {
160                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
161                         return -1;
162                 }
163                 header_field.append(buf, len);
164                 return 0;
165         }
166
167         int OnHeaderValue(const char* buf, size_t len)
168         {
169                 header_state = HEADER_VALUE;
170                 if (!AcceptData(len))
171                 {
172                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
173                         return -1;
174                 }
175                 header_value.append(buf, len);
176                 return 0;
177         }
178
179         int OnHeadersComplete()
180         {
181                 if (header_state != HEADER_NONE)
182                         OnHeaderComplete();
183                 return 0;
184         }
185
186         int OnBody(const char* buf, size_t len)
187         {
188                 if (!AcceptData(len))
189                 {
190                         status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
191                         return -1;
192                 }
193                 body.append(buf, len);
194                 return 0;
195         }
196
197         int OnMessageComplete()
198         {
199                 messagecomplete = true;
200                 ServeData();
201                 return 0;
202         }
203
204  public:
205         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
206                 : BufferedSocket(newfd)
207                 , Timer(timeoutsec)
208                 , ip(IP)
209                 , status_code(0)
210                 , waitingcull(false)
211                 , messagecomplete(false)
212         {
213                 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
214                 {
215                         via->iohookprovs.back()->OnAccept(this, client, server);
216                         // IOHook may have errored
217                         if (!getError().empty())
218                         {
219                                 AddToCull();
220                                 return;
221                         }
222                 }
223
224                 parser.data = this;
225                 http_parser_init(&parser, HTTP_REQUEST);
226                 ServerInstance->Timers.AddTimer(this);
227         }
228
229         ~HttpServerSocket()
230         {
231                 sockets.erase(this);
232         }
233
234         void OnError(BufferedSocketError) CXX11_OVERRIDE
235         {
236                 AddToCull();
237         }
238
239         void SendHTTPError(unsigned int response)
240         {
241                 HTTPHeaders empty;
242                 std::string data = InspIRCd::Format(
243                         "<html><head></head><body>Server error %u: %s<br>"
244                         "<small>Powered by <a href='https://www.inspircd.org'>InspIRCd</a></small></body></html>", response, http_status_str((http_status)response));
245
246                 Page(data, response, &empty);
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, http_status_str((http_status)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 request(method, parsed, &headers, this, ip, body);
291                         FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (request));
292                         if (MOD_RESULT == MOD_RES_PASSTHRU)
293                         {
294                                 SendHTTPError(404);
295                         }
296                 }
297         }
298
299         void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders)
300         {
301                 SendHeaders(s.length(), response, *hheaders);
302                 WriteData(s);
303                 Close(true);
304         }
305
306         void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
307         {
308                 Page(n->str(), response, hheaders);
309         }
310
311         void AddToCull()
312         {
313                 if (waitingcull)
314                         return;
315
316                 waitingcull = true;
317                 Close();
318                 ServerInstance->GlobalCulls.AddItem(this);
319         }
320
321         bool ParseURI(const std::string& uristr, HTTPRequestURI& out)
322         {
323                 http_parser_url_init(&url);
324                 if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0)
325                         return false;
326
327                 if (url.field_set & (1 << UF_PATH))
328                         out.path = uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len);
329
330                 if (url.field_set & (1 << UF_FRAGMENT))
331                         out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
332
333                 std::string param_str;
334                 if (url.field_set & (1 << UF_QUERY))
335                         param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
336
337                 irc::sepstream param_stream(param_str, '&');
338                 std::string token;
339                 std::string::size_type eq_pos;
340                 while (param_stream.GetToken(token))
341                 {
342                         eq_pos = token.find('=');
343                         if (eq_pos == std::string::npos)
344                         {
345                                 out.query_params.insert(std::make_pair(token, ""));
346                         }
347                         else
348                         {
349                                 out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
350                         }
351                 }
352                 return true;
353         }
354 };
355
356 class HTTPdAPIImpl : public HTTPdAPIBase
357 {
358  public:
359         HTTPdAPIImpl(Module* parent)
360                 : HTTPdAPIBase(parent)
361         {
362         }
363
364         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
365         {
366                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
367         }
368 };
369
370 class ModuleHttpServer : public Module
371 {
372         HTTPdAPIImpl APIImpl;
373         unsigned int timeoutsec;
374         Events::ModuleEventProvider acleventprov;
375         Events::ModuleEventProvider reqeventprov;
376
377  public:
378         ModuleHttpServer()
379                 : APIImpl(this)
380                 , acleventprov(this, "event/http-acl")
381                 , reqeventprov(this, "event/http-request")
382         {
383                 aclevprov = &acleventprov;
384                 reqevprov = &reqeventprov;
385                 HttpServerSocket::ConfigureParser();
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->getDuration("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 (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
402                         return MOD_RES_PASSTHRU;
403
404                 sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
405                 return MOD_RES_ALLOW;
406         }
407
408         void OnUnloadModule(Module* mod) CXX11_OVERRIDE
409         {
410                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
411                 {
412                         HttpServerSocket* sock = *i;
413                         ++i;
414                         if (sock->GetModHook(mod))
415                         {
416                                 sock->cull();
417                                 delete sock;
418                         }
419                 }
420         }
421
422         CullResult cull() CXX11_OVERRIDE
423         {
424                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
425                 {
426                         HttpServerSocket* sock = *i;
427                         sock->AddToCull();
428                 }
429                 return Module::cull();
430         }
431
432         Version GetVersion() CXX11_OVERRIDE
433         {
434                 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
435         }
436 };
437
438 MODULE_INIT(ModuleHttpServer)