From afaded85f85704cbb7d0854b3cba2bc8627f661b Mon Sep 17 00:00:00 2001 From: sami Date: Thu, 10 Sep 2026 20:19:05 +0400 Subject: [PATCH] core: carry credentials through the probe and its auth handshake MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit libcurl with CURLAUTH_ANY answers a 401/407 by resending the request with an Authorization header. Two spots in net/ cut that short: - http_client's header callback delivered the response head exactly once and latched `head_delivered`, so after an auth challenge the caller only ever saw the 401 — never the 2xx of the authenticated resend. Reset the latch when a fresh status line follows a delivered 401/407 (redirects never reach that path — their head is suppressed). - the prober's head callbacks return DataAction::abort to skip the body, which also aborts the transfer mid-handshake. Return `proceed` for a 401/407 when credentials were supplied, so curl's resend can run; the real status lands on the next header block. Also give ProbeRequest an `auth` field (default scheme == none) and pass it through base_request(), so a re-probe after a 401 can present the credentials the user just entered. No behaviour change when no auth is configured. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS --- core/include/vdm/net/probe.hpp | 1 + core/src/net/http_client.cpp | 7 +++++++ core/src/net/probe.cpp | 16 ++++++++++++++-- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/core/include/vdm/net/probe.hpp b/core/include/vdm/net/probe.hpp index 2122190..5b1e425 100644 --- a/core/include/vdm/net/probe.hpp +++ b/core/include/vdm/net/probe.hpp @@ -35,6 +35,7 @@ struct ProbeRequest { std::string user_agent; std::string referrer; ProxyConfig proxy; + AuthConfig auth; // credentials for a re-probe after a 401 (leave scheme == none otherwise) long connect_timeout_ms = 15000; long overall_timeout_ms = 25000; // download.probe deadline is 30 s diff --git a/core/src/net/http_client.cpp b/core/src/net/http_client.cpp index 25f9575..a83b206 100644 --- a/core/src/net/http_client.cpp +++ b/core/src/net/http_client.cpp @@ -195,6 +195,13 @@ struct HttpClient::Impl { std::string_view line(buf, total); if (line.starts_with("HTTP/")) { + // A new status line after we already delivered a 401/407 means libcurl's + // CURLAUTH_ANY handshake just resent with credentials: let the head of this + // second response be delivered too, so callers see the real (2xx/4xx) status + // rather than the challenge. Redirects never reach here delivered — their head + // is suppressed below — so this only fires for the auth resend. + if (st->head_delivered && (st->line_status == 401 || st->line_status == 407)) + st->head_delivered = false; st->line_status = status_from_line(line); st->head.headers.clear(); // keep only the final response's headers return total; diff --git a/core/src/net/probe.cpp b/core/src/net/probe.cpp index 608e02e..0a2c2f2 100644 --- a/core/src/net/probe.cpp +++ b/core/src/net/probe.cpp @@ -134,6 +134,7 @@ struct Prober::Impl { r.user_agent = pr.user_agent; r.referrer = pr.referrer; r.proxy = pr.proxy; + r.auth = pr.auth; r.follow_redirects = true; r.accept_encoding = false; r.connect_timeout_ms = pr.connect_timeout_ms; @@ -155,7 +156,7 @@ struct Prober::Impl { TransferCallbacks cbs; cbs.on_head = [p](const ResponseHead &h) { absorb_head(p->result, h); - return DataAction::abort; + return head_action(p, h); }; cbs.on_data = [](ConstByteSpan) { return DataAction::abort; }; cbs.on_finished = [p](Result r) { on_head_done(p, std::move(r)); }; @@ -165,6 +166,17 @@ struct Prober::Impl { client_.start(std::move(req), std::move(cbs)); } + // We want no body from a probe, so the head callback normally aborts after headers. + // The exception: a 401/407 when we were handed credentials — libcurl's CURLAUTH_ANY + // has to see that response before it resends with Authorization, so let this one + // through (a HEAD has no body; the ranged GET's is a single byte). The final status + // then lands on the next header block. + static DataAction head_action(const std::shared_ptr

&p, const ResponseHead &h) { + if ((h.status == 401 || h.status == 407) && p->job.req.auth.scheme != AuthScheme::none) + return DataAction::proceed; + return DataAction::abort; + } + static void absorb_head(ProbeResult &res, const ResponseHead &h) { if (h.status) res.http_status = h.status; @@ -242,7 +254,7 @@ struct Prober::Impl { TransferCallbacks cbs; cbs.on_head = [p](const ResponseHead &h) { absorb_range_head(p->result, h); - return DataAction::abort; // we don't need the one body byte + return head_action(p, h); // abort after headers, except a 401/407 with creds }; cbs.on_data = [](ConstByteSpan) { return DataAction::abort; }; cbs.on_finished = [p](Result r) { on_range_done(p, std::move(r)); };