/*
* 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) 2007-2008 Robin Burchell <robin+git@viroteck.net>
- * Copyright (C) 2008 Pippijn van Steenhoven <pip88nl@gmail.com>
- * Copyright (C) 2006-2008 Craig Edwards <craigedwards@brainbox.cc>
- * Copyright (C) 2007 John Brooks <john.brooks@dereferenced.net>
+ * 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
#include "iohook.h"
#include "modules/httpd.h"
-// Fix warnings about the use of commas at end of enumerator lists on C++03.
+#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
#endif
// Fix warnings about shadowing in http_parser.
-#pragma GCC diagnostic ignored "-Wshadow"
+#ifdef __GNUC__
+# pragma GCC diagnostic ignored "-Wshadow"
+#endif
#include <http_parser.c>
+#ifdef __GNUC__
+# pragma GCC diagnostic pop
+#endif
+
class ModuleHttpServer;
static ModuleHttpServer* HttpModule;
*/
class HttpServerSocket : public BufferedSocket, public Timer, public insp::intrusive_list_node<HttpServerSocket>
{
- friend ModuleHttpServer;
+ private:
+ friend class ModuleHttpServer;
http_parser parser;
+ http_parser_url url;
std::string ip;
std::string uri;
HTTPHeaders headers;
/** True if this object is in the cull list
*/
bool waitingcull;
+ bool messagecomplete;
bool Tick(time_t currtime) CXX11_OVERRIDE
{
- AddToCull();
- return false;
+ if (!messagecomplete)
+ {
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d timed out", GetFd());
+ Close();
+ return false;
+ }
+
+ return true;
}
template<int (HttpServerSocket::*f)()>
int OnMessageComplete()
{
+ messagecomplete = true;
ServeData();
return 0;
}
, ip(IP)
, status_code(0)
, waitingcull(false)
+ , messagecomplete(false)
{
if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
{
// IOHook may have errored
if (!getError().empty())
{
- AddToCull();
+ ServerInstance->Logs->Log(MODNAME, LOG_DEBUG, "HTTP socket %d encountered a hook error: %s",
+ GetFd(), getError().c_str());
+ Close();
return;
}
}
sockets.erase(this);
}
- void OnError(BufferedSocketError) CXX11_OVERRIDE
+ void Close() CXX11_OVERRIDE
{
- AddToCull();
+ if (waitingcull || !HasFd())
+ return;
+
+ waitingcull = true;
+ BufferedSocket::Close();
+ ServerInstance->GlobalCulls.AddItem(this);
}
- const char* Response(unsigned int response)
+ void OnError(BufferedSocketError err) CXX11_OVERRIDE
{
- switch (response)
- {
-#define HTTP_STATUS_CASE(n, m, s) case n: return #s;
- HTTP_STATUS_MAP(HTTP_STATUS_CASE)
- default:
- return "WTF";
- break;
- }
+ 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)
+ void SendHTTPError(unsigned int response, const char* errstr = NULL)
{
- HTTPHeaders empty;
+ 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>Server error %u: %s<br>"
- "<small>Powered by <a href='http://www.inspircd.org'>InspIRCd</a></small></body></html>", response, Response(response));
+ "<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);
- SendHeaders(data.length(), response, empty);
- WriteData(data);
- Close();
+ 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, Response(response)));
+ 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);
else
rheaders.RemoveHeader("Content-Type");
- /* Supporting Connection: keep-alive causes a whole world of hurt syncronizing timeouts,
+ /* 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");
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))
+ 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));
- HTTPRequest acl(method, uri, &headers, this, ip, body);
+ 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 url(method, uri, &headers, this, ip, body);
- FIRST_MOD_RESULT_CUSTOM(*reqevprov, HTTPRequestEventListener, OnHTTPRequest, MOD_RESULT, (url));
+ 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)
{
- SendHeaders(n->str().length(), response, *hheaders);
- WriteData(n->str());
- Close();
+ Page(n->str(), response, hheaders);
}
- void AddToCull()
+ bool ParseURI(const std::string& uristr, HTTPRequestURI& out)
{
- if (waitingcull)
- return;
+ http_parser_url_init(&url);
+ if (http_parser_parse_url(uristr.c_str(), uristr.size(), 0, &url) != 0)
+ return false;
- waitingcull = true;
- Close();
- ServerInstance->GlobalCulls.AddItem(this);
+ 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;
}
};
for (insp::intrusive_list<HttpServerSocket>::const_iterator i = sockets.begin(); i != sockets.end(); ++i)
{
HttpServerSocket* sock = *i;
- sock->AddToCull();
+ sock->Close();
}
return Module::cull();
}
Version GetVersion() CXX11_OVERRIDE
{
- return Version("Provides HTTP serving facilities to modules", VF_VENDOR);
+ return Version("Allows the server administrator to serve various useful resources over HTTP.", VF_VENDOR);
}
};