summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-11 15:27:23 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-11 15:27:23 +0100
commite679b0241662ea7c1910b9fc02ed0cb8f59b0de6 (patch)
tree3ee941465c80f9331de48cb3230515fb27460305
parent478e9f340fe303f3171f4184f494947bf39e3dbf (diff)
Test CGI
-rw-r--r--TODO8
-rw-r--r--config.cpp1
-rw-r--r--debian/changelog3
-rw-r--r--debian/webserver.docs1
-rw-r--r--debian/webserver.example-cgi.conf26
-rw-r--r--plugins/cgi/cgi.cpp4
-rw-r--r--plugins/fcgi/fcgi.cpp2
-rw-r--r--plugins/static-files/static-files.cpp2
-rw-r--r--plugins/websocket/websocket.cpp2
-rw-r--r--tests/test-webserver.cpp165
10 files changed, 203 insertions, 11 deletions
diff --git a/TODO b/TODO
index 65d9195..ce61789 100644
--- a/TODO
+++ b/TODO
@@ -1,7 +1,13 @@
+example conf files:
+- websockets
+- php
+test:
+- bad config
+- FCGI
Big file bug
- dynamic plugin interface (file buffer, ...)
Websockets
-- forward subprotocol
+- http+https
http+https=CRTP
FastCGI from command line
diff --git a/config.cpp b/config.cpp
index 2b37f91..49a78d9 100644
--- a/config.cpp
+++ b/config.cpp
@@ -173,6 +173,7 @@ void Config::readConfigfile(const std::filesystem::path& filename)
void Config::expand_socket_sites()
{
+ // if no serving site is defined for a socket, serve all sites there
for (auto& socket: m_sockets) {
if (socket.serve_sites.empty()) {
for (const auto& site: m_sites) {
diff --git a/debian/changelog b/debian/changelog
index ea55a13..ce7835f 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,6 +1,7 @@
webserver (1.18~pre1) UNRELEASED; urgency=medium
- *
+ * Added websockets support (configurable forwarding proxy)
+ * CGI bugfix: Executable execute bits check
-- Roland Reichwein <mail@reichwein.it> Sun, 08 Jan 2023 15:26:48 +0100
diff --git a/debian/webserver.docs b/debian/webserver.docs
index 71dfd5b..0b7c406 100644
--- a/debian/webserver.docs
+++ b/debian/webserver.docs
@@ -1 +1,2 @@
README.txt
+webserver.example-cgi.conf
diff --git a/debian/webserver.example-cgi.conf b/debian/webserver.example-cgi.conf
new file mode 100644
index 0000000..99faa52
--- /dev/null
+++ b/debian/webserver.example-cgi.conf
@@ -0,0 +1,26 @@
+<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>/var/lib/webserver/stats.db</statisticspath>
+ <plugin-directory>/usr/lib/webserver/plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>localhost</host>
+ <host>[::1]</host>
+ <path requested="/cgi-bin">
+ <plugin>cgi</plugin>
+ <target>/var/lib/webserver/cgi-bin</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>
diff --git a/plugins/cgi/cgi.cpp b/plugins/cgi/cgi.cpp
index 23c9bc4..0ad5e2b 100644
--- a/plugins/cgi/cgi.cpp
+++ b/plugins/cgi/cgi.cpp
@@ -196,7 +196,7 @@ namespace {
std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)
{
SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
+ SetResponseHeader("content_type", "text/plain");
return status + " " + message;
}
@@ -266,7 +266,7 @@ std::string cgi_plugin::generate_page(
}
try {
- if ((fs::status(path).permissions() & fs::perms::others_exec) == fs::perms::none) {
+ if ((fs::status(path).permissions() & (fs::perms::owner_all | fs::perms::group_all | fs::perms::others_exec)) == fs::perms::none) {
return HttpStatus("500", "Script not executable: "s + rel_target, SetResponseHeader);
}
} catch (const std::exception& ex) {
diff --git a/plugins/fcgi/fcgi.cpp b/plugins/fcgi/fcgi.cpp
index f2743c3..95f03f6 100644
--- a/plugins/fcgi/fcgi.cpp
+++ b/plugins/fcgi/fcgi.cpp
@@ -310,7 +310,7 @@ namespace {
std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)
{
SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
+ SetResponseHeader("content_type", "text/plain");
return status + " " + message;
}
diff --git a/plugins/static-files/static-files.cpp b/plugins/static-files/static-files.cpp
index 4dd8499..e9cff0f 100644
--- a/plugins/static-files/static-files.cpp
+++ b/plugins/static-files/static-files.cpp
@@ -46,7 +46,7 @@ fs::path extend_index_html(fs::path path)
std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)
{
SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
+ SetResponseHeader("content_type", "text/plain");
return status + " " + message;
}
diff --git a/plugins/websocket/websocket.cpp b/plugins/websocket/websocket.cpp
index 884f691..c7119c6 100644
--- a/plugins/websocket/websocket.cpp
+++ b/plugins/websocket/websocket.cpp
@@ -23,7 +23,7 @@ namespace {
std::string HttpStatus(std::string status, std::string message, std::function<plugin_interface_setter_type>& SetResponseHeader)
{
SetResponseHeader("status", status);
- SetResponseHeader("content_type", "text/html");
+ SetResponseHeader("content_type", "text/plain");
return status + " " + message;
}
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp
index 602eb77..f23b8cd 100644
--- a/tests/test-webserver.cpp
+++ b/tests/test-webserver.cpp
@@ -399,10 +399,17 @@ std::pair<std::string,std::string> HTTPS(const std::string& target, bool ipv6 =
class Fixture
{
public:
- Fixture(){}
+ Fixture()
+ {
+ std::error_code ec;
+ fs::remove_all("testdir", ec);
+ fs::create_directory("testdir");
+ }
~Fixture()
{
- fs::remove("stats.db");
+ std::error_code ec;
+ fs::remove("stats.db", ec);
+ fs::remove_all("testdir", ec);
}
};
@@ -443,14 +450,15 @@ class WebsocketServerProcess
{
// shared data between Unix processes
struct shared_data_t {
- std::mutex mutex;
- char subprotocol[1024]{};
+ std::mutex mutex; // for synchronization between processes (!)
+ char subprotocol[1024]{}; // instead of std::string since std::string allocates data on heap
char target[1024]{};
};
public:
WebsocketServerProcess()
{
+ // RAII pattern for shared memory allocation/deallocation
m_shared = std::unique_ptr<shared_data_t, std::function<void(shared_data_t*)>>(
(shared_data_t*)mmap(NULL, sizeof(shared_data_t), PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS, -1, 0),
[this](shared_data_t*){munmap(m_shared.get(), sizeof(shared_data_t));});
@@ -893,3 +901,152 @@ BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, Fixture)
BOOST_REQUIRE(serverProcess.is_running());
}
+BOOST_FIXTURE_TEST_CASE(plugin_cgi, Fixture)
+{
+ std::string webserver_config{R"CONFIG(<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>localhost</host>
+ <host>[::1]</host>
+ <path requested="/cgi-test">
+ <plugin>cgi</plugin>
+ <target>testdir</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>
+)CONFIG"};
+ WebserverProcess serverProcess{webserver_config};
+ BOOST_REQUIRE(serverProcess.is_running());
+
+ File::setFile("testdir/test1.sh", R"(#!/bin/bash
+
+echo -ne "Content-Type: text/plain\r\n"
+echo -ne "\r\n"
+echo -ne "Test 1:\r\n"
+echo -ne "HTTP_CONNECTION: $HTTP_CONNECTION\r\n"
+echo -ne "HTTP_HOST: $HTTP_HOST\r\n"
+echo -ne "HTTP_USER_AGENT: $HTTP_USER_AGENT\r\n"
+echo -ne "SERVER_PORT: $SERVER_PORT\r\n"
+echo -ne "QUERY_STRING: $QUERY_STRING\r\n"
+echo -ne "SCRIPT_NAME: $SCRIPT_NAME\r\n"
+echo -ne "PATH_INFO: $PATH_INFO\r\n"
+echo -ne "REQUEST_METHOD: $REQUEST_METHOD\r\n"
+echo -ne "SERVER_NAME: $SERVER_NAME\r\n"
+echo -ne "HTTP_HOST: $HTTP_HOST\r\n"
+)");
+
+ fs::permissions("testdir/test1.sh", fs::perms::owner_all | fs::perms::group_all, fs::perm_options::add);
+
+ auto result {HTTP("/cgi-test/test1.sh/path1?q=2")};
+
+ BOOST_CHECK_EQUAL(result.first, fmt::format(
+"HTTP/1.1 200 OK\r\n"
+"Server: Reichwein.IT Webserver {}\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Length: {}\r\n"
+"\r\n"
+ , VERSION, result.second.size()));
+ BOOST_CHECK_EQUAL(result.second,
+"Test 1:\r\n"
+"HTTP_CONNECTION: \r\n"
+"HTTP_HOST: [::1]\r\n"
+"HTTP_USER_AGENT: Webserver Testsuite\r\n"
+"SERVER_PORT: 8080\r\n"
+"QUERY_STRING: q=2\r\n"
+"SCRIPT_NAME: /cgi-test/test1.sh\r\n"
+"PATH_INFO: /path1\r\n"
+"REQUEST_METHOD: GET\r\n"
+"SERVER_NAME: [::1]\r\n"
+"HTTP_HOST: [::1]\r\n"
+ );
+}
+
+BOOST_FIXTURE_TEST_CASE(plugin_cgi_missing_exe, Fixture)
+{
+ std::string webserver_config{R"CONFIG(<webserver>
+ <user>www-data</user>
+ <group>www-data</group>
+ <threads>10</threads>
+ <statisticspath>stats.db</statisticspath>
+ <plugin-directory>../plugins</plugin-directory>
+ <sites>
+ <site>
+ <name>localhost</name>
+ <host>localhost</host>
+ <host>[::1]</host>
+ <path requested="/cgi-test">
+ <plugin>cgi</plugin>
+ <target>testdir</target>
+ </path>
+ </site>
+ </sites>
+ <sockets>
+ <socket>
+ <address>::1</address>
+ <port>8080</port>
+ <protocol>http</protocol>
+ <site>localhost</site>
+ </socket>
+ </sockets>
+</webserver>
+)CONFIG"};
+ WebserverProcess serverProcess{webserver_config};
+ BOOST_REQUIRE(serverProcess.is_running());
+
+ File::setFile("testdir/test1.sh", R"(#!/bin/bash
+
+echo -ne "Content-Type: text/plain\r\n"
+echo -ne "\r\n"
+echo -ne "Test 1:\r\n"
+echo -ne "HTTP_CONNECTION: $HTTP_CONNECTION\r\n"
+echo -ne "HTTP_HOST: $HTTP_HOST\r\n"
+echo -ne "HTTP_USER_AGENT: $HTTP_USER_AGENT\r\n"
+echo -ne "SERVER_PORT: $SERVER_PORT\r\n"
+echo -ne "QUERY_STRING: $QUERY_STRING\r\n"
+echo -ne "SCRIPT_NAME: $SCRIPT_NAME\r\n"
+echo -ne "PATH_INFO: $PATH_INFO\r\n"
+echo -ne "REQUEST_METHOD: $REQUEST_METHOD\r\n"
+echo -ne "SERVER_NAME: $SERVER_NAME\r\n"
+echo -ne "HTTP_HOST: $HTTP_HOST\r\n"
+)");
+
+ fs::permissions("testdir/test1.sh", fs::perms::owner_all | fs::perms::group_all, fs::perm_options::add);
+
+ auto result {HTTP("/cgi-test/test2.sh")};
+
+ BOOST_CHECK_EQUAL(result.first, fmt::format(
+"HTTP/1.1 500 Internal Server Error\r\n"
+"Server: Reichwein.IT Webserver {}\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Length: {}\r\n"
+"\r\n"
+ , VERSION, result.second.size()));
+ BOOST_CHECK_EQUAL(result.second, "500 Bad Script: test2.sh");
+
+ result = HTTP("/cgi-test/test2.sh/path1?q=2");
+
+ BOOST_CHECK_EQUAL(result.first, fmt::format(
+"HTTP/1.1 500 Internal Server Error\r\n"
+"Server: Reichwein.IT Webserver {}\r\n"
+"Content-Type: text/plain\r\n"
+"Content-Length: {}\r\n"
+"\r\n"
+ , VERSION, result.second.size()));
+ BOOST_CHECK_EQUAL(result.second, "500 Bad Script: test2.sh/path1");
+}
+