summaryrefslogtreecommitdiffhomepage
path: root/tests/helper.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'tests/helper.cpp')
-rw-r--r--tests/helper.cpp228
1 files changed, 228 insertions, 0 deletions
diff --git a/tests/helper.cpp b/tests/helper.cpp
new file mode 100644
index 0000000..644b9ca
--- /dev/null
+++ b/tests/helper.cpp
@@ -0,0 +1,228 @@
+#include "helper.h"
+
+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"};
+
+// tcp: tcp or tcp6
+bool tcp_is_pid_listening_on(const std::string& tcp, pid_t pid, int port)
+{
+ std::string filename{fmt::format("/proc/{}/net/{}", pid, tcp)};
+ std::ifstream f{filename, std::ios::in};
+ // e.g.:
+ // sl local_address rem_address st tx_queue rx_queue tr tm->when retrnsmt uid timeout inode
+ // 0: 00000000:C799 00000000:0000 0A 00000000:00000000 00:00000000 00000000 107 0 21869 1 00000000335416a4 100 0 0 10 0
+ std::string s;
+ std::getline(f, s); // skip head line
+ while (std::getline(f, s)) {
+ boost::algorithm::trim_left(s);
+
+ size_t pos_space1{s.find(' ')};
+ if (pos_space1 == std::string::npos)
+ throw std::runtime_error("Expected first space in " + filename);
+
+ size_t pos_colon1{s.find(':', pos_space1 + 1)};
+ if (pos_colon1 == std::string::npos)
+ throw std::runtime_error("Expected first colon in " + filename);
+
+ size_t pos_space2{s.find(' ', pos_colon1 + 1)};
+ if (pos_space2 == std::string::npos)
+ throw std::runtime_error("Expected second space in " + filename);
+
+ std::string port_s{s.substr(pos_colon1 + 1, pos_space2 - (pos_colon1 + 1))};
+ auto current_port{std::stoul(port_s, nullptr, 16)};
+ if (current_port != port)
+ continue;
+
+ // now, we are in a line related to matching local port
+
+ size_t pos_space3{s.find(' ', pos_space2 + 1)};
+ if (pos_space3 == std::string::npos)
+ throw std::runtime_error("Expected third space in " + filename);
+
+ size_t pos_space4{s.find(' ', pos_space3 + 1)};
+ if (pos_space4 == std::string::npos)
+ throw std::runtime_error("Expected fourth space in " + filename);
+
+ std::string state_s{s.substr(pos_space3 + 1, pos_space4 - (pos_space3 + 1))};
+ if (state_s == "0A") // listening state TCP_LISTEN, from net/tcp_states.h
+ return true;
+ }
+
+ return false; // not found
+}
+
+bool is_pid_listening_on(pid_t pid, int port)
+{
+ return tcp_is_pid_listening_on("tcp", pid, port) || tcp_is_pid_listening_on("tcp6", pid, port);
+}
+
+void wait_for_pid_listening_on(pid_t pid, int port)
+{
+ while (!is_pid_listening_on(pid, port)) {
+ std::this_thread::sleep_for(std::chrono::milliseconds(10));
+ }
+}
+
+// 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<int>("webserver.sockets.socket.port");
+ } catch(...) {
+ return -1;
+ }
+}
+
+std::pair<std::string,std::string> 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<boost::beast::http::string_body> 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<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 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<std::string,std::string> 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<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;
+ 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<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()};
+}
+