2 * InspIRCd -- Internet Relay Chat Daemon
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>
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.
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
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/>.
24 /// $CompilerFlags: -Ivendor_directory("http_parser")
29 #include "modules/httpd.h"
31 // Fix warnings about the use of commas at end of enumerator lists on C++03.
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"
38 # pragma GCC diagnostic ignored "-pedantic"
42 // Fix warnings about shadowing in http_parser.
44 # pragma GCC diagnostic ignored "-Wshadow"
47 #include <http_parser.c>
49 class ModuleHttpServer;
51 static ModuleHttpServer* HttpModule;
52 static insp::intrusive_list<HttpServerSocket> sockets;
53 static Events::ModuleEventProvider* aclevprov;
54 static Events::ModuleEventProvider* reqevprov;
55 static http_parser_settings parser_settings;
57 /** A socket used for HTTP transport
59 class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
61 friend ModuleHttpServer;
72 /** True if this object is in the cull list
76 bool Tick(time_t currtime) CXX11_OVERRIDE
82 template<int (HttpServerSocket::*f)()>
83 static int Callback(http_parser* p)
85 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
89 template<int (HttpServerSocket::*f)(const char*, size_t)>
90 static int DataCallback(http_parser* p, const char* buf, size_t len)
92 HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
93 return (sock->*f)(buf, len);
96 static void ConfigureParser()
98 http_parser_settings_init(&parser_settings);
99 parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>;
100 parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>;
101 parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>;
102 parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>;
103 parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>;
109 header_state = HEADER_NONE;
115 bool AcceptData(size_t len)
117 total_buffers += len;
118 return total_buffers < 8192;
121 int OnUrl(const char* buf, size_t len)
123 if (!AcceptData(len))
125 status_code = HTTP_STATUS_URI_TOO_LONG;
128 uri.append(buf, len);
132 enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
133 std::string header_field;
134 std::string header_value;
136 void OnHeaderComplete()
138 headers.SetHeader(header_field, header_value);
139 header_field.clear();
140 header_value.clear();
143 int OnHeaderField(const char* buf, size_t len)
145 if (header_state == HEADER_VALUE)
147 header_state = HEADER_FIELD;
148 if (!AcceptData(len))
150 status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
153 header_field.append(buf, len);
157 int OnHeaderValue(const char* buf, size_t len)
159 header_state = HEADER_VALUE;
160 if (!AcceptData(len))
162 status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
165 header_value.append(buf, len);
169 int OnHeadersComplete()
171 if (header_state != HEADER_NONE)
176 int OnBody(const char* buf, size_t len)
178 if (!AcceptData(len))
180 status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
183 body.append(buf, len);
187 int OnMessageComplete()
194 HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
195 : BufferedSocket(newfd)
201 if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
203 via->iohookprovs.back()->OnAccept(this, client, server);
204 // IOHook may have errored
205 if (!getError().empty())
213 http_parser_init(&parser, HTTP_REQUEST);
214 ServerInstance->Timers.AddTimer(this);
222 void OnError(BufferedSocketError) CXX11_OVERRIDE
227 const char* Response(unsigned int response)
231 #define HTTP_STATUS_CASE(n, m, s) case n: return #s;
232 HTTP_STATUS_MAP(HTTP_STATUS_CASE)
239 void SendHTTPError(unsigned int response)
242 std::string data = InspIRCd::Format(
243 "<html><head></head><body>Server error %u: %s<br>"
244 "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>", response, Response(response));
246 SendHeaders(data.length(), response, empty);
251 void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders)
253 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)));
255 rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
256 rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
257 rheaders.SetHeader("Content-Length", ConvToStr(size));
260 rheaders.CreateHeader("Content-Type", "text/html");
262 rheaders.RemoveHeader("Content-Type");
264 /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
265 * so remove it, its not essential for what we need.
267 rheaders.SetHeader("Connection", "Close");
269 WriteData(rheaders.GetFormattedHeaders());
273 void OnDataReady() CXX11_OVERRIDE
275 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
277 http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size());
278 if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
279 SendHTTPError(status_code ? status_code : 400);
284 ModResult MOD_RESULT;
285 std::string method = http_method_str(static_cast<http_method>(parser.method));
286 HTTPRequestURI parsed;
287 ParseURI(uri, parsed);
288 HTTPRequest acl(method, parsed, &headers, this, ip, body);
289 FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
290 if (MOD_RESULT != MOD_RES_DENY)
292 HTTPRequest url(method, parsed, &headers, this, ip, body);
293 FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
294 if (MOD_RESULT == MOD_RES_PASSTHRU)
301 void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
303 SendHeaders(n->str().length(), response, *hheaders);
315 ServerInstance->GlobalCulls.AddItem(this);
318 bool ParseURI(const std::string& uri, HTTPRequestURI& out)
320 http_parser_url_init(&url);
321 if (http_parser_parse_url(uri.c_str(), uri.size(), 0, &url) != 0)
324 if (url.field_set & (1 << UF_PATH))
325 out.path = uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len);
327 if (url.field_set & (1 << UF_FRAGMENT))
328 out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
330 std::string param_str;
331 if (url.field_set & (1 << UF_QUERY))
332 param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
334 irc::sepstream param_stream(param_str, '&');
336 std::string::size_type eq_pos;
337 while (param_stream.GetToken(token))
339 eq_pos = token.find('=');
340 if (eq_pos == std::string::npos)
342 out.query_params.insert(std::make_pair(token, ""));
346 out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
353 class HTTPdAPIImpl : public HTTPdAPIBase
356 HTTPdAPIImpl(Module* parent)
357 : HTTPdAPIBase(parent)
361 void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
363 resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
367 class ModuleHttpServer : public Module
369 HTTPdAPIImpl APIImpl;
370 unsigned int timeoutsec;
371 Events::ModuleEventProvider acleventprov;
372 Events::ModuleEventProvider reqeventprov;
377 , acleventprov(this, "event/http-acl")
378 , reqeventprov(this, "event/http-request")
380 aclevprov = &acleventprov;
381 reqevprov = &reqeventprov;
382 HttpServerSocket::ConfigureParser();
385 void init() CXX11_OVERRIDE
390 void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
392 ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
393 timeoutsec = tag->getDuration("timeout", 10, 1);
396 ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
398 if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
399 return MOD_RES_PASSTHRU;
401 sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
402 return MOD_RES_ALLOW;
405 void OnUnloadModule(Module* mod) CXX11_OVERRIDE
407 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
409 HttpServerSocket* sock = *i;
411 if (sock->GetModHook(mod))
419 CullResult cull() CXX11_OVERRIDE
421 for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
423 HttpServerSocket* sock = *i;
426 return Module::cull();
429 Version GetVersion() CXX11_OVERRIDE
431 return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
435 MODULE_INIT(ModuleHttpServer)