core: carry credentials through the probe and its auth handshake
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 <[email protected]> Claude-Session: https://claude.ai/code/session_01HPPSGhiArbvQgwC2DNiURS
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
+14
-2
@@ -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<TransferStats> 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> &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<TransferStats> r) { on_range_done(p, std::move(r)); };
|
||||
|
||||
Reference in New Issue
Block a user