Repository navigation
feat!: generated clients keep credentials on the origin a request was sent to across redirects - #146
Merged
Merged
Conversation
… sent to across redirects NewClient now follows redirects through the exported CheckRedirect policy, applied to any http.Client without a policy of its own: - A redirect on the origin that was asked (or from http up to https on the same host) is followed as before. - A redirect to another origin is followed only for GET and HEAD, carrying only Accept, Accept-Language and User-Agent. net/http alone would carry Authorization to another port or a subdomain of the same host, and every custom header (an API key header, for one) anywhere. - Any other method gets the 3xx back instead of resending its body elsewhere. - A redirect from https to plain http is refused with ErrInsecureRedirect. The retry loop no longer matches the "stopped after" text of a redirect error: it declines ErrInsecureRedirect and ErrTooManyRedirects by errors.Is. BREAKING CHANGE: a non-GET request redirected to another origin returns the 3xx as an APIError instead of following it, and an https to http redirect fails instead of being followed.
…eep more of a request on the same origin - CheckRedirect decides on the redirected request (RFC 9110 section 15.4): a 301, 302 or 303 after a POST is a GET without a body and may follow to another origin; a 307 or 308 of any other method, or any request with a body, gets the 3xx back. - A redirect to another origin rebuilds its headers from an allowlist, so a header set under a non-canonical key no longer survives. The allowlist keeps the conditional and range headers (Range, If-Range, If-Match, If-None-Match, If-Modified-Since, If-Unmodified-Since) and Accept-Encoding, which carry no credential. - The origin compares scheme, host and port with default ports; http counts as upgrading to https only from 80 to 443. Userinfo makes a redirect another origin only when it differs from the request's. - send applies SafeRedirects on each request, so a change to the caller's http.Client after NewClient takes effect. - The retry loop declines ErrInsecureRedirect and ErrTooManyRedirects by errors.Is and still declines a "stopped after" error, which a caller's own CheckRedirect may return.
…2 no longer takes that origin's page for its answer net/http turns a POST, PUT, PATCH or DELETE into a GET after a 301 or 302 as well as a 303. Followed to another origin, that GET's 200 (a proxy's login page, say) became the write's answer. CheckRedirect now lets a write follow to another origin only after a 303, which asks for a GET of another resource (RFC 9110 section 15.4.4); a 301 or 302 is handed back.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Generated clients now follow redirects through an exported policy,
CheckRedirect.sendapplies it on each request, throughSafeRedirects, to anyhttp.Clientthat has noCheckRedirectof its own.Accept,Accept-Encoding,Accept-Language,User-Agentand the conditional and range headers (Range,If-Range,If-Match,If-None-Match,If-Modified-Since,If-Unmodified-Since). The headers are rebuilt from that list, so a header set under a non-canonical key is dropped too.APIError, so no body is resent elsewhere and no other origin answers for the write.ErrInsecureRedirect.ErrTooManyRedirects.The retry loop declines
ErrInsecureRedirectandErrTooManyRedirectsbyerrors.Is. It still declines an error starting "stopped after", which a caller's ownCheckRedirectmay return, as net/http's does.Why
http.DefaultClient's policy dropsAuthorizationandCookieonly when the target is not the same host or a subdomain of it. It still sendsAuthorizationto another port on the same host and to any subdomain, sends every other header (such as anAPIKeyAuthheader) anywhere, resends a body across a 307 or 308 to another origin, and follows https to http. A client generated from a spec carries a credential on nearly every request, so the default should not hand it to whatever a redirect names.A GET to another origin still works, which a redirect to a CDN or an avatar host needs, and a ranged or conditional GET keeps its range and validators.
Breaking
The policy applies to any
http.Clientpassed withWithHTTPClientthat has noCheckRedirectof its own, not only to the default client.AuthorizationandCookie.A caller that wants the old behavior passes an
http.Clientwith its ownCheckRedirect, which is kept.Testing
TestE2E_Redirectsgenerates a client and runs it againsthttptestservers:Authorization, an API key header or a non-canonically keyed header.Range,If-RangeandIf-None-Matchon another origin.NewClientused.go vet ./...,go test ./...andgolangci-lint runpass.