2 * InspIRCd -- Internet Relay Chat Daemon
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>
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.
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
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/>.
29 /// $CompilerFlags: -Ivendor_directory("http_parser")
34 #include "modules/httpd.h"
37 # pragma GCC diagnostic push
40 // Fix warnings about the use of commas at end of enumerator lists and long long
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"
50 # pragma GCC diagnostic ignored "-pedantic"
54 // Fix warnings about shadowing in http_parser.
56 # pragma GCC diagnostic ignored "-Wshadow"
59 #include <http_parser.c>
62 # pragma GCC diagnostic pop
65 class ModuleHttpServer;
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;
73 /** A socket used for HTTP transport
75 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
78 friend class ModuleHttpServer;
89 /** True if this object is in the cull list
94 bool Tick(time_t currtime) CXX11_OVERRIDE
98 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd());
106 template<int (HttpServerSocket::*f)()>
107 static int Callback(http_parser* p)
109 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
113 template<int (HttpServerSocket::*f)(const char*, size_t)>
114 static int DataCallback(http_parser* p, const char* buf, size_t len)
116 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
117 return (sock->*f)(buf, len);
120 static void ConfigureParser()
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>;
133 header_state = HEADER_NONE;
139 bool AcceptData(size_t len)
141 total_buffers += len;
142 return total_buffers < 8192;
145 int OnUrl(const char* buf, size_t len)
147 if (!AcceptData(len))
149 status_code = HTTP_STATUS_URI_TOO_LONG;
152 uri.append(buf, len);
156 enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
157 std::string header_field;
158 std::string header_value;
160 void OnHeaderComplete()
162 headers.SetHeader(header_field, header_value);
163 header_field.clear();
164 header_value.clear();
167 int OnHeaderField(const char* buf, size_t len)
169 if (header_state == HEADER_VALUE)
171 header_state = HEADER_FIELD;
172 if (!AcceptData(len))
174 status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
177 header_field.append(buf, len);
181 int OnHeaderValue(const char* buf, size_t len)
183 header_state = HEADER_VALUE;
184 if (!AcceptData(len))
186 status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
189 header_value.append(buf, len);
193 int OnHeadersComplete()
195 if (header_state != HEADER_NONE)
200 int OnBody(const char* buf, size_t len)
202 if (!AcceptData(len))
204 status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
207 body.append(buf, len);
211 int OnMessageComplete()
213 messagecomplete = true;
219 HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
220 : BufferedSocket(newfd)
225 , messagecomplete(false)
227 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
229 via->iohookprovs.back()->OnAccept(this, client, server);
230 // IOHook may have errored
231 if (!getError().empty())
233 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered a hook error: %s",
234 GetFd(), getError().c_str());
241 http_parser_init(&parser, HTTP_REQUEST);
242 ServerInstance->Timers.AddTimer(this);
250 void Close() CXX11_OVERRIDE
252 if (waitingcull || !HasFd())
256 BufferedSocket::Close();
257 ServerInstance->GlobalCulls.AddItem(this);
260 void OnError(BufferedSocketError err) CXX11_OVERRIDE
262 ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered an error: %d - %s",
263 GetFd(), err, getError().c_str());
267 void SendHTTPError(unsigned int response, const char* errstr = NULL)
270 errstr = http_status_str((http_status)response);
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>",
280 Page(data, response, &empty);
283 void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders)
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)));
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));
292 rheaders.CreateHeader("Content-Type", "text/html");
294 rheaders.RemoveHeader("Content-Type");
296 /* Supporting Connection: keep-alive causes a whole world of hurt synchronizing timeouts,
297 * so remove it, its not essential for what we need.
299 rheaders.SetHeader("Connection", "Close");
301 WriteData(rheaders.GetFormattedHeaders());
305 void OnDataReady() CXX11_OVERRIDE
307 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
309 http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size());
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));
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)
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)
335 void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders)
337 SendHeaders(s.length(), response, *hheaders);
339 BufferedSocket::Close(true);
342 void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
344 Page(n->str(), response, hheaders);
347 bool ParseURI(const std::string& uristr, HTTPRequestURI& out)
349 http_parser_url_init(&url);
350 if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0)
353 if (url.field_set & (1 << UF_PATH))
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); )
360 if (pathsegment == ".")
362 // Stay at the current level.
366 if (pathsegment == "..")
368 // Traverse up to the previous level.
369 if (!pathsegments.empty())
370 pathsegments.pop_back();
374 pathsegments.push_back(pathsegment);
377 out.path.reserve(url.field_data[UF_PATH].len);
378 out.path.append("/").append(stdalgo::string::join(pathsegments, '/'));
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);
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);
388 irc::sepstream param_stream(param_str, '&');
390 std::string::size_type eq_pos;
391 while (param_stream.GetToken(token))
393 eq_pos = token.find('=');
394 if (eq_pos == std::string::npos)
396 out.query_params.insert(std::make_pair(token, ""));
400 out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
407 class HTTPdAPIImpl : public HTTPdAPIBase
410 HTTPdAPIImpl(Module* parent)
411 : HTTPdAPIBase(parent)
415 void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
417 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
421 class ModuleHttpServer : public Module
423 HTTPdAPIImpl APIImpl;
424 unsigned int timeoutsec;
425 Events::ModuleEventProvider acleventprov;
426 Events::ModuleEventProvider reqeventprov;
431 , acleventprov(this, "event/http-acl")
432 , reqeventprov(this, "event/http-request")
434 aclevprov = &acleventprov;
435 reqevprov = &reqeventprov;
436 HttpServerSocket::ConfigureParser();
439 void init() CXX11_OVERRIDE
444 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
446 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
447 timeoutsec = tag->getDuration("timeout", 10, 1);
450 ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
452 if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
453 return MOD_RES_PASSTHRU;
455 sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
456 return MOD_RES_ALLOW;
459 void OnUnloadModule(Module* mod) CXX11_OVERRIDE
461 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
463 HttpServerSocket* sock = *i;
465 if (sock->GetModHook(mod))
473 CullResult cull() CXX11_OVERRIDE
475 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
477 HttpServerSocket* sock = *i;
480 return Module::cull();
483 Version GetVersion() CXX11_OVERRIDE
485 return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR);
489 MODULE_INIT(ModuleHttpServer)