mirror of
https://github.com/yhirose/cpp-httplib.git
synced 2026-10-15 10:39:37 +00:00
Fix #2457
This commit is contained in:
1 parent
4c4b62dd7e
commit
d278f965cc
2 files changed
+183
-4
No files matched your search
@@ -13200,10 +13200,12 @@ inline bool ClientImpl::handle_request(Stream &strm, Request &req,
|
|||||||
req.authorization_count_ < 5) {
|
req.authorization_count_ < 5) {
|
||||||
auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
|
auto is_proxy = res.status == StatusCode::ProxyAuthenticationRequired_407;
|
||||||
|
|
||||||
// A 407 from a direct (NO_PROXY-bypassed) origin is meaningless and
|
// Only retry when the 407 actually came from a proxy hop: plain HTTP
|
||||||
// must not trigger a retry — that would let the origin extract the
|
// through an enabled proxy. HTTPS via CONNECT tunnels the 407 from the
|
||||||
// user's proxy digest credentials.
|
// origin (#2457); direct/bypassed origins have no proxy hop at all.
|
||||||
if (is_proxy && !is_proxy_enabled_for_host(host_)) { return ret; }
|
if (is_proxy && !(!is_ssl() && is_proxy_enabled_for_host(host_))) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
const auto &username =
|
const auto &username =
|
||||||
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
is_proxy ? proxy_digest_auth_username_ : digest_auth_username_;
|
||||||
|
|||||||
+177
@@ -18873,3 +18873,180 @@ TEST(NoProxyTest, KeepAliveSocketInvalidatedOnSetNoProxy) {
|
|||||||
EXPECT_EQ(1, proxy_hits.load());
|
EXPECT_EQ(1, proxy_hits.load());
|
||||||
EXPECT_EQ(1, target_hits.load());
|
EXPECT_EQ(1, target_hits.load());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if defined(CPPHTTPLIB_SSL_ENABLED) && !defined(_WIN32)
|
||||||
|
namespace proxy_tunnel_test {
|
||||||
|
|
||||||
|
// SSLServer counterpart to no_proxy_test::ScopedServer.
|
||||||
|
class ScopedSSLServer {
|
||||||
|
public:
|
||||||
|
ScopedSSLServer() : svr_(SERVER_CERT_FILE, SERVER_PRIVATE_KEY_FILE) {
|
||||||
|
port_ = svr_.bind_to_any_port("127.0.0.1");
|
||||||
|
}
|
||||||
|
~ScopedSSLServer() {
|
||||||
|
svr_.stop();
|
||||||
|
if (th_.joinable()) { th_.join(); }
|
||||||
|
}
|
||||||
|
SSLServer &svr() { return svr_; }
|
||||||
|
int port() const { return port_; }
|
||||||
|
void listen() {
|
||||||
|
th_ = std::thread([this] { svr_.listen_after_bind(); });
|
||||||
|
svr_.wait_until_ready();
|
||||||
|
}
|
||||||
|
|
||||||
|
private:
|
||||||
|
SSLServer svr_;
|
||||||
|
std::thread th_;
|
||||||
|
int port_ = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Accepts CONNECT, replies 200, byte-forwards to forward_port.
|
||||||
|
class ScopedConnectProxy {
|
||||||
|
public:
|
||||||
|
explicit ScopedConnectProxy(int forward_port) : forward_port_(forward_port) {
|
||||||
|
listen_fd_ = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
if (listen_fd_ < 0) { return; }
|
||||||
|
int yes = 1;
|
||||||
|
::setsockopt(listen_fd_, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes));
|
||||||
|
sockaddr_in a{};
|
||||||
|
a.sin_family = AF_INET;
|
||||||
|
a.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
a.sin_port = 0;
|
||||||
|
if (::bind(listen_fd_, reinterpret_cast<sockaddr *>(&a), sizeof(a)) != 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
socklen_t alen = sizeof(a);
|
||||||
|
if (::getsockname(listen_fd_, reinterpret_cast<sockaddr *>(&a), &alen) !=
|
||||||
|
0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
port_ = ntohs(a.sin_port);
|
||||||
|
if (::listen(listen_fd_, 1) != 0) { return; }
|
||||||
|
th_ = std::thread([this] { run(); });
|
||||||
|
}
|
||||||
|
|
||||||
|
~ScopedConnectProxy() {
|
||||||
|
stop_ = true;
|
||||||
|
if (listen_fd_ >= 0) {
|
||||||
|
::shutdown(listen_fd_, SHUT_RDWR);
|
||||||
|
::close(listen_fd_);
|
||||||
|
}
|
||||||
|
if (th_.joinable()) { th_.join(); }
|
||||||
|
}
|
||||||
|
|
||||||
|
int port() const { return port_; }
|
||||||
|
int connect_hits() const { return connect_hits_.load(); }
|
||||||
|
|
||||||
|
private:
|
||||||
|
void run() {
|
||||||
|
while (!stop_.load()) {
|
||||||
|
fd_set rfds;
|
||||||
|
FD_ZERO(&rfds);
|
||||||
|
FD_SET(listen_fd_, &rfds);
|
||||||
|
timeval tv{0, 100 * 1000};
|
||||||
|
int sel = ::select(listen_fd_ + 1, &rfds, nullptr, nullptr, &tv);
|
||||||
|
if (sel <= 0) { continue; }
|
||||||
|
|
||||||
|
int client_fd = ::accept(listen_fd_, nullptr, nullptr);
|
||||||
|
if (client_fd < 0) { continue; }
|
||||||
|
|
||||||
|
std::string req;
|
||||||
|
char buf[2048];
|
||||||
|
while (req.find("\r\n\r\n") == std::string::npos) {
|
||||||
|
ssize_t n = ::recv(client_fd, buf, sizeof(buf), 0);
|
||||||
|
if (n <= 0) { break; }
|
||||||
|
req.append(buf, static_cast<size_t>(n));
|
||||||
|
}
|
||||||
|
if (req.compare(0, 7, "CONNECT") != 0) {
|
||||||
|
::close(client_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
connect_hits_++;
|
||||||
|
|
||||||
|
const char *ok = "HTTP/1.1 200 Connection established\r\n\r\n";
|
||||||
|
::send(client_fd, ok, std::strlen(ok), 0);
|
||||||
|
|
||||||
|
int origin_fd = ::socket(AF_INET, SOCK_STREAM, 0);
|
||||||
|
sockaddr_in o{};
|
||||||
|
o.sin_family = AF_INET;
|
||||||
|
o.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
|
||||||
|
o.sin_port = htons(static_cast<unsigned short>(forward_port_));
|
||||||
|
if (::connect(origin_fd, reinterpret_cast<sockaddr *>(&o), sizeof(o)) !=
|
||||||
|
0) {
|
||||||
|
::close(client_fd);
|
||||||
|
::close(origin_fd);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto forward = [](int from, int to) {
|
||||||
|
char b[8192];
|
||||||
|
for (;;) {
|
||||||
|
ssize_t n = ::recv(from, b, sizeof(b), 0);
|
||||||
|
if (n <= 0) { break; }
|
||||||
|
ssize_t off = 0;
|
||||||
|
while (off < n) {
|
||||||
|
ssize_t s = ::send(to, b + off, static_cast<size_t>(n - off), 0);
|
||||||
|
if (s <= 0) {
|
||||||
|
off = n;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
off += s;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
::shutdown(to, SHUT_WR);
|
||||||
|
};
|
||||||
|
|
||||||
|
std::thread t1([&] { forward(client_fd, origin_fd); });
|
||||||
|
std::thread t2([&] { forward(origin_fd, client_fd); });
|
||||||
|
t1.join();
|
||||||
|
t2.join();
|
||||||
|
::close(client_fd);
|
||||||
|
::close(origin_fd);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int forward_port_ = -1;
|
||||||
|
int listen_fd_ = -1;
|
||||||
|
int port_ = 0;
|
||||||
|
std::thread th_;
|
||||||
|
std::atomic<bool> stop_{false};
|
||||||
|
std::atomic<int> connect_hits_{0};
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace proxy_tunnel_test
|
||||||
|
|
||||||
|
TEST(ProxyTunnelTest, OriginReturning407InsideTunnelDoesNotLeakProxyDigest) {
|
||||||
|
// Origin inside a CONNECT tunnel replying 407 must not trigger the digest
|
||||||
|
// retry; otherwise proxy creds would be sent to the origin.
|
||||||
|
std::atomic<int> origin_hits{0};
|
||||||
|
std::atomic<bool> origin_saw_proxy_authz{false};
|
||||||
|
|
||||||
|
proxy_tunnel_test::ScopedSSLServer origin;
|
||||||
|
origin.svr().Get(".*", [&](const Request &req, Response &res) {
|
||||||
|
origin_hits++;
|
||||||
|
if (req.has_header("Proxy-Authorization")) {
|
||||||
|
origin_saw_proxy_authz = true;
|
||||||
|
}
|
||||||
|
res.status = StatusCode::ProxyAuthenticationRequired_407;
|
||||||
|
res.set_header("Proxy-Authenticate",
|
||||||
|
"Digest realm=\"evil\", qop=\"auth\", nonce=\"abc\", "
|
||||||
|
"algorithm=MD5");
|
||||||
|
});
|
||||||
|
origin.listen();
|
||||||
|
|
||||||
|
proxy_tunnel_test::ScopedConnectProxy proxy(origin.port());
|
||||||
|
ASSERT_NE(0, proxy.port());
|
||||||
|
|
||||||
|
SSLClient cli(HOST, origin.port());
|
||||||
|
cli.enable_server_certificate_verification(false);
|
||||||
|
cli.set_proxy(HOST, proxy.port());
|
||||||
|
cli.set_proxy_digest_auth("proxy-user", "proxy-pass");
|
||||||
|
|
||||||
|
auto res = cli.Get("/x");
|
||||||
|
ASSERT_TRUE(res);
|
||||||
|
EXPECT_EQ(StatusCode::ProxyAuthenticationRequired_407, res->status);
|
||||||
|
EXPECT_EQ(1, origin_hits.load());
|
||||||
|
EXPECT_FALSE(origin_saw_proxy_authz.load());
|
||||||
|
EXPECT_EQ(1, proxy.connect_hits());
|
||||||
|
}
|
||||||
|
#endif
|
||||||
Reference in new issue
Block a user