summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
-rw-r--r--Makefile4
-rw-r--r--README.txt2
-rw-r--r--debian/README.Debian2
-rw-r--r--debian/control5
-rw-r--r--plugins/cgi/Makefile124
-rw-r--r--plugins/cgi/cgi.cpp294
-rw-r--r--plugins/cgi/cgi.h21
-rw-r--r--plugins/static-files/Makefile2
-rw-r--r--plugins/webbox/Makefile2
-rw-r--r--response.cpp58
-rw-r--r--server.cpp2
-rw-r--r--server.h4
-rw-r--r--webserver.conf5
13 files changed, 506 insertions, 19 deletions
diff --git a/Makefile b/Makefile
index c7834d0..d45bb7f 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 cgi # weblog fcgi
CXX=clang++-10
@@ -77,7 +77,7 @@ TESTSRC=\
SRC=$(PROGSRC) webserver.cpp
build: $(PROJECTNAME) test-$(PROJECTNAME)
- set -e ; for i in $(PLUGINS) ; do make -C plugins/$$i ; done
+ +set -e ; for i in $(PLUGINS) ; do make -C plugins/$$i ; done
./test-$(PROJECTNAME)
all: build
diff --git a/README.txt b/README.txt
index 74b4875..439faab 100644
--- a/README.txt
+++ b/README.txt
@@ -2,8 +2,10 @@ Features
--------
* Support for IPv4 and IPv6
+* Support for HTTP and HTTPS via OpenSSL
* Virtual servers
* Plugin interface
+* CGI interface
Configuration
diff --git a/debian/README.Debian b/debian/README.Debian
index 23ff42c..35011f3 100644
--- a/debian/README.Debian
+++ b/debian/README.Debian
@@ -1,7 +1,7 @@
webserver for Debian
====================
-This package is the Debian version ofr webserver.
+This package is the Debian version of webserver.
Contact
diff --git a/debian/control b/debian/control
index afe971d..d94b772 100644
--- a/debian/control
+++ b/debian/control
@@ -15,5 +15,6 @@ Description: Web server
Webserver is a web server
.
Features:
- - IPv4 / IPv6
- - C++-Plugin-Interface
+ - IPv4 / IPv6
+ - C++-Plugin-Interface
+ - HTTP and HTTPs via OpenSSL
diff --git a/plugins/cgi/Makefile b/plugins/cgi/Makefile
new file mode 100644
index 0000000..b3e8548
--- /dev/null
+++ b/plugins/cgi/Makefile
@@ -0,0 +1,124 @@
+DISTROS=debian10
+VERSION=$(shell dpkg-parsechangelog --show-field Version)
+PROJECTNAME=cgi
+
+CXX=clang++-10
+
+ifeq ($(shell which $(CXX)),)
+CXX=clang++
+endif
+
+ifeq ($(shell which $(CXX)),)
+CXX=g++-9
+endif
+
+ifeq ($(CXXFLAGS),)
+#CXXFLAGS=-O2 -DNDEBUG
+CXXFLAGS=-O0 -g -D_DEBUG
+endif
+# -fprofile-instr-generate -fcoverage-mapping
+# gcc:--coverage
+
+CXXFLAGS+= -Wall -I.
+
+CXXFLAGS+= -pthread -fvisibility=hidden -fPIC
+ifeq ($(CXX),clang++-10)
+CXXFLAGS+=-std=c++20 #-stdlib=libc++
+else
+CXXFLAGS+=-std=c++17
+endif
+
+CXXTESTFLAGS=-Igoogletest/include -Igooglemock/include/ -Igoogletest -Igooglemock
+
+LIBS=\
+-lboost_context \
+-lboost_coroutine \
+-lboost_program_options \
+-lboost_system \
+-lboost_thread \
+-lboost_filesystem \
+-lboost_regex \
+-lpthread \
+-lssl -lcrypto \
+-ldl
+
+ifeq ($(CXX),clang++-10)
+LIBS+= \
+-fuse-ld=lld-10 \
+-lstdc++
+#-lc++ \
+#-lc++abi
+#-lc++fs
+#-lstdc++fs
+else
+LIBS+= \
+-lstdc++ \
+-lstdc++fs
+endif
+
+PROGSRC=\
+ cgi.cpp
+
+TESTSRC=\
+ test-webserver.cpp \
+ googlemock/src/gmock-all.cpp \
+ googletest/src/gtest-all.cpp \
+ $(PROGSRC)
+
+SRC=$(PROGSRC)
+
+all: $(PROJECTNAME).so
+
+# testsuite ----------------------------------------------
+test-$(PROJECTNAME): $(TESTSRC:.cpp=.o)
+ $(CXX) $(CXXFLAGS) $^ $(LIBS) -o $@
+
+$(PROJECTNAME).so: $(SRC:.cpp=.o)
+ $(CXX) -shared $(CXXFLAGS) $^ $(LIBS) -o $@
+
+dep: $(TESTSRC:.cpp=.d)
+
+%.d: %.cpp
+ $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -MM -MP -MF $@ -c $<
+
+%.o: %.cpp %.d
+ $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
+
+googletest/src/%.o: googletest/src/%.cc
+ $(CXX) $(CXXFLAGS) $(CXXTESTFLAGS) -c $< -o $@
+
+# dependencies
+
+ADD_DEP=Makefile
+
+install:
+ mkdir -p $(DESTDIR)/usr/lib/webserver/plugins
+ cp $(PROJECTNAME).so $(DESTDIR)/usr/lib/webserver/plugins
+
+# misc ---------------------------------------------------
+deb:
+ # build binary deb package
+ dpkg-buildpackage -us -uc -rfakeroot
+
+deb-src:
+ dpkg-source -b .
+
+$(DISTROS): deb-src
+ sudo pbuilder build --basetgz /var/cache/pbuilder/$@.tgz --buildresult result/$@ ../webserver_$(VERSION).dsc ; \
+
+debs: $(DISTROS)
+
+clean:
+ -rm -f test-$(PROJECTNAME) $(PROJECTNAME)
+ -find . -name '*.o' -o -name '*.so' -o -name '*.d' -o -name '*.gcno' -o -name '*.gcda' | xargs rm -f
+
+zip: clean
+ -rm -f ../$(PROJECTNAME).zip
+ zip -r ../$(PROJECTNAME).zip *
+ ls -l ../$(PROJECTNAME).zip
+
+
+
+.PHONY: clean all zip install deb deb-src debs all $(DISTROS)
+
+-include $(wildcard $(SRC:.cpp=.d))
diff --git a/plugins/cgi/cgi.cpp b/plugins/cgi/cgi.cpp
new file mode 100644
index 0000000..5921e98
--- /dev/null
+++ b/plugins/cgi/cgi.cpp
@@ -0,0 +1,294 @@
+#include "cgi.h"
+
+#include <boost/algorithm/string/predicate.hpp>
+#include <boost/coroutine2/coroutine.hpp>
+#include <boost/process.hpp>
+
+#include <filesystem>
+#include <fstream>
+#include <iostream>
+#include <string>
+#include <unordered_map>
+
+using namespace std::string_literals;
+namespace bp = boost::process;
+namespace fs = std::filesystem;
+
+namespace {
+
+ const std::string gateway_interface{"CGI/1.1"};
+
+ struct CGIContext
+ {
+ 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
+ fs::path& path;
+
+ CGIContext(std::function<std::string(const std::string& key)>& p_GetServerParam,
+ std::function<std::string(const std::string& key)>& p_GetRequestParam,
+ std::function<void(const std::string& key, const std::string& value)>& p_SetResponseHeader,
+ fs::path& p_path
+ )
+ : GetServerParam(p_GetServerParam)
+ , GetRequestParam(p_GetRequestParam)
+ , SetResponseHeader(p_SetResponseHeader)
+ , path(p_path)
+ {
+ }
+ };
+
+ // Return a reasonable mime type based on the extension of a file.
+ std::string mime_type(fs::path path)
+ {
+ using boost::algorithm::iequals;
+ auto const ext = [&path]
+ {
+ size_t pos = path.string().rfind(".");
+ if (pos == std::string::npos)
+ return std::string{};
+ return path.string().substr(pos);
+ }();
+ if(iequals(ext, ".htm")) return "text/html"; // TODO: unordered_map
+ if(iequals(ext, ".html")) return "text/html";
+ if(iequals(ext, ".php")) return "text/html";
+ if(iequals(ext, ".css")) return "text/css";
+ if(iequals(ext, ".txt")) return "text/plain";
+ if(iequals(ext, ".js")) return "application/javascript";
+ if(iequals(ext, ".json")) return "application/json";
+ if(iequals(ext, ".xml")) return "application/xml";
+ if(iequals(ext, ".swf")) return "application/x-shockwave-flash";
+ if(iequals(ext, ".flv")) return "video/x-flv";
+ if(iequals(ext, ".png")) return "image/png";
+ if(iequals(ext, ".jpe")) return "image/jpeg";
+ if(iequals(ext, ".jpeg")) return "image/jpeg";
+ if(iequals(ext, ".jpg")) return "image/jpeg";
+ if(iequals(ext, ".gif")) return "image/gif";
+ if(iequals(ext, ".bmp")) return "image/bmp";
+ if(iequals(ext, ".ico")) return "image/vnd.microsoft.icon";
+ if(iequals(ext, ".tiff")) return "image/tiff";
+ if(iequals(ext, ".tif")) return "image/tiff";
+ if(iequals(ext, ".svg")) return "image/svg+xml";
+ if(iequals(ext, ".svgz")) return "image/svg+xml";
+ return "application/text";
+ }
+
+ typedef boost::coroutines2::coroutine<std::string> coro_t;
+
+ // returns true iff std::string is empty or contains newline
+ bool isEmpty(const std::string& s)
+ {
+ return s.empty() || s == "\r" || s == "\n"s || s == "\r\n"s;
+ }
+
+ void trimLinebreak(std::string& s)
+ {
+ size_t pos = s.find_last_not_of("\r\n");
+ if (pos != s.npos)
+ s = s.substr(0, pos + 1);
+ }
+
+ std::unordered_map<std::string, std::function<void(std::string&, CGIContext&)>> headerMap {
+ { "Content-Type", [](std::string& v, CGIContext& c){ c.SetResponseHeader("content_type", v); } }
+ };
+
+ void handleHeader(const std::string& s, CGIContext& context)
+ {
+ size_t pos = s.find(": ");
+ if (pos == s.npos)
+ return;
+
+ std::string key {s.substr(0, pos)};
+ std::string value {s.substr(pos + 2)};
+
+
+ auto it {headerMap.find(key)};
+ if (it == headerMap.end())
+ std::cout << "Warning: Unhandled CGI header: " << s << std::endl;
+ else
+ it->second(value, context);
+ }
+
+ void setCGIEnvironment(bp::environment& env, CGIContext& c)
+ {
+ std::string authorization {c.GetRequestParam("authorization")};
+ if (!authorization.empty())
+ env["AUTH_TYPE"] = c.GetRequestParam("authorization");
+
+ env["CONTENT_LENGTH"] = c.GetRequestParam("content_length");
+ env["CONTENT_TYPE"] = c.GetRequestParam("content_type");
+ env["GATEWAY_INTERFACE"] = gateway_interface;
+
+ std::string target {c.GetRequestParam("target")};
+ size_t query_pos {target.find("?")};
+ std::string query;
+ if (query_pos != target.npos) {
+ query = target.substr(0, query_pos);
+ target = target.substr(query_pos + 1);
+ }
+
+ env["PATH_INFO"] = target;
+ env["PATH_TRANSLATED"] = c.path.string();
+ env["QUERY_STRING"] = query;
+ env["REMOTE_ADDR"] = "";
+ env["REMOTE_HOST"] = "";
+ env["REMOTE_IDENT"] = "";
+ env["REMOTE_USER"] = "";
+ env["REQUEST_METHOD"] = c.GetRequestParam("method");
+ env["SCRIPT_NAME"] = c.GetRequestParam("rel_target");
+ env["SERVER_NAME"] = c.GetRequestParam("host");
+ env["SERVER_PORT"] = c.GetServerParam("port");
+ env["SERVER_PROTOCOL"] = c.GetRequestParam("http_version");
+ env["SERVER_SOFTWARE"] = c.GetServerParam("version");
+
+ env["HTTP_ACCEPT"] = c.GetRequestParam("http_accept");
+ env["HTTP_ACCEPT_CHARSET"] = c.GetRequestParam("http_accept_charset");
+ env["HTTP_ACCEPT_ENCODING"] = c.GetRequestParam("http_accept_encoding");
+ env["HTTP_ACCEPT_LANGUAGE"] = c.GetRequestParam("http_accept_language");
+ env["HTTP_CONNECTION"] = c.GetRequestParam("http_connection");
+ env["HTTP_HOST"] = c.GetRequestParam("http_host");
+ env["HTTP_USER_AGENT"] = c.GetRequestParam("http_user_agent");
+ }
+
+ std::string executeFile(const fs::path& filename, CGIContext& context)
+ {
+ bp::opstream is_in;
+ bp::ipstream is_out;
+
+ //std::cout << "Executing " << filename << std::endl;
+
+ bp::environment env {boost::this_process::environment()};
+ setCGIEnvironment(env, context);
+
+ bp::child child(filename.string(), env, (bp::std_out & bp::std_err) > is_out, bp::std_in < is_in);
+
+ is_in << context.GetRequestParam("body");
+
+ std::string output;
+ std::string line;
+
+ // TODO: C++20 coroutine
+ coro_t::push_type processLine( [&](coro_t::pull_type& in){
+ std::string line;
+ // read header lines
+ while (in && !isEmpty(line = in.get())) {
+ trimLinebreak(line);
+ handleHeader(line, context);
+ in();
+ }
+
+ // read empty line
+ if (!isEmpty(line))
+ throw std::runtime_error("Missing empty line between CGI header and body");
+ if (in)
+ in();
+
+ // read remainder
+ while (in) {
+ line = in.get();
+ output += line + '\n';
+ in();
+ }
+
+ throw std::runtime_error("Input missing on processing CGI body");
+ });
+
+ while (child.running() && std::getline(is_out, line)) {
+ processLine(line);
+ }
+
+ child.wait();
+
+ return output;
+ }
+
+ // 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)
+ {
+ SetResponseHeader("status", status);
+ SetResponseHeader("content_type", "text/html");
+ return status + " " + message;
+ }
+
+} // anonymous namespace
+
+std::string cgi_plugin::name()
+{
+ return "cgi";
+}
+
+cgi_plugin::cgi_plugin()
+{
+ //std::cout << "Plugin constructor" << std::endl;
+}
+
+cgi_plugin::~cgi_plugin()
+{
+ //std::cout << "Plugin destructor" << std::endl;
+}
+
+std::string cgi_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
+)
+{
+ try {
+ // Make sure we can handle the method
+ std::string method {GetRequestParam("method")};
+ if (method != "GET" && method != "HEAD")
+ return HttpStatus("400", "Unknown HTTP method", SetResponseHeader);
+
+ // Request path must not contain "..".
+ std::string rel_target{GetRequestParam("rel_target")};
+ size_t query_pos{rel_target.find("?")};
+ if (query_pos != rel_target.npos)
+ rel_target = rel_target.substr(0, query_pos);
+
+ std::string target{GetRequestParam("target")};
+ if (rel_target.find("..") != std::string::npos) {
+ return HttpStatus("400", "Illegal request: "s + target, SetResponseHeader);
+ }
+
+ // Build the path to the requested file
+ std::string doc_root{GetRequestParam("doc_root")};
+ fs::path path {fs::path{doc_root} / rel_target};
+ if (target.size() && target.back() != '/' && fs::is_directory(path)) {
+ std::string location{GetRequestParam("location") + "/"s};
+ SetResponseHeader("location", location);
+ return HttpStatus("301", "Correcting directory path", SetResponseHeader);
+ }
+
+ try {
+ if (!fs::is_regular_file(path)) {
+ return HttpStatus("500", "Bad Script: "s + rel_target, SetResponseHeader);
+ }
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Bad file access: "s + rel_target, SetResponseHeader);
+ }
+
+ try {
+ if ((fs::status(path).permissions() & fs::perms::others_exec) == fs::perms::none) {
+ return HttpStatus("500", "Script not executable: "s + rel_target, SetResponseHeader);
+ }
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Bad file status access: "s + rel_target, SetResponseHeader);
+ }
+
+ SetResponseHeader("content_type", mime_type(path));
+
+ CGIContext context(GetServerParam, GetRequestParam, SetResponseHeader, path);
+
+ try {
+ return executeFile(path, context);
+ } catch (const std::runtime_error& ex) {
+ return HttpStatus("404", "Not found: "s + GetRequestParam("target"), SetResponseHeader);
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Internal Server Error: "s + ex.what(), SetResponseHeader);
+ }
+
+ } catch (const std::exception& ex) {
+ return HttpStatus("500", "Unknown Error: "s + ex.what(), SetResponseHeader);
+ }
+}
+
diff --git a/plugins/cgi/cgi.h b/plugins/cgi/cgi.h
new file mode 100644
index 0000000..467a6c4
--- /dev/null
+++ b/plugins/cgi/cgi.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include "../../plugin_interface.h"
+
+class cgi_plugin: public webserver_plugin_interface
+{
+public:
+ cgi_plugin();
+ ~cgi_plugin();
+
+ std::string name();
+ std::string 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
+ );
+
+};
+
+extern "C" BOOST_SYMBOL_EXPORT cgi_plugin webserver_plugin;
+cgi_plugin webserver_plugin;
diff --git a/plugins/static-files/Makefile b/plugins/static-files/Makefile
index 6577785..fdc0896 100644
--- a/plugins/static-files/Makefile
+++ b/plugins/static-files/Makefile
@@ -25,7 +25,7 @@ CXXFLAGS+= -pthread -fvisibility=hidden -fPIC
ifeq ($(CXX),clang++-10)
CXXFLAGS+=-std=c++20 #-stdlib=libc++
else
-CXXFLAGS+=-std=c++2a
+CXXFLAGS+=-std=c++17
endif
CXXTESTFLAGS=-Igoogletest/include -Igooglemock/include/ -Igoogletest -Igooglemock
diff --git a/plugins/webbox/Makefile b/plugins/webbox/Makefile
index 1850018..54c954c 100644
--- a/plugins/webbox/Makefile
+++ b/plugins/webbox/Makefile
@@ -25,7 +25,7 @@ CXXFLAGS+= -pthread -fvisibility=hidden -fPIC
ifeq ($(CXX),clang++-10)
CXXFLAGS+=-std=c++20 #-stdlib=libc++
else
-CXXFLAGS+=-std=c++2a
+CXXFLAGS+=-std=c++17
endif
CXXTESTFLAGS=-Igoogletest/include -Igooglemock/include/ -Igoogletest -Igooglemock
diff --git a/response.cpp b/response.cpp
index 0c619a2..ca7a58d 100644
--- a/response.cpp
+++ b/response.cpp
@@ -59,6 +59,10 @@ public:
std::string GetTarget() const {return m_target;}
std::string GetHost() const {return m_host;}
+
+ Server& GetServer() const {return m_server; }
+
+ const Socket& GetSocket() const {return m_server.GetSocket(); }
};
std::string extend_index_html(std::string path)
@@ -68,30 +72,64 @@ std::string extend_index_html(std::string path)
return path;
}
+std::unordered_map<std::string, std::function<std::string(Server&)>> GetServerParamFunctions{
+ // following are the supported fields:
+ {"version", [](Server& server) { return Server::VersionString; }},
+ {"address", [](Server& server) { return server.GetSocket().address; }},
+ {"port", [](Server& server) { return server.GetSocket().port; }},
+};
+
std::string GetServerParam(const std::string& key, Server& server)
{
- // following are the supported fields:
- // ...
+ auto it = GetServerParamFunctions.find(key);
+ if (it != GetServerParamFunctions.end())
+ return it->second(server);
+
throw std::runtime_error("Unsupported server param: "s + key);
}
std::unordered_map<std::string, std::function<std::string(RequestContext&)>> GetRequestParamFunctions{
// following are the supported fields:
- {"target", [](RequestContext& req_ctx) {return req_ctx.GetTarget();}},
+ {"authorization", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::authorization]}; }},
+
+ {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }},
- {"rel_target", [](RequestContext& req_ctx) {return req_ctx.GetRelativePath();}},
+ {"content_length", [](RequestContext& req_ctx) { return std::to_string(req_ctx.GetReq().body().size()); }},
+
+ {"content_type", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::content_type]}; }},
{"doc_root", [](RequestContext& req_ctx) { return req_ctx.GetDocRoot();}},
+
+ {"host", [](RequestContext& req_ctx) { return req_ctx.GetHost();}},
- {"body", [](RequestContext& req_ctx) { return req_ctx.GetReq().body(); }},
+ {"http_accept", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::accept]};}},
- {"content_length", [](RequestContext& req_ctx) { return std::to_string(req_ctx.GetReq().body().size()); }},
+ {"http_accept_charset", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::accept_charset]};}},
- {"content_type", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::content_type]}; }},
+ {"http_accept_encoding", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::accept_encoding]};}},
- {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}},
+ {"http_accept_language", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::accept_language]};}},
+
+ {"http_connection", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::connection]};}},
+
+ {"http_host", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::host]};}},
+
+ {"http_user_agent", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq()[http::field::user_agent]};}},
+ {"http_version", [](RequestContext& req_ctx) {
+ unsigned version {req_ctx.GetReq().version()};
+ unsigned major{version / 10};
+ unsigned minor{version % 10};
+ return "HTTP/"s + std::to_string(major) + "."s + std::to_string(minor);
+ }},
+
{"location", [](RequestContext& req_ctx) { return req_ctx.GetTarget(); }},
+
+ {"method", [](RequestContext& req_ctx) { return std::string{req_ctx.GetReq().method_string()};}},
+
+ {"rel_target", [](RequestContext& req_ctx) {return req_ctx.GetRelativePath();}},
+
+ {"target", [](RequestContext& req_ctx) {return req_ctx.GetTarget();}},
};
std::string GetRequestParam(const std::string& key, RequestContext& req_ctx)
@@ -187,7 +225,7 @@ response_type HttpStatus(std::string status, std::string message, response_type&
res.set(http::field::content_type, "text/html");
if (res.result_int() == 401)
res.set(http::field::www_authenticate, "Basic realm=\"Webbox Login\"");
- res.body() = "<html><body><h1>"s + VersionString + " Error</h1><p>"s + status + " "s + message + "</p></body></html>"s;
+ res.body() = "<html><body><h1>"s + Server::VersionString + " Error</h1><p>"s + status + " "s + message + "</p></body></html>"s;
res.prepare_payload();
return res;
@@ -198,7 +236,7 @@ response_type HttpStatus(std::string status, std::string message, response_type&
response_type generate_response(request_type& req, Server& server)
{
response_type res{http::status::ok, req.version()};
- res.set(http::field::server, VersionString);
+ res.set(http::field::server, Server::VersionString);
res.set(http::field::content_type, mime_type(extend_index_html(std::string(req.target()))));
res.keep_alive(req.keep_alive());
diff --git a/server.cpp b/server.cpp
index 395be7d..81ee454 100644
--- a/server.cpp
+++ b/server.cpp
@@ -35,6 +35,8 @@ namespace net = boost::asio; // from <boost/asio.hpp>
namespace ssl = boost::asio::ssl; // from <boost/asio/ssl.hpp>
using tcp = boost::asio::ip::tcp; // from <boost/asio/ip/tcp.hpp>
+const std::string Server::VersionString{ "Webserver "s + std::string{VERSION} };
+
Server::Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& plugins)
: m_config(config)
, m_ioc(ioc)
diff --git a/server.h b/server.h
index ec674b7..11a8826 100644
--- a/server.h
+++ b/server.h
@@ -7,8 +7,6 @@
using namespace std::string_literals;
-static const std::string VersionString{ "Webserver "s + std::string{VERSION} };
-
// Base class for HTTP and HTTPS classes
class Server
{
@@ -19,6 +17,8 @@ protected:
plugins_container_type& m_plugins;
public:
+ static const std::string VersionString;
+
Server(Config& config, boost::asio::io_context& ioc, const Socket& socket, plugins_container_type& m_plugins);
virtual ~Server();
diff --git a/webserver.conf b/webserver.conf
index a617102..1a5f5c8 100644
--- a/webserver.conf
+++ b/webserver.conf
@@ -32,6 +32,11 @@
<WEBBOX_READONLY>0</WEBBOX_READONLY>
<auth login="abc" password="def"/>
</path>
+ <path requested="/cgi-bin">
+ <plugin>cgi</plugin>
+ <target>/home/ernie/code/webserver/cgi-bin</target>
+ <auth login="abc" password="def"/>
+ </path>
<certpath>/home/ernie/code/webserver/fullchain.pem</certpath>
<keypath>/home/ernie/code/webserver/privkey.pem</keypath>
</site>