diff --git a/sdk_v2/cpp/CMakeLists.txt b/sdk_v2/cpp/CMakeLists.txt index 3dc080875..44962487e 100644 --- a/sdk_v2/cpp/CMakeLists.txt +++ b/sdk_v2/cpp/CMakeLists.txt @@ -168,6 +168,7 @@ set(FOUNDRY_LOCAL_SOURCES src/exception.cc src/inferencing/generative/genai_config.cc src/http/http_client.cc + src/http/curl_transport.cc src/inferencing/generative/genai_model_instance.cc src/inferencing/generative/tokenizer.cc src/logger.cc diff --git a/sdk_v2/cpp/src/download/blob_downloader.cc b/sdk_v2/cpp/src/download/blob_downloader.cc index 1fec2daf7..b3345d59c 100644 --- a/sdk_v2/cpp/src/download/blob_downloader.cc +++ b/sdk_v2/cpp/src/download/blob_downloader.cc @@ -29,6 +29,8 @@ // does not honor SSL_CERT_FILE. On non-Windows builds we install a CurlTransport preconfigured with // CAInfo (see MakeBlobClientOptions below). Desktop Windows uses the default WinHTTP transport. #if !defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) +#include "http/curl_transport.h" + #include #endif @@ -50,10 +52,11 @@ constexpr size_t kStreamingBufferBytes = 64 * 1024; Azure::Storage::Blobs::BlobClientOptions MakeBlobClientOptions() { Azure::Storage::Blobs::BlobClientOptions options; #if !defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) - if (std::string ca_bundle = fl::http::CaBundleFile(); !ca_bundle.empty()) { - Azure::Core::Http::CurlTransportOptions curl_opts; - curl_opts.CAInfo = std::move(ca_bundle); - options.Transport.Transport = std::make_shared(curl_opts); + // Only override the Storage SDK's default transport when a CA bundle is configured. + auto curl_options = fl::http::MakeCurlTransportOptions(); + if (!curl_options.CAInfo.empty()) { + options.Transport.Transport = + std::make_shared(curl_options); } #endif return options; diff --git a/sdk_v2/cpp/src/http/curl_transport.cc b/sdk_v2/cpp/src/http/curl_transport.cc new file mode 100644 index 000000000..ed14d0e22 --- /dev/null +++ b/sdk_v2/cpp/src/http/curl_transport.cc @@ -0,0 +1,26 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#include "http/curl_transport.h" + +#if !defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) + +#include "http/http_client.h" + +#include + +namespace fl { +namespace http { + +Azure::Core::Http::CurlTransportOptions MakeCurlTransportOptions() { + Azure::Core::Http::CurlTransportOptions options; + if (const std::string& ca_bundle = CABundleFilePath(); !ca_bundle.empty()) { + options.CAInfo = ca_bundle; + } + + return options; +} + +} // namespace http +} // namespace fl + +#endif // !FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT diff --git a/sdk_v2/cpp/src/http/curl_transport.h b/sdk_v2/cpp/src/http/curl_transport.h new file mode 100644 index 000000000..362c22db4 --- /dev/null +++ b/sdk_v2/cpp/src/http/curl_transport.h @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. +#pragma once + +// Desktop Windows uses the WinHTTP transport and never links libcurl, so the curl transport options +// are only meaningful for the non-WinHTTP (libcurl) builds. Guard the whole header accordingly so +// includers on Windows do not pull in the Azure curl transport dependency. +#if !defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) + +#include + +namespace fl { +namespace http { + +/// Creates libcurl transport options with `CAInfo` populated from `SSL_CERT_FILE` via `CABundleFilePath`. +/// When `SSL_CERT_FILE` is unset, `CAInfo` is empty and libcurl falls back to its compiled-in default. +Azure::Core::Http::CurlTransportOptions MakeCurlTransportOptions(); + +} // namespace http +} // namespace fl + +#endif // !FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT diff --git a/sdk_v2/cpp/src/http/http_client.cc b/sdk_v2/cpp/src/http/http_client.cc index f1fc9fa12..996920ff2 100644 --- a/sdk_v2/cpp/src/http/http_client.cc +++ b/sdk_v2/cpp/src/http/http_client.cc @@ -18,11 +18,12 @@ #if defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) #include #else +#include "http/curl_transport.h" + #include #endif #include -#include #include #include #include @@ -32,12 +33,14 @@ namespace fl { namespace http { -std::string CaBundleFile() { - const char* cert_file = std::getenv("SSL_CERT_FILE"); - if (cert_file != nullptr && cert_file[0] != '\0') { - return cert_file; - } - return {}; +const std::string& CABundleFilePath() { + // SSL_CERT_FILE is fixed for the process lifetime (callers set it before loading the library), so + // read it once and return a reference to the cached path for every request. + static const std::string ca_bundle = [] { + auto cert_file = Utils::GetEnv("SSL_CERT_FILE"); + return (cert_file && !cert_file->empty()) ? std::move(*cert_file) : std::string(); + }(); + return ca_bundle; } namespace { @@ -58,14 +61,9 @@ HttpRawResult HttpRequestRaw(const Azure::Core::Http::HttpMethod& method, #if defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) WinHttpTransport transport; #else - // The bundled libcurl does not honor the SSL_CERT_FILE environment variable (it was built with a - // compiled-in default CA path that does not exist on platforms like Android). Explicitly pass the - // CA bundle via CAInfo so the caller-provided trust store is actually used for TLS verification. - CurlTransportOptions curl_opts; - if (std::string ca_bundle = CaBundleFile(); !ca_bundle.empty()) { - curl_opts.CAInfo = std::move(ca_bundle); - } - CurlTransport transport(curl_opts); + // libcurl does not honor SSL_CERT_FILE (its compiled-in default CA path is absent on Android), so + // pass the CA bundle explicitly via CAInfo (see http/curl_transport.h). + CurlTransport transport(MakeCurlTransportOptions()); #endif // Build the request. For methods with a body (POST), attach a MemoryBodyStream. diff --git a/sdk_v2/cpp/src/http/http_client.h b/sdk_v2/cpp/src/http/http_client.h index 81c99edff..be0483489 100644 --- a/sdk_v2/cpp/src/http/http_client.h +++ b/sdk_v2/cpp/src/http/http_client.h @@ -28,12 +28,13 @@ struct HttpRequestOptions { }; /// Returns the CA bundle file path from the `SSL_CERT_FILE` environment variable, or an empty -/// string when it is unset/empty. The bundled libcurl does not consult `SSL_CERT_FILE` -/// automatically (it was built with a compiled-in default CA path that does not exist on platforms -/// like Android), so every libcurl-based transport we construct — direct requests, file downloads, -/// and the Azure Storage blob client — must pass this explicitly as `CAInfo`. On desktop Windows the -/// WinHTTP transport uses the OS trust store and ignores this. -std::string CaBundleFile(); +/// string when it is unset/empty. The value is read once and cached for the process lifetime, so a +/// reference to the cached string is returned (valid until process exit). The bundled libcurl does +/// not consult `SSL_CERT_FILE` automatically (it was built with a compiled-in default CA path that +/// does not exist on platforms like Android), so every libcurl-based transport we construct — direct +/// requests, file downloads, and the Azure Storage blob client — must pass this explicitly as +/// `CAInfo`. On desktop Windows the WinHTTP transport uses the OS trust store and ignores this. +const std::string& CABundleFilePath(); /// Perform an HTTP POST and return status, headers, and body without throwing on non-2xx responses. /// Transport failures are returned as `status == 0` with the error message in `body`. diff --git a/sdk_v2/cpp/src/http/http_download.cc b/sdk_v2/cpp/src/http/http_download.cc index 83e21d6bf..dbe9dae82 100644 --- a/sdk_v2/cpp/src/http/http_download.cc +++ b/sdk_v2/cpp/src/http/http_download.cc @@ -15,6 +15,8 @@ #if defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) #include #else +#include "http/curl_transport.h" + #include #endif @@ -43,14 +45,9 @@ bool HttpDownloadFile(const std::string& url, #if defined(FOUNDRY_LOCAL_USE_WINHTTP_TRANSPORT) WinHttpTransport transport; #else - // The bundled libcurl does not honor the SSL_CERT_FILE environment variable (it was built with a - // compiled-in default CA path that does not exist on platforms like Android). Explicitly pass the - // CA bundle via CAInfo so the caller-provided trust store is actually used for TLS verification. - CurlTransportOptions curl_opts; - if (std::string ca_bundle = http::CaBundleFile(); !ca_bundle.empty()) { - curl_opts.CAInfo = std::move(ca_bundle); - } - CurlTransport transport(curl_opts); + // libcurl does not honor SSL_CERT_FILE (its compiled-in default CA path is absent on Android), so + // pass the CA bundle explicitly via CAInfo (see http/curl_transport.h). + CurlTransport transport(http::MakeCurlTransportOptions()); #endif Request request(HttpMethod::Get, Url(url)); request.SetHeader("User-Agent", user_agent);