From 941745c33d8af20d37de7ade8b768c3c9f0a3c79 Mon Sep 17 00:00:00 2001 From: yhirose Date: Mon, 12 Oct 2026 22:32:08 -0400 Subject: [PATCH] Send proxy Basic authentication when one credential is empty Proxy-Authorization was added only when both the proxy username and the proxy password were set, and a cross-host redirect carried the proxy credentials over only when the username was set. Authorization has been sent when either one is set since #958; do the same for the proxy. A client that has both a proxy Basic credential with one empty field and a proxy bearer token now sends Basic rather than Bearer, the order Authorization already follows. --- httplib.h | 7 ++++--- test/test.cc | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/httplib.h b/httplib.h index e6b2fceb..8e029f02 100644 --- a/httplib.h +++ b/httplib.h @@ -15944,7 +15944,8 @@ inline void ClientImpl::setup_redirect_client(ClientType &client) { if (!proxy_host_.empty() && proxy_port_ != -1) { client.set_proxy(proxy_host_, proxy_port_); - if (!proxy_basic_auth_username_.empty()) { + if (!proxy_basic_auth_username_.empty() || + !proxy_basic_auth_password_.empty()) { client.set_proxy_basic_auth(proxy_basic_auth_username_, proxy_basic_auth_password_); } @@ -16047,8 +16048,8 @@ inline bool ClientImpl::write_request(Stream &strm, Request &req, // otherwise NO_PROXY-matched requests, and requests inside a TLS tunnel, // would leak proxy credentials to the destination server. if (is_proxy_enabled_for_host(host_) && (!is_ssl() || is_connect)) { - if (!proxy_basic_auth_username_.empty() && - !proxy_basic_auth_password_.empty() && + if ((!proxy_basic_auth_username_.empty() || + !proxy_basic_auth_password_.empty()) && !req.has_header("Proxy-Authorization")) { req.headers.insert(make_basic_authentication_header( proxy_basic_auth_username_, proxy_basic_auth_password_, true)); diff --git a/test/test.cc b/test/test.cc index ede009a0..3371b8dd 100644 --- a/test/test.cc +++ b/test/test.cc @@ -26962,6 +26962,39 @@ TEST(NoProxyTest, ProxyAuthorizationSentWhenNotBypassed) { EXPECT_TRUE(s.last_had_proxy_authz()); } +TEST(NoProxyTest, ProxyAuthorizationSentWithOneEmptyCredential) { + std::atomic hits{0}; + std::atomic hits_with_authz{0}; + + no_proxy_test::ScopedServer proxy_mock; + proxy_mock.svr().Get(".*", [&](const Request &req, Response &res) { + hits++; + if (req.has_header("Proxy-Authorization")) { hits_with_authz++; } + if (req.target.find("/redir") != std::string::npos) { + res.set_redirect("http://other.example/landed"); + } else { + res.set_content("via-proxy", "text/plain"); + } + }); + proxy_mock.listen(); + + for (const auto &c : {std::make_pair("u", ""), std::make_pair("", "p")}) { + hits = 0; + hits_with_authz = 0; + + Client cli("public.example", 80); + cli.set_proxy("127.0.0.1", proxy_mock.port()); + cli.set_proxy_basic_auth(c.first, c.second); + cli.set_follow_location(true); + + // The redirect to another host goes through the proxy as well + auto res = cli.Get("/redir"); + ASSERT_TRUE(res) << "Error: " << to_string(res.error()); + EXPECT_EQ(2, hits.load()); + EXPECT_EQ(2, hits_with_authz.load()); + } +} + TEST(NoProxyTest, EmptyNoProxyKeepsProxyOn) { ProxyAndTargetServers s; auto cli = make_client("anything.test", s);