diff options
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/test-webserver.cpp | 200 | 
1 files changed, 193 insertions, 7 deletions
diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp index 10f6dca..602eb77 100644 --- a/tests/test-webserver.cpp +++ b/tests/test-webserver.cpp @@ -29,6 +29,8 @@  #include <exception>  #include <filesystem>  #include <iostream> +#include <memory> +#include <mutex>  #include <sstream>  #include <stdexcept>  #include <string> @@ -38,11 +40,14 @@  #include <signal.h>  #include <sys/wait.h>  #include <unistd.h> +#include <sys/mman.h> +#include <sys/types.h>  #include <libreichwein/file.h>  #include <libreichwein/process.h>  #include "webserver.h" +#include "response.h"  using namespace std::string_literals;  namespace fs = std::filesystem; @@ -436,9 +441,19 @@ BOOST_DATA_TEST_CASE_F(Fixture, http_get_file_not_found, data::make({false, true  // Test server  class WebsocketServerProcess  { + // shared data between Unix processes + struct shared_data_t { +  std::mutex mutex; +  char subprotocol[1024]{}; +  char target[1024]{}; + }; +  public:   WebsocketServerProcess()   { +  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));});    start();   } @@ -447,7 +462,6 @@ public:    stop();   } - // Echoes back all received WebSocket messages   void do_session(boost::asio::ip::tcp::socket socket)   {    try @@ -463,18 +477,27 @@ public:              std::string("Reichwein.IT Test Websocket Server"));         })); -   // Accept the websocket handshake -   ws.accept(); +   boost::beast::http::request_parser<boost::beast::http::string_body> parser; +   request_type req; +   boost::beast::flat_buffer buffer; + +   boost::beast::http::read(ws.next_layer(), buffer, parser); +   req = parser.get(); +   { +    std::lock_guard lock{m_shared->mutex}; +    strncpy(m_shared->subprotocol, std::string{req[http::field::sec_websocket_protocol]}.data(), sizeof(m_shared->subprotocol)); +    strncpy(m_shared->target, std::string{req.target()}.data(), sizeof(m_shared->target)); +   } + +   ws.accept(req);     for(;;)     { -       // This buffer will hold the incoming message      boost::beast::flat_buffer buffer; -    // Read a message      ws.read(buffer); -    // Echo the message back +    // Reply with <request>: <counter>      ws.text(ws.got_text());      std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data()));      data += ": " + std::to_string(m_count++); @@ -562,9 +585,22 @@ public:    m_pid = 0;   } + std::string subprotocol() + { +  std::lock_guard lock{m_shared->mutex}; +  return m_shared->subprotocol; + } +  + std::string target() + { +  std::lock_guard lock{m_shared->mutex}; +  return m_shared->target; + } +  private:   int m_pid{};   int m_count{}; + std::unique_ptr<shared_data_t, std::function<void(shared_data_t*)>> m_shared;  }; // class WebsocketServerProcess  BOOST_FIXTURE_TEST_CASE(websocket, Fixture) @@ -658,7 +694,8 @@ BOOST_FIXTURE_TEST_CASE(websocket, Fixture)   // Update the host_ string. This will provide the value of the   // Host HTTP header during the WebSocket handshake.   // See https://tools.ietf.org/html/rfc7230#section-5.4 - host = "[" + host + "]"; + if (host == "::1") +  host = "[" + host + "]";   host += ':' + std::to_string(ep.port());   // Perform the SSL handshake @@ -707,3 +744,152 @@ BOOST_FIXTURE_TEST_CASE(websocket, Fixture)   BOOST_REQUIRE(websocketProcess.is_running());  } +BOOST_FIXTURE_TEST_CASE(websocket_subprotocol, 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>ip6-localhost</host> +   <host>localhost</host> +   <host>127.0.0.1</host> +   <host>[::1]</host> +   <path requested="/"> +    <plugin>websocket</plugin> +    <target>::1:8765</target> +   </path> +   <certpath>testchain.pem</certpath> +   <keypath>testkey.pem</keypath> +  </site> + </sites> + <sockets> +  <socket> +   <address>127.0.0.1</address> +   <port>8080</port> +   <protocol>http</protocol> +   <site>localhost</site> +  </socket> +  <socket> +   <address>::1</address> +   <port>8080</port> +   <protocol>http</protocol> +   <site>localhost</site> +  </socket> +  <socket> +   <address>127.0.0.1</address> +   <port>8081</port> +   <protocol>https</protocol> +   <site>localhost</site> +  </socket> +  <socket> +   <address>::1</address> +   <port>8081</port> +   <protocol>https</protocol> +   <site>localhost</site> +  </socket> + </sockets> +</webserver> +)CONFIG"}; + WebserverProcess serverProcess{webserver_config}; + BOOST_REQUIRE(serverProcess.is_running()); +  + WebsocketServerProcess websocketProcess; + BOOST_REQUIRE(websocketProcess.is_running()); + + std::string host = "::1"; + auto const  port = "8081" ; + auto const  text = "request1"; + + // The io_context is required for all I/O + boost::asio::io_context ioc; + + // The SSL context is required, and holds certificates + boost::asio::ssl::context ctx{boost::asio::ssl::context::tlsv13_client}; + + // This holds the root certificate used for verification + load_root_certificates(ctx); + + // These objects perform our I/O + boost::asio::ip::tcp::resolver resolver{ioc}; + boost::beast::websocket::stream<boost::beast::ssl_stream<boost::asio::ip::tcp::socket>> ws{ioc, ctx}; + + // Look up the domain name + auto const results = resolver.resolve(host, port); + + // Make the connection on the IP address we get from a lookup + auto ep = boost::asio::connect(get_lowest_layer(ws), results); + + // Set SNI Hostname (many hosts need this to handshake successfully) + if(! SSL_set_tlsext_host_name(ws.next_layer().native_handle(), host.c_str())) +     throw boost::beast::system_error( +         boost::beast::error_code( +             static_cast<int>(::ERR_get_error()), +             boost::asio::error::get_ssl_category()), +         "Failed to set SNI Hostname"); + + // Update the host_ string. This will provide the value of the + // Host HTTP header during the WebSocket handshake. + // See https://tools.ietf.org/html/rfc7230#section-5.4 + if (host == "::1") +  host = "[" + host + "]"; + host += ':' + std::to_string(ep.port()); + + // Perform the SSL handshake + ws.next_layer().handshake(boost::asio::ssl::stream_base::client); + + // Set a decorator to change the User-Agent of the handshake + ws.set_option(boost::beast::websocket::stream_base::decorator( +     [](boost::beast::websocket::request_type& req) +     { +         req.set(boost::beast::http::field::user_agent, +             std::string("Reichwein.IT Test Websocket Client")); +     })); +  + ws.set_option(boost::beast::websocket::stream_base::decorator( +  [](boost::beast::websocket::request_type& req) +  { +   req.set(boost::beast::http::field::sec_websocket_protocol, "protocol1"); +  })); + + // Perform the websocket handshake + ws.handshake(host, "/path1/target1"); + + // Send the message + ws.write(boost::asio::buffer(std::string(text))); + + // This buffer will hold the incoming message + boost::beast::flat_buffer buffer; + + // Read a message into our buffer + ws.read(buffer); + std::string data(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); + BOOST_CHECK_EQUAL(data, "request1: 0"); + + buffer.consume(buffer.size()); + + ws.write(boost::asio::buffer(std::string(text))); + ws.read(buffer); + data = std::string(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); + BOOST_CHECK_EQUAL(data, "request1: 1"); + + buffer.consume(buffer.size()); + + ws.write(boost::asio::buffer(std::string(text))); + ws.read(buffer); + data = std::string(boost::asio::buffers_begin(buffer.data()), boost::asio::buffers_end(buffer.data())); + BOOST_CHECK_EQUAL(data, "request1: 2"); + + // Close the WebSocket connection + ws.close(boost::beast::websocket::close_code::normal); + + BOOST_CHECK_EQUAL(websocketProcess.subprotocol(), "protocol1"); + BOOST_CHECK_EQUAL(websocketProcess.target(), "/path1/target1"); + BOOST_REQUIRE(websocketProcess.is_running()); + BOOST_REQUIRE(serverProcess.is_running()); +} +  | 
