From 4732dc63657f4c6fc342f7674f7dc7c666b293dc Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Sun, 12 Apr 2020 22:20:33 +0200 Subject: webbox (WIP) --- Makefile | 5 +- README.txt | 28 +- TODO | 2 +- debian/webserver.docs | 1 + plugins/static-files/static-files.cpp | 2 +- plugins/webbox/Makefile | 1 + plugins/webbox/stringutil.cpp | 66 ++++ plugins/webbox/stringutil.h | 10 + plugins/webbox/webbox.cpp | 586 +++++++++++++++------------------- plugins/webbox/webbox.h | 7 + response.cpp | 7 +- stringutil.cpp | 66 ---- stringutil.h | 10 - test-webserver.cpp | 35 ++ webserver.conf | 10 +- webserver.cpp | 8 + 16 files changed, 427 insertions(+), 417 deletions(-) create mode 100644 debian/webserver.docs create mode 100644 plugins/webbox/stringutil.cpp create mode 100644 plugins/webbox/stringutil.h delete mode 100644 stringutil.cpp delete mode 100644 stringutil.h diff --git a/Makefile b/Makefile index d37ed1e..f0e9bfd 100644 --- a/Makefile +++ b/Makefile @@ -1,7 +1,7 @@ DISTROS=debian10 VERSION=$(shell dpkg-parsechangelog --show-field Version) PROJECTNAME=webserver -PLUGINS=static-files #webbox # weblog cgi fcgi +PLUGINS=static-files# webbox # weblog cgi fcgi CXX=clang++-10 @@ -65,8 +65,7 @@ PROGSRC=\ plugin.cpp \ privileges.cpp \ response.cpp \ - server.cpp \ - stringutil.cpp + server.cpp TESTSRC=\ test-webserver.cpp \ diff --git a/README.txt b/README.txt index 37b57c5..74b4875 100644 --- a/README.txt +++ b/README.txt @@ -1,14 +1,26 @@ -Edit /etc/webserver.conf +Features +-------- -Enable in Debian: +* Support for IPv4 and IPv6 +* Virtual servers +* Plugin interface -systemctl enable webserver.service -Start: +Configuration +------------- -systemctl start webserver +* Edit /etc/webserver.conf -Status: +* Enable in Debian: -systemctl status webserver -or /var/log/syslog + # systemctl enable webserver.service + +* Start: + + # systemctl start webserver + +* Query Status: + + # systemctl status webserver + + or observe /var/log/syslog diff --git a/TODO b/TODO index de791c4..7be5ec6 100644 --- a/TODO +++ b/TODO @@ -1,5 +1,5 @@ Certbot: https://certbot.eff.org/lets-encrypt/debianbuster-other -Webbox +Webbox: html, minify Request properties: Remote Address, e.g. [::1]:8081 -> ipv6 / ipv4 Speed up DocRoot, use string_view diff --git a/debian/webserver.docs b/debian/webserver.docs new file mode 100644 index 0000000..71dfd5b --- /dev/null +++ b/debian/webserver.docs @@ -0,0 +1 @@ +README.txt diff --git a/plugins/static-files/static-files.cpp b/plugins/static-files/static-files.cpp index ad85f22..b137cb8 100644 --- a/plugins/static-files/static-files.cpp +++ b/plugins/static-files/static-files.cpp @@ -72,7 +72,7 @@ std::string static_files_plugin::generate_page( if (method != "GET" && method != "HEAD") return HttpStatus("400", "Unknown HTTP method", SetResponseHeader); - // Request path must be absolute and not contain "..". + // Request path must not contain "..". std::string rel_target{GetRequestParam("rel_target")}; if (rel_target.find("..") != std::string::npos) { std::string target{GetRequestParam("target")}; diff --git a/plugins/webbox/Makefile b/plugins/webbox/Makefile index e16171d..2ddec0e 100644 --- a/plugins/webbox/Makefile +++ b/plugins/webbox/Makefile @@ -58,6 +58,7 @@ endif PROGSRC=\ file.cpp \ + stringutil.cpp \ webbox.cpp TESTSRC=\ diff --git a/plugins/webbox/stringutil.cpp b/plugins/webbox/stringutil.cpp new file mode 100644 index 0000000..f87fa00 --- /dev/null +++ b/plugins/webbox/stringutil.cpp @@ -0,0 +1,66 @@ +#include "stringutil.h" + +#include +#include + +#include + +std::string strfmt(const char* fmt, ...) +{ + va_list args; + + va_start(args, fmt); + int size = std::vsnprintf(nullptr, 0, fmt, args); + va_end(args); + + std::string result(size, ' '); + + va_start(args, fmt); + std::vsnprintf(result.data(), size + 1, fmt, args); + va_end(args); + + return result; +} + +std::vector split(std::string value, const std::string separators) +{ + std::vector result; + + size_t pos0 = 0; + size_t pos1 = 0; + while (pos0 < value.size()) { + pos1 = value.find_first_of(separators, pos0); + if (pos1 == std::string::npos) + pos1 = value.size(); + std::string part = value.substr(pos0, pos1 - pos0); + //std::cout << "DEBUG: " << part << std::endl << std::flush; + if (part != "") + result.push_back(part); + pos0 = value.find_first_not_of(separators, pos1); + if (pos0 == std::string::npos) + pos0 = value.size(); + } + + return result; +} + +std::string join(std::vector vs, std::string separator) +{ + std::string s; + for (const auto& line : vs) { + if (s.size() > 0) + s += separator; + s += line; + } + + return s; +} + +bool startsWithAnyOfLower(const std::string &s, const std::vector &list) { + for (const std::string& element : list) { + if (boost::algorithm::starts_with(boost::algorithm::to_lower_copy(s), boost::algorithm::to_lower_copy(element))) + return true; + } + return false; +} + diff --git a/plugins/webbox/stringutil.h b/plugins/webbox/stringutil.h new file mode 100644 index 0000000..5110e2e --- /dev/null +++ b/plugins/webbox/stringutil.h @@ -0,0 +1,10 @@ +#pragma once + +#include +#include + +std::string strfmt(const char* fmt, ...); + +std::vector split(std::string value, const std::string separators = "\r\n "); +std::string join(std::vector vs, std::string separator = "\n"); +bool startsWithAnyOfLower(const std::string &s, const std::vector &list); diff --git a/plugins/webbox/webbox.cpp b/plugins/webbox/webbox.cpp index 5d3f64c..363df6c 100644 --- a/plugins/webbox/webbox.cpp +++ b/plugins/webbox/webbox.cpp @@ -1,15 +1,25 @@ #include "webbox.h" +#include "stringutil.h" + #include +#include +#include +#include #include #include #include using namespace std::string_literals; +namespace fs = std::filesystem; +namespace pt = boost::property_tree; namespace { + void registerCommand(std::unordered_map>& commands, std::shared_ptr command) { + commands[command.getCommandName()] = command; + }; unordered_map status_map { { "400", "Bad Request"}, @@ -18,11 +28,57 @@ namespace { { "505", "Internal Server Error" }, }; +std::unordered_map ParseQueryString(std::string s) +{ + std::unordered_map result; + + size_t pos = s.find('?'); + if (pos != s.npos) { + auto list {split(s.substr(pos), "&")}; + for (auto i: list) { + pos = i.find('='); + if (pos != i.npos) { + result[i.substr(0, pos)] = i.substr(pos + 1); + } + } + } + + return result; +} + +struct CommandParameters +{ + std::function& m_GetServerParam; + std::function& m_GetRequestParam; // request including body (POST...) + std::function& m_SetResponseHeader; // to be added to result string + + std::unordered_map paramHash; + + std::string webboxPath; + std::string webboxName; + bool webboxReadOnly; + + CommandParameters( + std::function& GetServerParam, + std::function& GetRequestParam, + std::function& SetResponseHeader + ) + : m_GetServerParam(GetServerParam) + , m_GetRequestParam(GetRequestParam) + , m_SetResponseHeader(SetResponseHeader) + , paramHash(ParseQueryString(GetRequestParam("rel_target"))) // rel_target contains query string + , webboxPath(m_GetRequestParam("doc_root")) + , webboxName(m_GetRequestParam("WEBBOX_NAME")) + , webboxReadOnly(m_GetRequestParam("WEBBOX_READONLY") == "1") + { + } +}; + // Used to return errors by generating response page and HTTP status code -std::string HttpStatus(std::string status, std::string message, std::function& SetResponseHeader) +std::string HttpStatus(std::string status, std::string message, CommandParameters& commandParameters) { - SetResponseHeader("status", status); - SetResponseHeader("content_type", "text/html"); + commandParameters.m_SetResponseHeader("status", status); + commandParameters.m_SetResponseHeader("content_type", "text/html"); auto it{status_map.find(status)}; std::string description{"(Unknown)"}; @@ -32,169 +88,134 @@ std::string HttpStatus(std::string status, std::string message, std::function

"s + status + " "s + description + "

"s + message + "

"; } - -struct CommandParameters { - std::function& GetServerParam; - std::function& GetRequestParam; // request including body (POST...) - std::function& SetResponseHeader; // to be added to result string - FCGX_Request request; // the request - - QUrlQuery urlQuery; // derived from request - QHash paramHash; // derived from urlQuery - - int count; // request count for this instance - - // Webbox parameters, constant and set globally in Web server config - QString webboxPath; - QString webboxName; - bool webboxReadOnly; -}; - -class Command { - public: - // call interface - void execute(CommandParameters& p) { - // check if this webbox is writable and enforce this - if (p.webboxReadOnly && m_isWriteCommand) { - printHttpError(400, QString("Webbox is Read-Only"), p); - return; - } - - // check for correct method GET/POST - QString requestMethod(FCGX_GetParam("REQUEST_METHOD", p.request.envp)); - if (requestMethod != m_requestMethod) { - printHttpError(403, QString("Bad request method"), p); - return; - } - - // Set parameters from FastCGI request environment - m_pathInfo = FCGX_GetParam("PATH_INFO", p.request.envp); - if (m_pathInfo == "") { - m_pathInfo = "/"; - } - if (m_pathInfo.contains("..")) { - printHttpError(403, QString("Bad path: %1"), p); - return; - } - - m_path = p.webboxPath + m_pathInfo; - - this->start(p); - } - - QString getCommandName() { - return m_commandName; - } - - protected: - // helper function for writing http error - void printHttpError(int httpStatusCode, QString message, CommandParameters& p) { - FCGX_PutS(httpError(httpStatusCode, message).toUtf8().data(), p.request.out); - } - - // implemented in implementation classes - virtual void start(CommandParameters& p) = 0; - - // Implementation class constants - QString m_commandName; - QString m_requestMethod; - bool m_isWriteCommand; // if true, command must be prevented if p.webboxReadOnly - - // calculated during start of execute() - QString m_pathInfo; // path inside webbox, derived from request - QString m_path; // complete path +class Command +{ +public: + // call interface + std::string execute(CommandParameters& p) + { + // check if this webbox is writable and enforce this + if (p.webboxReadOnly && m_isWriteCommand) { + return HttpStatus("400", "Webbox is Read-Only", p); + } + + // check for correct method GET/POST + std::string requestMethod{p.m_GetRequestParam("method")}; + if (requestMethod != m_requestMethod) { + return HttpStatus("403", "Bad request method", p); + } + + // Set parameters from FastCGI request environment + m_pathInfo = p.m_GetRequestParam("rel_path"); + if (m_pathInfo == "") { + m_pathInfo = "/"; + } + if (m_pathInfo.find("..") != m_pathInfo.npos) { + return HttpStatus("403", "Bad path: "s + m_pathInfo, p); + } + + m_path = p.webboxPath + m_pathInfo; + + return this->start(p); + } + + std::string getCommandName() + { + return m_commandName; + } + +protected: + // implemented in implementation classes + virtual std::string start(CommandParameters& p) = 0; + + // Implementation class constants + std::string m_commandName; + std::string m_requestMethod; + bool m_isWriteCommand; // if true, command must be prevented if p.webboxReadOnly + + // calculated during start of execute() + std::string m_pathInfo; // path inside webbox, derived from request + std::string m_path; // complete path }; -class GetCommand: public Command { - public: - GetCommand() { - m_requestMethod = "GET"; - } +class GetCommand: public Command +{ +public: + GetCommand() { + m_requestMethod = "GET"; + } }; -class PostCommand: public Command { - public: - PostCommand() { - m_requestMethod = "POST"; - } - - protected: - // prepare POST handler implementation: read m_contentLength and m_content - // needs to be called at beginning of post implementations start() - // returns true on success - bool readContent(CommandParameters& p) { - QString contentLengthString(FCGX_GetParam("CONTENT_LENGTH", p.request.envp)); - bool ok; - m_contentLength = contentLengthString.toInt(&ok); - - if (!ok) { - printHttpError(400, QString("Bad content length"), p); - return false; - } else { - m_content.resize(m_contentLength); - - int result = FCGX_GetStr(m_content.data(), m_content.size(), p.request.in); - if (result != m_content.size()) { - printHttpError(400, QString("Read error (%1/%2)").arg(result).arg(m_content.size()), p); - return false; - } - } - return true; - } - - int m_contentLength; - QByteArray m_content; +class PostCommand: public Command +{ +public: + PostCommand() + { + m_requestMethod = "POST"; + } + +protected: + // prepare POST handler implementation: read m_contentLength and m_content + // needs to be called at beginning of post implementations start() + // returns true on success + void readContent(CommandParameters& p) + { + m_content = p.m_GetRequestParam("body"); + m_contentLength = m_content.size(); + } + + int m_contentLength; + std::string m_content; }; -class DiagCommand: public GetCommand { - public: - DiagCommand() { - m_commandName = "diag"; - m_isWriteCommand = false; - } - - protected: - virtual void start(CommandParameters& p) { - QString serverName(FCGX_GetParam("SERVER_NAME", p.request.envp)); - // provide diag only on "localhost" - if (serverName != "localhost") { - printHttpError(403, QString("Command not available"), p); - return; - } +class DiagCommand: public GetCommand +{ +public: + DiagCommand() + { + m_commandName = "diag"; + m_isWriteCommand = false; + } - FCGX_PutS("Content-Type: text/html\r\n\r\n", p.request.out); +protected: + virtual std::string start(CommandParameters& p) + { + std::string serverName(p.m_GetRequestParam("host")); + + // provide diag only on "localhost" + if (serverName != "localhost") + throw std::runtime_error("Command not available"); - FCGX_PutS("Params\r\n", p.request.out); + p.m_SetRequestParam("content_type", "text/html"); - FCGX_PutS(QString("Request no. %1

\r\n").arg(p.count).toUtf8().data(), p.request.out); + std::string result {"Params\r\n"}; - char** tmp = p.request.envp; + result += "WEBBOX_PATH="s + p.webboxPath + "
\r\n"s; - while (*tmp) { - FCGX_PutS(QString("%1
\r\n").arg(*tmp).toUtf8().data(), p.request.out); - tmp++; - } - - FCGX_PutS(QString("
WEBBOX_PATH=%1
\r\n").arg(p.webboxPath).toUtf8().data(), p.request.out); + result += "
URL Query="s + p.m_GetRequestParam("rel_target") + "
\r\n";; - FCGX_PutS(QString("
URL Query=%1
\r\n").arg(p.urlQuery.toString()).toUtf8().data(), p.request.out); + result += "\r\n"; - - FCGX_PutS("\r\n", p.request.out); - } + return result; + } }; -class ListCommand: public GetCommand { - public: - ListCommand() { - m_commandName = "list"; - m_isWriteCommand = false; - } - - protected: - virtual void start(CommandParameters& p) { - FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); - +class ListCommand: public GetCommand +{ +public: + ListCommand() + { + m_commandName = "list"; + m_isWriteCommand = false; + } + +protected: + virtual std::string start(CommandParameters& p) { + FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); + + fs::directory_iterator dir(m_path); + for (auto it& + pt::ptree QDir dir(m_path); QFileInfoList dirEntryList = dir.entryInfoList(QDir::NoDot | QDir::AllEntries, QDir::DirsFirst | QDir::Name | QDir::IgnoreCase); @@ -227,7 +248,7 @@ class ServerInfoCommand: public GetCommand { } protected: - virtual void start(CommandParameters& p) { + virtual std::string start(CommandParameters& p) { FCGX_PutS("Content-Type: text/xml\r\n\r\n", p.request.out); QByteArray xmlData; @@ -253,9 +274,9 @@ class VersionCommand: public GetCommand { } protected: - virtual void start(CommandParameters& p) { + virtual std::string start(CommandParameters& p) { FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - FCGX_PutS(QString("webbox %1
(C) 2018 Reichwein.IT\r\n").arg(PROGRAMVERSION).toUtf8().data(), p.request.out); + FCGX_PutS(std::string("webbox %1
(C) 2018 Reichwein.IT\r\n").arg(PROGRAMVERSION).toUtf8().data(), p.request.out); } }; @@ -267,9 +288,8 @@ class NewDirCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); QXmlStreamReader xml(m_content); @@ -277,7 +297,7 @@ class NewDirCommand: public PostCommand { while (!xml.atEnd()) { while (xml.readNextStartElement()) { if (xml.name() == "dirname") { - QString dirname = xml.readElementText(); + std::string dirname = xml.readElementText(); QDir dir(m_path); if (dir.mkdir(dirname)) { FCGX_PutS("Successfully created directory", p.request.out); @@ -298,9 +318,8 @@ class InfoCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); QXmlStreamReader xml(m_content); @@ -310,16 +329,16 @@ class InfoCommand: public PostCommand { if (xml.name() == "files") { while (xml.readNextStartElement()) { if (xml.name() == "file") { - QString filename = xml.readElementText(); + std::string filename = xml.readElementText(); QFileInfo fileInfo(m_path + "/" + filename); qint64 size = fileInfo.size(); - QString date = fileInfo.lastModified().toString(); + std::string date = fileInfo.lastModified().toString(); if (fileInfo.isDir()) { - FCGX_PutS(QString("%1, %2 (folder)
") + FCGX_PutS(std::string("%1, %2 (folder)
") .arg(filename) .arg(date).toUtf8().data(), p.request.out); } else { - FCGX_PutS(QString("%1, %2 bytes, %3 (file)
") + FCGX_PutS(std::string("%1, %2 bytes, %3 (file)
") .arg(filename) .arg(size) .arg(date).toUtf8().data(), p.request.out); @@ -339,19 +358,18 @@ class DownloadZipCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); QXmlStreamReader xml(m_content); QByteArray zipData; - QStringList argumentList; + std::stringList argumentList; QTemporaryFile tempfile(QDir::tempPath() + "/webboxXXXXXX.zip"); tempfile.open(); QFileInfo fileInfo(tempfile); - QString tempfilePath = fileInfo.absolutePath(); - QString tempfileName = fileInfo.fileName(); + std::string tempfilePath = fileInfo.absolutePath(); + std::string tempfileName = fileInfo.fileName(); tempfile.close(); tempfile.remove(); @@ -363,7 +381,7 @@ class DownloadZipCommand: public PostCommand { if (xml.name() == "files") { while (xml.readNextStartElement()) { if (xml.name() == "file") { - QString filename = xml.readElementText(); + std::string filename = xml.readElementText(); argumentList.append(filename); // add parts } @@ -379,7 +397,7 @@ class DownloadZipCommand: public PostCommand { process.start(); process.waitForFinished(); - QString debugText = process.readAll(); + std::string debugText = process.readAll(); process.setReadChannel(QProcess::StandardError); debugText += process.readAll(); @@ -387,7 +405,7 @@ class DownloadZipCommand: public PostCommand { process.exitCode() != 0 || process.exitStatus() != QProcess::NormalExit) { - printHttpError(500, QString("Error running process: %1 %2 %3 %4 %5 %6 %7"). + printHttpError(500, std::string("Error running process: %1 %2 %3 %4 %5 %6 %7"). arg(process.state()). arg(process.exitCode()). arg(process.exitStatus()). @@ -399,11 +417,11 @@ class DownloadZipCommand: public PostCommand { QFile tempfile(tempfilePath + "/" + tempfileName); if (!tempfile.open(QIODevice::ReadOnly)) { - printHttpError(500, QString("Error reading file"), p); + printHttpError(500, std::string("Error reading file"), p); } else { zipData = tempfile.readAll(); - FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg("webbox-download.zip").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Content-Disposition: attachment; filename=\"%1\"\r\n").arg("webbox-download.zip").toUtf8().data(), p.request.out); FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out); FCGX_PutStr(zipData.data(), zipData.size(), p.request.out); @@ -423,34 +441,33 @@ class DeleteCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); QXmlStreamReader xml(m_content); - QString response = ""; + std::string response = ""; while (!xml.atEnd()) { while (xml.readNextStartElement()) { if (xml.name() == "files") { while (xml.readNextStartElement()) { if (xml.name() == "file") { - QString filename = xml.readElementText(); + std::string filename = xml.readElementText(); QFileInfo fileInfo(m_path + "/" + filename); if (fileInfo.isDir()) { QDir dir(m_path); if (!dir.rmdir(filename)) { - response += QString("Error on removing directory %1
").arg(filename); + response += std::string("Error on removing directory %1
").arg(filename); } } else if (fileInfo.isFile()) { QFile file(m_path + "/" + filename); if (!file.remove()) { - response += QString("Error on removing file %1
").arg(filename); + response += std::string("Error on removing file %1
").arg(filename); } } else { - response += QString("Error: %1 is neither file nor directory.
").arg(filename); + response += std::string("Error: %1 is neither file nor directory.
").arg(filename); } } } @@ -475,14 +492,13 @@ class MoveCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); QXmlStreamReader xml(m_content); - QString response = ""; - QString targetDir; + std::string response = ""; + std::string targetDir; while (!xml.atEnd()) { while (xml.readNextStartElement()) { @@ -491,21 +507,21 @@ class MoveCommand: public PostCommand { if (xml.name() == "target") { targetDir = xml.readElementText(); } else if (xml.name() == "file") { - QString filename = xml.readElementText(); + std::string filename = xml.readElementText(); QFileInfo fileInfo(m_path + "/" + filename); if (fileInfo.isDir()) { QDir dir(m_path); if (!dir.rename(filename, targetDir + "/" + filename)) { - response += QString("Error moving directory %1
").arg(filename); + response += std::string("Error moving directory %1
").arg(filename); } } else if (fileInfo.isFile()) { QFile file(m_path + "/" + filename); if (!file.rename(m_path + "/" + targetDir + "/" + filename)) { - response += QString("Error on moving file %1
").arg(filename); + response += std::string("Error on moving file %1
").arg(filename); } } else { - response += QString("Error: %1 is neither file nor directory.
").arg(filename); + response += std::string("Error: %1 is neither file nor directory.
").arg(filename); } } } @@ -530,14 +546,13 @@ class RenameCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); QXmlStreamReader xml(m_content); - QString oldname; - QString newname; + std::string oldname; + std::string newname; while (!xml.atEnd()) { while (xml.readNextStartElement()) { @@ -555,9 +570,9 @@ class RenameCommand: public PostCommand { } QDir dir(m_path); - QString response; + std::string response; if (!dir.rename(oldname, newname)) { - response = QString("Error renaming %1 to %2
").arg(oldname).arg(newname); + response = std::string("Error renaming %1 to %2
").arg(oldname).arg(newname); } else { response = "OK"; } @@ -575,21 +590,20 @@ class UploadCommand: public PostCommand { } protected: - virtual void start(CommandParameters& p) { - if (!readContent(p)) - return; + virtual std::string start(CommandParameters& p) { + readContent(p); FCGX_PutS("Content-Type: text/plain\r\n\r\n", p.request.out); - QString contentType(FCGX_GetParam("CONTENT_TYPE", p.request.envp)); + std::string contentType(FCGX_GetParam("CONTENT_TYPE", p.request.envp)); - QString separator("boundary="); + std::string separator("boundary="); if (!contentType.contains(separator)) { - FCGX_PutS(QString("No boundary defined").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("No boundary defined").toUtf8().data(), p.request.out); } else { QByteArray boundary = QByteArray("--") + contentType.split(separator)[1].toUtf8(); int boundaryCount = m_content.count(boundary); if (boundaryCount < 2) { - FCGX_PutS(QString("Bad boundary number found: %1").arg(boundaryCount).toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Bad boundary number found: %1").arg(boundaryCount).toUtf8().data(), p.request.out); } else { while (true) { int start = m_content.indexOf(boundary) + boundary.size(); @@ -605,34 +619,34 @@ class UploadCommand: public PostCommand { // Read filename start = filecontent.indexOf("filename=\""); if (start == -1) { - FCGX_PutS(QString("Error reading filename / start").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Error reading filename / start").toUtf8().data(), p.request.out); } else { start += QByteArray("filename=\"").size(); end = filecontent.indexOf(QByteArray("\""), start); if (end == -1) { - FCGX_PutS(QString("Error reading filename / end").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Error reading filename / end").toUtf8().data(), p.request.out); } else { - QString filename = QString::fromUtf8(filecontent.mid(start, end - start)); + std::string filename = std::string::fromUtf8(filecontent.mid(start, end - start)); if (filename.size() < 1) { - FCGX_PutS(QString("Bad filename").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Bad filename").toUtf8().data(), p.request.out); } else { // Remove header start = filecontent.indexOf(QByteArray("\r\n\r\n")); if (start == -1) { - FCGX_PutS(QString("Error removing upload header").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Error removing upload header").toUtf8().data(), p.request.out); } else { - filecontent = filecontent.mid(start + QString("\r\n\r\n").toUtf8().size()); + filecontent = filecontent.mid(start + std::string("\r\n\r\n").toUtf8().size()); QFile file(m_path + "/" + filename); if (!file.open(QIODevice::WriteOnly)) { - FCGX_PutS(QString("Error opening file").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Error opening file").toUtf8().data(), p.request.out); } else { qint64 written = file.write(filecontent); if (written != filecontent.size()) { - FCGX_PutS(QString("Error writing file").toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Error writing file").toUtf8().data(), p.request.out); } } } @@ -654,11 +668,11 @@ class DownloadCommand: public GetCommand { } protected: - virtual void start(CommandParameters& p) { + virtual std::string start(CommandParameters& p) { QFile file(m_path); if (file.open(QIODevice::ReadOnly)) { QFileInfo fileInfo(m_path); - FCGX_PutS(QString("Content-Disposition: attachment; filename=\"%1\"\r\n").arg(fileInfo.fileName()).toUtf8().data(), p.request.out); + FCGX_PutS(std::string("Content-Disposition: attachment; filename=\"%1\"\r\n").arg(fileInfo.fileName()).toUtf8().data(), p.request.out); FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out); while (!file.atEnd()) { @@ -666,116 +680,11 @@ class DownloadCommand: public GetCommand { FCGX_PutStr(ba.data(), ba.size(), p.request.out); } } else { - FCGX_PutS(httpError(500, QString("Bad file: %1").arg(m_pathInfo)).toUtf8().data(), p.request.out); + FCGX_PutS(httpError(500, std::string("Bad file: %1").arg(m_pathInfo)).toUtf8().data(), p.request.out); } } }; -// Hash of commands for fast access -class Commands: public QHash { - public: - void registerCommand(Command& command) { - (*this)[command.getCommandName()] = &command; - } -}; - -void initlocale() { - if (setenv("LC_ALL", "UTF-8", 1)) { - exit(1); - } -} - -int main(int argc, char* argv[]) { - initlocale(); - - int result = FCGX_Init(); - if (result != 0) { - return 1; // error on init - } - - CommandParameters commandParameters; - - FCGX_Request& request = commandParameters.request; - - if (FCGX_InitRequest(&request, 0, 0) != 0) { - return 1; // error on init - } - - commandParameters.count = 0; - - // values constant to this instance: - commandParameters.webboxPath = getenv("WEBBOX_PATH"); - commandParameters.webboxName = getenv("WEBBOX_NAME"); - char* WEBBOX_READONLY = getenv("WEBBOX_READONLY"); - commandParameters.webboxReadOnly = ((WEBBOX_READONLY != NULL) && !strcmp(WEBBOX_READONLY, "On")); - - Commands commands; - - DiagCommand diagCommand; - commands.registerCommand(diagCommand); - - ListCommand listCommand; - commands.registerCommand(listCommand); - - ServerInfoCommand serverInfoCommand; - commands.registerCommand(serverInfoCommand); - - VersionCommand versionCommand; - commands.registerCommand(versionCommand); - - NewDirCommand newDirCommand; - commands.registerCommand(newDirCommand); - - InfoCommand infoCommand; - commands.registerCommand(infoCommand); - - DownloadZipCommand downloadZipCommand; - commands.registerCommand(downloadZipCommand); - - DeleteCommand deleteCommand; - commands.registerCommand(deleteCommand); - - MoveCommand moveCommand; - commands.registerCommand(moveCommand); - - RenameCommand renameCommand; - commands.registerCommand(renameCommand); - - UploadCommand uploadCommand; - commands.registerCommand(uploadCommand); - - DownloadCommand downloadCommand; - commands.registerCommand(downloadCommand); - - while (FCGX_Accept_r(&request) == 0) { - - commandParameters.count++; - - QString queryString(FCGX_GetParam("QUERY_STRING", request.envp)); - - // URL parameters - commandParameters.urlQuery.setQuery(queryString); - - QList > items = commandParameters.urlQuery.queryItems(); - commandParameters.paramHash.clear(); - - for (int i = 0; i < items.size(); i++) { - commandParameters.paramHash[items[i].first] = items[i].second; - } - - QString commandName = commandParameters.paramHash["command"]; - if (commands.contains(commandName)) { - Command* command = commands[commandName]; - - command->execute(commandParameters); - } else { - FCGX_PutS(httpError(400, QString("Bad command: %1").arg(commandName)).toUtf8().data(), request.out); - } - } - - return 0; -} - } // anonymous namespace std::string webbox_plugin::name() @@ -786,6 +695,18 @@ std::string webbox_plugin::name() webbox_plugin::webbox_plugin() { //std::cout << "Plugin constructor" << std::endl; + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); + registerCommand(m_commands, std::make_shared()); } webbox_plugin::~webbox_plugin() @@ -799,5 +720,22 @@ std::string webbox_plugin::generate_page( std::function& SetResponseHeader // to be added to result string ) { - return "Webbox"; + CommandParameters commandParameters(GetServerParam, GetRequestParam, SetResponseHeader); + + auto it {commandParameters.paramHash.find("command")}; + if (it != commandParameters.paramHash.end()) { + std::string& commandName{it->second}; + + auto commands_it{commands.find(commandName)}; + if (commands_it != commands.end()) { + try { + return commands_it->second.execute(commandParameters); + } catch (const std::exception& ex) { + return HttpStatus("500", "Processing command: "s + commandName, commandParameters); + } + } else + return HttpStatus("400", "Bad command: "s + commandName, commandParameters); + } else + return HttpStatus("400", "No command specified"s, commandParameters); } + diff --git a/plugins/webbox/webbox.h b/plugins/webbox/webbox.h index d6d26c1..e2644f3 100644 --- a/plugins/webbox/webbox.h +++ b/plugins/webbox/webbox.h @@ -2,8 +2,15 @@ #include "../../plugin_interface.h" +#include +#include +#include + class webbox_plugin: public webserver_plugin_interface { +private: + std::unordered_map> m_commands; + public: webbox_plugin(); ~webbox_plugin(); diff --git a/response.cpp b/response.cpp index 1ee8932..ffd72b6 100644 --- a/response.cpp +++ b/response.cpp @@ -78,6 +78,8 @@ std::unordered_map> Get {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }}, + {"content_length", [](RequestContext& req_ctx) { return std::to_string(req_ctx.GetReq().body().size()); }}, + {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}}, }; @@ -100,10 +102,11 @@ std::string GetRequestParam(const std::string& key, RequestContext& req_ctx) } // third, look up req parameters + // contains: host { try { return std::string{req_ctx.GetReq()[key]}; - } catch(...){ + } catch(...) { // not found } } @@ -202,6 +205,8 @@ response_type generate_response(request_type& req, Server& server) return res; } catch(const std::out_of_range& ex) { return HttpStatus("400", "Bad request: Host "s + std::string{req["host"]} + ":"s + std::string{req.target()} + " unknown"s, res); + } catch(const std::exception& ex) { + return HttpStatus("400", "Bad request: "s + std::string{ex.what()}, res); } } diff --git a/stringutil.cpp b/stringutil.cpp deleted file mode 100644 index f87fa00..0000000 --- a/stringutil.cpp +++ /dev/null @@ -1,66 +0,0 @@ -#include "stringutil.h" - -#include -#include - -#include - -std::string strfmt(const char* fmt, ...) -{ - va_list args; - - va_start(args, fmt); - int size = std::vsnprintf(nullptr, 0, fmt, args); - va_end(args); - - std::string result(size, ' '); - - va_start(args, fmt); - std::vsnprintf(result.data(), size + 1, fmt, args); - va_end(args); - - return result; -} - -std::vector split(std::string value, const std::string separators) -{ - std::vector result; - - size_t pos0 = 0; - size_t pos1 = 0; - while (pos0 < value.size()) { - pos1 = value.find_first_of(separators, pos0); - if (pos1 == std::string::npos) - pos1 = value.size(); - std::string part = value.substr(pos0, pos1 - pos0); - //std::cout << "DEBUG: " << part << std::endl << std::flush; - if (part != "") - result.push_back(part); - pos0 = value.find_first_not_of(separators, pos1); - if (pos0 == std::string::npos) - pos0 = value.size(); - } - - return result; -} - -std::string join(std::vector vs, std::string separator) -{ - std::string s; - for (const auto& line : vs) { - if (s.size() > 0) - s += separator; - s += line; - } - - return s; -} - -bool startsWithAnyOfLower(const std::string &s, const std::vector &list) { - for (const std::string& element : list) { - if (boost::algorithm::starts_with(boost::algorithm::to_lower_copy(s), boost::algorithm::to_lower_copy(element))) - return true; - } - return false; -} - diff --git a/stringutil.h b/stringutil.h deleted file mode 100644 index 5110e2e..0000000 --- a/stringutil.h +++ /dev/null @@ -1,10 +0,0 @@ -#pragma once - -#include -#include - -std::string strfmt(const char* fmt, ...); - -std::vector split(std::string value, const std::string separators = "\r\n "); -std::string join(std::vector vs, std::string separator = "\n"); -bool startsWithAnyOfLower(const std::string &s, const std::vector &list); diff --git a/test-webserver.cpp b/test-webserver.cpp index baf8b3f..1b2c043 100644 --- a/test-webserver.cpp +++ b/test-webserver.cpp @@ -1,6 +1,41 @@ #include "gmock/gmock.h" #include "gtest/gtest.h" +#include +#include + +#include +#include + +using namespace std::string_literals; +namespace pt = boost::property_tree; + +TEST(property_tree, put) +{ + pt::ptree p; + + pt::ptree entry; + entry.put_value("name1.txt"); + entry.put(".type", "file1"); + + p.push_back(pt::ptree::value_type("listentry", entry)); + + entry.put_value("name2.txt"); + entry.put(".type", "file2"); + + p.push_back(pt::ptree::value_type("listentry", entry)); + + pt::ptree list; + list.push_back(pt::ptree::value_type("list", p)); + + std::stringstream ss; + + pt::xml_parser::write_xml(ss, list /*, pt::xml_parser::xml_writer_make_settings(' ', 1)*/); + + EXPECT_EQ(ss.str(), "\nname1.txtname2.txt"); +} + + int main(int argc, char* argv[]) { ::testing::InitGoogleMock(&argc, argv); return RUN_ALL_TESTS(); diff --git a/webserver.conf b/webserver.conf index e3731bc..365e015 100644 --- a/webserver.conf +++ b/webserver.conf @@ -21,12 +21,16 @@ static-files /home/ernie/homepage/test - /home/ernie/code/webserver/fullchain.pem /home/ernie/code/webserver/privkey.pem diff --git a/webserver.cpp b/webserver.cpp index 51dc6a7..3e312f4 100644 --- a/webserver.cpp +++ b/webserver.cpp @@ -13,8 +13,16 @@ void usage() std::cout << "usage: webserver [-c ]" << std::endl; } +void initlocale() { + if (setenv("LC_ALL", "UTF-8", 1)) { + exit(1); + } +} + int main(int argc, char* argv[]) { + initlocale(); + std::string config_filename; if (!(argc == 1 || argc == 3)) { -- cgit v1.2.3