From e23e9440eb0c625c30d6c40266e9335071a4debc Mon Sep 17 00:00:00 2001 From: "Alessandro de Oliveira Faria (A.K.A.CABELO)" Date: Mon, 10 Aug 2026 04:57:45 -0300 Subject: [PATCH] vendor : update cpp-httplib to 0.53.0 (#26821) --- scripts/sync_vendor.py | 2 +- vendor/cpp-httplib/httplib.cpp | 763 +++++++++++++++++++++------------ vendor/cpp-httplib/httplib.h | 105 ++++- 3 files changed, 577 insertions(+), 293 deletions(-) diff --git a/scripts/sync_vendor.py b/scripts/sync_vendor.py index 98840ac724..b8330c5753 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.52.0" +HTTPLIB_VERSION = "refs/tags/v0.53.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 30e2de8962..3b687ff4f2 100644 --- a/vendor/cpp-httplib/httplib.cpp +++ b/vendor/cpp-httplib/httplib.cpp @@ -385,8 +385,7 @@ void set_verify_client(ctx_t ctx, bool require); // Session management session_t create_session(ctx_t ctx, socket_t sock); void free_session(session_t session); -bool set_sni(session_t session, const char *hostname); -bool set_hostname(session_t session, const char *hostname); +bool set_sni(session_t session, const char *hostname, bool verify_hostname); // Handshake (non-blocking capable) TlsError connect(session_t session); @@ -1877,6 +1876,39 @@ int shutdown_socket(socket_t sock) noexcept { #endif } +// Half-closes the write side and drains any in-flight/queued bytes before +// the final shutdown+close. Closing with unread data in the receive queue +// (or bytes arriving after the receive side is closed) makes the stack send +// an abortive RST instead of a graceful FIN, which can make the peer see the +// response as a failed read even though it was fully written. +void drain_and_close_socket(socket_t sock) noexcept { +#ifdef _WIN32 + shutdown(sock, SD_SEND); +#else + shutdown(sock, SHUT_WR); +#endif + + char buf[CPPHTTPLIB_RECV_BUFSIZ]; + size_t total = 0; + const auto deadline = std::chrono::steady_clock::now() + + std::chrono::milliseconds(100); // bound #1 + + while (total < size_t(1024u * 1024u)) { // bound #2 + const auto remaining = + std::chrono::duration_cast( + deadline - std::chrono::steady_clock::now()) + .count(); + if (remaining <= 0) { break; } + if (select_read(sock, 0, static_cast(remaining)) <= 0) { break; } + const auto n = read_socket(sock, buf, sizeof(buf), CPPHTTPLIB_RECV_FLAGS); + if (n <= 0) { break; } + total += static_cast(n); + } + + shutdown_socket(sock); + close_socket(sock); +} + std::string escape_abstract_namespace_unix_domain(const std::string &s) { if (s.size() > 1 && s[0] == '\0') { auto ret = s; @@ -3320,42 +3352,94 @@ bool read_headers(Stream &strm, Headers &headers) { return true; } +bool parse_status_line(const char *line, std::string &version, + int &status, std::string &reason) { +#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); +#else + thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); +#endif + + std::cmatch m; + if (!std::regex_match(line, m, re)) { return false; } + version = std::string(m[1]); + status = std::stoi(std::string(m[2])); + reason = std::string(m[3]); + return true; +} + +// Everything WebSocketClient::connect() reports about the upgrade exchange. +// status stays -1 until a status line is parsed, mirroring stream::Result. +struct WebSocketUpgradeResponse { + Error error = Error::Success; + int status = -1; + Headers headers; + std::string selected_subprotocol; +}; + bool read_websocket_upgrade_response(Stream &strm, const std::string &expected_accept, - std::string &selected_subprotocol) { + WebSocketUpgradeResponse &upgrade) { // Read status line const auto bufsiz = 2048; char buf[bufsiz]; stream_line_reader line_reader(strm, buf, bufsiz); - if (!line_reader.getline()) { return false; } + if (!line_reader.getline()) { + upgrade.error = Error::Read; + return false; + } - // Check for "HTTP/1.1 101" - auto line = std::string(line_reader.ptr(), line_reader.size()); - if (line.find("HTTP/1.1 101") == std::string::npos) { return false; } + std::string version; + std::string reason; + if (!parse_status_line(line_reader.ptr(), version, upgrade.status, reason)) { + upgrade.error = Error::WebSocketHandshake; + return false; + } - // Parse headers using existing read_headers - Headers headers; - if (!read_headers(strm, headers)) { return false; } + // Read the headers even for a rejection so the caller can see why the + // server refused the upgrade. A non-101 response may carry a body; it is + // deliberately left unread since the caller closes the socket right away. + if (!read_headers(strm, upgrade.headers)) { + upgrade.error = Error::Read; + return false; + } + + const auto &headers = upgrade.headers; + + if (upgrade.status != StatusCode::SwitchingProtocol_101) { + upgrade.error = Error::WebSocketHandshake; + return false; + } // Verify Upgrade: websocket (case-insensitive) auto upgrade_it = headers.find("Upgrade"); - if (upgrade_it == headers.end()) { return false; } - auto upgrade_val = case_ignore::to_lower(upgrade_it->second); - if (upgrade_val != "websocket") { return false; } + if (upgrade_it == headers.end() || + case_ignore::to_lower(upgrade_it->second) != "websocket") { + upgrade.error = Error::WebSocketHandshake; + return false; + } // Verify Connection header contains "Upgrade" (case-insensitive) auto connection_it = headers.find("Connection"); - if (connection_it == headers.end()) { return false; } - auto connection_val = case_ignore::to_lower(connection_it->second); - if (connection_val.find("upgrade") == std::string::npos) { return false; } + if (connection_it == headers.end() || + case_ignore::to_lower(connection_it->second).find("upgrade") == + std::string::npos) { + upgrade.error = Error::WebSocketHandshake; + return false; + } // Verify Sec-WebSocket-Accept header value auto it = headers.find("Sec-WebSocket-Accept"); - if (it == headers.end() || it->second != expected_accept) { return false; } + if (it == headers.end() || it->second != expected_accept) { + upgrade.error = Error::WebSocketHandshake; + return false; + } // Extract negotiated subprotocol auto proto_it = headers.find("Sec-WebSocket-Protocol"); - if (proto_it != headers.end()) { selected_subprotocol = proto_it->second; } + if (proto_it != headers.end()) { + upgrade.selected_subprotocol = proto_it->second; + } return true; } @@ -5105,7 +5189,7 @@ bool is_field_valid(const std::string &name, const std::string &value) { } // namespace fields bool perform_websocket_handshake(Stream &strm, Request &req, - std::string &selected_subprotocol) { + WebSocketUpgradeResponse &upgrade) { // Generate random Sec-WebSocket-Key thread_local std::mt19937 rng(std::random_device{}()); std::string key_bytes(16, '\0'); @@ -5130,20 +5214,26 @@ bool perform_websocket_handshake(Stream &strm, Request &req, // and would emit one small write per header. BufferStream bstrm; - if (write_request_line(bstrm, req.method, req.path) < 0) { return false; } + if (write_request_line(bstrm, req.method, req.path) < 0) { + upgrade.error = Error::Write; + return false; + } auto error = Error::Success; if (!check_and_write_headers(bstrm, req.headers, write_headers, error)) { + upgrade.error = error; return false; } const auto &data = bstrm.get_buffer(); - if (!write_data(strm, data.data(), data.size())) { return false; } + if (!write_data(strm, data.data(), data.size())) { + upgrade.error = Error::Write; + return false; + } // Verify 101 response and Sec-WebSocket-Accept header auto expected_accept = websocket_accept_key(client_key); - return read_websocket_upgrade_response(strm, expected_accept, - selected_subprotocol); + return read_websocket_upgrade_response(strm, expected_accept, upgrade); } bool is_ip_address(const std::string &host) { @@ -5637,50 +5727,144 @@ bool load_client_ca_config(tls::ctx_t ctx, return ret; } -bool setup_client_tls_session(const std::string &host, tls::ctx_t ctx, - tls::session_t &session, socket_t sock, - bool server_certificate_verification, - time_t timeout_sec, time_t timeout_usec) { +// The parts of session setup that only SSLClient needs, plus the handful +// WebSocketClient also exposes; everything else takes the defaults, which is +// what keeps the two clients on one implementation. +struct ClientTlsSessionOptions { + // Both SSLClient and WebSocketClient expose this independently of + // certificate verification. + bool server_hostname_verification = true; + std::function session_verifier; + // When non-null, guards session creation against concurrent use of the + // context. A WebSocketClient is not safe to use from several threads to + // begin with, so it passes nothing. + std::mutex *ctx_mutex = nullptr; +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + // The caller decides whether Schannel has anything to say about this + // connection; see SSLClient::initialize_ssl(). + bool windows_cert_verification = false; +#endif +}; + +// Filled in on failure for callers that report error details. +struct ClientTlsSessionError { + Error error = Error::Success; + int ssl_error = 0; + uint64_t backend_error = 0; +}; + +// Establishes a client TLS session on an already connected socket. On failure +// the session is left for the caller to free: SSLClient frees it right away, +// WebSocketClient keeps it in a member that shutdown_and_close() cleans up. +bool setup_client_tls_session( + const std::string &host, tls::ctx_t ctx, tls::session_t &session, + socket_t sock, bool server_certificate_verification, time_t timeout_sec, + time_t timeout_usec, ClientTlsSessionError *out_error = nullptr, + const ClientTlsSessionOptions &options = ClientTlsSessionOptions()) { using namespace tls; - if (!ctx) { return false; } + auto fail = [&](Error error, int ssl_error, uint64_t backend_error) { + if (out_error) { + out_error->error = error; + out_error->ssl_error = ssl_error; + out_error->backend_error = backend_error; + } + return false; + }; - bool is_ip = is_ip_address(host); + if (!ctx) { + session = nullptr; + return fail(Error::SSLConnection, 0, 0); + } #if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) - // Chain verification happens during the handshake even for IP hosts; the - // certificate identity is verified post-handshake via verify_hostname() + // Mbed TLS and wolfSSL need the verification mode set explicitly; OpenSSL + // uses SSL_VERIFY_NONE and does all verification post-handshake. Chain + // verification happens during the handshake even for IP hosts; the + // certificate identity is verified post-handshake via verify_hostname(). set_verify_client(ctx, server_certificate_verification); #endif - session = create_session(ctx, sock); - if (!session) { return false; } + { + std::unique_lock guard; + if (options.ctx_mutex) { + guard = std::unique_lock(*options.ctx_mutex); + } + session = create_session(ctx, sock); + } + if (!session) { return fail(Error::SSLConnection, 0, get_error()); } - // RFC 6066: SNI must not be set for IP addresses. On Mbed TLS and wolfSSL - // set_hostname also sets SNI, so it must be skipped for IP hosts as well; - // their identity is checked post-handshake below instead. - if (!is_ip) { - if (server_certificate_verification) { - set_hostname(session, host.c_str()); - } else { - set_sni(session, host.c_str()); + // RFC 6066: SNI must not be set for IP addresses; skip it for IP hosts, so + // their identity is checked post-handshake below instead. On Mbed TLS and + // wolfSSL, set_sni also drives handshake-time hostname verification, so + // options.server_hostname_verification is threaded through here. + if (!is_ip_address(host)) { + if (!set_sni(session, host.c_str(), options.server_hostname_verification)) { + return fail(Error::SSLConnection, 0, get_error()); } } - if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) { - return false; + TlsError tls_err; + if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, + &tls_err)) { + auto error = Error::SSLConnection; + if (tls_err.code == ErrorCode::CertVerifyFailed) { + error = Error::SSLServerVerification; + } else if (tls_err.code == ErrorCode::HostnameMismatch) { + error = Error::SSLServerHostnameVerification; + } + return fail(error, static_cast(tls_err.code), tls_err.backend_code); } - if (server_certificate_verification) { - if (get_verify_result(session) != 0) { return false; } + auto verification_status = SSLVerifierResponse::NoDecisionMade; + if (options.session_verifier) { + verification_status = options.session_verifier(session); + } + + if (verification_status == SSLVerifierResponse::CertificateRejected) { + return fail(Error::SSLServerVerification, 0, get_error()); + } + + if (verification_status == SSLVerifierResponse::NoDecisionMade && + server_certificate_verification) { + auto verify_result = get_verify_result(session); + if (verify_result != 0) { + return fail(Error::SSLServerVerification, 0, + static_cast(verify_result)); + } + + auto server_cert = get_peer_cert(session); + if (!server_cert) { + return fail(Error::SSLServerVerification, 0, get_error()); + } + auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); // Identity check against the peer certificate, post-handshake for all - // backends (same as SSLClient). For IP hosts this is the only identity - // verification since no hostname is bound during the handshake. - auto server_cert = get_peer_cert(session); - if (!server_cert) { return false; } - auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); - if (!verify_hostname(server_cert, host.c_str())) { return false; } + // backends. For IP hosts this is the only identity verification, since no + // hostname is bound during the handshake. + if (options.server_hostname_verification) { + if (!verify_hostname(server_cert, host.c_str())) { + return fail(Error::SSLServerHostnameVerification, 0, + hostname_mismatch_code()); + } + } + +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + // Additional Windows Schannel verification. + // This provides real-time certificate validation with Windows Update + // integration, working with both OpenSSL and MbedTLS backends. + if (options.windows_cert_verification) { + std::vector der; + if (get_cert_der(server_cert, der)) { + uint64_t wincrypt_error = 0; + if (!verify_cert_with_windows_schannel( + der, host, options.server_hostname_verification, + wincrypt_error)) { + return fail(Error::SSLServerVerification, 0, wincrypt_error); + } + } + } +#endif } return true; @@ -5833,6 +6017,7 @@ std::string to_string(const Error error) { case Error::HTTPParsing: return "HTTP parsing failed"; case Error::InvalidRangeHeader: return "Invalid Range header"; case Error::UnsupportedContentEncoding: return "Unsupported Content-Encoding"; + case Error::WebSocketHandshake: return "WebSocket handshake failed"; default: break; } @@ -7039,6 +7224,26 @@ make_host_and_port_string_always_port(const std::string &host, int port) { return prepare_host_string(host) + ":" + std::to_string(port); } +// Value for the Host header a client sends when the caller supplied none. +// Only the value: callers decide where in their header list it goes. +std::string make_default_host_header_value(const std::string &host, + int port, bool is_ssl, + int address_family) { + if (address_family == AF_UNIX) { return "localhost"; } + return make_host_and_port_string(host, port, is_ssl); +} + +void add_default_user_agent_header(Request &req) { +#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT + if (!req.has_header("User-Agent")) { + req.set_header("User-Agent", + std::string("cpp-httplib/") + CPPHTTPLIB_VERSION); + } +#else + (void)req; +#endif +} + bool parse_no_proxy_entry(const std::string &token, NoProxyEntry &out); NormalizedTarget normalize_target(const std::string &host); bool ip_in_cidr(const IPBytes &ip, const IPBytes &net, int prefix_bits); @@ -8094,8 +8299,14 @@ bool Server::read_content_core( bool Server::handle_file_request(Request &req, Response &res) { for (const auto &entry : base_dirs_) { - // Prefix match - if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point)) { + // Prefix match, on a path segment boundary. A mount point of "/mount" + // covers "/mount" and "/mount/...", but must not swallow "/mountdir/...". + // One that already ends in '/' (the root mount among them) carries its own + // boundary; set_mount_point() guarantees the mount point is not empty. + if (!req.path.compare(0, entry.mount_point.size(), entry.mount_point) && + (entry.mount_point.back() == '/' || + req.path.size() == entry.mount_point.size() || + req.path[entry.mount_point.size()] == '/')) { std::string sub_path = "/" + req.path.substr(entry.mount_point.size()); if (detail::is_valid_path(sub_path)) { auto path = entry.base_dir + sub_path; @@ -8985,8 +9196,7 @@ bool Server::process_and_close_socket(socket_t sock) { nullptr, &websocket_upgraded); }); - detail::shutdown_socket(sock); - detail::close_socket(sock); + detail::drain_and_close_socket(sock); return ret; } @@ -9198,29 +9408,20 @@ bool ClientImpl::read_response_line(Stream &strm, const Request &req, if (!line_reader.getline()) { return false; } -#ifdef CPPHTTPLIB_ALLOW_LF_AS_LINE_TERMINATOR - thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r?\n"); -#else - thread_local const std::regex re("(HTTP/1\\.[01]) (\\d{3})(?: (.*?))?\r\n"); -#endif - - std::cmatch m; - if (!std::regex_match(line_reader.ptr(), m, re)) { + if (!detail::parse_status_line(line_reader.ptr(), res.version, res.status, + res.reason)) { return req.method == "CONNECT"; } - res.version = std::string(m[1]); - res.status = std::stoi(std::string(m[2])); - res.reason = std::string(m[3]); // Ignore '100 Continue' (only when not using Expect: 100-continue explicitly) while (skip_100_continue && res.status == StatusCode::Continue_100) { if (!line_reader.getline()) { return false; } // CRLF if (!line_reader.getline()) { return false; } // next response line - if (!std::regex_match(line_reader.ptr(), m, re)) { return false; } - res.version = std::string(m[1]); - res.status = std::stoi(std::string(m[2])); - res.reason = std::string(m[3]); + if (!detail::parse_status_line(line_reader.ptr(), res.version, res.status, + res.reason)) { + return false; + } } return true; @@ -9356,12 +9557,9 @@ void ClientImpl::prepare_default_headers(Request &r, bool for_stream, // RFC 9110 5.3 recommends sending control data such as Host first, so // prepend it rather than appending it after the caller's own fields. if (!r.has_header("Host")) { - if (address_family_ == AF_UNIX) { - r.headers.emplace_front("Host", "localhost"); - } else { - r.headers.emplace_front( - "Host", detail::make_host_and_port_string(host_, port_, is_ssl())); - } + r.headers.emplace_front( + "Host", detail::make_default_host_header_value(host_, port_, is_ssl(), + address_family_)); } if (!r.has_header("Accept")) { r.headers.emplace("Accept", "*/*"); } @@ -9383,12 +9581,7 @@ void ClientImpl::prepare_default_headers(Request &r, bool for_stream, r.set_header("Accept-Encoding", accept_encoding); } -#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT - if (!r.has_header("User-Agent")) { - auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; - r.set_header("User-Agent", agent); - } -#endif + detail::add_default_user_agent_header(r); } if (!r.body.empty()) { @@ -12684,6 +12877,8 @@ void SSLClient::load_ca_cert_store(const char *ca_cert, bool SSLClient::load_certs() { auto ret = true; + // call_once rather than the plain flag WebSocketClient::create_stream() uses: + // one client is shared across concurrent requests here. std::call_once(initialize_cert_, [&]() { std::lock_guard guard(ctx_mutex_); @@ -12697,8 +12892,6 @@ bool SSLClient::load_certs() { } bool SSLClient::initialize_ssl(Socket &socket, Error &error) { - using namespace tls; - // Load CA certificates if server verification is enabled if (server_certificate_verification_) { if (!load_certs()) { @@ -12708,134 +12901,40 @@ bool SSLClient::initialize_ssl(Socket &socket, Error &error) { } } - bool is_ip = detail::is_ip_address(host_); - -#if defined(CPPHTTPLIB_MBEDTLS_SUPPORT) || defined(CPPHTTPLIB_WOLFSSL_SUPPORT) - // MbedTLS/wolfSSL need explicit verification mode (OpenSSL uses - // SSL_VERIFY_NONE by default and performs all verification post-handshake). - // Chain verification happens during the handshake even for IP hosts; the - // certificate identity is verified post-handshake via verify_hostname(). - set_verify_client(ctx_, server_certificate_verification_); + detail::ClientTlsSessionOptions options; + options.server_hostname_verification = server_hostname_verification_; + options.session_verifier = session_verifier_; + options.ctx_mutex = &ctx_mutex_; +#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE + // Skip Schannel when a custom CA cert is specified, as the Windows + // certificate store would not know about user-provided CA certificates. + // Also skip when system CA trust is explicitly disabled. + options.windows_cert_verification = + enable_windows_cert_verification_ && + system_ca_mode_ != SystemCAMode::Disabled && ca_cert_file_path_.empty() && + ca_cert_dir_path_.empty() && ca_cert_pem_.empty() && !ca_cert_store_set_; #endif - // Create TLS session - session_t session = nullptr; - { - std::lock_guard guard(ctx_mutex_); - session = create_session(ctx_, socket.sock); - } - - if (!session) { - error = Error::SSLConnection; - last_backend_error_ = get_error(); - return false; - } + tls::session_t session = nullptr; // Use scope_exit to ensure session is freed on error paths bool success = false; auto session_guard = detail::scope_exit([&] { - if (!success) { free_session(session); } + if (!success) { tls::free_session(session); } }); - // Set SNI extension (skip for IP addresses per RFC 6066). - // On MbedTLS, set_sni also enables hostname verification internally. - // On OpenSSL, set_sni only sets SNI; verification is done post-handshake. - if (!is_ip) { - if (!set_sni(session, host_.c_str())) { - error = Error::SSLConnection; - last_backend_error_ = get_error(); - return false; - } - } - - // Perform non-blocking TLS handshake with timeout - TlsError tls_err; - if (!connect_nonblocking(session, socket.sock, connection_timeout_sec_, - connection_timeout_usec_, &tls_err)) { - last_ssl_error_ = static_cast(tls_err.code); - last_backend_error_ = tls_err.backend_code; - if (tls_err.code == ErrorCode::CertVerifyFailed) { - error = Error::SSLServerVerification; - } else if (tls_err.code == ErrorCode::HostnameMismatch) { - error = Error::SSLServerHostnameVerification; - } else { - error = Error::SSLConnection; - } + detail::ClientTlsSessionError tls_error; + if (!detail::setup_client_tls_session( + host_, ctx_, session, socket.sock, server_certificate_verification_, + connection_timeout_sec_, connection_timeout_usec_, &tls_error, + options)) { + error = tls_error.error; + last_ssl_error_ = tls_error.ssl_error; + last_backend_error_ = tls_error.backend_error; output_error_log(error, nullptr); return false; } - // Post-handshake session verifier callback - auto verification_status = SSLVerifierResponse::NoDecisionMade; - if (session_verifier_) { verification_status = session_verifier_(session); } - - if (verification_status == SSLVerifierResponse::CertificateRejected) { - last_backend_error_ = get_error(); - error = Error::SSLServerVerification; - output_error_log(error, nullptr); - return false; - } - - // Default server certificate verification - if (verification_status == SSLVerifierResponse::NoDecisionMade && - server_certificate_verification_) { - verify_result_ = tls::get_verify_result(session); - if (verify_result_ != 0) { - last_backend_error_ = static_cast(verify_result_); - error = Error::SSLServerVerification; - output_error_log(error, nullptr); - return false; - } - - auto server_cert = get_peer_cert(session); - if (!server_cert) { - last_backend_error_ = get_error(); - error = Error::SSLServerVerification; - output_error_log(error, nullptr); - return false; - } - auto cert_guard = detail::scope_exit([&] { free_cert(server_cert); }); - - // Hostname verification (post-handshake for all cases). - // On OpenSSL, verification is always post-handshake (SSL_VERIFY_NONE). - // On MbedTLS, set_sni already enabled hostname verification during - // handshake for non-IP hosts, but this check is still needed for IP - // addresses where SNI is not set. - if (server_hostname_verification_) { - if (!verify_hostname(server_cert, host_.c_str())) { - last_backend_error_ = hostname_mismatch_code(); - error = Error::SSLServerHostnameVerification; - output_error_log(error, nullptr); - return false; - } - } - -#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE - // Additional Windows Schannel verification. - // This provides real-time certificate validation with Windows Update - // integration, working with both OpenSSL and MbedTLS backends. - // Skip when a custom CA cert is specified, as the Windows certificate - // store would not know about user-provided CA certificates. Also skip - // when system CA trust is explicitly disabled. - if (enable_windows_cert_verification_ && - system_ca_mode_ != SystemCAMode::Disabled && - ca_cert_file_path_.empty() && ca_cert_dir_path_.empty() && - ca_cert_pem_.empty() && !ca_cert_store_set_) { - std::vector der; - if (get_cert_der(server_cert, der)) { - uint64_t wincrypt_error = 0; - if (!detail::verify_cert_with_windows_schannel( - der, host_, server_hostname_verification_, wincrypt_error)) { - last_backend_error_ = wincrypt_error; - error = Error::SSLServerVerification; - output_error_log(error, nullptr); - return false; - } - } - } -#endif - } - success = true; socket.ssl = session; return true; @@ -13550,12 +13649,15 @@ void free_session(session_t session) { if (session) { SSL_free(static_cast(session)); } } -bool set_sni(session_t session, const char *hostname) { +bool set_sni(session_t session, const char *hostname, + bool /*verify_hostname*/) { if (!session || !hostname) return false; auto ssl = static_cast(session); - // Set SNI (Server Name Indication) only - does not enable verification + // Set SNI (Server Name Indication) only - does not enable verification. + // OpenSSL never binds identity checking to SNI (that happens post- + // handshake in setup_client_tls_session()), so verify_hostname is unused. #if defined(OPENSSL_IS_BORINGSSL) return SSL_set_tlsext_host_name(ssl, hostname) == 1; #else @@ -13565,32 +13667,6 @@ bool set_sni(session_t session, const char *hostname) { #endif } -bool set_hostname(session_t session, const char *hostname) { - if (!session || !hostname) return false; - - auto ssl = static_cast(session); - - // Enable hostname verification - auto param = SSL_get0_param(ssl); - if (!param) return false; - - if (detail::is_ip_address(hostname)) { - // RFC 6066: SNI must not be set for IP addresses; verify against the - // certificate's IP SANs instead of its DNS names - if (X509_VERIFY_PARAM_set1_ip_asc(param, hostname) != 1) { return false; } - } else { - // Set SNI (Server Name Indication) - if (!set_sni(session, hostname)) { return false; } - - X509_VERIFY_PARAM_set_hostflags(param, - X509_CHECK_FLAG_NO_PARTIAL_WILDCARDS); - if (X509_VERIFY_PARAM_set1_host(param, hostname, 0) != 1) { return false; } - } - - SSL_set_verify(ssl, SSL_VERIFY_PEER, nullptr); - return true; -} - TlsError connect(session_t session) { if (!session) { return TlsError(); } @@ -14219,6 +14295,21 @@ struct MbedTlsSession { unsigned char peeked_byte = 0; bool has_peeked_byte = false; + // Set by set_sni() when the caller disabled hostname verification, so the + // verify callback can clear the CN/SAN mismatch flag while still enforcing + // the rest of the chain (Mbed TLS ties SNI and identity checking together; + // OpenSSL and wolfSSL keep them independent). + bool suppress_hostname_mismatch = false; + + // Copied from the owning MbedTlsContext at creation. set_sni() uses this to + // decide which verify callback to install when hostname verification is + // disabled: mbedtls_verify_callback() when a user callback is genuinely + // wired for this context, or a self-contained one otherwise, so a session + // that never opted into a callback never consults the process-wide + // set_verify_callback() slot (which some other, unrelated client may have + // populated). + bool has_verify_callback = false; + MbedTlsSession() { mbedtls_ssl_init(&ssl); } ~MbedTlsSession() { mbedtls_ssl_free(&ssl); } @@ -14235,7 +14326,8 @@ int &mbedtls_last_error() { } // Helper to map Mbed TLS error to ErrorCode -ErrorCode map_mbedtls_error(int ret, int &out_errno) { +ErrorCode map_mbedtls_error(int ret, int &out_errno, + uint32_t verify_flags) { if (ret == 0) { return ErrorCode::Success; } if (ret == MBEDTLS_ERR_SSL_WANT_READ) { return ErrorCode::WantRead; } if (ret == MBEDTLS_ERR_SSL_WANT_WRITE) { return ErrorCode::WantWrite; } @@ -14248,11 +14340,34 @@ ErrorCode map_mbedtls_error(int ret, int &out_errno) { return ErrorCode::SyscallError; } if (ret == MBEDTLS_ERR_X509_CERT_VERIFY_FAILED) { + // Unlike OpenSSL/wolfSSL, Mbed TLS folds the CN/SAN identity check into + // the handshake's chain verification (see set_sni()); a mismatch there + // is reported the same way as any other verify_flags bit. Report it as + // HostnameMismatch, matching the other backends and the post-handshake + // identity check below, but only when naming is the sole problem - + // if the chain itself is also untrusted/expired/etc., that takes + // priority over the naming detail. + if (verify_flags == static_cast(hostname_mismatch_code())) { + return ErrorCode::HostnameMismatch; + } return ErrorCode::CertVerifyFailed; } return ErrorCode::Fatal; } +// Populates a TlsError from a failed (non-zero) mbedtls_ssl_handshake() +// return value, including the verify-flags-dependent HostnameMismatch +// mapping; shared by connect() and connect_nonblocking() so the +// backend_code policy for that mapping only lives in one place. +void fill_mbedtls_tls_error(TlsError &err, mbedtls_ssl_context &ssl, + int ret) { + auto verify_flags = mbedtls_ssl_get_verify_result(&ssl); + err.code = map_mbedtls_error(ret, err.sys_errno, verify_flags); + err.backend_code = err.code == ErrorCode::HostnameMismatch + ? static_cast(verify_flags) + : static_cast(-ret); +} + // A TLS 1.3 NewSessionTicket (signaled by default on Mbed TLS 4.x) is a // non-fatal notification delivered between records, not an error and not // application data, so I/O calls that see it should just be retried. Kept in @@ -14362,18 +14477,44 @@ int mbedtls_sni_callback(void *p_ctx, mbedtls_ssl_context *ssl, return 0; // Accept any SNI } +void mbedtls_clear_cn_mismatch(uint32_t *flags) { + *flags &= ~static_cast(hostname_mismatch_code()); +} + +// Verify callback used when hostname verification is disabled for a session +// that has no user-supplied verify callback of its own (MbedTlsSession:: +// has_verify_callback is false). Deliberately does not consult +// get_verify_callback(): that slot is process-wide, so reading it here would +// pick up whatever another, unrelated client last installed there. +int mbedtls_mask_hostname_mismatch_callback(void *data, + mbedtls_x509_crt *, int, + uint32_t *flags) { + (void)data; + mbedtls_clear_cn_mismatch(flags); + return 0; +} + int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, int cert_depth, uint32_t *flags); // MbedTLS verify callback wrapper int mbedtls_verify_callback(void *data, mbedtls_x509_crt *crt, int cert_depth, uint32_t *flags) { - auto &callback = get_verify_callback(); - if (!callback) { return 0; } // Continue with default verification - // data points to the MbedTlsSession auto *session = static_cast(data); + // set_sni() disabled hostname verification for this session: drop the + // CN/SAN mismatch flag so it doesn't fail the chain check below, mirroring + // the OpenSSL/wolfSSL backends where identity checking is independent of + // SNI. The final pass/fail decision still comes from the remaining flags + // (or, below, from the user's own verify callback). + if (session && session->suppress_hostname_mismatch) { + mbedtls_clear_cn_mismatch(flags); + } + + auto &callback = get_verify_callback(); + if (!callback) { return 0; } // Continue with default verification + // Build context VerifyContext verify_ctx; verify_ctx.session = static_cast(session); @@ -14789,6 +14930,7 @@ session_t create_session(ctx_t ctx, socket_t sock) { // Set per-session verify callback with session pointer if callback is // registered + session->has_verify_callback = mctx->has_verify_callback; if (mctx->has_verify_callback) { mbedtls_ssl_set_verify(&session->ssl, impl::mbedtls_verify_callback, session); @@ -14801,10 +14943,15 @@ void free_session(session_t session) { if (session) { delete static_cast(session); } } -bool set_sni(session_t session, const char *hostname) { +bool set_sni(session_t session, const char *hostname, + bool verify_hostname) { if (!session || !hostname) { return false; } auto msession = static_cast(session); + // mbedtls_ssl_set_hostname() both sends the SNI extension and binds the + // handshake-time CN/SAN check to `hostname`; the two can't be requested + // independently, so a disabled hostname check is handled below by masking + // the resulting mismatch flag instead of skipping this call. int ret = mbedtls_ssl_set_hostname(&msession->ssl, hostname); if (ret != 0) { impl::mbedtls_last_error() = ret; @@ -14812,12 +14959,22 @@ bool set_sni(session_t session, const char *hostname) { } msession->hostname = hostname; - return true; -} -bool set_hostname(session_t session, const char *hostname) { - // In Mbed TLS, set_hostname also sets up hostname verification - return set_sni(session, hostname); + if (!verify_hostname) { + msession->suppress_hostname_mismatch = true; + // If a user verify callback is already wired for this session, + // mbedtls_verify_callback() masks the mismatch flag itself before + // consulting it (see suppress_hostname_mismatch above) - reinstalling it + // here would be redundant. Otherwise install the self-contained masking + // callback, which never touches the process-wide callback slot. + if (!msession->has_verify_callback) { + mbedtls_ssl_set_verify(&msession->ssl, + impl::mbedtls_mask_hostname_mismatch_callback, + msession); + } + } + + return true; } TlsError connect(session_t session) { @@ -14836,8 +14993,7 @@ TlsError connect(session_t session) { if (ret == 0) { err.code = ErrorCode::Success; } else { - err.code = impl::map_mbedtls_error(ret, err.sys_errno); - err.backend_code = static_cast(-ret); + impl::fill_mbedtls_tls_error(err, msession->ssl, ret); impl::mbedtls_last_error() = ret; } @@ -14888,10 +15044,7 @@ bool connect_nonblocking(session_t session, socket_t sock, } // TlsError or timeout - if (err) { - err->code = impl::map_mbedtls_error(ret, err->sys_errno); - err->backend_code = static_cast(-ret); - } + if (err) { impl::fill_mbedtls_tls_error(*err, msession->ssl, ret); } impl::mbedtls_last_error() = ret; return false; } @@ -14957,7 +15110,7 @@ ssize_t read(session_t session, void *buf, size_t len, TlsError &err) { return 0; } - err.code = impl::map_mbedtls_error(ret, err.sys_errno); + err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0); err.backend_code = static_cast(-ret); impl::mbedtls_last_error() = ret; // mbedTLS signals a clean close_notify via a negative error code rather @@ -14990,7 +15143,7 @@ ssize_t write(session_t session, const void *buf, size_t len, return 0; } - err.code = impl::map_mbedtls_error(ret, err.sys_errno); + err.code = impl::map_mbedtls_error(ret, err.sys_errno, 0); err.backend_code = static_cast(-ret); impl::mbedtls_last_error() = ret; return -1; @@ -15953,7 +16106,8 @@ void free_session(session_t session) { if (session) { delete static_cast(session); } } -bool set_sni(session_t session, const char *hostname) { +bool set_sni(session_t session, const char *hostname, + bool verify_hostname) { if (!session || !hostname) { return false; } auto wsession = static_cast(session); @@ -15965,18 +16119,15 @@ bool set_sni(session_t session, const char *hostname) { return false; } - // Also set hostname for verification - wolfSSL_check_domain_name(wsession->ssl, hostname); + // wolfSSL_check_domain_name binds identity checking to the handshake, + // separately from the SNI extension sent above; skip it when hostname + // verification is disabled so only the chain is checked, matching OpenSSL. + if (verify_hostname) { wolfSSL_check_domain_name(wsession->ssl, hostname); } wsession->hostname = hostname; return true; } -bool set_hostname(session_t session, const char *hostname) { - // In wolfSSL, set_hostname also sets up hostname verification - return set_sni(session, hostname); -} - TlsError connect(session_t session) { TlsError err; if (!session) { @@ -16922,6 +17073,24 @@ WebSocketClient::WebSocketClient( } } +#ifdef CPPHTTPLIB_SSL_ENABLED +WebSocketClient::WebSocketClient( + const std::string &scheme_host_port_path, const PemMemory &pem, + const Headers &headers) + : WebSocketClient(scheme_host_port_path, headers) { + // For ws:// URLs the client certificate is silently ignored, consistent + // with the TLS-only setters such as set_ca_cert_path(). + if (is_valid_ && is_ssl_ && pem.cert_pem && pem.key_pem) { + if (!tls::set_client_cert_pem(tls_ctx_, pem.cert_pem, pem.key_pem, + pem.private_key_password)) { + tls::free_context(tls_ctx_); + tls_ctx_ = nullptr; + is_valid_ = false; + } + } +} +#endif + WebSocketClient::~WebSocketClient() { shutdown_and_close(); #ifdef CPPHTTPLIB_SSL_ENABLED @@ -16956,21 +17125,33 @@ void WebSocketClient::shutdown_and_close() { } } -bool WebSocketClient::create_stream(std::unique_ptr &strm) { +bool WebSocketClient::create_stream(std::unique_ptr &strm, + Error &error, int &ssl_error, + uint64_t &ssl_backend_error) { #ifdef CPPHTTPLIB_SSL_ENABLED if (is_ssl_) { + // A plain flag rather than SSLClient::load_certs()'s call_once: connect() + // is not safe to call concurrently on one client to begin with, since + // nothing else here is guarded either. if (server_certificate_verification_ && !certs_loaded_) { uint64_t backend_error = 0; - detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, std::string(), - custom_ca_loaded_, system_ca_mode_, - backend_error); + detail::load_client_ca_config(tls_ctx_, ca_cert_file_path_, + ca_cert_dir_path_, custom_ca_loaded_, + system_ca_mode_, backend_error); certs_loaded_ = true; } + detail::ClientTlsSessionOptions options; + options.server_hostname_verification = server_hostname_verification_; + + detail::ClientTlsSessionError tls_error; if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_, server_certificate_verification_, - read_timeout_sec_, - read_timeout_usec_)) { + read_timeout_sec_, read_timeout_usec_, + &tls_error, options)) { + error = tls_error.error; + ssl_error = tls_error.ssl_error; + ssl_backend_error = tls_error.backend_error; return false; } @@ -16979,6 +17160,10 @@ bool WebSocketClient::create_stream(std::unique_ptr &strm) { write_timeout_sec_, write_timeout_usec_)); return true; } +#else + (void)error; + (void)ssl_error; + (void)ssl_backend_error; #endif strm = std::unique_ptr( new detail::SocketStream(sock_, read_timeout_sec_, read_timeout_usec_, @@ -16994,24 +17179,15 @@ void WebSocketClient::prepare_default_headers(Request &req) { #endif if (!req.has_header("Host")) { - if (address_family_ == AF_UNIX) { - req.headers.emplace("Host", "localhost"); - } else { - req.headers.emplace( - "Host", detail::make_host_and_port_string(host_, port_, is_ssl)); - } + req.headers.emplace("Host", detail::make_default_host_header_value( + host_, port_, is_ssl, address_family_)); } -#ifndef CPPHTTPLIB_NO_DEFAULT_USER_AGENT - if (!req.has_header("User-Agent")) { - auto agent = std::string("cpp-httplib/") + CPPHTTPLIB_VERSION; - req.set_header("User-Agent", agent); - } -#endif + detail::add_default_user_agent_header(req); } -bool WebSocketClient::connect() { - if (!is_valid_) { return false; } +Result WebSocketClient::connect() { + if (!is_valid_) { return Result{Error::Connection, -1, Headers{}}; } shutdown_and_close(); // Check is custom IP or hostname specified for host_ @@ -17019,19 +17195,29 @@ bool WebSocketClient::connect() { std::string ip; detail::apply_addr_map(addr_map_, host_, connect_host, ip); - Error error; + auto error = Error::Success; sock_ = detail::create_client_socket( connect_host, ip, 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_, interface_, error); - if (sock_ == INVALID_SOCKET) { return false; } + if (sock_ == INVALID_SOCKET) { + if (error == Error::Success) { error = Error::Connection; } + return Result{error, -1, Headers{}}; + } std::unique_ptr strm; - if (!create_stream(strm)) { + auto stream_error = Error::SSLConnection; + int ssl_error = 0; + uint64_t ssl_backend_error = 0; + if (!create_stream(strm, stream_error, ssl_error, ssl_backend_error)) { shutdown_and_close(); - return false; +#ifdef CPPHTTPLIB_SSL_ENABLED + return Result{stream_error, -1, Headers{}, ssl_error, ssl_backend_error}; +#else + return Result{stream_error, -1, Headers{}}; +#endif } Request req; @@ -17040,17 +17226,17 @@ bool WebSocketClient::connect() { req.headers = headers_; prepare_default_headers(req); - std::string selected_subprotocol; - if (!detail::perform_websocket_handshake(*strm, req, selected_subprotocol)) { + detail::WebSocketUpgradeResponse upgrade; + if (!detail::perform_websocket_handshake(*strm, req, upgrade)) { shutdown_and_close(); - return false; + return Result{upgrade.error, upgrade.status, std::move(upgrade.headers)}; } - subprotocol_ = std::move(selected_subprotocol); + subprotocol_ = std::move(upgrade.selected_subprotocol); ws_ = std::unique_ptr(new WebSocket(std::move(strm), req, false, websocket_ping_interval_sec_, websocket_max_missed_pongs_)); - return true; + return Result{Error::Success, upgrade.status, std::move(upgrade.headers)}; } ReadResult WebSocketClient::read(std::string &msg) { @@ -17125,8 +17311,11 @@ void WebSocketClient::set_hostname_addr_map( #ifdef CPPHTTPLIB_SSL_ENABLED -void WebSocketClient::set_ca_cert_path(const std::string &path) { - ca_cert_file_path_ = path; +void +WebSocketClient::set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path) { + ca_cert_file_path_ = ca_cert_file_path; + ca_cert_dir_path_ = ca_cert_dir_path; } void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) { @@ -17152,6 +17341,10 @@ WebSocketClient::enable_server_certificate_verification(bool enabled) { server_certificate_verification_ = enabled; } +void WebSocketClient::enable_server_hostname_verification(bool enabled) { + server_hostname_verification_ = enabled; +} + void WebSocketClient::enable_system_ca(bool enabled) { system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled; } diff --git a/vendor/cpp-httplib/httplib.h b/vendor/cpp-httplib/httplib.h index 94605defa3..f82e58e49c 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.52.0" -#define CPPHTTPLIB_VERSION_NUM "0x003400" +#define CPPHTTPLIB_VERSION "0.53.0" +#define CPPHTTPLIB_VERSION_NUM "0x003500" #ifdef _WIN32 #if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00 @@ -1805,6 +1805,7 @@ enum class Error { HTTPParsing, InvalidRangeHeader, UnsupportedContentEncoding, + WebSocketHandshake, // For internal use only SSLPeerCouldBeClosed_, @@ -3233,8 +3234,6 @@ private: // Used to keep custom CA configuration exclusive with system CA loading. bool ca_cert_store_set_ = false; - long verify_result_ = 0; - std::function session_verifier_; #ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE @@ -4204,6 +4203,50 @@ enum class CloseStatus : uint16_t { enum ReadResult : int { Fail = 0, Text = 1, Binary = 2 }; +// Result of WebSocketClient::connect(). Truthy only when the WebSocket +// upgrade handshake fully succeeded. On failure error() identifies the +// failing layer; status()/headers() expose the server's upgrade response +// when one was received (status() is -1 otherwise). +class Result { +public: + Result() = default; + Result(Error err, int status, Headers &&headers) + : err_(err), status_(status), headers_(std::move(headers)) {} + + explicit operator bool() const { return err_ == Error::Success; } + Error error() const { return err_; } + + // Upgrade response info + int status() const { return status_; } + const Headers &headers() const { return headers_; } + std::string get_header_value(const std::string &key, + const char *def = "") const { + return detail::get_header_value(headers_, key, def, 0); + } + bool has_header(const std::string &key) const { + return headers_.find(key) != headers_.end(); + } + +#ifdef CPPHTTPLIB_SSL_ENABLED + Result(Error err, int status, Headers &&headers, int ssl_error, + uint64_t ssl_backend_error) + : err_(err), status_(status), headers_(std::move(headers)), + ssl_error_(ssl_error), ssl_backend_error_(ssl_backend_error) {} + + int ssl_error() const { return ssl_error_; } + uint64_t ssl_backend_error() const { return ssl_backend_error_; } +#endif + +private: + Error err_ = Error::Unknown; // a default-constructed Result is falsy + int status_ = -1; + Headers headers_; +#ifdef CPPHTTPLIB_SSL_ENABLED + int ssl_error_ = 0; + uint64_t ssl_backend_error_ = 0; +#endif +}; + class WebSocket { public: WebSocket(const WebSocket &) = delete; @@ -4270,7 +4313,7 @@ public: bool is_valid() const; - bool connect(); + Result connect(); ReadResult read(std::string &msg); bool send(const std::string &data); bool send(const char *data, size_t len); @@ -4279,28 +4322,52 @@ public: bool is_open() const; const std::string &subprotocol() const; void set_read_timeout(time_t sec, time_t usec = 0); + template + void set_read_timeout(const std::chrono::duration &duration); + void set_write_timeout(time_t sec, time_t usec = 0); + template + void set_write_timeout(const std::chrono::duration &duration); + void set_websocket_ping_interval(time_t sec); void set_websocket_max_missed_pongs(int count); 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); + template + void + set_connection_timeout(const std::chrono::duration &duration); + void set_interface(const std::string &intf); void set_hostname_addr_map(std::map addr_map); #ifdef CPPHTTPLIB_SSL_ENABLED - void set_ca_cert_path(const std::string &path); + struct PemMemory { + const char *cert_pem; + size_t cert_pem_len; + const char *key_pem; + size_t key_pem_len; + const char *private_key_password; + }; + explicit WebSocketClient(const std::string &scheme_host_port_path, + const PemMemory &pem, const Headers &headers = {}); + + void set_ca_cert_path(const std::string &ca_cert_file_path, + const std::string &ca_cert_dir_path = std::string()); void set_ca_cert_store(tls::ca_store_t store); void load_ca_cert_store(const char *ca_cert, std::size_t size); void enable_server_certificate_verification(bool enabled); + void enable_server_hostname_verification(bool enabled); void enable_system_ca(bool enabled); #endif private: void shutdown_and_close(); - bool create_stream(std::unique_ptr &strm); + bool create_stream(std::unique_ptr &strm, Error &error, + int &ssl_error, uint64_t &ssl_backend_error); void prepare_default_headers(Request &req); std::string host_; @@ -4335,13 +4402,37 @@ private: tls::ctx_t tls_ctx_ = nullptr; tls::session_t tls_session_ = nullptr; std::string ca_cert_file_path_; + std::string ca_cert_dir_path_; bool custom_ca_loaded_ = false; bool certs_loaded_ = false; SystemCAMode system_ca_mode_ = SystemCAMode::Auto; bool server_certificate_verification_ = true; + bool server_hostname_verification_ = true; #endif }; +template +inline void WebSocketClient::set_read_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_read_timeout(sec, usec); }); +} + +template +inline void WebSocketClient::set_write_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec( + duration, [&](time_t sec, time_t usec) { set_write_timeout(sec, usec); }); +} + +template +inline void WebSocketClient::set_connection_timeout( + const std::chrono::duration &duration) { + detail::duration_to_sec_and_usec(duration, [&](time_t sec, time_t usec) { + set_connection_timeout(sec, usec); + }); +} + namespace impl { bool is_valid_utf8(const std::string &s);