diff options
author | Roland Reichwein <mail@reichwein.it> | 2023-01-04 18:03:44 +0100 |
---|---|---|
committer | Roland Reichwein <mail@reichwein.it> | 2023-01-04 18:03:44 +0100 |
commit | 685a7e3407a8450ce71ff9d7dea94de6d4847e65 (patch) | |
tree | 868bf324b3d8d3f0d93e0bafee5a4bb6f1bcfa44 | |
parent | 5fb5b34f5c2f5d0a3210708c04779367b1072c32 (diff) |
Test HTTPS+HTTP * IPv6+IPv4 * HTTP1.0+HTTP1.1
-rw-r--r-- | common.mk | 1 | ||||
-rw-r--r-- | tests/Makefile | 3 | ||||
-rw-r--r-- | tests/test-webserver.cpp | 131 |
3 files changed, 122 insertions, 13 deletions
@@ -98,7 +98,6 @@ CXXTYPE=g++ endif CXXFLAGS+=$(shell pkg-config --cflags fmt) -LIBS+=$(shell pkg-config --libs fmt) SRC_ROOT=$(shell echo $(MAKEFILE_LIST) | tr " " "\n" | grep common.mk | sed -e 's/\([^ ]*\)common.mk/\1/g') VERSION=$(shell dpkg-parsechangelog --show-field Version --file $(SRC_ROOT)/debian/changelog) 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 <boost/test/included/unit_test.hpp> +// Support both boost in Debian unstable (BOOST_LATEST) and in stable (boost 1.67) +#if BOOST_VERSION >= 107100 +#define BOOST_LATEST +#endif + #include <boost/test/data/dataset.hpp> #include <boost/test/data/monomorphic.hpp> #include <boost/test/data/test_case.hpp> #include <boost/beast/core.hpp> #include <boost/beast/http.hpp> +#ifdef BOOST_LATEST +#include <boost/beast/ssl.hpp> +#endif #include <boost/beast/version.hpp> #include <boost/asio/connect.hpp> #include <boost/asio/ip/tcp.hpp> +#include <boost/asio/ssl/error.hpp> +#include <boost/asio/ssl/stream.hpp> #include <boost/property_tree/ptree.hpp> #include <boost/property_tree/xml_parser.hpp> @@ -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: <plugin>static-files</plugin> <target>.</target> </path> + <certpath>../fullchain.pem</certpath> + <keypath>../privkey.pem</keypath> </site> </sites> <sockets> @@ -74,6 +87,18 @@ public: <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> @@ -147,11 +172,11 @@ private: pid_t m_pid; }; -std::string HTTPGet(const std::string& target) +std::pair<std::string,std::string> 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<boost::beast::http::string_body> 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<std::string,std::string> 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<boost::beast::tcp_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<int>(::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<boost::beast::http::string_body> 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<boost::beast::http::dynamic_body> 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()); } |