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 /tests | |
| parent | 5fb5b34f5c2f5d0a3210708c04779367b1072c32 (diff) | |
Test HTTPS+HTTP * IPv6+IPv4 * HTTP1.0+HTTP1.1
Diffstat (limited to 'tests')
| -rw-r--r-- | tests/Makefile | 3 | ||||
| -rw-r--r-- | tests/test-webserver.cpp | 131 | 
2 files changed, 122 insertions, 12 deletions
| 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());  } | 
