diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 4d254afcd6..6524cae83a 100755 --- a/scripts/sync_vendor.py +++ b/scripts/sync_vendor.py @@ -5,7 +5,7 @@ import os import sys import subprocess -HTTPLIB_VERSION = "refs/tags/v0.38.0" +HTTPLIB_VERSION = "refs/tags/v0.39.0" vendor = { "https://github.com/nlohmann/json/releases/latest/download/json.hpp": "vendor/nlohmann/json.hpp", diff --git a/vendor/cpp-httplib/httplib.cpp b/vendor/cpp-httplib/httplib.cpp index fa0718218e..caa87abff6 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -142,6 +142,12 @@ SSEClient &SSEClient::set_max_reconnect_attempts(int n) { return *this; } +SSEClient &SSEClient::set_headers(const Headers &headers) { + std::lock_guard lock(headers_mutex_); + headers_ = headers; + return *this; +} + bool SSEClient::is_connected() const { return connected_.load(); } const std::string &SSEClient::last_event_id() const { @@ -220,7 +226,11 @@ void SSEClient::run_event_loop() { while (running_.load()) { // Build headers, including Last-Event-ID if we have one - auto request_headers = headers_; + Headers request_headers; + { + std::lock_guard lock(headers_mutex_); + request_headers = headers_; + } if (!last_event_id_.empty()) { request_headers.emplace("Last-Event-ID", last_event_id_); } @@ -239,19 +249,19 @@ void SSEClient::run_event_loop() { continue; } - if (result.status() != 200) { + if (result.status() != StatusCode::OK_200) { connected_.store(false); - // For certain errors, don't reconnect - if (result.status() == 204 || // No Content - server wants us to stop - result.status() == 404 || // Not Found - result.status() == 401 || // Unauthorized - result.status() == 403) { // Forbidden - if (on_error_) { on_error_(Error::Connection); } + if (on_error_) { on_error_(Error::Connection); } + + // For certain errors, don't reconnect. + // Note: 401 is intentionally absent so that handlers can refresh + // credentials via set_headers() and let the client reconnect. + if (result.status() == StatusCode::NoContent_204 || + result.status() == StatusCode::NotFound_404 || + result.status() == StatusCode::Forbidden_403) { break; } - if (on_error_) { on_error_(Error::Connection); } - if (!should_reconnect(reconnect_count)) { break; } wait_for_reconnect(); reconnect_count++; @@ -9168,18 +9178,11 @@ void ClientImpl::setup_redirect_client(ClientType &client) { client.set_compress(compress_); client.set_decompress(decompress_); - // Copy authentication settings BEFORE proxy setup - if (!basic_auth_username_.empty()) { - client.set_basic_auth(basic_auth_username_, basic_auth_password_); - } - if (!bearer_token_auth_token_.empty()) { - client.set_bearer_token_auth(bearer_token_auth_token_); - } -#ifdef CPPHTTPLIB_SSL_ENABLED - if (!digest_auth_username_.empty()) { - client.set_digest_auth(digest_auth_username_, digest_auth_password_); - } -#endif + // NOTE: Authentication credentials (basic auth, bearer token, digest auth) + // are intentionally NOT copied to the redirect client. Per RFC 9110 Section + // 15.4, credentials must not be forwarded when redirecting to a different + // host. This function is only called for cross-host redirects; same-host + // redirects are handled directly in ClientImpl::redirect(). // Setup proxy configuration (CRITICAL ORDER - proxy must be set // before proxy auth) @@ -11425,7 +11428,8 @@ void Client::set_follow_location(bool on) { void Client::set_path_encode(bool on) { cli_->set_path_encode(on); } -[[deprecated("Use set_path_encode instead")]] +[[deprecated("Use set_path_encode() instead. " + "This function will be removed by v1.0.0.")]] void Client::set_url_encode(bool on) { cli_->set_path_encode(on); } @@ -16330,9 +16334,10 @@ bool WebSocketClient::connect() { Error error; sock_ = detail::create_client_socket( - host_, std::string(), port_, AF_UNSPEC, false, false, nullptr, 5, 0, + host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_, + socket_options_, connection_timeout_sec_, connection_timeout_usec_, read_timeout_sec_, read_timeout_usec_, write_timeout_sec_, - write_timeout_usec_, std::string(), error); + write_timeout_usec_, interface_, error); if (sock_ == INVALID_SOCKET) { return false; } @@ -16398,6 +16403,27 @@ void WebSocketClient::set_websocket_ping_interval(time_t sec) { websocket_ping_interval_sec_ = sec; } +void WebSocketClient::set_tcp_nodelay(bool on) { tcp_nodelay_ = on; } + +void WebSocketClient::set_address_family(int family) { + address_family_ = family; +} + +void WebSocketClient::set_ipv6_v6only(bool on) { ipv6_v6only_ = on; } + +void WebSocketClient::set_socket_options(SocketOptions socket_options) { + socket_options_ = std::move(socket_options); +} + +void WebSocketClient::set_connection_timeout(time_t sec, time_t usec) { + connection_timeout_sec_ = sec; + connection_timeout_usec_ = usec; +} + +void WebSocketClient::set_interface(const std::string &intf) { + interface_ = intf; +} + #ifdef CPPHTTPLIB_SSL_ENABLED void WebSocketClient::set_ca_cert_path(const std::string &path) { diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index 6ec949ac51..ce1681fcbe 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.38.0" -#define CPPHTTPLIB_VERSION_NUM "0x002600" +#define CPPHTTPLIB_VERSION "0.39.0" +#define CPPHTTPLIB_VERSION_NUM "0x002700" #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 @@ -1001,8 +1001,8 @@ private: protected: std::streamsize xsputn(const char *s, std::streamsize n) override { - sink_.write(s, static_cast(n)); - return n; + if (sink_.write(s, static_cast(n))) { return n; } + return 0; } private: @@ -1058,9 +1058,12 @@ make_file_provider(const std::string &name, const std::string &filepath, inline std::pair make_file_body(const std::string &filepath) { - std::ifstream f(filepath, std::ios::binary | std::ios::ate); - if (!f) { return {0, ContentProvider{}}; } - auto size = static_cast(f.tellg()); + size_t size = 0; + { + std::ifstream f(filepath, std::ios::binary | std::ios::ate); + if (!f) { return {0, ContentProvider{}}; } + size = static_cast(f.tellg()); + } ContentProvider provider = [filepath](size_t offset, size_t length, DataSink &sink) -> bool { @@ -1882,7 +1885,8 @@ private: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: - [[deprecated("Use ssl_backend_error() instead")]] + [[deprecated("Use ssl_backend_error() instead. " + "This function will be removed by v1.0.0.")]] uint64_t ssl_openssl_error() const { return ssl_backend_error_; } @@ -2362,13 +2366,16 @@ protected: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: - [[deprecated("Use load_ca_cert_store() instead")]] + [[deprecated("Use load_ca_cert_store() instead. " + "This function will be removed by v1.0.0.")]] void set_ca_cert_store(X509_STORE *ca_cert_store); - [[deprecated("Use tls::create_ca_store() instead")]] + [[deprecated("Use tls::create_ca_store() instead. " + "This function will be removed by v1.0.0.")]] X509_STORE *create_ca_cert_store(const char *ca_cert, std::size_t size) const; - [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead")]] + [[deprecated("Use set_server_certificate_verifier(VerifyCallback) instead. " + "This function will be removed by v1.0.0.")]] virtual void set_server_certificate_verifier( std::function verifier); #endif @@ -2597,14 +2604,17 @@ private: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: - [[deprecated("Use tls_context() instead")]] + [[deprecated("Use tls_context() instead. " + "This function will be removed by v1.0.0.")]] SSL_CTX *ssl_context() const; - [[deprecated("Use set_session_verifier(session_t) instead")]] + [[deprecated("Use set_session_verifier(session_t) instead. " + "This function will be removed by v1.0.0.")]] void set_server_certificate_verifier( std::function verifier); - [[deprecated("Use Result::ssl_backend_error() instead")]] + [[deprecated("Use Result::ssl_backend_error() instead. " + "This function will be removed by v1.0.0.")]] long get_verify_result() const; #endif }; @@ -2656,18 +2666,22 @@ private: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: [[deprecated("Use SSLServer(PemMemory) or " - "SSLServer(ContextSetupCallback) instead")]] + "SSLServer(ContextSetupCallback) instead. " + "This constructor will be removed by v1.0.0.")]] SSLServer(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); - [[deprecated("Use SSLServer(ContextSetupCallback) instead")]] + [[deprecated("Use SSLServer(ContextSetupCallback) instead. " + "This constructor will be removed by v1.0.0.")]] SSLServer( const std::function &setup_ssl_ctx_callback); - [[deprecated("Use tls_context() instead")]] + [[deprecated("Use tls_context() instead. " + "This function will be removed by v1.0.0.")]] SSL_CTX *ssl_context() const; - [[deprecated("Use update_certs_pem() instead")]] + [[deprecated("Use update_certs_pem() instead. " + "This function will be removed by v1.0.0.")]] void update_certs(X509 *cert, EVP_PKEY *private_key, X509_STORE *client_ca_cert_store = nullptr); #endif @@ -2752,18 +2766,22 @@ private: #ifdef CPPHTTPLIB_OPENSSL_SUPPORT public: - [[deprecated("Use SSLClient(host, port, PemMemory) instead")]] + [[deprecated("Use SSLClient(host, port, PemMemory) instead. " + "This constructor will be removed by v1.0.0.")]] explicit SSLClient(const std::string &host, int port, X509 *client_cert, EVP_PKEY *client_key, const std::string &private_key_password = std::string()); - [[deprecated("Use Result::ssl_backend_error() instead")]] + [[deprecated("Use Result::ssl_backend_error() instead. " + "This function will be removed by v1.0.0.")]] long get_verify_result() const; - [[deprecated("Use tls_context() instead")]] + [[deprecated("Use tls_context() instead. " + "This function will be removed by v1.0.0.")]] SSL_CTX *ssl_context() const; - [[deprecated("Use set_session_verifier(session_t) instead")]] + [[deprecated("Use set_session_verifier(session_t) instead. " + "This function will be removed by v1.0.0.")]] void set_server_certificate_verifier( std::function verifier) override; @@ -3641,6 +3659,9 @@ public: SSEClient &set_reconnect_interval(int ms); SSEClient &set_max_reconnect_attempts(int n); + // Update headers (thread-safe) + SSEClient &set_headers(const Headers &headers); + // State accessors bool is_connected() const; const std::string &last_event_id() const; @@ -3665,6 +3686,7 @@ private: Client &client_; std::string path_; Headers headers_; + mutable std::mutex headers_mutex_; // Callbacks MessageHandler on_message_; @@ -3785,6 +3807,12 @@ public: void set_read_timeout(time_t sec, time_t usec = 0); void set_write_timeout(time_t sec, time_t usec = 0); void set_websocket_ping_interval(time_t sec); + void set_tcp_nodelay(bool on); + void set_address_family(int family); + void set_ipv6_v6only(bool on); + void set_socket_options(SocketOptions socket_options); + void set_connection_timeout(time_t sec, time_t usec = 0); + void set_interface(const std::string &intf); #ifdef CPPHTTPLIB_SSL_ENABLED void set_ca_cert_path(const std::string &path); @@ -3810,6 +3838,13 @@ private: time_t write_timeout_usec_ = CPPHTTPLIB_CLIENT_WRITE_TIMEOUT_USECOND; time_t websocket_ping_interval_sec_ = CPPHTTPLIB_WEBSOCKET_PING_INTERVAL_SECOND; + int address_family_ = AF_UNSPEC; + bool tcp_nodelay_ = CPPHTTPLIB_TCP_NODELAY; + bool ipv6_v6only_ = CPPHTTPLIB_IPV6_V6ONLY; + SocketOptions socket_options_ = nullptr; + time_t connection_timeout_sec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_SECOND; + time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND; + std::string interface_; #ifdef CPPHTTPLIB_SSL_ENABLED bool is_ssl_ = false;