]> git.netwichtig.de Git - user/henk/code/inspircd.git/blob - src/modules/m_httpd.cpp
Fix a bunch of weird indentation and spacing issues.
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
1 /*
2  * InspIRCd -- Internet Relay Chat Daemon
3  *
4  *   Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
5  *   Copyright (C) 2018 edef <edef@edef.eu>
6  *   Copyright (C) 2013-2014, 2017-2020 Sadie Powell <sadie@witchery.services>
7  *   Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
8  *   Copyright (C) 2012 Robby <robby@chatbelgie.be>
9  *   Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
10  *   Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
11  *   Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
12  *   Copyright (C) 2007 John Brooks <special@inspircd.org>
13  *   Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
14  *   Copyright (C) 2006, 2008, 2010 Craig Edwards <brain@inspircd.org>
15  *
16  * This file is part of InspIRCd.  InspIRCd is free software: you can
17  * redistribute it and/or modify it under the terms of the GNU General Public
18  * License as published by the Free Software Foundation, version 2.
19  *
20  * This program is distributed in the hope that it will be useful, but WITHOUT
21  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
22  * FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more
23  * details.
24  *
25  * You should have received a copy of the GNU General Public License
26  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
27  */
28
29 /// $CompilerFlags: -Ivendor_directory("http_parser")
30
31
32 #include "inspircd.h"
33 #include "iohook.h"
34 #include "modules/httpd.h"
35
36 #ifdef __GNUC__
37 # pragma GCC diagnostic push
38 #endif
39
40 // Fix warnings about the use of commas at end of enumerator lists and long long
41 // on C++03.
42 #if defined __clang__
43 # pragma clang diagnostic ignored "-Wc++11-extensions"
44 # pragma clang diagnostic ignored "-Wc++11-long-long"
45 #elif defined __GNUC__
46 # pragma GCC diagnostic ignored "-Wlong-long"
47 # if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8))
48 #  pragma GCC diagnostic ignored "-Wpedantic"
49 # else
50 #  pragma GCC diagnostic ignored "-pedantic"
51 # endif
52 #endif
53
54 // Fix warnings about shadowing in http_parser.
55 #ifdef __GNUC__
56 # pragma GCC diagnostic ignored "-Wshadow"
57 #endif
58
59 #include <http_parser.c>
60
61 #ifdef __GNUC__
62 # pragma GCC diagnostic pop
63 #endif
64
65 class ModuleHttpServer;
66
67 static ModuleHttpServer* HttpModule;
68 static insp::intrusive_list<HttpServerSocket> sockets;
69 static Events::ModuleEventProvider* aclevprov;
70 static Events::ModuleEventProvider* reqevprov;
71 static http_parser_settings parser_settings;
72
73 /** A socket used for HTTP transport
74  */
75 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
76 {
77  private:
78         friend class ModuleHttpServer;
79
80         http_parser parser;
81         http_parser_url url;
82         std::string ip;
83         std::string uri;
84         HTTPHeaders headers;
85         std::string body;
86         size_t total_buffers;
87         int status_code;
88
89         /** True if this object is in the cull list
90          */
91         bool waitingcull;
92         bool messagecomplete;
93
94         bool Tick(time_t currtime) CXX11_OVERRIDE
95         {
96                 if (!messagecomplete)
97                 {
98                         ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd());
99                         Close();
100                         return false;
101                 }
102
103                 return true;
104         }
105
106         template<int (HttpServerSocket::*f)()>
107         static int Callback(http_parser* p)
108         {
109                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
110                 return (sock->*f)();
111         }
112
113         template<int (HttpServerSocket::*f)(const char*, size_t)>
114         static int DataCallback(http_parser* p, const char* buf, size_t len)
115         {
116                 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
117                 return (sock->*f)(buf, len);
118         }
119
120         static void ConfigureParser()
121         {
122                 http_parser_settings_init(&parser_settings);
123                 parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>;
124                 parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>;
125                 parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>;
126                 parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>;
127                 parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>;
128         }
129
130         int OnMessageBegin()
131         {
132                 uri.clear();
133                 header_state = HEADER_NONE;
134                 body.clear();
135                 total_buffers = 0;
136                 return 0;
137         }
138
139         bool AcceptData(size_t len)
140         {
141                 total_buffers += len;
142                 return total_buffers < 8192;
143         }
144
145         int OnUrl(const char* buf, size_t len)
146         {
147                 if (!AcceptData(len))
148                 {
149                         status_code = HTTP_STATUS_URI_TOO_LONG;
150                         return -1;
151                 }
152                 uri.append(buf, len);
153                 return 0;
154         }
155
156         enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
157         std::string header_field;
158         std::string header_value;
159
160         void OnHeaderComplete()
161         {
162                 headers.SetHeader(header_field, header_value);
163                 header_field.clear();
164                 header_value.clear();
165         }
166
167         int OnHeaderField(const char* buf, size_t len)
168         {
169                 if (header_state == HEADER_VALUE)
170                         OnHeaderComplete();
171                 header_state = HEADER_FIELD;
172                 if (!AcceptData(len))
173                 {
174                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
175                         return -1;
176                 }
177                 header_field.append(buf, len);
178                 return 0;
179         }
180
181         int OnHeaderValue(const char* buf, size_t len)
182         {
183                 header_state = HEADER_VALUE;
184                 if (!AcceptData(len))
185                 {
186                         status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
187                         return -1;
188                 }
189                 header_value.append(buf, len);
190                 return 0;
191         }
192
193         int OnHeadersComplete()
194         {
195                 if (header_state != HEADER_NONE)
196                         OnHeaderComplete();
197                 return 0;
198         }
199
200         int OnBody(const char* buf, size_t len)
201         {
202                 if (!AcceptData(len))
203                 {
204                         status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
205                         return -1;
206                 }
207                 body.append(buf, len);
208                 return 0;
209         }
210
211         int OnMessageComplete()
212         {
213                 messagecomplete = true;
214                 ServeData();
215                 return 0;
216         }
217
218  public:
219         HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
220                 : BufferedSocket(newfd)
221                 , Timer(timeoutsec)
222                 , ip(IP)
223                 , status_code(0)
224                 , waitingcull(false)
225                 , messagecomplete(false)
226         {
227                 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
228                 {
229                         via->iohookprovs.back()->OnAccept(this, client, server);
230                         // IOHook may have errored
231                         if (!getError().empty())
232                         {
233                                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered a hook error: %s",
234                                         GetFd(), getError().c_str());
235                                 Close();
236                                 return;
237                         }
238                 }
239
240                 parser.data = this;
241                 http_parser_init(&parser, HTTP_REQUEST);
242                 ServerInstance->Timers.AddTimer(this);
243         }
244
245         ~HttpServerSocket()
246         {
247                 sockets.erase(this);
248         }
249
250         void Close() CXX11_OVERRIDE
251         {
252                 if (waitingcull || !HasFd())
253                         return;
254
255                 waitingcull = true;
256                 BufferedSocket::Close();
257                 ServerInstance->GlobalCulls.AddItem(this);
258         }
259
260         void OnError(BufferedSocketError err) CXX11_OVERRIDE
261         {
262                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered an error: %d - %s",
263                         GetFd(), err, getError().c_str());
264                 Close();
265         }
266
267         void SendHTTPError(unsigned int response, const char* errstr = NULL)
268         {
269                 if (!errstr)
270                         errstr = http_status_str((http_status)response);
271
272                 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Sending HTTP error %u: %s", response, errstr);
273                 static HTTPHeaders empty;
274                 std::string data = InspIRCd::Format(
275                         "<html><head></head><body style='font-family: sans-serif; text-align: center'>"
276                         "<h1 style='font-size: 48pt'>Error %u</h1><h2 style='font-size: 24pt'>%s</h2><hr>"
277                         "<small>Powered by <a href='https://www.inspircd.org'>InspIRCd</a></small></body></html>",
278                         response, errstr);
279
280                 Page(data, response, &empty);
281         }
282
283         void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders)
284         {
285                 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)));
286
287                 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
288                 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
289                 rheaders.SetHeader("Content-Length", ConvToStr(size));
290
291                 if (size)
292                         rheaders.CreateHeader("Content-Type", "text/html");
293                 else
294                         rheaders.RemoveHeader("Content-Type");
295
296                 /* Supporting Connection: keep-alive causes a whole world of hurt synchronizing timeouts,
297                  * so remove it, its not essential for what we need.
298                  */
299                 rheaders.SetHeader("Connection", "Close");
300
301                 WriteData(rheaders.GetFormattedHeaders());
302                 WriteData("\r\n");
303         }
304
305         void OnDataReady() CXX11_OVERRIDE
306         {
307                 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
308                         return;
309                 http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size());
310                 if (parser.upgrade)
311                         SendHTTPError(status_code ? status_code : 400);
312                 else if (HTTP_PARSER_ERRNO(&parser))
313                         SendHTTPError(status_code ? status_code : 400, http_errno_description((http_errno)parser.http_errno));
314         }
315
316         void ServeData()
317         {
318                 ModResult MOD_RESULT;
319                 std::string method = http_method_str(static_cast<http_method>(parser.method));
320                 HTTPRequestURI parsed;
321                 ParseURI(uri, parsed);
322                 HTTPRequest acl(method, parsed, &headers, this, ip, body);
323                 FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
324                 if (MOD_RESULT != MOD_RES_DENY)
325                 {
326                         HTTPRequest request(method, parsed, &headers, this, ip, body);
327                         FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (request));
328                         if (MOD_RESULT == MOD_RES_PASSTHRU)
329                         {
330                                 SendHTTPError(404);
331                         }
332                 }
333         }
334
335         void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders)
336         {
337                 SendHeaders(s.length(), response, *hheaders);
338                 WriteData(s);
339                 BufferedSocket::Close(true);
340         }
341
342         void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
343         {
344                 Page(n->str(), response, hheaders);
345         }
346
347         bool ParseURI(const std::string& uristr, HTTPRequestURI& out)
348         {
349                 http_parser_url_init(&url);
350                 if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0)
351                         return false;
352
353                 if (url.field_set & (1 << UF_PATH))
354                 {
355                         // Normalise the path.
356                         std::vector<std::string> pathsegments;
357                         irc::sepstream pathstream(uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len), '/');
358                         for (std::string pathsegment; pathstream.GetToken(pathsegment); )
359                         {
360                                 if (pathsegment == ".")
361                                 {
362                                         // Stay at the current level.
363                                         continue;
364                                 }
365
366                                 if (pathsegment == "..")
367                                 {
368                                         // Traverse up to the previous level.
369                                         if (!pathsegments.empty())
370                                                 pathsegments.pop_back();
371                                         continue;
372                                 }
373
374                                 pathsegments.push_back(pathsegment);
375                         }
376
377                         out.path.reserve(url.field_data[UF_PATH].len);
378                         out.path.append("/").append(stdalgo::string::join(pathsegments, '/'));
379                 }
380
381                 if (url.field_set & (1 << UF_FRAGMENT))
382                         out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
383
384                 std::string param_str;
385                 if (url.field_set & (1 << UF_QUERY))
386                         param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
387
388                 irc::sepstream param_stream(param_str, '&');
389                 std::string token;
390                 std::string::size_type eq_pos;
391                 while (param_stream.GetToken(token))
392                 {
393                         eq_pos = token.find('=');
394                         if (eq_pos == std::string::npos)
395                         {
396                                 out.query_params.insert(std::make_pair(token, ""));
397                         }
398                         else
399                         {
400                                 out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
401                         }
402                 }
403                 return true;
404         }
405 };
406
407 class HTTPdAPIImpl : public HTTPdAPIBase
408 {
409  public:
410         HTTPdAPIImpl(Module* parent)
411                 : HTTPdAPIBase(parent)
412         {
413         }
414
415         void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
416         {
417                 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
418         }
419 };
420
421 class ModuleHttpServer : public Module
422 {
423         HTTPdAPIImpl APIImpl;
424         unsigned int timeoutsec;
425         Events::ModuleEventProvider acleventprov;
426         Events::ModuleEventProvider reqeventprov;
427
428  public:
429         ModuleHttpServer()
430                 : APIImpl(this)
431                 , acleventprov(this, "event/http-acl")
432                 , reqeventprov(this, "event/http-request")
433         {
434                 aclevprov = &acleventprov;
435                 reqevprov = &reqeventprov;
436                 HttpServerSocket::ConfigureParser();
437         }
438
439         void init() CXX11_OVERRIDE
440         {
441                 HttpModule = this;
442         }
443
444         void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
445         {
446                 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
447                 timeoutsec = tag->getDuration("timeout", 10, 1);
448         }
449
450         ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
451         {
452                 if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
453                         return MOD_RES_PASSTHRU;
454
455                 sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
456                 return MOD_RES_ALLOW;
457         }
458
459         void OnUnloadModule(Module* mod) CXX11_OVERRIDE
460         {
461                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
462                 {
463                         HttpServerSocket* sock = *i;
464                         ++i;
465                         if (sock->GetModHook(mod))
466                         {
467                                 sock->cull();
468                                 delete sock;
469                         }
470                 }
471         }
472
473         CullResult cull() CXX11_OVERRIDE
474         {
475                 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
476                 {
477                         HttpServerSocket* sock = *i;
478                         sock->Close();
479                 }
480                 return Module::cull();
481         }
482
483         Version GetVersion() CXX11_OVERRIDE
484         {
485                 return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR);
486         }
487 };
488
489 MODULE_INIT(ModuleHttpServer)