]> git.netwichtig.de Git - user/henk/code/inspircd.git/blobdiff - src/modules/m_httpd.cpp
Sync helpop chmodes s and p with docs
[user/henk/code/inspircd.git] / src / modules / m_httpd.cpp
index 3a0d4f86102fffc121d29624c37344456b251ed3..f3ec3298bd8823b154b4c9fb5017159ba7bea800 100644 (file)
@@ -1,12 +1,17 @@
 /*
  * 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;
@@ -56,9 +74,11 @@ static http_parser_settings parser_settings;
  */
 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;
@@ -69,11 +89,18 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
        /** 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)()>
@@ -183,6 +210,7 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
 
        int OnMessageComplete()
        {
+               messagecomplete = true;
                ServeData();
                return 0;
        }
@@ -194,6 +222,7 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                , ip(IP)
                , status_code(0)
                , waitingcull(false)
+               , messagecomplete(false)
        {
                if ((!via->iohookprovs.empty()) && (via->iohookprovs.back()))
                {
@@ -201,7 +230,9 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                        // 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;
                        }
                }
@@ -216,38 +247,42 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                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);
@@ -258,7 +293,7 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                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");
@@ -272,20 +307,24 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                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);
@@ -293,21 +332,75 @@ class HttpServerSocket : public BufferedSocket, public Timer, public insp::intru
                }
        }
 
+       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;
        }
 };
 
@@ -382,14 +475,14 @@ class ModuleHttpServer : public Module
                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);
        }
 };