#include "helper.h" #include using namespace std::string_literals; namespace fs = std::filesystem; namespace pt = boost::property_tree; using namespace boost::unit_test; using namespace Reichwein; const fs::path testConfigFilename{"./webserver.conf"}; const fs::path testCertFilename{"./testchain.pem"}; const fs::path testKeyFilename{"./testkey.pem"}; // returns -1 if no port found in config int port_from_config(const std::string& config) { pt::ptree tree; std::istringstream stream{config}; pt::read_xml(stream, tree); try { return tree.get("webserver.sockets.socket.port"); } catch(...) { return -1; } } std::pair HTTP(const std::string& target, bool ipv6, bool HTTP11, boost::beast::http::verb method) { auto const host = ipv6 ? "::1" : "127.0.0.1"; auto const port = "8080"; int version = HTTP11 ? 11 : 10; // The io_context is required for all I/O boost::asio::io_context ioc; // These objects perform our I/O boost::asio::ip::tcp::resolver resolver(ioc); boost::beast::tcp_stream stream(ioc); // Look up the domain name auto const results = resolver.resolve(host, port); // Make the connection on the IP address we get from a lookup stream.connect(results); // Set up an HTTP GET request message boost::beast::http::request req; req.method(method); req.target(target); req.version(version); req.set(boost::beast::http::field::host, 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 socket boost::beast::error_code ec; stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); // not_connected happens sometimes // so don't bother reporting it. // if (ec && ec != boost::beast::errc::not_connected) throw boost::beast::system_error{ec}; return {header_stream.str(), body_stream.str()}; } void load_root_certificates(boost::asio::ssl::context& ctx) { std::string cert_chain{File::getFile(testCertFilename)}; ctx.add_certificate_authority(boost::asio::buffer(cert_chain.data(), cert_chain.size())); } std::pair HTTPS(const std::string& target, bool ipv6, bool HTTP11, boost::beast::http::verb method) { 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(boost::asio::ssl::context::tlsv13_client); // 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_peer); // 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; req.method(method); req.target(target); req.version(version); req.set(boost::beast::http::field::host, 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()}; }