From e679b0241662ea7c1910b9fc02ed0cb8f59b0de6 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 11 Jan 2023 15:27:23 +0100 Subject: Test CGI --- TODO | 8 +- config.cpp | 1 + debian/changelog | 3 +- debian/webserver.docs | 1 + debian/webserver.example-cgi.conf | 26 ++++++ plugins/cgi/cgi.cpp | 4 +- plugins/fcgi/fcgi.cpp | 2 +- plugins/static-files/static-files.cpp | 2 +- plugins/websocket/websocket.cpp | 2 +- tests/test-webserver.cpp | 165 +++++++++++++++++++++++++++++++++- 10 files changed, 203 insertions(+), 11 deletions(-) create mode 100644 debian/webserver.example-cgi.conf 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 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 @@ + + www-data + www-data + 10 + /var/lib/webserver/stats.db + /usr/lib/webserver/plugins + + + localhost + localhost + [::1] + + cgi + /var/lib/webserver/cgi-bin + + + + + +
::1
+ 8080 + http + localhost +
+
+
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& 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& 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& 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& 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 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*)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( + www-data + www-data + 10 + stats.db + ../plugins + + + localhost + localhost + [::1] + + cgi + testdir + + + + + +
::1
+ 8080 + http + localhost +
+
+
+)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( + www-data + www-data + 10 + stats.db + ../plugins + + + localhost + localhost + [::1] + + cgi + testdir + + + + + +
::1
+ 8080 + http + localhost +
+
+
+)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"); +} + -- cgit v1.2.3