From 6e21f0fbd4f0c90eb2cb67ba4e43c98071ec79a1 Mon Sep 17 00:00:00 2001 From: Lorenz Stechauner Date: Mon, 30 Nov 2020 18:17:04 +0100 Subject: [PATCH] Clean client main handler function --- src/client.cpp | 599 +++++++++++++++++++++++++------------------------ 1 file changed, 310 insertions(+), 289 deletions(-) diff --git a/src/client.cpp b/src/client.cpp index 3c95d14..6187b06 100644 --- a/src/client.cpp +++ b/src/client.cpp @@ -221,296 +221,10 @@ bool connection_handler(const char *preprefix, const char *col1, const char *col bool error = false; char buffer[1024]; char *prefix = (char *) preprefix; + + HttpConnection *req = nullptr; try { - HttpConnection req(socket); - try { - if (req.isExistingField("Connection") && req.getField("Connection") == "keep-alive") { - req.setField("Connection", "keep-alive"); - req.setField("Keep-Alive", "timeout=3600, max=100"); - } else { - req.setField("Connection", "close"); - error = true; - } - - string host = ""; - - if (!req.isExistingField("Host")) { - req.respond(400); - } else { - host = req.getField("Host"); - long pos = host.find(':'); - if (pos != string::npos) { - host.erase(pos, host.length() - pos); - } - - /*FILE *name = popen(("dig @8.8.8.8 +time=1 -x " + socket->getPeerAddress()->toString() + - " | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r"); - char hostbuffer[1024]; - memset(hostbuffer, 0, 1024); - size_t size = fread(hostbuffer, 1, 1024, name); - hostbuffer[size - 1] = 0; // remove \n - if (size <= 1) { - sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str()); - }*/ - - sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1, - info->host.c_str(), socket->getPeerPort(), col2); - prefix = buffer; - - log(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m"); - log_to_file(prefix, "\x1B[1m" + req.getMethod() + " " + req.getPath() + "\x1B[0m", host); - - bool noRedirect = req.getPath().find("/.well-known/") == 0 || (req.getPath().find("/files/") == 0); - - bool redir = true; - if (!noRedirect) { - if (getWebRoot(host).empty()) { - req.redirect(303, "https://www.necronda.net" + req.getPath()); - } else if (socket->getSocketPort() != 443) { - req.redirect(302, "https://" + host + req.getPath()); - } else { - redir = false; - } - } else { - redir = false; - } - - URI path = URI(getWebRoot(host), req.getPath()); - pid_t childpid = 0; - - if (redir) { - - } else if (!path.getNewPath().empty() && req.getMethod() != "POST") { - req.redirect(303, path.getNewPath()); - } else { - FILE *file = path.openFile(); - if (file == nullptr) { - req.setField("Cache-Control", "public, max-age=60"); - req.respond(404); - } else { - string type = path.getFileType(); - - if (type.find("inode/") == 0) { - req.respond(403); - } else if (path.getRelativeFilePath().find("/.") != string::npos && !noRedirect) { - req.respond(403); - } else { - req.setField("Content-Type", type); - req.setField("Last-Modified", getHttpDate(path.getFilePath())); - - bool invalidMethod = false; - bool etag = false; - - if (path.isStatic()) { - string hash = getETag(path.getFilePath()); - req.setField("ETag", hash); - req.setField("Accept-Ranges", "bytes"); - if (type.find("text/") == 0) { - req.setField("Cache-Control", "public, max-age=3600"); - } else { - req.setField("Cache-Control", "public, max-age=86400"); - } - req.setField("Allow", "GET"); - if (req.getMethod() != "GET") { - invalidMethod = true; - } - if (req.isExistingField("If-None-Match") && req.getField("If-None-Match") == hash) { - etag = true; - } - } else { - req.setField("Accept-Ranges", "none"); - req.setField("Cache-Control", "private, no-cache"); - req.setField("Allow", "GET, POST, PUT"); - if (req.getMethod() != "GET" && req.getMethod() != "POST" && req.getMethod() != "PUT") { - invalidMethod = true; - } - } - - if (invalidMethod) { - req.respond(405); - } else if (etag) { - req.respond(304); - } else { - int statuscode = 0; - if (!path.isStatic()) { - string cmd = (string) "env -i" + - " REDIRECT_STATUS=" + cli_encode("CGI") + - " DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) + - " " + req.cgiExport() + - (req.isExistingField("Content-Length") ? " CONTENT_LENGTH=" + - cli_encode(req.getField( - "Content-Length")) - : "") + - (req.isExistingField("Content-Type") ? " CONTENT_TYPE=" + cli_encode( - req.getField("Content-Type")) : "") + - ((socket->isSecured()) ? " HTTPS=on" : "") + - " PATH_INFO=" + cli_encode(path.getFilePathInfo()) + - " PATH_TRANSLATED=" + cli_encode(path.getAbsolutePath()) + - " QUERY_STRING=" + cli_encode(path.getQuery()) + - " REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) + - " REMOTE_HOST=" + cli_encode(info->host) + - " REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) + - " REQUEST_METHOD=" + cli_encode(req.getMethod()) + - " REQUEST_URI=" + cli_encode(req.getPath()) + - " SCRIPT_FILENAME=" + cli_encode(path.getFilePath()) + - " SCRIPT_NAME=" + cli_encode(path.getRelativePath()) + - " SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") + - " SERVER_NAME=" + cli_encode(host) + - " SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) + - " SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") + - " SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") + - " GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") + - " /usr/bin/php-cgi"; - - stds pipes = procopen(cmd.c_str()); - childpid = pipes.pid; - - long len = req.isExistingField("Content-Length") ? strtol(req.getField("Content-Length").c_str(), nullptr, 10) : (req.getMethod() == "POST" || req.getMethod() == "PUT")?-1:0; - socket->receive(pipes.stdin, len); - fclose(pipes.stdin); - - thread *t = new thread(php_error_handler, prefix, pipes.stderr); - - string line; - while (!(line = read_line(pipes.stdout)).empty()) { - long pos = line.find(':'); - string index = line.substr(0, pos); - string data = line.substr(pos + 1, line.length() - pos); - - while (index[0] == ' ') index.erase(index.begin() + 0); - while (index[index.length() - 1] == ' ') index.erase(index.end() - 1); - while (data[0] == ' ') data.erase(data.begin() + 0); - while (data[data.length() - 1] == ' ') data.erase(data.end() - 1); - - if (index == "Status") { - statuscode = (int) strtol(data.substr(0, 3).c_str(), nullptr, 10); - } else { - if (index == "Location" && statuscode == 0) { - statuscode = 303; - } else if (index == "Content-Type") { - type = data; - } - req.setField(index, data); - } - } - - fclose(file); - int c = fgetc(pipes.stdout); - if (c == -1) { - // No Data -> Error - req.respond((statuscode == 0) ? 500 : statuscode); - statuscode = -1; - } else { - ungetc(c, pipes.stdout); - } - file = pipes.stdout; - } - - if (statuscode != -1) { - statuscode = (statuscode == 0) ? 200 : statuscode; - - bool compress = (type.find("text/") == 0 || - (type.find("application/") == 0 && type.find("+xml") != string::npos) || - type == "application/json" || - type == "application/javascript") && - req.isExistingField("Accept-Encoding") && - req.getField("Accept-Encoding").find("deflate") != string::npos; - - if (compress) { - req.setField("Accept-Ranges", "none"); - } - - if (compress && req.isExistingField("Range")) { - req.respond(416); - } else if (req.isExistingField("Range")) { - string range = req.getField("Range"); - if (range.find("bytes=") != 0 || !path.isStatic()) { - req.respond(416); - } else { - fseek(file, 0L, SEEK_END); - long len = ftell(file); - fseek(file, 0L, SEEK_SET); - long p = range.find('-'); - if (p == string::npos) { - req.respond(416); - } else { - string part1 = range.substr(6, (unsigned long) (p - 6)); - string part2 = range.substr((unsigned long) (p + 1), - range.length() - p - 1); - long num1 = stol(part1, nullptr, 10); - long num2 = len - 1; - if (!part2.empty()) { - num2 = stol(part2, nullptr, 10); - } - if (num1 < 0 || num1 >= len || num2 < 0 || num2 >= len) { - req.respond(416); - } else { - req.setField("Content-Range", - (string) "bytes " + to_string(num1) + "-" + - to_string(num2) + - "/" + to_string(len)); - req.respond(206, file, compress, num1, num2); - } - } - } - } else { - req.respond(statuscode, file, compress); - } - } - } - } - fclose(file); - if (childpid > 0) { - waitpid(childpid, nullptr, 0); - } - } - } - } - HttpStatusCode status = req.getStatusCode(); - int code = status.code; - string color = ""; - string comment = ""; - if ((code >= 200 && code < 300) || code == 304) { - color = "\x1B[1;32m"; // Success (Cached): Green - } else if (code >= 100 && code < 200) { - color = "\x1B[1;93m"; // Continue: Yellow - } else if (code >= 300 && code < 400) { - color = "\x1B[1;93m"; // Redirect: Yellow - comment = " -> " + - (req.isExistingResponseField("Location") ? req.getResponseField("Location") : ""); - } else if (code >= 400 && code < 500) { - color = "\x1B[1;31m"; // Client Error: Red - //comment = " -> " + req.getPath(); - } else if (code >= 500 & code < 600) { - color = "\x1B[1;31m"; // Server Error: Red - //comment = " -> " + req.getPath(); - } - string msg = color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req.getDuration()) + ")\x1B[0m"; - log(prefix, msg); - if (!host.empty()) { - log_to_file(prefix, msg, host); - } - } catch (char *msg) { - HttpStatusCode status = req.getStatusCode(); - log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req.getDuration()) + ")"); - try { - if (strncmp(msg, "timeout", strlen(msg)) == 0) { - log(prefix, "Timeout!"); - req.setField("Connection", "close"); - req.respond(408); - error = true; - } else if (strncmp(msg, "Invalid path", strlen(msg)) == 0) { - log(prefix, "Timeout!"); - req.setField("Connection", "close"); - req.respond(400); - } else { - log(prefix, (string) "Unable to receive from socket: " + msg); - error = true; - } - } catch (char *msg2) { - - } - } + *req = HttpConnection(socket); } catch (char *msg) { try { if (msg == "Malformed header") { @@ -529,6 +243,313 @@ bool connection_handler(const char *preprefix, const char *col1, const char *col } } + + try { + bool noRedirect, redir, invalidMethod, etag; + URI *path; + pid_t childpid; + FILE *file; + int statuscode; + string hash, type, host; + thread *t; + long pos; + + if (req->isExistingField("Connection") && req->getField("Connection") == "keep-alive") { + req->setField("Connection", "keep-alive"); + req->setField("Keep-Alive", "timeout=3600, max=100"); + } else { + req->setField("Connection", "close"); + error = true; + } + + host = ""; + + if (!req->isExistingField("Host")) { + req->respond(400); + goto respond; + } + host = req->getField("Host"); + pos = host.find(':'); + if (pos != string::npos) { + host.erase(pos, host.length() - pos); + } + + /*FILE *name = popen(("dig @8.8.8.8 +time=1 -x " + socket->getPeerAddress()->toString() + + " | grep -oP \"^[^;].*\\t\\K([^ ]*)\\w\"").c_str(), "r"); + char hostbuffer[1024]; + memset(hostbuffer, 0, 1024); + size_t size = fread(hostbuffer, 1, 1024, name); + hostbuffer[size - 1] = 0; // remove \n + if (size <= 1) { + sprintf(hostbuffer, "%s", socket->getPeerAddress()->toString().c_str()); + }*/ + + sprintf(buffer, "[\x1B[1m%s\x1B[0m][%i]%s[%s][%i]%s ", host.c_str(), socket->getSocketPort(), col1, + info->host.c_str(), socket->getPeerPort(), col2); + prefix = buffer; + + log(prefix, "\x1B[1m" + req->getMethod() + " " + req->getPath() + "\x1B[0m"); + log_to_file(prefix, "\x1B[1m" + req->getMethod() + " " + req->getPath() + "\x1B[0m", host); + + noRedirect = req->getPath().find("/.well-known/") == 0 || (req->getPath().find("/files/") == 0); + + redir = true; + if (!noRedirect) { + if (getWebRoot(host).empty()) { + req->redirect(303, "https://www.necronda.net" + req->getPath()); + } else if (socket->getSocketPort() != 443) { + req->redirect(302, "https://" + host + req->getPath()); + } else { + redir = false; + } + } else { + redir = false; + } + + *path = URI(getWebRoot(host), req->getPath()); + childpid = 0; + + if (redir) { + goto respond; + } else if (!path->getNewPath().empty() && req->getMethod() != "POST") { + req->redirect(303, path->getNewPath()); + goto respond; + } + + file = path->openFile(); + if (file == nullptr) { + req->setField("Cache-Control", "public, max-age=60"); + req->respond(404); + goto respond; + } + type = path->getFileType(); + + if (type.find("inode/") == 0) { + req->respond(403); + goto respond; + } else if (path->getRelativeFilePath().find("/.") != string::npos && !noRedirect) { + req->respond(403); + goto respond; + } + + req->setField("Content-Type", type); + req->setField("Last-Modified", getHttpDate(path->getFilePath())); + + invalidMethod = false; + etag = false; + + if (path->isStatic()) { + hash = getETag(path->getFilePath()); + req->setField("ETag", hash); + req->setField("Accept-Ranges", "bytes"); + if (type.find("text/") == 0) { + req->setField("Cache-Control", "public, max-age=3600"); + } else { + req->setField("Cache-Control", "public, max-age=86400"); + } + req->setField("Allow", "GET"); + if (req->getMethod() != "GET") { + invalidMethod = true; + } + if (req->isExistingField("If-None-Match") && req->getField("If-None-Match") == hash) { + etag = true; + } + } else { + req->setField("Accept-Ranges", "none"); + req->setField("Cache-Control", "private, no-cache"); + req->setField("Allow", "GET, POST, PUT"); + if (req->getMethod() != "GET" && req->getMethod() != "POST" && req->getMethod() != "PUT") { + invalidMethod = true; + } + } + + if (invalidMethod) { + req->respond(405); + goto respond; + } else if (etag) { + req->respond(304); + goto respond; + } + + statuscode = 0; + if (!path->isStatic()) { + string cmd = (string) "env -i" + + " REDIRECT_STATUS=" + cli_encode("CGI") + + " DOCUMENT_ROOT=" + cli_encode(getWebRoot(host)) + + " " + req->cgiExport() + + (req->isExistingField("Content-Length") ? " CONTENT_LENGTH=" + + cli_encode(req->getField( + "Content-Length")) + : "") + + (req->isExistingField("Content-Type") ? " CONTENT_TYPE=" + cli_encode( + req->getField("Content-Type")) : "") + + ((socket->isSecured()) ? " HTTPS=on" : "") + + " PATH_INFO=" + cli_encode(path->getFilePathInfo()) + + " PATH_TRANSLATED=" + cli_encode(path->getAbsolutePath()) + + " QUERY_STRING=" + cli_encode(path->getQuery()) + + " REMOTE_ADDR=" + cli_encode(socket->getPeerAddress()->toString()) + + " REMOTE_HOST=" + cli_encode(info->host) + + " REMOTE_PORT=" + cli_encode(to_string(socket->getPeerPort())) + + " REQUEST_METHOD=" + cli_encode(req->getMethod()) + + " REQUEST_URI=" + cli_encode(req->getPath()) + + " SCRIPT_FILENAME=" + cli_encode(path->getFilePath()) + + " SCRIPT_NAME=" + cli_encode(path->getRelativePath()) + + " SERVER_ADMIN=" + cli_encode("lorenz.stechauner@gmail.com") + + " SERVER_NAME=" + cli_encode(host) + + " SERVER_PORT=" + cli_encode(to_string(socket->getSocketPort())) + + " SERVER_SOFTWARE=" + cli_encode("Necronda 3.0") + + " SERVER_PROTOCOL=" + cli_encode("HTTP/1.1") + + " GATEWAY_INTERFACE=" + cli_encode("CGI/1.1") + + " /usr/bin/php-cgi"; + + stds pipes = procopen(cmd.c_str()); + childpid = pipes.pid; + + long len = req->isExistingField("Content-Length") ? strtol(req->getField("Content-Length").c_str(), nullptr, 10) : (req->getMethod() == "POST" || req->getMethod() == "PUT")?-1:0; + socket->receive(pipes.stdin, len); + fclose(pipes.stdin); + + t = new thread(php_error_handler, prefix, pipes.stderr); + + string line; + while (!(line = read_line(pipes.stdout)).empty()) { + long pos = line.find(':'); + string index = line.substr(0, pos); + string data = line.substr(pos + 1, line.length() - pos); + + while (index[0] == ' ') index.erase(index.begin() + 0); + while (index[index.length() - 1] == ' ') index.erase(index.end() - 1); + while (data[0] == ' ') data.erase(data.begin() + 0); + while (data[data.length() - 1] == ' ') data.erase(data.end() - 1); + + if (index == "Status") { + statuscode = (int) strtol(data.substr(0, 3).c_str(), nullptr, 10); + } else { + if (index == "Location" && statuscode == 0) { + statuscode = 303; + } else if (index == "Content-Type") { + type = data; + } + req->setField(index, data); + } + } + + fclose(file); + int c = fgetc(pipes.stdout); + if (c == -1) { + // No Data -> Error + req->respond((statuscode == 0) ? 500 : statuscode); + statuscode = -1; + } else { + ungetc(c, pipes.stdout); + } + file = pipes.stdout; + } + + if (statuscode != -1) { + statuscode = (statuscode == 0) ? 200 : statuscode; + + bool compress = (type.find("text/") == 0 || + (type.find("application/") == 0 && type.find("+xml") != string::npos) || + type == "application/json" || + type == "application/javascript") && + req->isExistingField("Accept-Encoding") && + req->getField("Accept-Encoding").find("deflate") != string::npos; + + if (compress) { + req->setField("Accept-Ranges", "none"); + } + + if (compress && req->isExistingField("Range")) { + req->respond(416); + } else if (req->isExistingField("Range")) { + string range = req->getField("Range"); + if (range.find("bytes=") != 0 || !path->isStatic()) { + req->respond(416); + } else { + fseek(file, 0L, SEEK_END); + long len = ftell(file); + fseek(file, 0L, SEEK_SET); + long p = range.find('-'); + if (p == string::npos) { + req->respond(416); + } else { + string part1 = range.substr(6, (unsigned long) (p - 6)); + string part2 = range.substr((unsigned long) (p + 1), + range.length() - p - 1); + long num1 = stol(part1, nullptr, 10); + long num2 = len - 1; + if (!part2.empty()) { + num2 = stol(part2, nullptr, 10); + } + if (num1 < 0 || num1 >= len || num2 < 0 || num2 >= len) { + req->respond(416); + } else { + req->setField("Content-Range", + (string) "bytes " + to_string(num1) + "-" + + to_string(num2) + + "/" + to_string(len)); + req->respond(206, file, compress, num1, num2); + } + } + } + } else { + req->respond(statuscode, file, compress); + } + } + + fclose(file); + if (childpid > 0) { + waitpid(childpid, nullptr, 0); + } + + respond: + + HttpStatusCode status = req->getStatusCode(); + int code = status.code; + string color = ""; + string comment = ""; + if ((code >= 200 && code < 300) || code == 304) { + color = "\x1B[1;32m"; // Success (Cached): Green + } else if (code >= 100 && code < 200) { + color = "\x1B[1;93m"; // Continue: Yellow + } else if (code >= 300 && code < 400) { + color = "\x1B[1;93m"; // Redirect: Yellow + comment = " -> " + + (req->isExistingResponseField("Location") ? req->getResponseField("Location") : ""); + } else if (code >= 400 && code < 500) { + color = "\x1B[1;31m"; // Client Error: Red + //comment = " -> " + req->getPath(); + } else if (code >= 500 & code < 600) { + color = "\x1B[1;31m"; // Server Error: Red + //comment = " -> " + req->getPath(); + } + string msg = color + to_string(status.code) + " " + status.message + comment + " (" + formatTime(req->getDuration()) + ")\x1B[0m"; + log(prefix, msg); + if (!host.empty()) { + log_to_file(prefix, msg, host); + } + } catch (char *msg) { + HttpStatusCode status = req->getStatusCode(); + log(prefix, to_string(status.code) + " " + status.message + " (" + formatTime(req->getDuration()) + ")"); + try { + if (strncmp(msg, "timeout", strlen(msg)) == 0) { + log(prefix, "Timeout!"); + req->setField("Connection", "close"); + req->respond(408); + error = true; + } else if (strncmp(msg, "Invalid path", strlen(msg)) == 0) { + log(prefix, "Timeout!"); + req->setField("Connection", "close"); + req->respond(400); + } else { + log(prefix, (string) "Unable to receive from socket: " + msg); + error = true; + } + } catch (char *msg2) { + + } + } return !error; }