mirror of
https://github.com/lightpanda-io/browser.git
synced 2025-10-29 15:13:28 +00:00
Merge pull request #960 from lightpanda-io/auth-challenge
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
Some checks failed
e2e-test / zig build release (push) Has been cancelled
e2e-test / demo-scripts (push) Has been cancelled
e2e-test / cdp-and-hyperfine-bench (push) Has been cancelled
e2e-test / perf-fmt (push) Has been cancelled
zig-test / zig build dev (push) Has been cancelled
zig-test / browser fetch (push) Has been cancelled
zig-test / zig test (push) Has been cancelled
zig-test / perf-fmt (push) Has been cancelled
auth required interception
This commit is contained in:
@@ -328,6 +328,11 @@ fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void {
|
||||
}
|
||||
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PRIVATE, transfer));
|
||||
|
||||
// add credentials
|
||||
if (req.credentials) |creds| {
|
||||
try errorCheck(c.curl_easy_setopt(easy, c.CURLOPT_PROXYUSERPWD, creds.ptr));
|
||||
}
|
||||
}
|
||||
|
||||
// Once soon as this is called, our "perform" loop is responsible for
|
||||
@@ -378,6 +383,22 @@ fn perform(self: *Client, timeout_ms: c_int, socket: ?posix.socket_t) !bool {
|
||||
const easy = msg.easy_handle.?;
|
||||
const transfer = try Transfer.fromEasy(easy);
|
||||
|
||||
// In case of auth challenge
|
||||
if (transfer._auth_challenge != null and transfer._tries < 10) { // TODO give a way to configure the number of auth retries.
|
||||
if (transfer.client.notification) |notification| {
|
||||
var wait_for_interception = false;
|
||||
notification.dispatch(.http_request_auth_required, &.{ .transfer = transfer, .wait_for_interception = &wait_for_interception });
|
||||
if (wait_for_interception) {
|
||||
// the request is put on hold to be intercepted.
|
||||
// In this case we ignore callbacks for now.
|
||||
// Note: we don't deinit transfer on purpose: we want to keep
|
||||
// using it for the following request.
|
||||
self.endTransfer(transfer);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// release it ASAP so that it's available; some done_callbacks
|
||||
// will load more resources.
|
||||
self.endTransfer(transfer);
|
||||
@@ -557,6 +578,7 @@ pub const Request = struct {
|
||||
body: ?[]const u8 = null,
|
||||
cookie_jar: *CookieJar,
|
||||
resource_type: ResourceType,
|
||||
credentials: ?[:0]const u8 = null,
|
||||
|
||||
// arbitrary data that can be associated with this request
|
||||
ctx: *anyopaque = undefined,
|
||||
@@ -574,6 +596,46 @@ pub const Request = struct {
|
||||
};
|
||||
};
|
||||
|
||||
pub const AuthChallenge = struct {
|
||||
status: u16,
|
||||
source: enum { server, proxy },
|
||||
scheme: enum { basic, digest },
|
||||
realm: []const u8,
|
||||
|
||||
pub fn parse(status: u16, header: []const u8) !AuthChallenge {
|
||||
var ac: AuthChallenge = .{
|
||||
.status = status,
|
||||
.source = undefined,
|
||||
.realm = "TODO", // TODO parser and set realm
|
||||
.scheme = undefined,
|
||||
};
|
||||
|
||||
const sep = std.mem.indexOfPos(u8, header, 0, ": ") orelse return error.InvalidHeader;
|
||||
const hname = header[0..sep];
|
||||
const hvalue = header[sep + 2 ..];
|
||||
|
||||
if (std.ascii.eqlIgnoreCase("WWW-Authenticate", hname)) {
|
||||
ac.source = .server;
|
||||
} else if (std.ascii.eqlIgnoreCase("Proxy-Authenticate", hname)) {
|
||||
ac.source = .proxy;
|
||||
} else {
|
||||
return error.InvalidAuthChallenge;
|
||||
}
|
||||
|
||||
const pos = std.mem.indexOfPos(u8, std.mem.trim(u8, hvalue, std.ascii.whitespace[0..]), 0, " ") orelse hvalue.len;
|
||||
const _scheme = hvalue[0..pos];
|
||||
if (std.ascii.eqlIgnoreCase(_scheme, "basic")) {
|
||||
ac.scheme = .basic;
|
||||
} else if (std.ascii.eqlIgnoreCase(_scheme, "digest")) {
|
||||
ac.scheme = .digest;
|
||||
} else {
|
||||
return error.UnknownAuthChallengeScheme;
|
||||
}
|
||||
|
||||
return ac;
|
||||
}
|
||||
};
|
||||
|
||||
pub const Transfer = struct {
|
||||
arena: ArenaAllocator,
|
||||
id: usize = 0,
|
||||
@@ -586,7 +648,6 @@ pub const Transfer = struct {
|
||||
bytes_received: usize = 0,
|
||||
|
||||
// We'll store the response header here
|
||||
proxy_response_header: ?ResponseHeader = null,
|
||||
response_header: ?ResponseHeader = null,
|
||||
|
||||
// track if the header callbacks done have been called.
|
||||
@@ -597,7 +658,22 @@ pub const Transfer = struct {
|
||||
_handle: ?*Handle = null,
|
||||
|
||||
_redirecting: bool = false,
|
||||
_forbidden: bool = false,
|
||||
_auth_challenge: ?AuthChallenge = null,
|
||||
|
||||
// number of times the transfer has been tried.
|
||||
// incremented by reset func.
|
||||
_tries: u8 = 0,
|
||||
|
||||
pub fn reset(self: *Transfer) void {
|
||||
self._redirecting = false;
|
||||
self._auth_challenge = null;
|
||||
self._notified_fail = false;
|
||||
self._header_done_called = false;
|
||||
self.response_header = null;
|
||||
self.bytes_received = 0;
|
||||
|
||||
self._tries += 1;
|
||||
}
|
||||
|
||||
fn deinit(self: *Transfer) void {
|
||||
self.req.headers.deinit();
|
||||
@@ -615,7 +691,11 @@ pub const Transfer = struct {
|
||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_EFFECTIVE_URL, &url));
|
||||
|
||||
var status: c_long = undefined;
|
||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_RESPONSE_CODE, &status));
|
||||
if (self._auth_challenge) |_| {
|
||||
status = 407;
|
||||
} else {
|
||||
try errorCheck(c.curl_easy_getinfo(easy, c.CURLINFO_RESPONSE_CODE, &status));
|
||||
}
|
||||
|
||||
self.response_header = .{
|
||||
.url = url,
|
||||
@@ -648,6 +728,10 @@ pub const Transfer = struct {
|
||||
self.req.url = url;
|
||||
}
|
||||
|
||||
pub fn updateCredentials(self: *Transfer, userpwd: [:0]const u8) void {
|
||||
self.req.credentials = userpwd;
|
||||
}
|
||||
|
||||
pub fn replaceRequestHeaders(self: *Transfer, allocator: Allocator, headers: []const Http.Header) !void {
|
||||
self.req.headers.deinit();
|
||||
|
||||
@@ -672,6 +756,14 @@ pub const Transfer = struct {
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
// abortAuthChallenge is called when an auth chanllenge interception is
|
||||
// abort. We don't call self.client.endTransfer here b/c it has been done
|
||||
// before interception process.
|
||||
pub fn abortAuthChallenge(self: *Transfer) void {
|
||||
self.client.requestFailed(self, error.AbortAuthChallenge);
|
||||
self.deinit();
|
||||
}
|
||||
|
||||
// redirectionCookies manages cookies during redirections handled by Curl.
|
||||
// It sets the cookies from the current response to the cookie jar.
|
||||
// It also immediately sets cookies for the following request.
|
||||
@@ -797,20 +889,44 @@ pub const Transfer = struct {
|
||||
transfer._redirecting = false;
|
||||
|
||||
if (status == 401 or status == 407) {
|
||||
transfer._forbidden = true;
|
||||
// The auth challenge must be parsed from a following
|
||||
// WWW-Authenticate or Proxy-Authenticate header.
|
||||
transfer._auth_challenge = .{
|
||||
.status = status,
|
||||
.source = undefined,
|
||||
.scheme = undefined,
|
||||
.realm = undefined,
|
||||
};
|
||||
return buf_len;
|
||||
}
|
||||
transfer._forbidden = false;
|
||||
transfer._auth_challenge = null;
|
||||
|
||||
transfer.bytes_received = buf_len;
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
if (transfer._redirecting == false and transfer._forbidden == false) {
|
||||
if (transfer._redirecting == false and transfer._auth_challenge != null) {
|
||||
transfer.bytes_received += buf_len;
|
||||
}
|
||||
|
||||
if (buf_len != 2) {
|
||||
if (transfer._auth_challenge != null) {
|
||||
// try to parse auth challenge.
|
||||
if (std.ascii.startsWithIgnoreCase(header, "WWW-Authenticate") or
|
||||
std.ascii.startsWithIgnoreCase(header, "Proxy-Authenticate"))
|
||||
{
|
||||
const ac = AuthChallenge.parse(
|
||||
transfer._auth_challenge.?.status,
|
||||
header,
|
||||
) catch |err| {
|
||||
// We can't parse the auth challenge
|
||||
log.err(.http, "parse auth challenge", .{ .err = err, .header = header });
|
||||
// Should we cancel the request? I don't think so.
|
||||
return buf_len;
|
||||
};
|
||||
transfer._auth_challenge = ac;
|
||||
}
|
||||
}
|
||||
return buf_len;
|
||||
}
|
||||
|
||||
@@ -838,7 +954,7 @@ pub const Transfer = struct {
|
||||
return c.CURL_WRITEFUNC_ERROR;
|
||||
};
|
||||
|
||||
if (transfer._redirecting) {
|
||||
if (transfer._redirecting or transfer._auth_challenge != null) {
|
||||
return chunk_len;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user