From 685a7e3407a8450ce71ff9d7dea94de6d4847e65 Mon Sep 17 00:00:00 2001 From: Roland Reichwein Date: Wed, 4 Jan 2023 18:03:44 +0100 Subject: Test HTTPS+HTTP * IPv6+IPv4 * HTTP1.0+HTTP1.1 --- tests/Makefile | 3 +- tests/test-webserver.cpp | 131 +++++++++++++++++++++++++++++++++++++++++++---- 2 files changed, 122 insertions(+), 12 deletions(-) (limited to 'tests') diff --git a/tests/Makefile b/tests/Makefile index d4d047c..033ba95 100644 --- a/tests/Makefile +++ b/tests/Makefile @@ -26,7 +26,8 @@ LIBS=\ -lcrypt \ -lpthread \ -lssl -lcrypto \ --ldl +-ldl \ +$(shell pkg-config --libs fmt) LDFLAGS+=-pie diff --git a/tests/test-webserver.cpp b/tests/test-webserver.cpp index 7826a58..9321169 100644 --- a/tests/test-webserver.cpp +++ b/tests/test-webserver.cpp @@ -3,15 +3,25 @@ #define BOOST_TEST_MODULE webserver_test #include +// Support both boost in Debian unstable (BOOST_LATEST) and in stable (boost 1.67) +#if BOOST_VERSION >= 107100 +#define BOOST_LATEST +#endif + #include #include #include #include #include +#ifdef BOOST_LATEST +#include +#endif #include #include #include +#include +#include #include #include @@ -35,6 +45,7 @@ using namespace std::string_literals; namespace fs = std::filesystem; namespace pt = boost::property_tree; +using namespace boost::unit_test; using namespace Reichwein; class WebserverProcess @@ -59,6 +70,8 @@ public: static-files . + ../fullchain.pem + ../privkey.pem @@ -74,6 +87,18 @@ public: http localhost + +
127.0.0.1
+ 8081 + https + localhost +
+ +
::1
+ 8081 + https + localhost +
@@ -147,11 +172,11 @@ private: pid_t m_pid; }; -std::string HTTPGet(const std::string& target) +std::pair HTTPGet(const std::string& target, bool ipv6 = true, bool HTTP11 = true) { - auto const host = "127.0.0.1"; //"::1"; + auto const host = ipv6 ? "::1" : "127.0.0.1"; auto const port = "8080"; - int version = 11; // or 10 + int version = HTTP11 ? 11 : 10; // The io_context is required for all I/O boost::asio::io_context ioc; @@ -168,7 +193,7 @@ std::string HTTPGet(const std::string& target) // Set up an HTTP GET request message boost::beast::http::request req{boost::beast::http::verb::get, target, version}; - req.set(boost::beast::http::field::host, host); + req.set(boost::beast::http::field::host, ipv6 && host == "::1"s ? "["s + host + "]"s : host); req.set(boost::beast::http::field::user_agent, "Webserver Testsuite"); // Send the HTTP request to the remote host @@ -183,11 +208,11 @@ std::string HTTPGet(const std::string& target) // Receive the HTTP response boost::beast::http::read(stream, buffer, res); - // Write the message to standard out + // Return value std::ostringstream header_stream; header_stream << res.base(); - //std::ostringstream body_stream; - header_stream << boost::beast::buffers_to_string(res.body().data()); + std::ostringstream body_stream; + body_stream << boost::beast::buffers_to_string(res.body().data()); // Gracefully close the socket boost::beast::error_code ec; @@ -196,10 +221,92 @@ std::string HTTPGet(const std::string& target) // not_connected happens sometimes // so don't bother reporting it. // - if(ec && ec != boost::beast::errc::not_connected) + if (ec && ec != boost::beast::errc::not_connected) throw boost::beast::system_error{ec}; - return header_stream.str(); + return {header_stream.str(), body_stream.str()}; +} + +std::pair HTTPSGet(const std::string& target, bool ipv6 = true, bool HTTP11 = true) +{ + auto const host = ipv6 ? "::1" : "127.0.0.1"; + auto const port = "8081"; + int version = HTTP11 ? 11 : 10; + + // 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( +#ifdef BOOST_LATEST + boost::asio::ssl::context::tlsv13_client +#else + boost::asio::ssl::context::tlsv12_client +#endif + ); + + // This holds the root certificate used for verification + //load_root_certificates(ctx); + + // Verify the remote server's certificate + ctx.set_verify_mode(boost::asio::ssl::verify_none); // TODO: ssl::verify_peer w/ load_root_certificates() (above) + + // These objects perform our I/O + boost::asio::ip::tcp::resolver resolver(ioc); + boost::beast::ssl_stream stream(ioc, ctx); + + // Set SNI Hostname (many hosts need this to handshake successfully) + if (!SSL_set_tlsext_host_name(stream.native_handle(), host)) + { + boost::beast::error_code ec{static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category()}; + throw boost::beast::system_error{ec}; + } + + // Look up the domain name + auto const results = resolver.resolve(host, port); + + // Make the connection on the IP address we get from a lookup + boost::beast::get_lowest_layer(stream).connect(results); + + // Perform the SSL handshake + stream.handshake(boost::asio::ssl::stream_base::client); + + // Set up an HTTP GET request message + boost::beast::http::request req{boost::beast::http::verb::get, target, version}; + req.set(boost::beast::http::field::host, ipv6 && host == "::1"s ? "["s + host + "]"s : host); + req.set(boost::beast::http::field::user_agent, "Webserver Testsuite"); + + // Send the HTTP request to the remote host + boost::beast::http::write(stream, req); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + boost::beast::http::response res; + + // Receive the HTTP response + boost::beast::http::read(stream, buffer, res); + + // Return value + std::ostringstream header_stream; + header_stream << res.base(); + std::ostringstream body_stream; + body_stream << boost::beast::buffers_to_string(res.body().data()); + + // Gracefully close the stream + boost::beast::error_code ec; + stream.shutdown(ec); + if (ec == boost::asio::error::eof) + { + // Rationale: + // http://stackoverflow.com/questions/25587403/boost-asio-ssl-async-shutdown-always-finishes-with-an-error + ec = {}; + } + if (ec) + throw boost::beast::system_error{ec}; + + return {header_stream.str(), body_stream.str()}; } class Fixture @@ -209,10 +316,12 @@ public: ~Fixture(){} }; -BOOST_FIXTURE_TEST_CASE(http_download, Fixture) +BOOST_DATA_TEST_CASE_F(Fixture, http_download, data::make({false, true}) * data::make({false, true}) * data::make({false, true}), ipv6, http11, https) { WebserverProcess serverProcess; - std::cout << HTTPGet("/webserver.conf"); + BOOST_REQUIRE(serverProcess.isRunning()); + auto response{(https ? HTTPSGet("/webserver.conf") : HTTPGet("/webserver.conf"))}; + BOOST_REQUIRE(serverProcess.isRunning()); } -- cgit v1.2.3