Repository navigation
fix: send source headers only to the URL's origin across redirects - #74
Merged
Merged
Conversation
Remote downloads followed redirects with each platform's defaults, which disagreed and leaked credentials. Android's HttpURLConnection (AOSP's OkHttp) refused http → https and https → http redirects (http-error with the 3xx) and sent every source header but Authorization on to other hosts. iOS's URLSession followed https → http, also sent every header but Authorization to other hosts, and dropped Authorization on every redirect, so token-protected sources failed with 401 after a same-origin redirect. Both platforms now follow one RedirectPolicy (Kotlin and Swift): - at most 10 redirects; more fail with network-error; - http → https is followed, https → http (or a target that is not http(s)) is not: http-error with the redirect's status; - the source's headers, Authorization included, go only while every hop stays on the URL's origin (scheme, host and port); after the first hop to another origin, the rest of the chain is sent without them. If-None-Match and If-Modified-Since go on every hop, so a 304 from the target still validates the stored copy; - a refused redirect and too many redirects never show the stored copy: the server answered. Android follows redirects itself (instanceFollowRedirects = false, one connection per hop, the live one reachable by cancel()). iOS rebuilds each redirected request in a per-task URLSessionTaskDelegate, because the request URLSession proposes has already lost Authorization. The headers TSDoc, the sources, offline, troubleshooting and migration guides, ARCHITECTURE.md and api-design.md describe the policy. The fixture server gains /__redirect, /__redirect-loop and ?require= / ?forbid= header checks, and viewer-redirects covers a same-origin redirect, a cross-origin one (localhost → 127.0.0.1) and a loop on both platforms. Behavior change: an http URL that redirects to https on the same host changes origin, so its headers are not sent to the https URL. Send credentials to the https URL directly. Closes #70
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.
Summary
Remote downloads followed redirects with each platform's defaults, which disagreed and sent credentials on to other hosts (#70). Both platforms now follow one
RedirectPolicy(Kotlin and Swift):AuthorizationdroppedAuthorizationsentAuthorizationsenthttp-error3xxhttp-error3xxhttp-errorwith the 3xxnetwork-errorIOException)URLError)The "Before: Android" column is AOSP's OkHttp source as read in #70, not a device run of the old build; the "Before: iOS" column was reproduced on the iOS 26.5 simulator (see Testing).
If-None-Match/If-Modified-Sincego on every hop, so a304from the target still validates the stored copy.instanceFollowRedirects = false), one connection per hop;cancel()disconnects the live one.URLSessionTaskDelegate(iOS 15+): URLSession's proposed request has already lostAuthorization, even on the same origin.headersTSDoc, a "Redirects" section in the sources guide, the offline guide, two troubleshooting rows, the migration guide, the README guide table,docs/api.md, ARCHITECTURE.md and api-design.md./__redirect?to=,/__redirect-loopand?require=/?forbid=header checks (401 / 403). Three example entries and the newviewer-redirectsflow cover a same-origin redirect withAuthorization, a cross-origin one (localhost→127.0.0.1, whichadb reversecovers) and a loop.Behavior change: an
http://URL that redirects tohttps://on the same host changes origin, soheadersare not sent to the https URL (as the Fetch standard does withAuthorization). Send credentials to the https URL directly. No opt-in to keep headers across origins; that can wait for a request.Closes #70
Testing
DocumentCacheTest+ newRedirectPolicyTest): same-origin redirect keepsAuthorizationandX-Api-Key; cross-origin drops both, also after the chain comes back; a redirected revalidation sends the ETag and keeps the copy on304; 10 redirects followed, the 11th fails withnetwork-erroreven withallowStale; a redirect that is not followed ishttp-error302; cancelling mid-chain stores nothing. The https cases are pureRedirectPolicytests (the test server has no TLS). Full suite: 310 passed.HttpURLConnection(which also dropsAuthorizationacross hosts and refuses scheme changes), not AOSP's OkHttp. WithinstanceFollowRedirects = falseboth return the 3xx, so the new tests exercise our loop.DocumentCache/SharedDownload/RedirectPolicywith stand-ins for the Nitro types, run on the iOS 26.5 simulator against a local server with two http ports and one https port (test root certificate added to the simulator's keychain). 12 cases: same origin, cross origin, chain coming back, http → https, https → http (http-error302, nothing sent to http, no stale copy), 10 vs 11 redirects, 304 after a redirect, plus the pure policy cases. Unfixed code: 10 of 12 failed, as described in the issue. Fixed: all pass.viewer-redirectspasses on Android (API 37 emulator) and iOS (26.5 simulator); it ran on the fixed build only (the red runs are the JVM tests and the harness). The flows that download remotely still pass on both:viewer-offline,viewer-errors,viewer-sources,viewer-initial,pdf-document. These device runs came before a last cleanup of Android'sSharedDownload.download()(the connection is now closed in onefinally, no behavior change); the JVM suite was re-run after it.bun run typecheck,bun run lint,bun run test(124) pass.Need help on this PR? Tag
@codesmith-botwith what you need.