* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
+/// $CompilerFlags: -Ivendor_directory("http_parser")
+
#include "inspircd.h"
-#include "httpd.h"
+#include "iohook.h"
+#include "modules/httpd.h"
+
+// Fix warnings about the use of commas at end of enumerator lists on C++03.
+#if defined __clang__
+# pragma clang diagnostic ignored "-Wc++11-extensions"
+#elif defined __GNUC__
+# pragma GCC diagnostic ignored "-pedantic"
+#endif
+
+// Fix warnings about shadowing in http_parser.
+#pragma GCC diagnostic ignored "-Wshadow"
-/* $ModDesc: Provides HTTP serving facilities to modules */
-/* $ModDep: httpd.h */
+#include <http_parser.c>
class ModuleHttpServer;
static ModuleHttpServer* HttpModule;
-static bool claimed;
-
-/** HTTP socket states
- */
-enum HttpState
-{
- HTTP_SERVE_WAIT_REQUEST = 0, /* Waiting for a full request */
- HTTP_SERVE_RECV_POSTDATA = 1, /* Waiting to finish recieving POST data */
- HTTP_SERVE_SEND_DATA = 2 /* Sending response */
-};
+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
+class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
{
- HttpState InternalState;
- std::string ip;
+ friend ModuleHttpServer;
- HTTPHeaders headers;
- std::string reqbuffer;
- std::string postdata;
- unsigned int postsize;
- std::string request_type;
+ http_parser parser;
+ std::string ip;
std::string uri;
- std::string http_version;
+ HTTPHeaders headers;
+ std::string body;
+ size_t total_buffers;
+ int status_code;
- public:
+ /** True if this object is in the cull list
+ */
+ bool waitingcull;
- HttpServerSocket(int newfd, const std::string& IP, ListenSocket* via, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server)
- : BufferedSocket(newfd), ip(IP), postsize(0)
+ bool Tick(time_t currtime) CXX11_OVERRIDE
{
- InternalState = HTTP_SERVE_WAIT_REQUEST;
-
- FOREACH_MOD(I_OnHookIO, OnHookIO(this, via));
- if (GetIOHook())
- GetIOHook()->OnStreamSocketAccept(this, client, server);
+ AddToCull();
+ return false;
}
- virtual void OnError(BufferedSocketError)
+ template<int (HttpServerSocket::*f)()>
+ static int Callback(http_parser* p)
{
- ServerInstance->GlobalCulls.AddItem(this);
+ HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
+ return (sock->*f)();
}
- std::string Response(int response)
+ template<int (HttpServerSocket::*f)(const char*, size_t)>
+ static int DataCallback(http_parser* p, const char* buf, size_t len)
{
- switch (response)
- {
- case 100:
- return "CONTINUE";
- case 101:
- return "SWITCHING PROTOCOLS";
- case 200:
- return "OK";
- case 201:
- return "CREATED";
- case 202:
- return "ACCEPTED";
- case 203:
- return "NON-AUTHORITATIVE INFORMATION";
- case 204:
- return "NO CONTENT";
- case 205:
- return "RESET CONTENT";
- case 206:
- return "PARTIAL CONTENT";
- case 300:
- return "MULTIPLE CHOICES";
- case 301:
- return "MOVED PERMENANTLY";
- case 302:
- return "FOUND";
- case 303:
- return "SEE OTHER";
- case 304:
- return "NOT MODIFIED";
- case 305:
- return "USE PROXY";
- case 307:
- return "TEMPORARY REDIRECT";
- case 400:
- return "BAD REQUEST";
- case 401:
- return "UNAUTHORIZED";
- case 402:
- return "PAYMENT REQUIRED";
- case 403:
- return "FORBIDDEN";
- case 404:
- return "NOT FOUND";
- case 405:
- return "METHOD NOT ALLOWED";
- case 406:
- return "NOT ACCEPTABLE";
- case 407:
- return "PROXY AUTHENTICATION REQUIRED";
- case 408:
- return "REQUEST TIMEOUT";
- case 409:
- return "CONFLICT";
- case 410:
- return "GONE";
- case 411:
- return "LENGTH REQUIRED";
- case 412:
- return "PRECONDITION FAILED";
- case 413:
- return "REQUEST ENTITY TOO LARGE";
- case 414:
- return "REQUEST-URI TOO LONG";
- case 415:
- return "UNSUPPORTED MEDIA TYPE";
- case 416:
- return "REQUESTED RANGE NOT SATISFIABLE";
- case 417:
- return "EXPECTATION FAILED";
- case 500:
- return "INTERNAL SERVER ERROR";
- case 501:
- return "NOT IMPLEMENTED";
- case 502:
- return "BAD GATEWAY";
- case 503:
- return "SERVICE UNAVAILABLE";
- case 504:
- return "GATEWAY TIMEOUT";
- case 505:
- return "HTTP VERSION NOT SUPPORTED";
- default:
- return "WTF";
- break;
-
- }
+ HttpServerSocket* sock = static_cast<HttpServerSocket*>(p->data);
+ return (sock->*f)(buf, len);
}
- void SendHTTPError(int response)
+ static void ConfigureParser()
{
- HTTPHeaders empty;
- std::string data = "<html><head></head><body>Server error "+ConvToStr(response)+": "+Response(response)+"<br>"+
- "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>";
-
- SendHeaders(data.length(), response, empty);
- WriteData(data);
+ 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>;
}
- void SendHeaders(unsigned long size, int response, HTTPHeaders &rheaders)
+ int OnMessageBegin()
{
+ uri.clear();
+ header_state = HEADER_NONE;
+ body.clear();
+ total_buffers = 0;
+ return 0;
+ }
- WriteData(http_version + " "+ConvToStr(response)+" "+Response(response)+"\r\n");
-
- time_t local = ServerInstance->Time();
- struct tm *timeinfo = gmtime(&local);
- char *date = asctime(timeinfo);
- date[strlen(date) - 1] = '\0';
- rheaders.CreateHeader("Date", date);
-
- rheaders.CreateHeader("Server", "InspIRCd/m_httpd.so/1.2");
- rheaders.SetHeader("Content-Length", ConvToStr(size));
+ bool AcceptData(size_t len)
+ {
+ total_buffers += len;
+ return total_buffers < 8192;
+ }
- if (size)
- rheaders.CreateHeader("Content-Type", "text/html");
- else
- rheaders.RemoveHeader("Content-Type");
+ 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;
+ }
- /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
- * so remove it, its not essential for what we need.
- */
- rheaders.SetHeader("Connection", "Close");
+ enum { HEADER_NONE, HEADER_FIELD, HEADER_VALUE } header_state;
+ std::string header_field;
+ std::string header_value;
- WriteData(rheaders.GetFormattedHeaders());
- WriteData("\r\n");
+ void OnHeaderComplete()
+ {
+ headers.SetHeader(header_field, header_value);
+ header_field.clear();
+ header_value.clear();
}
- void OnDataReady()
+ int OnHeaderField(const char* buf, size_t len)
{
- if (InternalState == HTTP_SERVE_RECV_POSTDATA)
+ if (header_state == HEADER_VALUE)
+ OnHeaderComplete();
+ header_state = HEADER_FIELD;
+ if (!AcceptData(len))
{
- postdata.append(recvq);
- if (postdata.length() >= postsize)
- ServeData();
+ status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ return -1;
}
- else
+ header_field.append(buf, len);
+ return 0;
+ }
+
+ int OnHeaderValue(const char* buf, size_t len)
+ {
+ header_state = HEADER_VALUE;
+ if (!AcceptData(len))
{
- reqbuffer.append(recvq);
+ status_code = HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE;
+ return -1;
+ }
+ header_value.append(buf, len);
+ return 0;
+ }
- if (reqbuffer.length() >= 8192)
- {
- ServerInstance->Logs->Log("m_httpd",DEBUG, "m_httpd dropped connection due to an oversized request buffer");
- reqbuffer.clear();
- SetError("Buffer");
- }
+ int OnHeadersComplete()
+ {
+ if (header_state != HEADER_NONE)
+ OnHeaderComplete();
+ return 0;
+ }
- if (InternalState == HTTP_SERVE_WAIT_REQUEST)
- CheckRequestBuffer();
+ 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;
}
- void CheckRequestBuffer()
+ int OnMessageComplete()
{
- std::string::size_type reqend = reqbuffer.find("\r\n\r\n");
- if (reqend == std::string::npos)
- return;
+ ServeData();
+ return 0;
+ }
- // We have the headers; parse them all
- std::string::size_type hbegin = 0, hend;
- while ((hend = reqbuffer.find("\r\n", hbegin)) != std::string::npos)
+ 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)
+ {
+ if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
{
- if (hbegin == hend)
- break;
-
- if (request_type.empty())
+ via->iohookprovs.back()->OnAccept(this, client, server);
+ // IOHook may have errored
+ if (!getError().empty())
{
- std::istringstream cheader(std::string(reqbuffer, hbegin, hend - hbegin));
- cheader >> request_type;
- cheader >> uri;
- cheader >> http_version;
-
- if (request_type.empty() || uri.empty() || http_version.empty())
- {
- SendHTTPError(400);
- return;
- }
-
- hbegin = hend + 2;
- continue;
+ AddToCull();
+ return;
}
+ }
- std::string cheader = reqbuffer.substr(hbegin, hend - hbegin);
+ parser.data = this;
+ http_parser_init(&parser, HTTP_REQUEST);
+ ServerInstance->Timers.AddTimer(this);
+ }
- std::string::size_type fieldsep = cheader.find(':');
- if ((fieldsep == std::string::npos) || (fieldsep == 0) || (fieldsep == cheader.length() - 1))
- {
- SendHTTPError(400);
- return;
- }
+ ~HttpServerSocket()
+ {
+ sockets.erase(this);
+ }
- headers.SetHeader(cheader.substr(0, fieldsep), cheader.substr(fieldsep + 2));
+ void OnError(BufferedSocketError) CXX11_OVERRIDE
+ {
+ AddToCull();
+ }
- hbegin = hend + 2;
+ const char* Response(unsigned int response)
+ {
+ switch (response)
+ {
+#define HTTP_STATUS_CASE(n, m, s) case n: return #s;
+ HTTP_STATUS_MAP(HTTP_STATUS_CASE)
+ default:
+ return "WTF";
+ break;
}
+ }
- reqbuffer.erase(0, reqend + 4);
+ void SendHTTPError(unsigned int response)
+ {
+ HTTPHeaders empty;
+ std::string data = InspIRCd::Format(
+ "<html><head></head><body>Server error %u: %s<br>"
+ "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>", response, Response(response));
- std::transform(request_type.begin(), request_type.end(), request_type.begin(), ::toupper);
- std::transform(http_version.begin(), http_version.end(), http_version.begin(), ::toupper);
+ SendHeaders(data.length(), response, empty);
+ WriteData(data);
+ Close();
+ }
- if ((http_version != "HTTP/1.1") && (http_version != "HTTP/1.0"))
- {
- SendHTTPError(505);
- return;
- }
+ 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, Response(response)));
- if (headers.IsSet("Content-Length") && (postsize = atoi(headers.GetHeader("Content-Length").c_str())) != 0)
- {
- InternalState = HTTP_SERVE_RECV_POSTDATA;
+ 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 (reqbuffer.length() >= postsize)
- {
- postdata = reqbuffer.substr(0, postsize);
- reqbuffer.erase(0, postsize);
- }
- else if (!reqbuffer.empty())
- {
- postdata = reqbuffer;
- reqbuffer.clear();
- }
+ if (size)
+ rheaders.CreateHeader("Content-Type", "text/html");
+ else
+ rheaders.RemoveHeader("Content-Type");
- if (postdata.length() >= postsize)
- ServeData();
+ /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
+ * so remove it, its not essential for what we need.
+ */
+ rheaders.SetHeader("Connection", "Close");
- return;
- }
+ WriteData(rheaders.GetFormattedHeaders());
+ WriteData("\r\n");
+ }
- ServeData();
+ 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 || HTTP_PARSER_ERRNO(&parser))
+ SendHTTPError(status_code ? status_code : 400);
}
void ServeData()
{
- InternalState = HTTP_SERVE_SEND_DATA;
-
- claimed = false;
- HTTPRequest acl((Module*)HttpModule, "httpd_acl", request_type, uri, &headers, this, ip, postdata);
- acl.Send();
- if (!claimed)
+ ModResult MOD_RESULT;
+ std::string method = http_method_str(static_cast<http_method>(parser.method));
+ HTTPRequest acl(method, uri, &headers, this, ip, body);
+ FIRST_MOD_RESULT_CUSTOM(*aclevprov, HTTPACLEventListener, OnHTTPACLCheck, MOD_RESULT, (acl));
+ if (MOD_RESULT != MOD_RES_DENY)
{
- HTTPRequest url((Module*)HttpModule, "httpd_url", request_type, uri, &headers, this, ip, postdata);
- url.Send();
- if (!claimed)
+ HTTPRequest url(method, uri, &headers, this, ip, body);
+ FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
+ if (MOD_RESULT == MOD_RES_PASSTHRU)
{
SendHTTPError(404);
}
}
}
- void Page(std::stringstream* n, int response, HTTPHeaders *hheaders)
+ void Page(std::stringstream* n, unsigned int response, HTTPHeaders* hheaders)
{
SendHeaders(n->str().length(), response, *hheaders);
WriteData(n->str());
+ Close();
+ }
+
+ void AddToCull()
+ {
+ if (waitingcull)
+ return;
+
+ waitingcull = true;
+ Close();
+ ServerInstance->GlobalCulls.AddItem(this);
+ }
+};
+
+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
{
- std::vector<HttpServerSocket *> httpsocks;
+ 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();
+ }
- ModuleHttpServer() {
+ void init() CXX11_OVERRIDE
+ {
HttpModule = this;
- ServerInstance->Modules->Attach(I_OnAcceptConnection, this);
}
- void OnRequest(Request& request)
+ void ReadConfig(ConfigStatus& status) CXX11_OVERRIDE
{
- if (strcmp(request.id, "HTTP-DOC") != 0)
- return;
- HTTPDocumentResponse& resp = static_cast<HTTPDocumentResponse&>(request);
- claimed = true;
- resp.src.sock->Page(resp.document, resp.responsecode, &resp.headers);
+ 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)
+ ModResult OnAcceptConnection(int nfd, ListenSocket* from, irc::sockets::sockaddrs* client, irc::sockets::sockaddrs* server) CXX11_OVERRIDE
{
- if (from->bind_tag->getString("type") != "httpd")
+ if (!stdalgo::string::equalsci(from->bind_tag->getString("type"), "httpd"))
return MOD_RES_PASSTHRU;
- int port;
- std::string incomingip;
- irc::sockets::satoap(*client, incomingip, port);
- new HttpServerSocket(nfd, incomingip, from, client, server);
+
+ 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;
+ }
+ }
+ }
- virtual ~ModuleHttpServer()
+ CullResult cull() CXX11_OVERRIDE
{
- for (size_t i = 0; i < httpsocks.size(); i++)
+ for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
{
- httpsocks[i]->cull();
- delete httpsocks[i];
+ HttpServerSocket* sock = *i;
+ sock->AddToCull();
}
+ return Module::cull();
}
- virtual Version GetVersion()
+ Version GetVersion() CXX11_OVERRIDE
{
return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
}