Files
2026-07-27 15:02:44 +02:00

223 lines
8.1 KiB
C++

/*
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 <http://www.gnu.org/licenses/>.
*/
#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 <functional>
#include <memory>
#include <optional>
#include <string>
#if defined TORRENT_USE_OPENSSL
#include <ctime>
#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<std::string, std::string> 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<std::uint8_t, struct http_server_flags_tag>;
#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<void(lt::aux::ssl::context&)> ssl_setup = nullptr
#endif
);
void stop();
using handler_t = std::function<std::string(
std::string, std::string, std::map<std::string, std::string>&)>;
using generator_t = std::function<std::string(std::int64_t, std::int64_t)>;
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<lt::tcp::socket, lt::aux::ssl_stream<lt::tcp::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<lt::aux::ssl::context> 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<connection_t> m_connection;
asio::ip::tcp::endpoint m_ep;
std::unordered_map<std::string, handler_t> m_handlers;
std::set<std::string> 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