}
bool is_hex(char c, int &v) {
- if (isdigit(c)) {
+ if (isdigit(static_cast<unsigned char>(c))) {
v = c - '0';
return true;
} else if ('A' <= c && c <= 'F') {
// Pre-processing: adding padding bits
std::string msg = input;
uint64_t original_bit_len = static_cast<uint64_t>(msg.size()) * 8;
- msg.push_back(static_cast<char>(0x80));
+ msg.push_back(static_cast<char>(0x80u));
while (msg.size() % 64 != 56) {
msg.push_back(0);
}
auto valid = true;
for (size_t i = 0; i < boundary.size(); i++) {
auto c = boundary[i];
- if (!std::isalnum(c) && c != '-' && c != '_') {
+ if (!std::isalnum(static_cast<unsigned char>(c)) && c != '-' && c != '_') {
valid = false;
break;
}
bool range_error(Request &req, Response &res) {
if (!req.ranges.empty() && 200 <= res.status && res.status < 300) {
+ if (res.body.empty() && res.content_provider_ && res.content_length_ == 0) {
+ req.ranges.clear();
+ if (res.status == StatusCode::PartialContent_206) {
+ res.status = StatusCode::OK_200;
+ }
+ return false;
+ }
+
ssize_t content_len = static_cast<ssize_t>(
res.content_length_ ? res.content_length_ : res.body.size());
namespace fields {
bool is_token_char(char c) {
- return std::isalnum(c) || c == '!' || c == '#' || c == '$' || c == '%' ||
- c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' ||
- c == '.' || c == '^' || c == '_' || c == '`' || c == '|' || c == '~';
+ return std::isalnum(static_cast<unsigned char>(c)) || c == '!' || c == '#' ||
+ c == '$' || c == '%' || c == '&' || c == '\'' || c == '*' ||
+ c == '+' || c == '-' || c == '.' || c == '^' || c == '_' || c == '`' ||
+ c == '|' || c == '~';
}
bool is_token(const std::string &s) {
}
#endif // _WIN32
-bool setup_client_tls_session(const std::string &host, tls::ctx_t &ctx,
+// Loads CA file/dir configuration and applies the system CA policy to a
+// client TLS context. PEM data and native stores are applied to the context
+// directly at set time; has_custom_store reflects them for the Auto policy
+// decision.
+bool load_client_ca_config(tls::ctx_t ctx,
+ const std::string &ca_cert_file_path,
+ const std::string &ca_cert_dir_path,
+ bool has_custom_store, SystemCAMode mode,
+ uint64_t &backend_error) {
+ auto ret = true;
+
+ if (!ca_cert_file_path.empty()) {
+ if (!tls::load_ca_file(ctx, ca_cert_file_path.c_str())) {
+ backend_error = tls::get_error();
+ ret = false;
+ }
+ } else if (!ca_cert_dir_path.empty()) {
+ if (!tls::load_ca_dir(ctx, ca_cert_dir_path.c_str())) {
+ backend_error = tls::get_error();
+ ret = false;
+ }
+ }
+
+ auto has_custom_ca = !ca_cert_file_path.empty() ||
+ !ca_cert_dir_path.empty() || has_custom_store;
+ if (mode == SystemCAMode::Enabled ||
+ (mode == SystemCAMode::Auto && !has_custom_ca)) {
+ if (!tls::load_system_certs(ctx)) { backend_error = tls::get_error(); }
+ }
+
+ 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,
- const std::string &ca_cert_file_path,
- tls::ca_store_t ca_cert_store,
time_t timeout_sec, time_t timeout_usec) {
using namespace tls;
- ctx = create_client_context();
if (!ctx) { return false; }
- if (server_certificate_verification) {
- if (!ca_cert_file_path.empty()) {
- load_ca_file(ctx, ca_cert_file_path.c_str());
- }
- if (ca_cert_store) { set_ca_store(ctx, ca_cert_store); }
- load_system_certs(ctx);
- }
-
bool is_ip = is_ip_address(host);
-#ifdef CPPHTTPLIB_MBEDTLS_SUPPORT
- if (is_ip && server_certificate_verification) {
- set_verify_client(ctx, false);
- } else {
- set_verify_client(ctx, server_certificate_verification);
- }
+#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()
+ set_verify_client(ctx, server_certificate_verification);
#endif
session = create_session(ctx, sock);
if (!session) { return false; }
- // RFC 6066: SNI must not be set for IP addresses
- if (!is_ip) { set_sni(session, host.c_str()); }
- if (server_certificate_verification) { set_hostname(session, host.c_str()); }
+ // 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());
+ }
+ }
if (!connect_nonblocking(session, sock, timeout_sec, timeout_usec, nullptr)) {
return false;
if (server_certificate_verification) {
if (get_verify_result(session) != 0) { return false; }
+
+ // 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; }
}
return true;
return *this;
}
+Server &Server::set_start_handler(StartHandler handler) {
+ start_handler_ = std::move(handler);
+ return *this;
+}
+
Server &Server::set_address_family(int family) {
address_family_ = family;
return *this;
is_running_ = true;
auto se = detail::scope_exit([&]() { is_running_ = false; });
+ if (start_handler_) { start_handler_(); }
+
{
std::unique_ptr<TaskQueue> task_queue(new_task_queue());
}
}
- // Read content into `req.body`
- if (!read_content(strm, req, res)) {
- output_error_log(Error::Read, &req);
- return false;
- }
+ // NOTE: `req.body` is not read here. For a regular handler the body is
+ // read inside dispatch_request(), after the route has matched and the
+ // pre-request handler has approved the request, so that a rejected
+ // request (e.g. failed authentication) never forces us to buffer a
+ // potentially large body.
}
// Regular handler
if (req.method == "GET" || req.method == "HEAD") {
- return dispatch_request(req, res, get_handlers_);
+ return dispatch_request(req, res, get_handlers_, strm);
} else if (req.method == "POST") {
- return dispatch_request(req, res, post_handlers_);
+ return dispatch_request(req, res, post_handlers_, strm);
} else if (req.method == "PUT") {
- return dispatch_request(req, res, put_handlers_);
+ return dispatch_request(req, res, put_handlers_, strm);
} else if (req.method == "DELETE") {
- return dispatch_request(req, res, delete_handlers_);
+ return dispatch_request(req, res, delete_handlers_, strm);
} else if (req.method == "OPTIONS") {
- return dispatch_request(req, res, options_handlers_);
+ return dispatch_request(req, res, options_handlers_, strm);
} else if (req.method == "PATCH") {
- return dispatch_request(req, res, patch_handlers_);
+ return dispatch_request(req, res, patch_handlers_, strm);
}
res.status = StatusCode::BadRequest_400;
}
bool Server::dispatch_request(Request &req, Response &res,
- const Handlers &handlers) const {
+ const Handlers &handlers, Stream &strm) {
for (const auto &x : handlers) {
const auto &matcher = x.first;
const auto &handler = x.second;
if (matcher->match(req)) {
req.matched_route = matcher->pattern();
- if (!pre_request_handler_ ||
- pre_request_handler_(req, res) != HandlerResponse::Handled) {
- handler(req, res);
+
+ // Run the pre-request handler before reading the body so a rejected
+ // request (e.g. failed authentication) never forces us to buffer a
+ // potentially large body. `req.matched_route` is available here.
+ if (pre_request_handler_ &&
+ pre_request_handler_(req, res) == HandlerResponse::Handled) {
+ return true;
+ }
+
+ // The route matched and the request was approved; read the body now.
+ if (detail::expect_content(req) && !read_content(strm, req, res)) {
+ output_error_log(Error::Read, &req);
+ return false;
}
+
+ handler(req, res);
return true;
}
}
ca_cert_dir_path_ = rhs.ca_cert_dir_path_;
server_certificate_verification_ = rhs.server_certificate_verification_;
server_hostname_verification_ = rhs.server_hostname_verification_;
+ system_ca_mode_ = rhs.system_ca_mode_;
#endif
}
server_certificate_verification_);
redirect_client.enable_server_hostname_verification(
server_hostname_verification_);
+ redirect_client.system_ca_mode_ = system_ca_mode_;
// Transfer CA certificate to redirect client
if (!ca_cert_pem_.empty()) {
void ClientImpl::enable_server_hostname_verification(bool enabled) {
server_hostname_verification_ = enabled;
}
+
+void ClientImpl::enable_system_ca(bool enabled) {
+ system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
+}
#endif
void ClientImpl::set_logger(Logger logger) {
if (ca_cert_store && ctx_) {
// set_ca_store takes ownership of ca_cert_store
tls::set_ca_store(ctx_, ca_cert_store);
+ ca_cert_store_set_ = true;
} else if (ca_cert_store) {
tls::free_ca_store(ca_cert_store);
}
std::call_once(initialize_cert_, [&]() {
std::lock_guard<std::mutex> guard(ctx_mutex_);
- if (!ca_cert_file_path_.empty()) {
- if (!tls::load_ca_file(ctx_, ca_cert_file_path_.c_str())) {
- last_backend_error_ = tls::get_error();
- ret = false;
- }
- } else if (!ca_cert_dir_path_.empty()) {
- if (!tls::load_ca_dir(ctx_, ca_cert_dir_path_.c_str())) {
- last_backend_error_ = tls::get_error();
- ret = false;
- }
- } else if (ca_cert_pem_.empty()) {
- if (!tls::load_system_certs(ctx_)) {
- last_backend_error_ = tls::get_error();
- }
- }
+ ret = detail::load_client_ca_config(
+ ctx_, ca_cert_file_path_, ca_cert_dir_path_,
+ !ca_cert_pem_.empty() || ca_cert_store_set_, system_ca_mode_,
+ last_backend_error_);
});
return ret;
#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).
- // For IP addresses with verification enabled, use OPTIONAL mode since
- // these backends require hostname for strict verification.
- if (is_ip && server_certificate_verification_) {
- set_verify_client(ctx_, false);
- } else {
- set_verify_client(ctx_, server_certificate_verification_);
- }
+ // 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
// Create TLS session
// 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.
- if (enable_windows_cert_verification_ && ca_cert_file_path_.empty() &&
- ca_cert_dir_path_.empty() && ca_cert_pem_.empty()) {
+ // 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<unsigned char> der;
if (get_cert_der(server_cert, der)) {
uint64_t wincrypt_error = 0;
cli_->enable_server_hostname_verification(enabled);
}
+void Client::enable_system_ca(bool enabled) {
+ cli_->enable_system_ca(enabled);
+}
+
#ifdef CPPHTTPLIB_WINDOWS_AUTOMATIC_ROOT_CERTIFICATES_UPDATE
void Client::enable_windows_certificate_verification(bool enabled) {
if (is_ssl_) {
}
void Client::load_ca_cert_store(const char *ca_cert, std::size_t size) {
- set_ca_cert_store(tls::create_ca_store(ca_cert, size));
+ if (is_ssl_) {
+ // Use the PEM-based path so the CA data is retained for redirect transfer
+ static_cast<SSLClient &>(*cli_).load_ca_cert_store(ca_cert, size);
+ }
}
void
auto ssl = static_cast<SSL *>(session);
- // Set SNI (Server Name Indication)
- if (!set_sni(session, hostname)) { return false; }
-
// Enable hostname verification
auto param = SSL_get0_param(ssl);
if (!param) 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; }
+ 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;
return nullptr;
}
+ // Explicitly opt out of in-handshake hostname verification by default;
+ // since Mbed TLS 3.6.4 a client handshake with certificate verification
+ // fails outright when no hostname was set. set_sni() installs the real
+ // hostname for DNS hosts; for IP hosts (where SNI must not be set) the
+ // caller verifies the certificate identity post-handshake via
+ // verify_hostname().
+ mbedtls_ssl_set_hostname(&session->ssl, nullptr);
+
// Set BIO callbacks
mbedtls_ssl_set_bio(&session->ssl, &session->sock, impl::mbedtls_net_send_cb,
impl::mbedtls_net_recv_cb, nullptr);
while (src != nullptr) {
int ret = mbedtls_x509_crt_parse_der(&mbed_ctx->ca_chain, src->raw.p,
src->raw.len);
- if (ret != 0) { return false; }
+ if (ret != 0) {
+ free_ca_store(store);
+ return false;
+ }
src = src->next;
}
+ // This function takes ownership of the store; the chain was deep-copied
+ // above, so release the source
+ free_ca_store(store);
+
// Update the SSL config to use the new CA chain
mbedtls_ssl_conf_ca_chain(&mbed_ctx->conf, &mbed_ctx->ca_chain, nullptr);
return true;
wctx->ctx, reinterpret_cast<const unsigned char *>(ca->pem_data.data()),
static_cast<long>(ca->pem_data.size()), SSL_FILETYPE_PEM);
if (ret == SSL_SUCCESS) { wctx->ca_pem_data_ += ca->pem_data; }
+ // This function takes ownership of the store; the PEM data was copied into
+ // the context, so release the source
+ free_ca_store(store);
return ret == SSL_SUCCESS;
}
if (!uc.port.empty() && !detail::parse_port(uc.port, port_)) { return; }
path_ = std::move(uc.path);
+ if (!uc.query.empty()) { path_ += uc.query; }
#ifdef CPPHTTPLIB_SSL_ENABLED
is_ssl_ = is_ssl;
+ if (is_ssl_) {
+ // The context lives as long as the client so that CA configuration
+ // survives reconnects; sessions are created per connection.
+ tls_ctx_ = tls::create_client_context();
+ if (!tls_ctx_) { return; }
+ }
#else
if (is_ssl) { return; }
#endif
}
}
-WebSocketClient::~WebSocketClient() { shutdown_and_close(); }
+WebSocketClient::~WebSocketClient() {
+ shutdown_and_close();
+#ifdef CPPHTTPLIB_SSL_ENABLED
+ if (tls_ctx_) {
+ tls::free_context(tls_ctx_);
+ tls_ctx_ = nullptr;
+ }
+#endif
+}
bool WebSocketClient::is_valid() const { return is_valid_; }
tls::free_session(tls_session_);
tls_session_ = nullptr;
}
- if (tls_ctx_) {
- tls::free_context(tls_ctx_);
- tls_ctx_ = nullptr;
- }
}
#endif
if (ws_ && ws_->is_open()) { ws_->close(); }
bool WebSocketClient::create_stream(std::unique_ptr<Stream> &strm) {
#ifdef CPPHTTPLIB_SSL_ENABLED
if (is_ssl_) {
- if (!detail::setup_client_tls_session(
- host_, tls_ctx_, tls_session_, sock_,
- server_certificate_verification_, ca_cert_file_path_,
- ca_cert_store_, read_timeout_sec_, read_timeout_usec_)) {
+ 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);
+ certs_loaded_ = true;
+ }
+
+ if (!detail::setup_client_tls_session(host_, tls_ctx_, tls_session_, sock_,
+ server_certificate_verification_,
+ read_timeout_sec_,
+ read_timeout_usec_)) {
return false;
}
if (!is_valid_) { return false; }
shutdown_and_close();
+ // Check is custom IP specified for host_
+ std::string ip;
+ auto it = addr_map_.find(host_);
+ if (it != addr_map_.end()) { ip = it->second; }
+
Error error;
sock_ = detail::create_client_socket(
- host_, std::string(), port_, address_family_, tcp_nodelay_, ipv6_v6only_,
+ 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);
interface_ = intf;
}
+void WebSocketClient::set_hostname_addr_map(
+ std::map<std::string, std::string> addr_map) {
+ addr_map_ = std::move(addr_map);
+}
+
#ifdef CPPHTTPLIB_SSL_ENABLED
void WebSocketClient::set_ca_cert_path(const std::string &path) {
}
void WebSocketClient::set_ca_cert_store(tls::ca_store_t store) {
- ca_cert_store_ = store;
+ if (store && tls_ctx_) {
+ // set_ca_store takes ownership of store
+ tls::set_ca_store(tls_ctx_, store);
+ custom_ca_loaded_ = true;
+ } else if (store) {
+ tls::free_ca_store(store);
+ }
+}
+
+void WebSocketClient::load_ca_cert_store(const char *ca_cert,
+ std::size_t size) {
+ if (tls_ctx_ && ca_cert && size > 0) {
+ tls::load_ca_pem(tls_ctx_, ca_cert, size);
+ custom_ca_loaded_ = true;
+ }
}
void
server_certificate_verification_ = enabled;
}
+void WebSocketClient::enable_system_ca(bool enabled) {
+ system_ca_mode_ = enabled ? SystemCAMode::Enabled : SystemCAMode::Disabled;
+}
+
#endif // CPPHTTPLIB_SSL_ENABLED
} // namespace ws
#ifndef CPPHTTPLIB_HTTPLIB_H
#define CPPHTTPLIB_HTTPLIB_H
-#define CPPHTTPLIB_VERSION "0.46.1"
-#define CPPHTTPLIB_VERSION_NUM "0x002e01"
+#define CPPHTTPLIB_VERSION "0.47.0"
+#define CPPHTTPLIB_VERSION_NUM "0x002f00"
#ifdef _WIN32
#if defined(_WIN32_WINNT) && _WIN32_WINNT < 0x0A00
CertificateRejected
};
+// System CA loading policy for SSL clients. Auto (the default) loads system
+// CA certs only when no custom CA is configured; enable_system_ca() switches
+// to an explicit policy.
+enum class SystemCAMode { Auto, Enabled, Disabled };
+
enum StatusCode {
// Information responses
Continue_100 = 100,
using Expect100ContinueHandler =
std::function<int(const Request &, Response &)>;
+ using StartHandler = std::function<void()>;
+
using WebSocketHandler =
std::function<void(const Request &, ws::WebSocket &)>;
using SubProtocolSelector =
Server &set_pre_request_handler(HandlerWithResponse handler);
Server &set_expect_100_continue_handler(Expect100ContinueHandler handler);
+
+ Server &set_start_handler(StartHandler handler);
+
Server &set_logger(Logger logger);
Server &set_pre_compression_logger(Logger logger);
Server &set_error_logger(ErrorLogger error_logger);
const std::string &etag, time_t mtime) const;
bool check_if_range(Request &req, const std::string &etag,
time_t mtime) const;
- bool dispatch_request(Request &req, Response &res,
- const Handlers &handlers) const;
+ bool dispatch_request(Request &req, Response &res, const Handlers &handlers,
+ Stream &strm);
bool dispatch_request_for_content_reader(
Request &req, Response &res, ContentReader content_reader,
const HandlersForContentReader &handlers) const;
Handler post_routing_handler_;
HandlerWithResponse pre_request_handler_;
Expect100ContinueHandler expect_100_continue_handler_;
+ StartHandler start_handler_;
mutable std::mutex logger_mutex_;
Logger logger_;
const std::string &ca_cert_dir_path = std::string());
void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
+ void enable_system_ca(bool enabled);
protected:
std::string digest_auth_username_;
std::string ca_cert_dir_path_;
bool server_certificate_verification_ = true;
bool server_hostname_verification_ = true;
+ SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
std::string ca_cert_pem_; // Store CA cert PEM for redirect transfer
int last_ssl_error_ = 0;
uint64_t last_backend_error_ = 0;
const std::string &password);
void enable_server_certificate_verification(bool enabled);
void enable_server_hostname_verification(bool enabled);
+ void enable_system_ca(bool enabled);
void set_ca_cert_path(const std::string &ca_cert_file_path,
const std::string &ca_cert_dir_path = std::string());
std::mutex ctx_mutex_;
std::once_flag initialize_cert_;
+ // Tracks whether a custom CA store was applied via set_ca_cert_store(),
+ // since the store handle itself is owned by ctx_ and leaves no other trace.
+ // Used to keep custom CA configuration exclusive with system CA loading.
+ bool ca_cert_store_set_ = false;
+
long verify_result_ = 0;
std::function<SSLVerifierResponse(tls::session_t)> session_verifier_;
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);
+ void set_hostname_addr_map(std::map<std::string, std::string> addr_map);
#ifdef CPPHTTPLIB_SSL_ENABLED
void set_ca_cert_path(const std::string &path);
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_system_ca(bool enabled);
#endif
private:
time_t connection_timeout_usec_ = CPPHTTPLIB_CONNECTION_TIMEOUT_USECOND;
std::string interface_;
+ // Hostname-IP map
+ std::map<std::string, std::string> addr_map_;
+
#ifdef CPPHTTPLIB_SSL_ENABLED
bool is_ssl_ = false;
tls::ctx_t tls_ctx_ = nullptr;
tls::session_t tls_session_ = nullptr;
std::string ca_cert_file_path_;
- tls::ca_store_t ca_cert_store_ = nullptr;
+ bool custom_ca_loaded_ = false;
+ bool certs_loaded_ = false;
+ SystemCAMode system_ca_mode_ = SystemCAMode::Auto;
bool server_certificate_verification_ = true;
#endif
};