-
Notifications
You must be signed in to change notification settings - Fork 286
auth required interception #960
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
bb381e5
a847a1f
6b47aa2
a75f9dd
5a40cbd
4c60225
520a572
5defb5c
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -325,6 +325,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 | ||
|
|
@@ -365,6 +370,22 @@ fn perform(self: *Client, timeout_ms: c_int) !void { | |
| 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); | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder whether we need to send a | don't see issue with the current consumers implementation.
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum, I suppose the point of the interception is to take place during the request lifecycle. So the following callbacks will be called depending the result of the interception and the optional request following. |
||
| continue; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // release it ASAP so that it's available; some done_callbacks | ||
| // will load more resources. | ||
| self.endTransfer(transfer); | ||
|
|
@@ -542,6 +563,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, | ||
|
|
@@ -559,6 +581,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, | ||
|
|
@@ -571,7 +633,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. | ||
|
|
@@ -582,7 +643,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(); | ||
|
|
@@ -600,7 +676,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, | ||
|
|
@@ -633,6 +713,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(); | ||
|
|
||
|
|
@@ -657,6 +741,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. | ||
|
|
@@ -782,20 +874,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; | ||
| } | ||
|
|
||
|
|
@@ -823,7 +939,7 @@ pub const Transfer = struct { | |
| return c.CURL_WRITEFUNC_ERROR; | ||
| }; | ||
|
|
||
| if (transfer._redirecting) { | ||
| if (transfer._redirecting or transfer._auth_challenge != null) { | ||
| return chunk_len; | ||
| } | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we should add a flag to make sure that it was a auth interception and not a normal intercept.
Otherwise the situation may occur that a user sends a continueWithAuth as a responce to a requestPaused.
In that case for example if we call abortAuthChallenge we do not do the correct cleanup. In general we do not know what could happen, so better to catch it.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about testing if the transfer has a auth challenge?
Or you do want a complete separate
intercept_statelist?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Checking and I guess then removing the auth challenge should work, then the opposite for continue/abort/fulfill