summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2020-04-12 22:20:33 +0200
committerRoland Reichwein <mail@reichwein.it>2020-04-12 22:20:33 +0200
commit4732dc63657f4c6fc342f7674f7dc7c666b293dc (patch)
treeda91a5dbbd62982284435d252dd89ac963952ee9
parent3f778eecc705990598f1033e6245522f42e2fcb5 (diff)
webbox (WIP)
-rw-r--r--Makefile5
-rw-r--r--README.txt28
-rw-r--r--TODO2
-rw-r--r--debian/webserver.docs1
-rw-r--r--plugins/static-files/static-files.cpp2
-rw-r--r--plugins/webbox/Makefile1
-rw-r--r--plugins/webbox/stringutil.cpp (renamed from stringutil.cpp)0
-rw-r--r--plugins/webbox/stringutil.h (renamed from stringutil.h)0
-rw-r--r--plugins/webbox/webbox.cpp586
-rw-r--r--plugins/webbox/webbox.h7
-rw-r--r--response.cpp7
-rw-r--r--test-webserver.cpp35
-rw-r--r--webserver.conf10
-rw-r--r--webserver.cpp8
14 files changed, 351 insertions, 341 deletions
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/stringutil.cpp b/plugins/webbox/stringutil.cpp
index f87fa00..f87fa00 100644
--- a/stringutil.cpp
+++ b/plugins/webbox/stringutil.cpp
diff --git a/stringutil.h b/plugins/webbox/stringutil.h
index 5110e2e..5110e2e 100644
--- a/stringutil.h
+++ b/plugins/webbox/stringutil.h
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 <boost/algorithm/string/replace.hpp>
+#include <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+#include <filesystem>
#include <iostream>
#include <string>
#include <unordered_map>
using namespace std::string_literals;
+namespace fs = std::filesystem;
+namespace pt = boost::property_tree;
namespace {
+ void registerCommand(std::unordered_map<std::string, std::shared_ptr<Command>>& commands, std::shared_ptr<Command> command) {
+ commands[command.getCommandName()] = command;
+ };
unordered_map<std::string> status_map {
{ "400", "Bad Request"},
@@ -18,11 +28,57 @@ namespace {
{ "505", "Internal Server Error" },
};
+std::unordered_map<std::string, std::string> ParseQueryString(std::string s)
+{
+ std::unordered_map<std::string, std::string> 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<std::string(const std::string& key)>& m_GetServerParam;
+ std::function<std::string(const std::string& key)>& m_GetRequestParam; // request including body (POST...)
+ std::function<void(const std::string& key, const std::string& value)>& m_SetResponseHeader; // to be added to result string
+
+ std::unordered_map<std::string, std::string> paramHash;
+
+ std::string webboxPath;
+ std::string webboxName;
+ bool webboxReadOnly;
+
+ CommandParameters(
+ std::function<std::string(const std::string& key)>& GetServerParam,
+ std::function<std::string(const std::string& key)>& GetRequestParam,
+ std::function<void(const std::string& key, const std::string& value)>& 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<plugin_interface_setter_type>& 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<pl
return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>";
}
-
-struct CommandParameters {
- std::function<std::string(const std::string& key)>& GetServerParam;
- std::function<std::string(const std::string& key)>& GetRequestParam; // request including body (POST...)
- std::function<void(const std::string& key, const std::string& value)>& SetResponseHeader; // to be added to result string
- FCGX_Request request; // the request
-
- QUrlQuery urlQuery; // derived from request
- QHash<QString, QString> 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("<html><head><title>Params</title></head><body>\r\n", p.request.out);
+ p.m_SetRequestParam("content_type", "text/html");
- FCGX_PutS(QString("Request no. %1<br/><br/>\r\n").arg(p.count).toUtf8().data(), p.request.out);
+ std::string result {"<html><head><title>Params</title></head><body>\r\n"};
- char** tmp = p.request.envp;
+ result += "WEBBOX_PATH="s + p.webboxPath + "<br/>\r\n"s;
- while (*tmp) {
- FCGX_PutS(QString("%1<br/>\r\n").arg(*tmp).toUtf8().data(), p.request.out);
- tmp++;
- }
-
- FCGX_PutS(QString("<br/>WEBBOX_PATH=%1<br/>\r\n").arg(p.webboxPath).toUtf8().data(), p.request.out);
+ result += "<br/>URL Query="s + p.m_GetRequestParam("rel_target") + "<br/>\r\n";;
- FCGX_PutS(QString("<br/>URL Query=%1<br/>\r\n").arg(p.urlQuery.toString()).toUtf8().data(), p.request.out);
+ result += "</body></html>\r\n";
-
- FCGX_PutS("</body></html>\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<br/>(C) 2018 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>\r\n").arg(PROGRAMVERSION).toUtf8().data(), p.request.out);
+ FCGX_PutS(std::string("webbox %1<br/>(C) 2018 <a href=\"https://www.reichwein.it/\">Reichwein.IT</a>\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)<br>")
+ FCGX_PutS(std::string("%1, %2 (folder)<br>")
.arg(filename)
.arg(date).toUtf8().data(), p.request.out);
} else {
- FCGX_PutS(QString("%1, %2 bytes, %3 (file)<br>")
+ FCGX_PutS(std::string("%1, %2 bytes, %3 (file)<br>")
.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<br/>").arg(filename);
+ response += std::string("Error on removing directory %1<br/>").arg(filename);
}
} else if (fileInfo.isFile()) {
QFile file(m_path + "/" + filename);
if (!file.remove()) {
- response += QString("Error on removing file %1<br/>").arg(filename);
+ response += std::string("Error on removing file %1<br/>").arg(filename);
}
} else {
- response += QString("Error: %1 is neither file nor directory.<br/>").arg(filename);
+ response += std::string("Error: %1 is neither file nor directory.<br/>").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<br/>").arg(filename);
+ response += std::string("Error moving directory %1<br/>").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<br/>").arg(filename);
+ response += std::string("Error on moving file %1<br/>").arg(filename);
}
} else {
- response += QString("Error: %1 is neither file nor directory.<br/>").arg(filename);
+ response += std::string("Error: %1 is neither file nor directory.<br/>").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<br/>").arg(oldname).arg(newname);
+ response = std::string("Error renaming %1 to %2<br/>").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<QString, Command*> {
- 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<QPair<QString, QString> > 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<DiagCommand>());
+ registerCommand(m_commands, std::make_shared<ListCommand>());
+ registerCommand(m_commands, std::make_shared<ServerInfoCommand>());
+ registerCommand(m_commands, std::make_shared<VersionCommand>());
+ registerCommand(m_commands, std::make_shared<NewDirCommand>());
+ registerCommand(m_commands, std::make_shared<InfoCommand>());
+ registerCommand(m_commands, std::make_shared<DownloadZipCommand>());
+ registerCommand(m_commands, std::make_shared<DeleteCommand>());
+ registerCommand(m_commands, std::make_shared<MoveCommand>());
+ registerCommand(m_commands, std::make_shared<RenameCommand>());
+ registerCommand(m_commands, std::make_shared<UploadCommand>());
+ registerCommand(m_commands, std::make_shared<DownloadCommand>());
}
webbox_plugin::~webbox_plugin()
@@ -799,5 +720,22 @@ std::string webbox_plugin::generate_page(
std::function<void(const std::string& key, const std::string& value)>& 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 <memory>
+#include <string>
+#include <unordered_map>
+
class webbox_plugin: public webserver_plugin_interface
{
+private:
+ std::unordered_map<std::string, std::shared_ptr<Command>> 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<std::string, std::function<std::string(RequestContext&)>> 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/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 <boost/property_tree/ptree.hpp>
+#include <boost/property_tree/xml_parser.hpp>
+
+#include <sstream>
+#include <string>
+
+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("<xmlattr>.type", "file1");
+
+ p.push_back(pt::ptree::value_type("listentry", entry));
+
+ entry.put_value("name2.txt");
+ entry.put("<xmlattr>.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<std::string>(' ', 1)*/);
+
+ EXPECT_EQ(ss.str(), "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<list><listentry type=\"file1\">name1.txt</listentry><listentry type=\"file2\">name2.txt</listentry></list>");
+}
+
+
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 @@
<plugin>static-files</plugin>
<target>/home/ernie/homepage/test</target>
</path>
- <!--
<path requested="/webbox">
+ <plugin>static-files</plugin>
+ <target>/home/ernie/webbox/html</target>
+ </path>
+ <path requested="/webbox/bin">
<plugin>webbox</plugin>
- <target>/var/lib/webbox</target>
+ <target>/home/ernie/testbox</target>
+ <WEBBOX_NAME>Testbox1</WEBBOX_NAME>
+ <WEBBOX_READONLY>0</WEBBOX_READONLY>
</path>
- -->
<certpath>/home/ernie/code/webserver/fullchain.pem</certpath>
<keypath>/home/ernie/code/webserver/privkey.pem</keypath>
</site>
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 <configuration-filename>]" << 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)) {