-/* +------------------------------------+\r * | Inspire Internet Relay Chat Daemon |\r * +------------------------------------+\r *\r * InspIRCd: (C) 2002-2007 InspIRCd Development Team\r * See: http://www.inspircd.org/wiki/index.php/Credits\r *\r * This program is free but copyrighted software; see\r * the file COPYING for details.\r *\r * ---------------------------------------------------\r */\r\r#include "inspircd.h"\r#include <algorithm>\r#include "modules.h"\r#include "httpd.h"\r\r/* $ModDesc: Provides HTTP serving facilities to modules */\r\rclass ModuleHttpServer;\r\rstatic ModuleHttpServer* HttpModule;\rstatic bool claimed;\r\r/** HTTP socket states\r */\renum HttpState\r{\r HTTP_LISTEN = 0,\r HTTP_SERVE_WAIT_REQUEST = 1,\r HTTP_SERVE_RECV_POSTDATA = 2,\r HTTP_SERVE_SEND_DATA = 3\r};\r\rclass HttpServerSocket;\r\r/** This class is used to handle HTTP socket timeouts\r */\rclass HttpServerTimeout : public InspTimer\r{\r private:\r /** HttpServerSocket we are attached to\r */\r HttpServerSocket* s;\r /** Socketengine the file descriptor is in\r */\r SocketEngine* SE;\r public:\r /** Attach timeout to HttpServerSocket\r */\r HttpServerTimeout(HttpServerSocket* sock, SocketEngine* engine);\r /** Handle timer tick\r */\r void Tick(time_t TIME);\r};\r\r/** A socket used for HTTP transport\r */\rclass HttpServerSocket : public InspSocket\r{\r FileReader* index;\r HttpState InternalState;\r std::stringstream headers;\r std::string postdata;\r std::string request_type;\r std::string uri;\r std::string http_version;\r unsigned int postsize;\r HttpServerTimeout* Timeout;\r\r public:\r\r HttpServerSocket(InspIRCd* SI, std::string host, int port, bool listening, unsigned long maxtime, FileReader* index_page) : InspSocket(SI, host, port, listening, maxtime), index(index_page), postsize(0)\r {\r InternalState = HTTP_LISTEN;\r Timeout = NULL;\r }\r\r HttpServerSocket(InspIRCd* SI, int newfd, char* ip, FileReader* ind) : InspSocket(SI, newfd, ip), index(ind), postsize(0)\r {\r InternalState = HTTP_SERVE_WAIT_REQUEST;\r Timeout = new HttpServerTimeout(this, Instance->SE);\r Instance->Timers->AddTimer(Timeout);\r }\r\r FileReader* GetIndex()\r {\r return index;\r }\r\r ~HttpServerSocket()\r {\r if (Timeout)\r {\r if (Instance->Time() < Timeout->GetTimer())\r Instance->Timers->DelTimer(Timeout);\r Timeout = NULL;\r }\r }\r\r virtual int OnIncomingConnection(int newsock, char* ip)\r {\r if (InternalState == HTTP_LISTEN)\r {\r HttpServerSocket* s = new HttpServerSocket(this->Instance, newsock, ip, index);\r s = s; /* Stop GCC whining */\r }\r return true;\r }\r\r virtual void OnClose()\r {\r }\r\r std::string Response(int response)\r {\r switch (response)\r {\r case 100:\r return "CONTINUE";\r case 101:\r return "SWITCHING PROTOCOLS";\r case 200:\r return "OK";\r case 201:\r return "CREATED";\r case 202:\r return "ACCEPTED";\r case 203:\r return "NON-AUTHORITATIVE INFORMATION";\r case 204:\r return "NO CONTENT";\r case 205:\r return "RESET CONTENT";\r case 206:\r return "PARTIAL CONTENT";\r case 300:\r return "MULTIPLE CHOICES";\r case 301:\r return "MOVED PERMENANTLY";\r case 302:\r return "FOUND";\r case 303:\r return "SEE OTHER";\r case 304:\r return "NOT MODIFIED";\r case 305:\r return "USE PROXY";\r case 307:\r return "TEMPORARY REDIRECT";\r case 400:\r return "BAD REQUEST";\r case 401:\r return "UNAUTHORIZED";\r case 402:\r return "PAYMENT REQUIRED";\r case 403:\r return "FORBIDDEN";\r case 404:\r return "NOT FOUND";\r case 405:\r return "METHOD NOT ALLOWED";\r case 406:\r return "NOT ACCEPTABLE";\r case 407:\r return "PROXY AUTHENTICATION REQUIRED";\r case 408:\r return "REQUEST TIMEOUT";\r case 409:\r return "CONFLICT";\r case 410:\r return "GONE";\r case 411:\r return "LENGTH REQUIRED";\r case 412:\r return "PRECONDITION FAILED";\r case 413:\r return "REQUEST ENTITY TOO LARGE";\r case 414:\r return "REQUEST-URI TOO LONG";\r case 415:\r return "UNSUPPORTED MEDIA TYPE";\r case 416:\r return "REQUESTED RANGE NOT SATISFIABLE";\r case 417:\r return "EXPECTATION FAILED";\r case 500:\r return "INTERNAL SERVER ERROR";\r case 501:\r return "NOT IMPLEMENTED";\r case 502:\r return "BAD GATEWAY";\r case 503:\r return "SERVICE UNAVAILABLE";\r case 504:\r return "GATEWAY TIMEOUT";\r case 505:\r return "HTTP VERSION NOT SUPPORTED";\r default:\r return "WTF";\r break;\r \r }\r }\r\r void SendHeaders(unsigned long size, int response, const std::string &extraheaders)\r {\r time_t local = this->Instance->Time();\r struct tm *timeinfo = gmtime(&local);\r this->Write("HTTP/1.1 "+ConvToStr(response)+" "+Response(response)+"\r\nDate: ");\r this->Write(asctime(timeinfo));\r if (extraheaders.empty())\r {\r this->Write("Content-Type: text/html\r\n");\r }\r else\r {\r this->Write(extraheaders);\r }\r this->Write("Server: InspIRCd/m_httpd.so/1.1\r\nContent-Length: "+ConvToStr(size)+\r "\r\nConnection: close\r\n\r\n");\r }\r\r virtual bool OnDataReady()\r {\r char* data = this->Read();\r\r /* Check that the data read is a valid pointer and it has some content */\r if (data && *data)\r {\r headers << data;\r\r if (headers.str().find("\r\n\r\n") != std::string::npos)\r {\r if (request_type.empty())\r {\r headers >> request_type;\r headers >> uri;\r headers >> http_version;\r\r std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);\r std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);\r }\r\r if ((InternalState == HTTP_SERVE_WAIT_REQUEST) && (request_type == "POST"))\r {\r /* Do we need to fetch postdata? */\r postdata.clear();\r InternalState = HTTP_SERVE_RECV_POSTDATA;\r std::string header_item;\r while (headers >> header_item)\r {\r if (header_item == "Content-Length:")\r {\r headers >> header_item;\r postsize = atoi(header_item.c_str());\r }\r }\r if (!postsize)\r {\r InternalState = HTTP_SERVE_SEND_DATA;\r SendHeaders(0, 400, "");\r Timeout = new HttpServerTimeout(this, Instance->SE);\r Instance->Timers->AddTimer(Timeout);\r }\r else\r {\r std::string::size_type x = headers.str().find("\r\n\r\n");\r postdata = headers.str().substr(x+4, headers.str().length());\r /* Get content length and store */\r if (postdata.length() >= postsize)\r ServeData();\r }\r }\r else if (InternalState == HTTP_SERVE_RECV_POSTDATA)\r {\r /* Add postdata, once we have it all, send the event */\r postdata.append(data);\r if (postdata.length() >= postsize)\r ServeData();\r }\r else\r {\r ServeData();\r }\r return true;\r }\r return true;\r }\r else\r {\r return false;\r }\r }\r\r void ServeData()\r {\r /* Headers are complete */\r InternalState = HTTP_SERVE_SEND_DATA;\r\r Instance->Timers->DelTimer(Timeout);\r Timeout = NULL;\r \r if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))\r {\r SendHeaders(0, 505, "");\r }\r else\r {\r if ((request_type == "GET") && (uri == "/"))\r {\r SendHeaders(index->ContentSize(), 200, "");\r this->Write(index->Contents());\r }\r else\r {\r claimed = false;\r HTTPRequest httpr(request_type,uri,&headers,this,this->GetIP(),postdata);\r Event e((char*)&httpr, (Module*)HttpModule, "httpd_url");\r e.Send(this->Instance);\r if (!claimed)\r {\r SendHeaders(0, 404, "");\r }\r }\r }\r Timeout = new HttpServerTimeout(this, Instance->SE);\r Instance->Timers->AddTimer(Timeout);\r }\r\r void Page(std::stringstream* n, int response, std::string& extraheaders)\r {\r SendHeaders(n->str().length(), response, extraheaders);\r this->Write(n->str());\r }\r};\r\rHttpServerTimeout::HttpServerTimeout(HttpServerSocket* sock, SocketEngine* engine) : InspTimer(60, time(NULL)), s(sock), SE(engine)\r{\r}\r\rvoid HttpServerTimeout::Tick(time_t TIME)\r{\r SE->DelFd(s);\r s->Close();\r}\r\rclass ModuleHttpServer : public Module\r{\r std::vector<HttpServerSocket*> httpsocks;\r public:\r\r void ReadConfig()\r {\r int port;\r std::string host;\r std::string bindip;\r std::string indexfile;\r FileReader* index;\r HttpServerSocket* http;\r ConfigReader c(ServerInstance);\r\r httpsocks.clear();\r\r for (int i = 0; i < c.Enumerate("http"); i++)\r {\r host = c.ReadValue("http", "host", i);\r bindip = c.ReadValue("http", "ip", i);\r port = c.ReadInteger("http", "port", i, true);\r indexfile = c.ReadValue("http", "index", i);\r index = new FileReader(ServerInstance, indexfile);\r if (!index->Exists())\r throw ModuleException("Can't read index file: "+indexfile);\r http = new HttpServerSocket(ServerInstance, bindip, port, true, 0, index);\r httpsocks.push_back(http);\r }\r }\r\r ModuleHttpServer(InspIRCd* Me) : Module(Me)\r {\r ReadConfig();\r }\r\r void OnEvent(Event* event)\r {\r }\r\r char* OnRequest(Request* request)\r {\r claimed = true;\r HTTPDocument* doc = (HTTPDocument*)request->GetData();\r HttpServerSocket* sock = (HttpServerSocket*)doc->sock;\r sock->Page(doc->GetDocument(), doc->GetResponseCode(), doc->GetExtraHeaders());\r return NULL;\r }\r\r void Implements(char* List)\r {\r List[I_OnEvent] = List[I_OnRequest] = 1;\r }\r\r virtual ~ModuleHttpServer()\r {\r for (size_t i = 0; i < httpsocks.size(); i++)\r {\r ServerInstance->SE->DelFd(httpsocks[i]);\r delete httpsocks[i]->GetIndex();\r delete httpsocks[i];\r }\r }\r\r virtual Version GetVersion()\r {\r return Version(1,1,0,0,VF_VENDOR|VF_SERVICEPROVIDER,API_VERSION);\r }\r};\r\rMODULE_INIT(ModuleHttpServer)\r
\ No newline at end of file
+/*
+ * InspIRCd -- Internet Relay Chat Daemon
+ *
+ * Copyright (C) 2019 linuxdaemon <linuxdaemon.irc@gmail.com>
+ * Copyright (C) 2018 edef <edef@edef.eu>
+ * Copyright (C) 2013-2014, 2017-2020 Sadie Powell <sadie@witchery.services>
+ * Copyright (C) 2012-2016 Attila Molnar <attilamolnar@hush.com>
+ * Copyright (C) 2012 Robby <robby@chatbelgie.be>
+ * Copyright (C) 2009 Uli Schlachter <psychon@inspircd.org>
+ * Copyright (C) 2009 Daniel De Graaf <danieldg@inspircd.org>
+ * Copyright (C) 2008 Robin Burchell <robin+git@viroteck.net>
+ * Copyright (C) 2007 John Brooks <special@inspircd.org>
+ * Copyright (C) 2007 Dennis Friis <peavey@inspircd.org>
+ * Copyright (C) 2006, 2008, 2010 Craig Edwards <brain@inspircd.org>
+ *
+ * This file is part of InspIRCd. InspIRCd is free software: you can
+ * redistribute it and/or modify it under the terms of the GNU General Public
+ * License as published by the Free Software Foundation, version 2.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
+ * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
+ * details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+/// $CompilerFlags: -Ivendor_directory("http_parser")
+
+
+#include "inspircd.h"
+#include "iohook.h"
+#include "modules/httpd.h"
+
+#ifdef __GNUC__
+# pragma GCC diagnostic push
+#endif
+
+// Fix warnings about the use of commas at end of enumerator lists and long long
+// on C++03.
+#if defined __clang__
+# pragma clang diagnostic ignored "-Wc++11-extensions"
+# pragma clang diagnostic ignored "-Wc++11-long-long"
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-Wlong-long"
+# if (__GNUC__ > 4) || ((__GNUC__ == 4) && (__GNUC_MINOR__ >= 8))
+# pragma GCC diagnostic ignored "-Wpedantic"
+# else
+# pragma GCC diagnostic ignored "-pedantic"
+# endif
+#endif
+
+// Fix warnings about shadowing in http_parser.
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wshadow"
+#endif
+
+#include <http_parser.c>
+
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
+class ModuleHttpServer;
+
+static ModuleHttpServer* HttpModule;
+static insp::intrusive_list<HttpServerSocket> sockets;
+static Events::ModuleEventProvider* aclevprov;
+static Events::ModuleEventProvider* reqevprov;
+static http_parser_settings parser_settings;
+
+/** A socket used for HTTP transport
+ */
+class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
+{
+ private:
+ friend class ModuleHttpServer;
+
+ http_parser parser;
+ http_parser_url url;
+ std::string ip;
+ std::string uri;
+ HTTPHeaders headers;
+ std::string body;
+ size_t total_buffers;
+ int status_code;
+
+ /** True if this object is in the cull list
+ */
+ bool waitingcull;
+ bool messagecomplete;
+
+ bool Tick(time_t currtime) CXX11_OVERRIDE
+ {
+ if (!messagecomplete)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd());
+ Close();
+ return false;
+ }
+
+ return true;
+ }
+
+ template<int (HttpServerSocket::*f)()>
+ static int Callback(http_parser* p)
+ {
+ HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
+ return (sock->*f)();
+ }
+
+ template<int (HttpServerSocket::*f)(const char*, size_t)>
+ static int DataCallback(http_parser* p, const char* buf, size_t len)
+ {
+ HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
+ return (sock->*f)(buf, len);
+ }
+
+ static void ConfigureParser()
+ {
+ http_parser_settings_init(&parser_settings);
+ parser_settings.on_message_begin = Callback<&HttpServerSocket::OnMessageBegin>;
+ parser_settings.on_url = DataCallback<&HttpServerSocket::OnUrl>;
+ parser_settings.on_header_field = DataCallback<&HttpServerSocket::OnHeaderField>;
+ parser_settings.on_body = DataCallback<&HttpServerSocket::OnBody>;
+ parser_settings.on_message_complete = Callback<&HttpServerSocket::OnMessageComplete>;
+ }
+
+ int OnMessageBegin()
+ {
+ uri.clear();
+ header_state = HEADER_NONE;
+ body.clear();
+ total_buffers = 0;
+ return 0;
+ }
+
+ bool AcceptData(size_t len)
+ {
+ total_buffers += len;
+ return total_buffers < 8192;
+ }
+
+ int OnUrl(const char* buf, size_t len)
+ {
+ if (!AcceptData(len))
+ {
+ status_code = HTTP_STATUS_URI_TOO_LONG;
+ return -1;
+ }
+ uri.append(buf, len);
+ return 0;
+ }
+
+ enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
+ std::string header_field;
+ std::string header_value;
+
+ void OnHeaderComplete()
+ {
+ headers.SetHeader(header_field, header_value);
+ header_field.clear();
+ header_value.clear();
+ }
+
+ int OnHeaderField(const char* buf, size_t len)
+ {
+ if (header_state == HEADER_VALUE)
+ OnHeaderComplete();
+ header_state = HEADER_FIELD;
+ if (!AcceptData(len))
+ {
+ status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ return -1;
+ }
+ header_field.append(buf, len);
+ return 0;
+ }
+
+ int OnHeaderValue(const char* buf, size_t len)
+ {
+ header_state = HEADER_VALUE;
+ if (!AcceptData(len))
+ {
+ status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ return -1;
+ }
+ header_value.append(buf, len);
+ return 0;
+ }
+
+ int OnHeadersComplete()
+ {
+ if (header_state != HEADER_NONE)
+ OnHeaderComplete();
+ return 0;
+ }
+
+ int OnBody(const char* buf, size_t len)
+ {
+ if (!AcceptData(len))
+ {
+ status_code = HTTP_STATUS_PAYLOAD_TOO_LARGE;
+ return -1;
+ }
+ body.append(buf, len);
+ return 0;
+ }
+
+ int OnMessageComplete()
+ {
+ messagecomplete = true;
+ ServeData();
+ return 0;
+ }
+
+ public:
+ HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server, unsigned int timeoutsec)
+ : BufferedSocket(newfd)
+ , Timer(timeoutsec)
+ , ip(IP)
+ , status_code(0)
+ , waitingcull(false)
+ , messagecomplete(false)
+ {
+ if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
+ {
+ via->iohookprovs.back()->OnAccept(this, client, server);
+ // IOHook may have errored
+ if (!getError().empty())
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered a hook error: %s",
+ GetFd(), getError().c_str());
+ Close();
+ return;
+ }
+ }
+
+ parser.data = this;
+ http_parser_init(&parser, HTTP_REQUEST);
+ ServerInstance->Timers.AddTimer(this);
+ }
+
+ ~HttpServerSocket()
+ {
+ sockets.erase(this);
+ }
+
+ void Close() CXX11_OVERRIDE
+ {
+ if (waitingcull || !HasFd())
+ return;
+
+ waitingcull = true;
+ BufferedSocket::Close();
+ ServerInstance->GlobalCulls.AddItem(this);
+ }
+
+ void OnError(BufferedSocketError err) CXX11_OVERRIDE
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered an error: %d - %s",
+ GetFd(), err, getError().c_str());
+ Close();
+ }
+
+ void SendHTTPError(unsigned int response, const char* errstr = NULL)
+ {
+ if (!errstr)
+ errstr = http_status_str((http_status)response);
+
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "Sending HTTP error %u: %s", response, errstr);
+ static HTTPHeaders empty;
+ std::string data = InspIRCd::Format(
+ "<html><head></head><body style='font-family: sans-serif; text-align: center'>"
+ "<h1 style='font-size: 48pt'>Error %u</h1><h2 style='font-size: 24pt'>%s</h2><hr>"
+ "<small>Powered by <a href='https://www.inspircd.org'>InspIRCd</a></small></body></html>",
+ response, errstr);
+
+ Page(data, response, &empty);
+ }
+
+ void SendHeaders(unsigned long size, unsigned int response, HTTPHeaders &rheaders)
+ {
+ 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)));
+
+ rheaders.CreateHeader("Date", InspIRCd::TimeString(ServerInstance->Time(), "%a, %d %b %Y %H:%M:%S GMT", true));
+ rheaders.CreateHeader("Server", INSPIRCD_BRANCH);
+ rheaders.SetHeader("Content-Length", ConvToStr(size));
+
+ if (size)
+ rheaders.CreateHeader("Content-Type", "text/html");
+ else
+ rheaders.RemoveHeader("Content-Type");
+
+ /* Supporting Connection: keep-alive causes a whole world of hurt synchronizing timeouts,
+ * so remove it, its not essential for what we need.
+ */
+ rheaders.SetHeader("Connection", "Close");
+
+ WriteData(rheaders.GetFormattedHeaders());
+ WriteData("\r\n");
+ }
+
+ void OnDataReady() CXX11_OVERRIDE
+ {
+ if (parser.upgrade || HTTP_PARSER_ERRNO(&parser))
+ return;
+ http_parser_execute(&parser, &parser_settings, recvq.data(), recvq.size());
+ if (parser.upgrade)
+ SendHTTPError(status_code ? status_code : 400);
+ else if (HTTP_PARSER_ERRNO(&parser))
+ SendHTTPError(status_code ? status_code : 400, http_errno_description((http_errno)parser.http_errno));
+ }
+
+ void ServeData()
+ {
+ ModResult MOD_RESULT;
+ std::string method = http_method_str(static_cast<http_method>(parser.method));
+ HTTPRequestURI parsed;
+ ParseURI(uri, parsed);
+ HTTPRequest acl(method, parsed, &headers, this, ip, body);
+ FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
+ if (MOD_RESULT != MOD_RES_DENY)
+ {
+ HTTPRequest request(method, parsed, &headers, this, ip, body);
+ FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (request));
+ if (MOD_RESULT == MOD_RES_PASSTHRU)
+ {
+ SendHTTPError(404);
+ }
+ }
+ }
+
+ void Page(const std::string& s, unsigned int response, HTTPHeaders* hheaders)
+ {
+ SendHeaders(s.length(), response, *hheaders);
+ WriteData(s);
+ BufferedSocket::Close(true);
+ }
+
+ void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
+ {
+ Page(n->str(), response, hheaders);
+ }
+
+ bool ParseURI(const std::string& uristr, HTTPRequestURI& out)
+ {
+ http_parser_url_init(&url);
+ if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0)
+ return false;
+
+ if (url.field_set & (1 << UF_PATH))
+ {
+ // Normalise the path.
+ std::vector<std::string> pathsegments;
+ irc::sepstream pathstream(uri.substr(url.field_data[UF_PATH].off, url.field_data[UF_PATH].len), '/');
+ for (std::string pathsegment; pathstream.GetToken(pathsegment); )
+ {
+ if (pathsegment == ".")
+ {
+ // Stay at the current level.
+ continue;
+ }
+
+ if (pathsegment == "..")
+ {
+ // Traverse up to the previous level.
+ if (!pathsegments.empty())
+ pathsegments.pop_back();
+ continue;
+ }
+
+ pathsegments.push_back(pathsegment);
+ }
+
+ out.path.reserve(url.field_data[UF_PATH].len);
+ out.path.append("/").append(stdalgo::string::join(pathsegments, '/'));
+ }
+
+ if (url.field_set & (1 << UF_FRAGMENT))
+ out.fragment = uri.substr(url.field_data[UF_FRAGMENT].off, url.field_data[UF_FRAGMENT].len);
+
+ std::string param_str;
+ if (url.field_set & (1 << UF_QUERY))
+ param_str = uri.substr(url.field_data[UF_QUERY].off, url.field_data[UF_QUERY].len);
+
+ irc::sepstream param_stream(param_str, '&');
+ std::string token;
+ std::string::size_type eq_pos;
+ while (param_stream.GetToken(token))
+ {
+ eq_pos = token.find('=');
+ if (eq_pos == std::string::npos)
+ {
+ out.query_params.insert(std::make_pair(token, ""));
+ }
+ else
+ {
+ out.query_params.insert(std::make_pair(token.substr(0, eq_pos), token.substr(eq_pos + 1)));
+ }
+ }
+ return true;
+ }
+};
+
+class HTTPdAPIImpl : public HTTPdAPIBase
+{
+ public:
+ HTTPdAPIImpl(Module* parent)
+ : HTTPdAPIBase(parent)
+ {
+ }
+
+ void SendResponse(HTTPDocumentResponse& resp) CXX11_OVERRIDE
+ {
+ resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
+ }
+};
+
+class ModuleHttpServer : public Module
+{
+ HTTPdAPIImpl APIImpl;
+ unsigned int timeoutsec;
+ Events::ModuleEventProvider acleventprov;
+ Events::ModuleEventProvider reqeventprov;
+
+ public:
+ ModuleHttpServer()
+ : APIImpl(this)
+ , acleventprov(this, "event/http-acl")
+ , reqeventprov(this, "event/http-request")
+ {
+ aclevprov = &acleventprov;
+ reqevprov = &reqeventprov;
+ HttpServerSocket::ConfigureParser();
+ }
+
+ void init() CXX11_OVERRIDE
+ {
+ HttpModule = this;
+ }
+
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
+ {
+ ConfigTag* tag = ServerInstance->Config->ConfValue("httpd");
+ timeoutsec = tag->getDuration("timeout", 10, 1);
+ }
+
+ ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
+ {
+ if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
+ return MOD_RES_PASSTHRU;
+
+ sockets.push_front(new HttpServerSocket(nfd, client->addr(), from, client, server, timeoutsec));
+ return MOD_RES_ALLOW;
+ }
+
+ void OnUnloadModule(Module* mod) CXX11_OVERRIDE
+ {
+ for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); )
+ {
+ HttpServerSocket* sock = *i;
+ ++i;
+ if (sock->GetModHook(mod))
+ {
+ sock->cull();
+ delete sock;
+ }
+ }
+ }
+
+ CullResult cull() CXX11_OVERRIDE
+ {
+ for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
+ {
+ HttpServerSocket* sock = *i;
+ sock->Close();
+ }
+ return Module::cull();
+ }
+
+ Version GetVersion() CXX11_OVERRIDE
+ {
+ return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR);
+ }
+};
+
+MODULE_INIT(ModuleHttpServer)