summaryrefslogtreecommitdiffhomepage
path: root/tests
diff options
context:
space:
mode:
authorRoland Reichwein <mail@reichwein.it>2023-01-04 18:03:44 +0100
committerRoland Reichwein <mail@reichwein.it>2023-01-04 18:03:44 +0100
commit685a7e3407a8450ce71ff9d7dea94de6d4847e65 (patch)
tree868bf324b3d8d3f0d93e0bafee5a4bb6f1bcfa44 /tests
parent5fb5b34f5c2f5d0a3210708c04779367b1072c32 (diff)
Test HTTPS+HTTP * IPv6+IPv4 * HTTP1.0+HTTP1.1
Diffstat (limited to 'tests')
-rw-r--r--tests/Makefile3
-rw-r--r--tests/test-webserver.cpp131
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());
}