From 3f609971987ea739c4a82459d17ee245c787ec51 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrien=20Gallou=C3=ABt?= Date: Wed, 29 Oct 2025 16:44:41 +0100 Subject: [PATCH] vendor : update cpp-httplib to 0.27.0 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Adrien Gallouët --- vendor/cpp-httplib/httplib.h | 268 +++++++++++++++++++++++++++-------- 1 file changed, 211 insertions(+), 57 deletions(-) diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index db55d07e25334..b76a17d07aca9 100644 --- a/vendor/cpp-httplib/httplib.h +++ b/vendor/cpp-httplib/httplib.h @@ -8,8 +8,8 @@ #ifndef CPPHTTPLIB_HTTPLIB_H #define CPPHTTPLIB_HTTPLIB_H -#define CPPHTTPLIB_VERSION "0.26.0" -#define CPPHTTPLIB_VERSION_NUM "0x001A00" +#define CPPHTTPLIB_VERSION "0.27.0" +#define CPPHTTPLIB_VERSION_NUM "0x001B00" /* * Platform compatibility check @@ -1052,6 +1052,9 @@ class RegexMatcher final : public MatcherBase { ssize_t write_headers(Stream &strm, const Headers &headers); +std::string make_host_and_port_string(const std::string &host, int port, + bool is_ssl); + } // namespace detail class Server { @@ -1129,6 +1132,8 @@ class Server { Server & set_header_writer(std::function const &writer); + Server &set_trusted_proxies(const std::vector &proxies); + Server &set_keep_alive_max_count(size_t count); Server &set_keep_alive_timeout(time_t sec); @@ -1167,6 +1172,9 @@ class Server { const std::function &setup_request); std::atomic svr_sock_{INVALID_SOCKET}; + + std::vector trusted_proxies_; + size_t keep_alive_max_count_ = CPPHTTPLIB_KEEPALIVE_MAX_COUNT; time_t keep_alive_timeout_sec_ = CPPHTTPLIB_KEEPALIVE_TIMEOUT_SECOND; time_t read_timeout_sec_ = CPPHTTPLIB_SERVER_READ_TIMEOUT_SECOND; @@ -1719,8 +1727,6 @@ class ClientImpl { const std::string &boundary, const UploadFormDataItems &items, const FormDataProviderItems &provider_items) const; - std::string adjust_host_string(const std::string &host) const; - virtual bool process_socket(const Socket &socket, std::chrono::time_point start_time, @@ -1953,14 +1959,17 @@ class SSLServer : public Server { void update_certs(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); + int ssl_last_error() const { return last_ssl_error_; } + private: bool process_and_close_socket(socket_t sock) override; + STACK_OF(X509_NAME) * extract_ca_names_from_x509_store(X509_STORE *store); + SSL_CTX *ctx_; std::mutex ctx_mutex_; -#ifdef CPPHTTPLIB_OPENSSL_SUPPORT + int last_ssl_error_ = 0; -#endif }; class SSLClient final : public ClientImpl { @@ -4596,13 +4605,35 @@ inline bool zstd_decompressor::decompress(const char *data, size_t data_length, } #endif +inline bool is_prohibited_header_name(const std::string &name) { + using udl::operator""_t; + + switch (str2tag(name)) { + case "REMOTE_ADDR"_t: + case "REMOTE_PORT"_t: + case "LOCAL_ADDR"_t: + case "LOCAL_PORT"_t: return true; + default: return false; + } +} + inline bool has_header(const Headers &headers, const std::string &key) { + if (is_prohibited_header_name(key)) { return false; } return headers.find(key) != headers.end(); } inline const char *get_header_value(const Headers &headers, const std::string &key, const char *def, size_t id) { + if (is_prohibited_header_name(key)) { +#ifndef CPPHTTPLIB_NO_EXCEPTIONS + std::string msg = "Prohibited header name '" + key + "' is specified."; + throw std::invalid_argument(msg); +#else + return ""; +#endif + } + auto rng = headers.equal_range(key); auto it = rng.first; std::advance(it, static_cast(id)); @@ -7261,6 +7292,30 @@ inline bool RegexMatcher::match(Request &request) const { return std::regex_match(request.path, request.matches, regex_); } +inline std::string make_host_and_port_string(const std::string &host, int port, + bool is_ssl) { + std::string result; + + // Enclose IPv6 address in brackets (but not if already enclosed) + if (host.find(':') == std::string::npos || + (!host.empty() && host[0] == '[')) { + // IPv4, hostname, or already bracketed IPv6 + result = host; + } else { + // IPv6 address without brackets + result = "[" + host + "]"; + } + + // Append port if not default + if ((!is_ssl && port == 80) || (is_ssl && port == 443)) { + ; // do nothing + } else { + result += ":" + std::to_string(port); + } + + return result; +} + } // namespace detail // HTTP server implementation @@ -7473,6 +7528,12 @@ inline Server &Server::set_header_writer( return *this; } +inline Server & +Server::set_trusted_proxies(const std::vector &proxies) { + trusted_proxies_ = proxies; + return *this; +} + inline Server &Server::set_keep_alive_max_count(size_t count) { keep_alive_max_count_ = count; return *this; @@ -8261,6 +8322,40 @@ inline bool Server::dispatch_request_for_content_reader( return false; } +inline std::string +get_client_ip(const std::string &x_forwarded_for, + const std::vector &trusted_proxies) { + // X-Forwarded-For is a comma-separated list per RFC 7239 + std::vector ip_list; + detail::split(x_forwarded_for.data(), + x_forwarded_for.data() + x_forwarded_for.size(), ',', + [&](const char *b, const char *e) { + auto r = detail::trim(b, e, 0, static_cast(e - b)); + ip_list.emplace_back(std::string(b + r.first, b + r.second)); + }); + + for (size_t i = 0; i < ip_list.size(); ++i) { + auto ip = ip_list[i]; + + auto is_trusted_proxy = + std::any_of(trusted_proxies.begin(), trusted_proxies.end(), + [&](const std::string &proxy) { return ip == proxy; }); + + if (is_trusted_proxy) { + if (i == 0) { + // If the trusted proxy is the first IP, there's no preceding client IP + return ip; + } else { + // Return the IP immediately before the trusted proxy + return ip_list[i - 1]; + } + } + } + + // If no trusted proxy is found, return the first IP in the list + return ip_list.front(); +} + inline bool Server::process_request(Stream &strm, const std::string &remote_addr, int remote_port, const std::string &local_addr, @@ -8324,15 +8419,16 @@ Server::process_request(Stream &strm, const std::string &remote_addr, connection_closed = true; } - req.remote_addr = remote_addr; + if (!trusted_proxies_.empty() && req.has_header("X-Forwarded-For")) { + auto x_forwarded_for = req.get_header_value("X-Forwarded-For"); + req.remote_addr = get_client_ip(x_forwarded_for, trusted_proxies_); + } else { + req.remote_addr = remote_addr; + } req.remote_port = remote_port; - req.set_header("REMOTE_ADDR", req.remote_addr); - req.set_header("REMOTE_PORT", std::to_string(req.remote_port)); req.local_addr = local_addr; req.local_port = local_port; - req.set_header("LOCAL_ADDR", req.local_addr); - req.set_header("LOCAL_PORT", std::to_string(req.local_port)); if (req.has_header("Accept")) { const auto &accept_header = req.get_header_value("Accept"); @@ -8522,7 +8618,7 @@ inline ClientImpl::ClientImpl(const std::string &host, int port, const std::string &client_cert_path, const std::string &client_key_path) : host_(detail::escape_abstract_namespace_unix_domain(host)), port_(port), - host_and_port_(adjust_host_string(host_) + ":" + std::to_string(port)), + host_and_port_(detail::make_host_and_port_string(host_, port, is_ssl())), client_cert_path_(client_cert_path), client_key_path_(client_key_path) {} inline ClientImpl::~ClientImpl() { @@ -8703,8 +8799,9 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { { std::lock_guard guard(socket_mutex_); - // Set this to false immediately - if it ever gets set to true by the end of - // the request, we know another thread instructed us to close the socket. + // Set this to false immediately - if it ever gets set to true by the end + // of the request, we know another thread instructed us to close the + // socket. socket_should_be_closed_when_request_is_done_ = false; auto is_alive = false; @@ -8720,10 +8817,10 @@ inline bool ClientImpl::send_(Request &req, Response &res, Error &error) { #endif if (!is_alive) { - // Attempt to avoid sigpipe by shutting down non-gracefully if it seems - // like the other side has already closed the connection Also, there - // cannot be any requests in flight from other threads since we locked - // request_mutex_, so safe to close everything immediately + // Attempt to avoid sigpipe by shutting down non-gracefully if it + // seems like the other side has already closed the connection Also, + // there cannot be any requests in flight from other threads since we + // locked request_mutex_, so safe to close everything immediately const bool shutdown_gracefully = false; shutdown_ssl(socket_, shutdown_gracefully); shutdown_socket(socket_); @@ -9027,7 +9124,8 @@ inline bool ClientImpl::create_redirect_client( } } -// New method for robust client setup (based on basic_manual_redirect.cpp logic) +// New method for robust client setup (based on basic_manual_redirect.cpp +// logic) template inline void ClientImpl::setup_redirect_client(ClientType &client) { // Copy basic settings first @@ -9131,18 +9229,8 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, // curl behavior) if (address_family_ == AF_UNIX) { req.set_header("Host", "localhost"); - } else if (is_ssl()) { - if (port_ == 443) { - req.set_header("Host", host_); - } else { - req.set_header("Host", host_and_port_); - } } else { - if (port_ == 80) { - req.set_header("Host", host_); - } else { - req.set_header("Host", host_and_port_); - } + req.set_header("Host", host_and_port_); } } @@ -9409,12 +9497,6 @@ inline Result ClientImpl::send_with_content_provider( #endif } -inline std::string -ClientImpl::adjust_host_string(const std::string &host) const { - if (host.find(':') != std::string::npos) { return "[" + host + "]"; } - return host; -} - inline void ClientImpl::output_log(const Request &req, const Response &res) const { if (logger_) { @@ -9538,8 +9620,8 @@ inline ContentProviderWithoutLength ClientImpl::get_multipart_content_provider( const FormDataProviderItems &provider_items) const { size_t cur_item = 0; size_t cur_start = 0; - // cur_item and cur_start are copied to within the std::function and maintain - // state between successive calls + // cur_item and cur_start are copied to within the std::function and + // maintain state between successive calls return [&, cur_item, cur_start](size_t offset, DataSink &sink) mutable -> bool { if (!offset && !items.empty()) { @@ -10251,8 +10333,8 @@ inline void ClientImpl::stop() { // If there is anything ongoing right now, the ONLY thread-safe thing we can // do is to shutdown_socket, so that threads using this socket suddenly // discover they can't read/write any more and error out. Everything else - // (closing the socket, shutting ssl down) is unsafe because these actions are - // not thread-safe. + // (closing the socket, shutting ssl down) is unsafe because these actions + // are not thread-safe. if (socket_requests_in_flight_ > 0) { shutdown_socket(socket_); @@ -10705,6 +10787,19 @@ inline SSLServer::SSLServer(const char *cert_path, const char *private_key_path, SSL_CTX_load_verify_locations(ctx_, client_ca_cert_file_path, client_ca_cert_dir_path); + // Set client CA list to be sent to clients during TLS handshake + if (client_ca_cert_file_path) { + auto ca_list = SSL_load_client_CA_file(client_ca_cert_file_path); + if (ca_list != nullptr) { + SSL_CTX_set_client_CA_list(ctx_, ca_list); + } else { + // Failed to load client CA list, but we continue since + // SSL_CTX_load_verify_locations already succeeded and + // certificate verification will still work + last_ssl_error_ = static_cast(ERR_get_error()); + } + } + SSL_CTX_set_verify( ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } @@ -10729,6 +10824,15 @@ inline SSLServer::SSLServer(X509 *cert, EVP_PKEY *private_key, } else if (client_ca_cert_store) { SSL_CTX_set_cert_store(ctx_, client_ca_cert_store); + // Extract CA names from the store and set them as the client CA list + auto ca_list = extract_ca_names_from_x509_store(client_ca_cert_store); + if (ca_list) { + SSL_CTX_set_client_CA_list(ctx_, ca_list); + } else { + // Failed to extract CA names, record the error + last_ssl_error_ = static_cast(ERR_get_error()); + } + SSL_CTX_set_verify( ctx_, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, nullptr); } @@ -10809,6 +10913,44 @@ inline bool SSLServer::process_and_close_socket(socket_t sock) { return ret; } +inline STACK_OF(X509_NAME) * SSLServer::extract_ca_names_from_x509_store( + X509_STORE *store) { + if (!store) { return nullptr; } + + auto ca_list = sk_X509_NAME_new_null(); + if (!ca_list) { return nullptr; } + + // Get all objects from the store + auto objs = X509_STORE_get0_objects(store); + if (!objs) { + sk_X509_NAME_free(ca_list); + return nullptr; + } + + // Iterate through objects and extract certificate subject names + for (int i = 0; i < sk_X509_OBJECT_num(objs); i++) { + auto obj = sk_X509_OBJECT_value(objs, i); + if (X509_OBJECT_get_type(obj) == X509_LU_X509) { + auto cert = X509_OBJECT_get0_X509(obj); + if (cert) { + auto subject = X509_get_subject_name(cert); + if (subject) { + auto name_dup = X509_NAME_dup(subject); + if (name_dup) { sk_X509_NAME_push(ca_list, name_dup); } + } + } + } + } + + // If no names were extracted, free the list and return nullptr + if (sk_X509_NAME_num(ca_list) == 0) { + sk_X509_NAME_free(ca_list); + return nullptr; + } + + return ca_list; +} + // SSL HTTP client implementation inline SSLClient::SSLClient(const std::string &host) : SSLClient(host, 443, std::string(), std::string()) {} @@ -10889,7 +11031,8 @@ inline void SSLClient::set_ca_cert_store(X509_STORE *ca_cert_store) { if (ca_cert_store) { if (ctx_) { if (SSL_CTX_get_cert_store(ctx_) != ca_cert_store) { - // Free memory allocated for old cert and use new store `ca_cert_store` + // Free memory allocated for old cert and use new store + // `ca_cert_store` SSL_CTX_set_cert_store(ctx_, ca_cert_store); ca_cert_store_ = ca_cert_store; } @@ -10911,10 +11054,15 @@ inline long SSLClient::get_openssl_verify_result() const { inline SSL_CTX *SSLClient::ssl_context() const { return ctx_; } inline bool SSLClient::create_and_connect_socket(Socket &socket, Error &error) { - return is_valid() && ClientImpl::create_and_connect_socket(socket, error); + if (!is_valid()) { + error = Error::SSLConnection; + return false; + } + return ClientImpl::create_and_connect_socket(socket, error); } -// Assumes that socket_mutex_ is locked and that there are no requests in flight +// Assumes that socket_mutex_ is locked and that there are no requests in +// flight inline bool SSLClient::connect_with_proxy( Socket &socket, std::chrono::time_point start_time, @@ -11128,6 +11276,11 @@ inline bool SSLClient::initialize_ssl(Socket &socket, Error &error) { return true; } + if (ctx_ == nullptr) { + error = Error::SSLConnection; + last_openssl_error_ = ERR_get_error(); + } + shutdown_socket(socket); close_socket(socket); return false; @@ -11221,21 +11374,22 @@ SSLClient::verify_host_with_subject_alt_name(X509 *server_cert) const { for (decltype(count) i = 0; i < count && !dsn_matched; i++) { auto val = sk_GENERAL_NAME_value(alt_names, i); - if (val->type == type) { - auto name = - reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); - auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); - - switch (type) { - case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; - - case GEN_IPADD: - if (!memcmp(&addr6, name, addr_len) || - !memcmp(&addr, name, addr_len)) { - ip_matched = true; - } - break; + if (!val || val->type != type) { continue; } + + auto name = + reinterpret_cast(ASN1_STRING_get0_data(val->d.ia5)); + if (name == nullptr) { continue; } + + auto name_len = static_cast(ASN1_STRING_length(val->d.ia5)); + + switch (type) { + case GEN_DNS: dsn_matched = check_host_name(name, name_len); break; + + case GEN_IPADD: + if (!memcmp(&addr6, name, addr_len) || !memcmp(&addr, name, addr_len)) { + ip_matched = true; } + break; } }