summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile2
-rw-r--r--config.cpp106
-rw-r--r--config.h15
-rw-r--r--plugins/webbox/Makefile1
-rw-r--r--plugins/webbox/file.cpp46
-rw-r--r--plugins/webbox/file.h15
-rw-r--r--plugins/webbox/webbox.cpp134
-rw-r--r--response.cpp151
-rw-r--r--server.h1
9 files changed, 228 insertions, 243 deletions
diff --git a/Makefile b/Makefile
index 4ebb4a7..d37ed1e 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
diff --git a/config.cpp b/config.cpp
index 8d6704c..0d9c117 100644
--- a/config.cpp
+++ b/config.cpp
@@ -194,11 +194,12 @@ void Config::dump() const
std::cout << "=============================================" << std::endl;
}
-std::string Config::DocRoot(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const
+// throws std::out_of_range if not found
+const Path& Config::GetPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const
{
// TODO: speed this up
std::string host{requested_host};
- std::string result;
+ const Path* result{nullptr};
size_t path_len{0}; // find longest matching prefix
RemovePortFromHostname(host);
@@ -210,44 +211,7 @@ std::string Config::DocRoot(const Socket& socket, const std::string& requested_h
for (const auto& path: site.paths) {
if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
path_len = path.requested.size();
- try {
- result = path.params.at("target");
- } catch (const std::out_of_range& ex) {
- std::cout << "Out of range at Config::DocRoot(): target" << std::endl;
- std::rethrow_exception(std::current_exception());
- }
- }
- }
- }
- }
- }
- }
-
- return result;
-}
-
-std::string Config::GetPlugin(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const
-{
- // TODO: speed this up
- std::string host{requested_host};
- std::string result;
- size_t path_len{0};
-
- RemovePortFromHostname(host);
-
- for (const auto& site: m_sites) {
- if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) {
- for (const auto& m_host: site.hosts) {
- if (m_host == host) {
- for (const auto& path: site.paths) {
- if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
- path_len = path.requested.size();
- try {
- result = path.params.at("plugin");
- } catch (const std::out_of_range& ex) {
- std::cout << "Out of range at Config::DocRoot(): target" << std::endl;
- std::rethrow_exception(std::current_exception());
- }
+ result = &path;
}
}
}
@@ -255,70 +219,18 @@ std::string Config::GetPlugin(const Socket& socket, const std::string& requested
}
}
- return result;
-}
-
-std::string Config::GetPluginPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const
-{
- // TODO: speed this up
- std::string host{requested_host};
- std::string result;
- size_t path_len{0};
-
- RemovePortFromHostname(host);
-
- for (const auto& site: m_sites) {
- if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) {
- for (const auto& m_host: site.hosts) {
- if (m_host == host) {
- for (const auto& path: site.paths) {
- if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
- path_len = path.requested.size();
- result = path.requested;
- }
- }
- }
- }
- }
- }
-
- return result;
-}
-
-std::string Config::GetRelativePath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const
-{
- // TODO: speed this up
- std::string host{requested_host};
- std::string result;
- size_t path_len{0};
-
- RemovePortFromHostname(host);
-
- for (const auto& site: m_sites) {
- if (std::find(socket.serve_sites.begin(), socket.serve_sites.end(), site.name) != socket.serve_sites.end()) {
- for (const auto& m_host: site.hosts) {
- if (m_host == host) {
- for (const auto& path: site.paths) {
- if (boost::starts_with(requested_path, path.requested) && path.requested.size() > path_len) {
- path_len = path.requested.size();
- result = requested_path.substr(path_len);
- }
- }
- }
- }
- }
- }
+ if (result == nullptr)
+ throw std::out_of_range("Path not found for "s + requested_host + " " + requested_path);
- if (result.size() > 0 && result[0] == '/')
- return result.substr(1);
- return result;
+ return *result;
}
bool Config::PluginIsConfigured(const std::string& name) const
{
for (const auto& site: m_sites) {
for (const auto& path: site.paths) {
- if (path.params.find("plugin") != path.params.end())
+ auto it{path.params.find("plugin")};
+ if (it != path.params.end() && it->second == name)
return true;
}
}
diff --git a/config.h b/config.h
index 61ad1ba..746b95c 100644
--- a/config.h
+++ b/config.h
@@ -11,7 +11,7 @@ namespace fs = std::filesystem;
struct Path
{
std::string requested; // the requested path
- // default entries: "plugin", "target"
+ // mandatory entries: "plugin", "target", others are optional
std::unordered_map<std::string, std::string> params; // what to serve, e.g. which filesystem path (target), and which plugin
};
@@ -68,17 +68,8 @@ class Config
// secondary calculation functions
//
- /// Root path in server's file system
- /// param[in] requested_host e.g. www.domain.com:8080 or www.domain.com
- std::string DocRoot(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const;
-
- /// Get name of plugin
- std::string GetPlugin(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const;
-
- /// requested_path = GetPluginPath() / GetRelativePath()
- std::string GetPluginPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const;
- std::string GetRelativePath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const;
-
+ const Path& GetPath(const Socket& socket, const std::string& requested_host, const std::string& requested_path) const;
+
// return true iff plugin "name" is mentioned in config
bool PluginIsConfigured(const std::string& name) const;
diff --git a/plugins/webbox/Makefile b/plugins/webbox/Makefile
index ed66b50..e16171d 100644
--- a/plugins/webbox/Makefile
+++ b/plugins/webbox/Makefile
@@ -57,6 +57,7 @@ LIBS+= \
endif
PROGSRC=\
+ file.cpp \
webbox.cpp
TESTSRC=\
diff --git a/plugins/webbox/file.cpp b/plugins/webbox/file.cpp
new file mode 100644
index 0000000..47ab8be
--- /dev/null
+++ b/plugins/webbox/file.cpp
@@ -0,0 +1,46 @@
+#include "file.h"
+
+#include <fstream>
+
+namespace fs = std::filesystem;
+
+using namespace std::string_literals;
+
+std::string File::getFile(const fs::path& filename)
+{
+ std::ifstream file(filename.string(), std::ios::in | std::ios::binary | std::ios::ate);
+
+ if (file.is_open()) {
+ std::ifstream::pos_type fileSize = file.tellg();
+ file.seekg(0, std::ios::beg);
+
+ std::string bytes(fileSize, ' ');
+ file.read(reinterpret_cast<char*>(bytes.data()), fileSize);
+
+ return bytes;
+
+ } else {
+ throw std::runtime_error("Opening "s + filename.string() + " for reading");
+ }
+}
+
+void File::setFile(const fs::path& filename, const std::string& s)
+{
+ File::setFile(filename, s.data(), s.size());
+}
+
+void File::setFile(const fs::path& filename, const char* data, size_t size)
+{
+ std::ofstream file(filename.string(), std::ios::out | std::ios::binary);
+ if (file.is_open()) {
+ file.write(data, size);
+ } else {
+ throw std::runtime_error("Opening "s + filename.string() + " for writing");
+ }
+}
+
+void File::setFile(const fs::path& filename, const std::vector<uint8_t>& data)
+{
+ File::setFile(filename, reinterpret_cast<const char*>(data.data()), data.size());
+}
+
diff --git a/plugins/webbox/file.h b/plugins/webbox/file.h
new file mode 100644
index 0000000..e7e4cf6
--- /dev/null
+++ b/plugins/webbox/file.h
@@ -0,0 +1,15 @@
+#pragma once
+
+#include <cstdint>
+#include <filesystem>
+#include <string>
+#include <vector>
+
+namespace File {
+
+std::string getFile(const std::filesystem::path& filename);
+void setFile(const std::filesystem::path& filename, const std::string& s);
+void setFile(const std::filesystem::path& filename, const char* data, size_t size);
+void setFile(const std::filesystem::path& filename, const std::vector<uint8_t>& data);
+
+}
diff --git a/plugins/webbox/webbox.cpp b/plugins/webbox/webbox.cpp
index 6166895..5d3f64c 100644
--- a/plugins/webbox/webbox.cpp
+++ b/plugins/webbox/webbox.cpp
@@ -1,101 +1,42 @@
#include "webbox.h"
+#include <boost/algorithm/string/replace.hpp>
+
#include <iostream>
+#include <string>
+#include <unordered_map>
using namespace std::string_literals;
-std::string webbox_plugin::name()
-{
- return "webbox";
-}
+namespace {
-webbox_plugin::webbox_plugin()
-{
- //std::cout << "Plugin constructor" << std::endl;
-}
-webbox_plugin::~webbox_plugin()
-{
- //std::cout << "Plugin destructor" << std::endl;
-}
+ unordered_map<std::string> status_map {
+ { "400", "Bad Request"},
+ { "403", "Forbidden" },
+ { "404", "Not Found" },
+ { "505", "Internal Server Error" },
+ };
-std::string webbox_plugin::generate_page(
- 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
-)
+// 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)
{
- return "Webbox";
-}
+ SetResponseHeader("status", status);
+ SetResponseHeader("content_type", "text/html");
-#if 0
-#include <fcgiapp.h>
-
-#include <QString>
-#include <QStringList>
-#include <QHash>
-#include <QDir>
-#include <QFileInfo>
-#include <QXmlStreamReader>
-#include <QXmlStreamWriter>
-#include <QDateTime>
-#include <QProcess>
-#include <QTemporaryFile>
-#include <QUrlQuery>
-#include <QPair>
-
-#define BUFSIZE 1000000
-
-// XML special characters:
-// < : &lt;
-// > : &gt;
-// & : &amp;
-// " : &quot;
-// ' : &apos;
-//
-// here:replace &
-QString escapeXML(QString s) {
- s.replace("&", "&amp;");
- return s;
-}
+ auto it{status_map.find(status)};
+ std::string description{"(Unknown)"};
+ if (it != status_map.end())
+ description = it->second;
-// revert escapeXML();
-QString unescapeXML(QString s) {
- s.replace("&amp;", "&");
- return s;
+ return "<html><body><h1>"s + status + " "s + description + "</h1><p>"s + message + "</p></body></html>";
}
-// supported httpStatusCode:
-// 400 Bad Request
-// 403 Forbidden
-// 404 Not Found
-// 500 Internal Server Error
-// message: additional message
-QString httpError(int httpStatusCode, QString message) {
- QString description;
-
- switch(httpStatusCode) {
- case 400:
- description = "Bad Request";
- break;
- case 403:
- description = "Forbidden";
- break;
- case 404:
- description = "Not Found";
- break;
- case 500:
- description = "Internal Server Error";
- break;
- default:
- message = QString("Bad error code: %1, message: %2").arg(httpStatusCode).arg(message);
- httpStatusCode = 500;
- description = "Internal Server Error";
- }
- return QString("Status: %1 %2\r\nContent-Type: text/html\r\n\r\n<html><body><h1>%1 %2</h1><p>%3</p></body></html>\r\n").arg(httpStatusCode).arg(description).arg(message);
-}
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
@@ -721,7 +662,7 @@ class DownloadCommand: public GetCommand {
FCGX_PutS("Content-Type: application/octet-stream\r\n\r\n", p.request.out);
while (!file.atEnd()) {
- QByteArray ba = file.read(BUFSIZE);
+ QByteArray ba = File::getFile();
FCGX_PutStr(ba.data(), ba.size(), p.request.out);
}
} else {
@@ -834,4 +775,29 @@ int main(int argc, char* argv[]) {
return 0;
}
-#endif
+
+} // anonymous namespace
+
+std::string webbox_plugin::name()
+{
+ return "webbox";
+}
+
+webbox_plugin::webbox_plugin()
+{
+ //std::cout << "Plugin constructor" << std::endl;
+}
+
+webbox_plugin::~webbox_plugin()
+{
+ //std::cout << "Plugin destructor" << std::endl;
+}
+
+std::string webbox_plugin::generate_page(
+ 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
+)
+{
+ return "Webbox";
+}
diff --git a/response.cpp b/response.cpp
index 4d6aaaf..1ee8932 100644
--- a/response.cpp
+++ b/response.cpp
@@ -1,14 +1,59 @@
#include "response.h"
#include "file.h"
+#include <boost/algorithm/string/predicate.hpp>
+
#include <functional>
#include <iostream>
+#include <string>
#include <unordered_map>
using namespace std::placeholders;
namespace {
+class RequestContext
+{
+private:
+ request_type& m_req;
+ std::string m_host;
+ std::string m_target;
+ Server& m_server;
+ const Path& m_path;
+
+public:
+ RequestContext(request_type& req, Server& server)
+ : m_req(req)
+ , m_host(req["host"])
+ , m_target(req.target())
+ , m_server(server)
+ , m_path(server.GetConfig().GetPath(server.GetSocket(), m_host, m_target))
+ {
+ }
+
+ const Path& GetPath() const {return m_path;}
+
+ std::string GetPluginName() const {return m_path.params.at("plugin");} // can throw std::out_of_range
+
+ std::string GetPluginPath() const {return m_path.requested;}
+
+ std::string GetDocRoot() const {return m_path.params.at("target");} // can throw std::out_of_range
+
+ std::string GetRelativePath() const { // can throw std::runtime_error
+ if (!boost::starts_with(m_target, m_path.requested))
+ throw std::runtime_error("Mismatch of target ("s + m_target + ") and plugin path(" + m_path.requested + ")"s);
+ return m_target.substr(m_path.requested.size());
+ }
+
+ std::string GetPluginParam(const std::string& key) const {return m_path.params.at(key);} // can throw std::out_of_range
+
+ plugin_type GetPlugin() const {return m_server.GetPlugin(m_path.params.at("plugin"));}; // can throw std::out_of_range
+
+ request_type& GetReq() const {return m_req;}
+
+ std::string GetTarget() const {return m_target;}
+};
+
std::string extend_index_html(std::string path)
{
if (path.size() && path.back() == '/')
@@ -23,39 +68,47 @@ std::string GetServerParam(const std::string& key, Server& server)
throw std::runtime_error("Unsupported server param: "s + key);
}
-std::unordered_map<std::string, std::function<std::string(request_type&, Server&)>> GetRequestParamFunctions{
+std::unordered_map<std::string, std::function<std::string(RequestContext&)>> GetRequestParamFunctions{
// following are the supported fields:
- {"target", [](request_type& req, Server& server){return std::string{req.target()};}},
-
- {"rel_target", [](request_type& req, Server& server){
- std::string host{req["host"]};
- std::string target{req.target()};
- return server.GetConfig().GetRelativePath(server.GetSocket(), host, target);
- }},
-
- {"doc_root", [](request_type& req, Server& server) {
- std::string host{req["host"]};
- std::string target{req.target()};
- return server.GetConfig().DocRoot(server.GetSocket(), host, target);
- }},
-
- {"method", [](request_type& req, Server& server){
- if (req.method() == http::verb::get)
- return "GET";
- else if (req.method() == http::verb::post)
- return "POST";
- else if (req.method() == http::verb::head)
- return "HEAD";
- else
- return "";
- }},
+ {"target", [](RequestContext& req_ctx) {return req_ctx.GetTarget();}},
+
+ {"rel_target", [](RequestContext& req_ctx) {return req_ctx.GetRelativePath();}},
+
+ {"doc_root", [](RequestContext& req_ctx) { return req_ctx.GetDocRoot();}},
+
+ {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }},
+
+ {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}},
};
-std::string GetRequestParam(const std::string& key, request_type& req, Server& server)
+std::string GetRequestParam(const std::string& key, RequestContext& req_ctx)
{
- auto it = GetRequestParamFunctions.find(key);
- if (it != GetRequestParamFunctions.end())
- return it->second(req, server);
+ // first, look up functions from GetRequestParamFunctions
+ {
+ auto it = GetRequestParamFunctions.find(key);
+ if (it != GetRequestParamFunctions.end())
+ return it->second(req_ctx);
+ }
+
+ // second, look up plugin parameters
+ {
+ try {
+ return req_ctx.GetPluginParam(key);
+ } catch(const std::out_of_range& ex) {
+ // not found
+ };
+ }
+
+ // third, look up req parameters
+ {
+ try {
+ return std::string{req_ctx.GetReq()[key]};
+ } catch(...){
+ // not found
+ }
+ }
+
+ // otherwise: error
throw std::runtime_error("Unsupported request param: "s + key);
}
@@ -129,27 +182,27 @@ response_type generate_response(request_type& req, Server& server)
res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target()))));
res.keep_alive(req.keep_alive());
- std::string host{req["host"]};
- std::string target{req.target()};
- std::string plugin_name { server.GetConfig().GetPlugin(server.GetSocket(), host, target)};
- if (plugin_name == "") {
- return HttpStatus("400", "Bad request: Host "s + host + ":"s + target + " unknown"s, res);
+ try {
+ RequestContext req_ctx{req, server}; // can throw std::out_of_range
+
+ plugin_type plugin{req_ctx.GetPlugin()};
+
+ auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))};
+ auto GetRequestParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetRequestParam, _1, std::ref(req_ctx)))};
+ auto SetResponseHeaderFunction{std::function<void(const std::string& key, const std::string& value)>(std::bind(SetResponseHeader, _1, _2, std::ref(res)))};
+
+ std::string res_data { plugin->generate_page(GetServerParamFunction, GetRequestParamFunction, SetResponseHeaderFunction)};
+ if (req.method() == http::verb::head) {
+ res.content_length(res_data.size());
+ } else {
+ res.body() = res_data;
+ res.prepare_payload();
+ }
+
+ 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);
}
- plugin_type plugin{server.GetPlugin(plugin_name)};
-
- auto GetServerParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetServerParam, _1, std::ref(server)))};
- auto GetRequestParamFunction {std::function<std::string(const std::string& key)>(std::bind(GetRequestParam, _1, std::ref(req), std::ref(server)))};
- auto SetResponseHeaderFunction{std::function<void(const std::string& key, const std::string& value)>(std::bind(SetResponseHeader, _1, _2, std::ref(res)))};
-
- std::string res_data { plugin->generate_page(GetServerParamFunction, GetRequestParamFunction, SetResponseHeaderFunction)};
- if (req.method() == http::verb::head) {
- res.content_length(res_data.size());
- } else {
- res.body() = res_data;
- res.prepare_payload();
- }
-
- return res;
}
diff --git a/server.h b/server.h
index c2b6d1c..ec674b7 100644
--- a/server.h
+++ b/server.h
@@ -9,6 +9,7 @@ using namespace std::string_literals;
static const std::string VersionString{ "Webserver "s + std::string{VERSION} };
+// Base class for HTTP and HTTPS classes
class Server
{
protected: