From adf78a4672ecb473c008b8f797658080dbc7b56c Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 15:55:02 +0200 Subject: [PATCH 1/8] In CurlImpl, capture proxy related environment variables at construction to avoid calls to getenv() from libcurl in the worker thread, leading to crashes in some cases --- src/datadog/curl.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++-- src/datadog/curl.h | 14 ++++---- test/test_curl.cpp | 83 ++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+), 11 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index e8f407836..13ce2b6fe 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -4,6 +4,7 @@ #include #include #include +#include #include #include @@ -11,6 +12,7 @@ #include #include #include +#include #include #include #include @@ -61,6 +63,10 @@ CURLcode CurlLibrary::easy_setopt_httpheader(CURL *handle, return curl_easy_setopt(handle, CURLOPT_HTTPHEADER, headers); } +CURLcode CurlLibrary::easy_setopt_noproxy(CURL *handle, const char *no_proxy) { + return curl_easy_setopt(handle, CURLOPT_NOPROXY, no_proxy); +} + CURLcode CurlLibrary::easy_setopt_post(CURL *handle, long post) { return curl_easy_setopt(handle, CURLOPT_POST, post); } @@ -77,6 +83,10 @@ CURLcode CurlLibrary::easy_setopt_private(CURL *handle, void *pointer) { return curl_easy_setopt(handle, CURLOPT_PRIVATE, pointer); } +CURLcode CurlLibrary::easy_setopt_proxy(CURL *handle, const char *proxy) { + return curl_easy_setopt(handle, CURLOPT_PROXY, proxy); +} + CURLcode CurlLibrary::easy_setopt_unix_socket_path(CURL *handle, const char *path) { return curl_easy_setopt(handle, CURLOPT_UNIX_SOCKET_PATH, path); @@ -162,6 +172,49 @@ using HeadersSetter = HTTPClient::HeadersSetter; using ResponseHandler = HTTPClient::ResponseHandler; using URL = HTTPClient::URL; +struct ProxyConfiguration { + Optional all_proxy; + Optional http_proxy; + Optional https_proxy; + Optional no_proxy; +}; + +namespace { + +Optional environment_variable(const char *name) { + const char *const value = std::getenv(name); + if (value == nullptr || *value == '\0') { + return nullopt; + } + return std::string{value}; +} + +ProxyConfiguration load_proxy_configuration() { + ProxyConfiguration config; + + config.all_proxy = environment_variable("all_proxy"); + if (!config.all_proxy) { + config.all_proxy = environment_variable("ALL_PROXY"); + } + + // Only the lowercase form for http, to avoid httpoxy (CVE-2016-5385). + config.http_proxy = environment_variable("http_proxy"); + + config.https_proxy = environment_variable("https_proxy"); + if (!config.https_proxy) { + config.https_proxy = environment_variable("HTTPS_PROXY"); + } + + config.no_proxy = environment_variable("no_proxy"); + if (!config.no_proxy) { + config.no_proxy = environment_variable("NO_PROXY"); + } + + return config; +} + +} // namespace + class CurlImpl { std::mutex mutex_; CurlLibrary &curl_; @@ -172,6 +225,7 @@ class CurlImpl { std::list new_handles_; bool shutting_down_; int num_active_handles_; + const ProxyConfiguration proxy_config_; std::condition_variable no_requests_; std::thread event_loop_; @@ -283,7 +337,8 @@ CurlImpl::CurlImpl(const std::shared_ptr &logger, const Clock &clock, logger_(logger), clock_(clock), shutting_down_(false), - num_active_handles_(0) { + num_active_handles_(0), + proxy_config_(load_proxy_configuration()) { curl_.global_init(CURL_GLOBAL_ALL); multi_handle_ = curl_.multi_init(); if (multi_handle_ == nullptr) { @@ -375,8 +430,30 @@ Expected CurlImpl::post( throw_on_error(curl_.easy_setopt_headerdata(handle.get(), request.get())); throw_on_error(curl_.easy_setopt_writefunction(handle.get(), &on_read_body)); throw_on_error(curl_.easy_setopt_writedata(handle.get(), request.get())); - if (url.scheme == "unix" || url.scheme == "http+unix" || - url.scheme == "https+unix") { + + const bool is_unix_socket = url.scheme == "unix" || + url.scheme == "http+unix" || + url.scheme == "https+unix"; + + throw_on_error(curl_.easy_setopt_noproxy( + handle.get(), + proxy_config_.no_proxy ? proxy_config_.no_proxy->c_str() : "")); + + const std::string *proxy = nullptr; + if (!is_unix_socket) { + const Optional &scheme_proxy = url.scheme == "https" + ? proxy_config_.https_proxy + : proxy_config_.http_proxy; + if (scheme_proxy) { + proxy = &*scheme_proxy; + } else if (proxy_config_.all_proxy) { + proxy = &*proxy_config_.all_proxy; + } + } + throw_on_error( + curl_.easy_setopt_proxy(handle.get(), proxy ? proxy->c_str() : "")); + + if (is_unix_socket) { throw_on_error(curl_.easy_setopt_unix_socket_path(handle.get(), url.authority.c_str())); // The authority section of the URL is ignored when a unix domain socket is diff --git a/src/datadog/curl.h b/src/datadog/curl.h index bbaf380a9..ad9e557ae 100644 --- a/src/datadog/curl.h +++ b/src/datadog/curl.h @@ -1,13 +1,11 @@ #pragma once // This component provides a `class`, `Curl`, that implements the `HTTPClient` -// interface in terms of [libcurl][1]. `class Curl` manages a thread that is -// used as the event loop for libcurl. +// interface in terms of [libcurl](https://curl.se/libcurl)]. `class Curl` +// manages a thread that is used as the event loop for libcurl. // // If this library was built in a mode that does not include libcurl, then this // file and its implementation, `curl.cpp`, will not be included. -// -// [1]: https://curl.se/libcurl/ #include #include @@ -19,8 +17,7 @@ #include #include -namespace datadog { -namespace tracing { +namespace datadog::tracing { // `class CurlLibrary` has one member function for every libcurl function used // in the implementation of this component. @@ -50,10 +47,12 @@ class CurlLibrary { virtual CURLcode easy_setopt_headerdata(CURL *handle, void *data); virtual CURLcode easy_setopt_headerfunction(CURL *handle, HeaderCallback); virtual CURLcode easy_setopt_httpheader(CURL *handle, curl_slist *headers); + virtual CURLcode easy_setopt_noproxy(CURL *handle, const char *no_proxy); virtual CURLcode easy_setopt_post(CURL *handle, long post); virtual CURLcode easy_setopt_postfields(CURL *handle, const char *data); virtual CURLcode easy_setopt_postfieldsize(CURL *handle, long size); virtual CURLcode easy_setopt_private(CURL *handle, void *pointer); + virtual CURLcode easy_setopt_proxy(CURL *handle, const char *proxy); virtual CURLcode easy_setopt_unix_socket_path(CURL *handle, const char *path); virtual CURLcode easy_setopt_url(CURL *handle, const char *url); virtual CURLcode easy_setopt_writedata(CURL *handle, void *data); @@ -104,5 +103,4 @@ class Curl : public HTTPClient { std::string config() const override; }; -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing diff --git a/test/test_curl.cpp b/test/test_curl.cpp index 3c88ef88a..efac2c5fb 100644 --- a/test/test_curl.cpp +++ b/test/test_curl.cpp @@ -8,9 +8,12 @@ #include #include +#include +#include #include #include +#include "common/environment.h" #include "datadog/clock.h" #include "mocks/loggers.h" #include "null_logger.h" @@ -495,3 +498,83 @@ CURL_TEST("post() deadline exceeded before request start") { REQUIRE(error_delivered->code == Error::CURL_DEADLINE_EXCEEDED_BEFORE_REQUEST_START); } + +CURL_TEST("proxy is taken from the environment and forwarded to libcurl") { + using datadog::test::EnvGuard; + + class ProxyCapturingCurlLibrary : public SingleRequestMockCurlLibrary { + public: + Optional proxy_; + Optional no_proxy_; + + CURLcode easy_setopt_proxy(CURL *, const char *proxy) override { + proxy_ = proxy; + return CURLE_OK; + } + CURLcode easy_setopt_noproxy(CURL *, const char *no_proxy) override { + no_proxy_ = no_proxy; + return CURLE_OK; + } + }; + + std::list cleared; + for (const char *name : + {"http_proxy", "HTTP_PROXY", "https_proxy", "HTTPS_PROXY", "all_proxy", + "ALL_PROXY", "no_proxy", "NO_PROXY"}) { + cleared.emplace_back(name, ""); + } + + const auto clock = default_clock; + const auto logger = std::make_shared(); + ProxyCapturingCurlLibrary library; + + const auto post_to = [&](const std::string &scheme) { + Curl client{logger, clock, library}; + const HTTPClient::URL url{scheme, "agent:8126", "/", ""}; + REQUIRE( + client.post(url, ignore, "body", ignore, ignore, clock().tick + 10s)); + client.drain(clock().tick + 1s); + }; + + SECTION("http scheme uses http_proxy") { + EnvGuard env{"http_proxy", "http://proxy:8080"}; + post_to("http"); + REQUIRE(library.proxy_ == "http://proxy:8080"); + } + + SECTION("https scheme uses https_proxy") { + EnvGuard env{"https_proxy", "http://secure:8080"}; + post_to("https"); + REQUIRE(library.proxy_ == "http://secure:8080"); + } + + SECTION("scheme-specific proxy falls back to all_proxy") { + EnvGuard env{"all_proxy", "http://any:8080"}; + post_to("http"); + REQUIRE(library.proxy_ == "http://any:8080"); + } + + SECTION("no_proxy is forwarded") { + EnvGuard env{"no_proxy", "agent,localhost"}; + post_to("http"); + REQUIRE(library.no_proxy_ == "agent,localhost"); + } + + SECTION("uppercase HTTP_PROXY is ignored (httpoxy)") { + EnvGuard env{"HTTP_PROXY", "http://attacker:8080"}; + post_to("http"); + REQUIRE(library.proxy_ == ""); + } + + SECTION("absent proxy environment yields empty options") { + post_to("http"); + REQUIRE(library.proxy_ == ""); + REQUIRE(library.no_proxy_ == ""); + } + + SECTION("unix sockets are never proxied") { + EnvGuard env{"http_proxy", "http://proxy:8080"}; + post_to("unix"); + REQUIRE(library.proxy_ == ""); + } +} From f0bce1da5ad412842cfda6205f4306f0a23be52c Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 16:57:58 +0200 Subject: [PATCH 2/8] code cleaning: remove useless includes --- src/datadog/curl.cpp | 9 --------- src/datadog/curl.h | 3 --- test/test_curl.cpp | 9 --------- 3 files changed, 21 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index 13ce2b6fe..66a82d906 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -2,19 +2,10 @@ #include #include -#include #include -#include -#include -#include -#include -#include #include -#include -#include #include -#include #include #include #include diff --git a/src/datadog/curl.h b/src/datadog/curl.h index ad9e557ae..3ff8e2398 100644 --- a/src/datadog/curl.h +++ b/src/datadog/curl.h @@ -11,10 +11,7 @@ #include #include -#include -#include #include -#include #include namespace datadog::tracing { diff --git a/test/test_curl.cpp b/test/test_curl.cpp index efac2c5fb..e99d2af8a 100644 --- a/test/test_curl.cpp +++ b/test/test_curl.cpp @@ -1,20 +1,11 @@ -#include #include -#include -#include -#include #include #include -#include -#include #include -#include -#include #include #include "common/environment.h" -#include "datadog/clock.h" #include "mocks/loggers.h" #include "null_logger.h" #include "test.h" From ba3e350f443be7fd5276ea37fc44d8b230437df8 Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 19:27:24 +0200 Subject: [PATCH 3/8] Allow getenv usage in curl.cpp --- bin/check-environment-variables | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bin/check-environment-variables b/bin/check-environment-variables index 07a7f27ce..9faae9137 100755 --- a/bin/check-environment-variables +++ b/bin/check-environment-variables @@ -1,5 +1,4 @@ #!/bin/sh -# TODO set -eu if grep -R -n \ @@ -10,6 +9,7 @@ if grep -R -n \ --include='*.h' \ --include='*.hpp' \ --exclude='environment.cpp' \ + --exclude='curl.cpp' \ 'std::getenv' include src; then echo "Check failed: std::getenv usage detected." exit 1 From 06ae4ea555d3b7369a4a55710fcf5d2314aed792 Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 19:36:58 +0200 Subject: [PATCH 4/8] code cleaning: clean namespaces usage --- src/datadog/curl.cpp | 105 ++++++++++++++++++++----------------------- 1 file changed, 48 insertions(+), 57 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index 66a82d906..3ea852c5d 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -14,14 +14,59 @@ #include "json.hpp" #include "string_util.h" -namespace datadog { -namespace tracing { +namespace datadog::tracing { + namespace { // `libcurl` is the default implementation: it calls `curl_*` functions under // the hood. CurlLibrary libcurl; +struct ProxyConfiguration { + Optional all_proxy; + Optional http_proxy; + Optional https_proxy; + Optional no_proxy; +}; + +Optional environment_variable(const char *name) { + const char *const value = std::getenv(name); + if (value == nullptr || *value == '\0') { + return nullopt; + } + return std::string{value}; +} + +ProxyConfiguration load_proxy_configuration() { + ProxyConfiguration config; + + config.all_proxy = environment_variable("all_proxy"); + if (!config.all_proxy) { + config.all_proxy = environment_variable("ALL_PROXY"); + } + + // Only the lowercase form for http, to avoid httpoxy (CVE-2016-5385). + config.http_proxy = environment_variable("http_proxy"); + + config.https_proxy = environment_variable("https_proxy"); + if (!config.https_proxy) { + config.https_proxy = environment_variable("HTTPS_PROXY"); + } + + config.no_proxy = environment_variable("no_proxy"); + if (!config.no_proxy) { + config.no_proxy = environment_variable("NO_PROXY"); + } + + return config; +} + +void throw_on_error(CURLcode result) { + if (result != CURLE_OK) { + throw result; + } +} + } // namespace CURL *CurlLibrary::easy_init() { return curl_easy_init(); } @@ -163,49 +208,6 @@ using HeadersSetter = HTTPClient::HeadersSetter; using ResponseHandler = HTTPClient::ResponseHandler; using URL = HTTPClient::URL; -struct ProxyConfiguration { - Optional all_proxy; - Optional http_proxy; - Optional https_proxy; - Optional no_proxy; -}; - -namespace { - -Optional environment_variable(const char *name) { - const char *const value = std::getenv(name); - if (value == nullptr || *value == '\0') { - return nullopt; - } - return std::string{value}; -} - -ProxyConfiguration load_proxy_configuration() { - ProxyConfiguration config; - - config.all_proxy = environment_variable("all_proxy"); - if (!config.all_proxy) { - config.all_proxy = environment_variable("ALL_PROXY"); - } - - // Only the lowercase form for http, to avoid httpoxy (CVE-2016-5385). - config.http_proxy = environment_variable("http_proxy"); - - config.https_proxy = environment_variable("https_proxy"); - if (!config.https_proxy) { - config.https_proxy = environment_variable("HTTPS_PROXY"); - } - - config.no_proxy = environment_variable("no_proxy"); - if (!config.no_proxy) { - config.no_proxy = environment_variable("NO_PROXY"); - } - - return config; -} - -} // namespace - class CurlImpl { std::mutex mutex_; CurlLibrary &curl_; @@ -283,16 +285,6 @@ class CurlImpl { void clear_requests(); }; -namespace { - -void throw_on_error(CURLcode result) { - if (result != CURLE_OK) { - throw result; - } -} - -} // namespace - Curl::Curl(const std::shared_ptr &logger, const Clock &clock) : Curl(logger, clock, libcurl) {} @@ -718,5 +710,4 @@ void CurlImpl::HeaderReader::visit( } } -} // namespace tracing -} // namespace datadog +} // namespace datadog::tracing From 96d8ac001974368f2770e63ca59e9c9b76e11cac Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 19:50:54 +0200 Subject: [PATCH 5/8] code cleaning: extract is_unix_socket() --- src/datadog/curl.cpp | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index 3ea852c5d..80993f7aa 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -61,6 +61,11 @@ ProxyConfiguration load_proxy_configuration() { return config; } +bool is_unix_socket(const HTTPClient::URL &url) { + return url.scheme == "unix" || url.scheme == "http+unix" || + url.scheme == "https+unix"; +} + void throw_on_error(CURLcode result) { if (result != CURLE_OK) { throw result; @@ -414,16 +419,12 @@ Expected CurlImpl::post( throw_on_error(curl_.easy_setopt_writefunction(handle.get(), &on_read_body)); throw_on_error(curl_.easy_setopt_writedata(handle.get(), request.get())); - const bool is_unix_socket = url.scheme == "unix" || - url.scheme == "http+unix" || - url.scheme == "https+unix"; - throw_on_error(curl_.easy_setopt_noproxy( handle.get(), proxy_config_.no_proxy ? proxy_config_.no_proxy->c_str() : "")); const std::string *proxy = nullptr; - if (!is_unix_socket) { + if (!is_unix_socket(url)) { const Optional &scheme_proxy = url.scheme == "https" ? proxy_config_.https_proxy : proxy_config_.http_proxy; @@ -436,7 +437,7 @@ Expected CurlImpl::post( throw_on_error( curl_.easy_setopt_proxy(handle.get(), proxy ? proxy->c_str() : "")); - if (is_unix_socket) { + if (is_unix_socket(url)) { throw_on_error(curl_.easy_setopt_unix_socket_path(handle.get(), url.authority.c_str())); // The authority section of the URL is ignored when a unix domain socket is From c468bc2dc965c9d179354bba920e97d5de2e9d28 Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 20:01:23 +0200 Subject: [PATCH 6/8] code cleaning: extract resolve_proxy() --- src/datadog/curl.cpp | 32 ++++++++++++++++++-------------- 1 file changed, 18 insertions(+), 14 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index 80993f7aa..30bccff89 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -66,6 +66,22 @@ bool is_unix_socket(const HTTPClient::URL &url) { url.scheme == "https+unix"; } +StringView resolve_proxy(const HTTPClient::URL &url, + const ProxyConfiguration &config) { + if (is_unix_socket(url)) { + return ""; + } + const Optional &scheme_proxy = + url.scheme == "https" ? config.https_proxy : config.http_proxy; + if (scheme_proxy) { + return *scheme_proxy; + } + if (config.all_proxy) { + return *config.all_proxy; + } + return ""; +} + void throw_on_error(CURLcode result) { if (result != CURLE_OK) { throw result; @@ -422,20 +438,8 @@ Expected CurlImpl::post( throw_on_error(curl_.easy_setopt_noproxy( handle.get(), proxy_config_.no_proxy ? proxy_config_.no_proxy->c_str() : "")); - - const std::string *proxy = nullptr; - if (!is_unix_socket(url)) { - const Optional &scheme_proxy = url.scheme == "https" - ? proxy_config_.https_proxy - : proxy_config_.http_proxy; - if (scheme_proxy) { - proxy = &*scheme_proxy; - } else if (proxy_config_.all_proxy) { - proxy = &*proxy_config_.all_proxy; - } - } - throw_on_error( - curl_.easy_setopt_proxy(handle.get(), proxy ? proxy->c_str() : "")); + const StringView proxy = resolve_proxy(url, proxy_config_); + throw_on_error(curl_.easy_setopt_proxy(handle.get(), proxy.data())); if (is_unix_socket(url)) { throw_on_error(curl_.easy_setopt_unix_socket_path(handle.get(), From 156487c3126c5edec1d17f9aa905a362f6c08a5b Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 20:18:04 +0200 Subject: [PATCH 7/8] code cleaning: extract configure_handle() --- src/datadog/curl.cpp | 68 +++++++++++++++++++++++--------------------- 1 file changed, 35 insertions(+), 33 deletions(-) diff --git a/src/datadog/curl.cpp b/src/datadog/curl.cpp index 30bccff89..10e5f8614 100644 --- a/src/datadog/curl.cpp +++ b/src/datadog/curl.cpp @@ -282,6 +282,7 @@ class CurlImpl { }; void run(); + void configure_handle(CURL *handle, Request &request, const URL &url); void handle_message(const CURLMsg &); CURLcode log_on_error(CURLcode result); CURLMcode log_on_error(CURLMcode result); @@ -419,39 +420,7 @@ Expected CurlImpl::post( "unable to initialize a curl handle for request sending"}; } - throw_on_error( - curl_.easy_setopt_httpheader(handle.get(), request->request_headers)); - throw_on_error(curl_.easy_setopt_private(handle.get(), request.get())); - throw_on_error( - curl_.easy_setopt_errorbuffer(handle.get(), request->error_buffer)); - throw_on_error(curl_.easy_setopt_post(handle.get(), 1)); - throw_on_error(curl_.easy_setopt_postfieldsize( - handle.get(), static_cast(request->request_body.size()))); - throw_on_error( - curl_.easy_setopt_postfields(handle.get(), request->request_body.data())); - throw_on_error( - curl_.easy_setopt_headerfunction(handle.get(), &on_read_header)); - throw_on_error(curl_.easy_setopt_headerdata(handle.get(), request.get())); - throw_on_error(curl_.easy_setopt_writefunction(handle.get(), &on_read_body)); - throw_on_error(curl_.easy_setopt_writedata(handle.get(), request.get())); - - throw_on_error(curl_.easy_setopt_noproxy( - handle.get(), - proxy_config_.no_proxy ? proxy_config_.no_proxy->c_str() : "")); - const StringView proxy = resolve_proxy(url, proxy_config_); - throw_on_error(curl_.easy_setopt_proxy(handle.get(), proxy.data())); - - if (is_unix_socket(url)) { - throw_on_error(curl_.easy_setopt_unix_socket_path(handle.get(), - url.authority.c_str())); - // The authority section of the URL is ignored when a unix domain socket is - // to be used. - throw_on_error(curl_.easy_setopt_url( - handle.get(), ("http://localhost" + url.path).c_str())); - } else { - throw_on_error(curl_.easy_setopt_url( - handle.get(), (url.scheme + "://" + url.authority + url.path).c_str())); - } + configure_handle(handle.get(), *request, url); { std::lock_guard lock(mutex_); @@ -468,6 +437,39 @@ Expected CurlImpl::post( return Error{Error::CURL_REQUEST_SETUP_FAILED, curl_.easy_strerror(error)}; } +void CurlImpl::configure_handle(CURL *handle, Request &request, + const URL &url) { + throw_on_error(curl_.easy_setopt_httpheader(handle, request.request_headers)); + throw_on_error(curl_.easy_setopt_private(handle, &request)); + throw_on_error(curl_.easy_setopt_errorbuffer(handle, request.error_buffer)); + throw_on_error(curl_.easy_setopt_post(handle, 1)); + throw_on_error(curl_.easy_setopt_postfieldsize( + handle, static_cast(request.request_body.size()))); + throw_on_error( + curl_.easy_setopt_postfields(handle, request.request_body.data())); + throw_on_error(curl_.easy_setopt_headerfunction(handle, &on_read_header)); + throw_on_error(curl_.easy_setopt_headerdata(handle, &request)); + throw_on_error(curl_.easy_setopt_writefunction(handle, &on_read_body)); + throw_on_error(curl_.easy_setopt_writedata(handle, &request)); + + throw_on_error(curl_.easy_setopt_noproxy( + handle, proxy_config_.no_proxy ? proxy_config_.no_proxy->c_str() : "")); + const StringView proxy = resolve_proxy(url, proxy_config_); + throw_on_error(curl_.easy_setopt_proxy(handle, proxy.data())); + + if (is_unix_socket(url)) { + throw_on_error( + curl_.easy_setopt_unix_socket_path(handle, url.authority.c_str())); + // The authority section of the URL is ignored when a unix domain socket is + // to be used. + throw_on_error( + curl_.easy_setopt_url(handle, ("http://localhost" + url.path).c_str())); + } else { + throw_on_error(curl_.easy_setopt_url( + handle, (url.scheme + "://" + url.authority + url.path).c_str())); + } +} + void CurlImpl::clear_requests() { for (const auto &handle : request_handles_) { char *user_data; From 6d126a40cfba3332b28c53e9080b4ca49e09adbc Mon Sep 17 00:00:00 2001 From: Xavier Lamorlette Date: Fri, 12 Jun 2026 20:26:17 +0200 Subject: [PATCH 8/8] skip test not relevant for Windwows --- test/test_curl.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/test/test_curl.cpp b/test/test_curl.cpp index e99d2af8a..e48a9690c 100644 --- a/test/test_curl.cpp +++ b/test/test_curl.cpp @@ -551,11 +551,13 @@ CURL_TEST("proxy is taken from the environment and forwarded to libcurl") { REQUIRE(library.no_proxy_ == "agent,localhost"); } +#ifndef _WIN32 // Windows environment variable names are case-insensitive SECTION("uppercase HTTP_PROXY is ignored (httpoxy)") { EnvGuard env{"HTTP_PROXY", "http://attacker:8080"}; post_to("http"); REQUIRE(library.proxy_ == ""); } +#endif SECTION("absent proxy environment yields empty options") { post_to("http");