/* Copyright (c) 2015, Arvid Norberg All rights reserved. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . */ #ifndef HTTP_SERVER_HPP_INCLUDED #define HTTP_SERVER_HPP_INCLUDED #include "simulator/simulator.hpp" #include "libtorrent/config.hpp" #include "libtorrent/flags.hpp" #include "libtorrent/socket.hpp" #if TORRENT_USE_SSL #include "libtorrent/aux_/deadline_timer.hpp" #include "libtorrent/aux_/polymorphic_socket.hpp" #include "libtorrent/aux_/proxy_base.hpp" // for wrap_allocator, used by ssl_stream #include "libtorrent/aux_/ssl.hpp" #include "libtorrent/aux_/ssl_stream.hpp" #include "libtorrent/error_code.hpp" #endif #include #include #include #include #if defined TORRENT_USE_OPENSSL #include #endif using lt::operator""_bit; namespace sim { std::string trim(std::string s); std::string lower_case(std::string s); std::string normalize(const std::string& s); // returns the index to the last byte of the request, or -1 if the buffer // does not contain a full http request int find_request_len(char const* buf, int len); struct http_request { std::string method; std::string req; std::string path; std::map headers; }; http_request parse_request(char const* start, int len); // builds an HTTP response buffer std::string send_response( int code, char const* status_message, int len = 0, char const** extra_header = NULL); using http_server_flags_t = lt::flags::bitfield_flag; #if TORRENT_USE_SSL // resolves a filename under test/ssl/ to a path usable from a // simulation test binary's working directory. std::string ssl_fixture_path(std::string const& name); // true if ec is an SSL/TLS-layer error (certificate verification, // handshake negotiation, etc), as opposed to e.g. a plain connection // failure. bool is_ssl_error(lt::error_code const& ec); #if defined TORRENT_USE_OPENSSL // overrides the time used to check a peer certificate's notBefore/ // notAfter, letting a test validate an otherwise-valid certificate as // though it were some other date. This is raw OpenSSL API (there's no // portable equivalent through boost::asio::ssl::context or the aux::ssl // abstraction), so it isn't available when built against GnuTLS. void set_verification_time(lt::aux::ssl::context& ctx, std::time_t t); #endif #endif // This is a very simple http server that only supports a single concurrent // connection struct http_server { static constexpr http_server_flags_t keep_alive = 0_bit; // behave like an HTTP/1.0 server: respond with an HTTP/1.0 status line // and close the connection after each response. HTTP/1.0 has no // persistent connections, and the Connection header is an HTTP/1.1 // mechanism, so no "Connection: close" is sent, the client must detect // the close from the protocol version (and the socket closing). static constexpr http_server_flags_t http_1_0 = 1_bit; // wrap the connection in TLS, using a self-signed test certificate // (test/ssl/server.pem). Only available when built with // TORRENT_USE_SSL. static constexpr http_server_flags_t https = 2_bit; // like keep_alive, but the server tears down the socket right after // writing each response, without ever advertising "Connection: close". // this simulates a server-side idle-timeout race that the client can't // see coming from the response headers, and must recover from by // transparently reconnecting. static constexpr http_server_flags_t stale_keep_alive = 3_bit; http_server(asio::io_context& ios, unsigned short listen_port, http_server_flags_t flags = http_server::keep_alive #if TORRENT_USE_SSL // only meaningful together with the https flag. cert_file and // key_file each name a PEM file under test/ssl/ (by default both // point at server.pem, which bundles a certificate and its // private key in one file; use_certificate_file()/ // use_private_key_file() each just scan for their own PEM block // type and ignore the rest, so pointing both at the same bundle // works). ssl_setup, when set, is called with the freshly // constructed ssl::context before the certificate and private // key are loaded, so tests can install a password callback // (needed by some encrypted test/ssl/ key files) or apply other // restrictions (e.g. supported TLS versions) ahead of time. , std::string cert_file = "server.pem", std::string key_file = "server.pem", std::function ssl_setup = nullptr #endif ); void stop(); using handler_t = std::function&)>; using generator_t = std::function; void register_handler(std::string const& path, handler_t h); void register_content(std::string const& path, std::int64_t const size, generator_t gen); void register_redirect(std::string const& path, std::string const& target); void register_stall_handler(std::string const& path); // the number of TCP connections that have been accepted so far int accepted_connections() const { return m_accepted_connections; } private: #if TORRENT_USE_SSL // a single-connection HTTP(S) server only ever needs to distinguish // between a plain TCP connection and one wrapped in TLS, so this uses // a smaller variant than libtorrent::aux::socket_type (which also // covers proxies, uTP, i2p and WebRTC). using connection_t = lt::aux::polymorphic_socket>; #else using connection_t = lt::tcp::socket; #endif void on_accept(boost::system::error_code const& ec, sim::asio::ip::tcp::socket peer); #if TORRENT_USE_SSL void on_handshake(boost::system::error_code const& ec); #endif void read(); void on_read(boost::system::error_code const& ec, size_t bytes_transferred); void on_write(boost::system::error_code const& ec, size_t bytes_transferred, bool close); // graceful=false skips the SSL close_notify shutdown attempt and // tears the socket down directly. void close_connection(bool graceful = true); void finish_close( boost::system::error_code const& shutdown_ec = boost::system::error_code()); asio::io_context& m_ios; asio::ip::tcp::acceptor m_listen_socket; #if TORRENT_USE_SSL std::unique_ptr m_ssl_ctx; // guards against a peer that never completes the TLS close_notify // handshake: close_connection() arms this for 3 seconds and force- // closes the connection if it fires before the shutdown completes, // so a stuck peer can't stall this server's accept loop forever. lt::aux::deadline_timer m_shutdown_timer; #endif // left empty until the first connection is accepted: the active // alternative is constructed in place (emplace), since the underlying // std::variant has no move-assignment operator when one of its // alternatives (ssl_stream) is move-constructible but not // move-assignable. std::optional m_connection; asio::ip::tcp::endpoint m_ep; std::unordered_map m_handlers; std::set m_stall_handlers; // read buffer, we receive bytes into this buffer for the connection std::string m_recv_buffer; // the number of bytes of m_recv_buffer that we've actually read data into. // The remaining is uninitialized, possibly being read into in an async call int m_bytes_used; std::string m_send_buffer; // set to true when shutting down bool m_close; http_server_flags_t m_flags; // counts the number of accepted TCP connections int m_accepted_connections = 0; }; } #endif