Replies: 1 comment
|
Body updated with the Public Suffix List design, moved from "out of scope" into the actual proposal: a new |
|
Body updated with the Public Suffix List design, moved from "out of scope" into the actual proposal: a new |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Overview
RequestDL has no cookie handling today — no
Cookietype, no jar, nothing. This proposes a full, automatic cookie jar: cookies fromSet-Cookieresponse headers get stored and automatically re-sent on subsequent matching requests, on by default, scoped per namedSession.What's already free, from
async-http-clientSet-Cookieparsing is already implemented upstream —HTTPClient.Cookie(header:defaultDomain:)handles name/value/path/domain/expires/max-age/httpOnly/secure, including all three date-format variants the spec allows. RequestDL's ownCookietype (see below) delegates to this internally rather than reimplementing RFC 6265 parsing from scratch.transformRequestForRedirect(inasync-http-client'sRedirectState.swift) already strips theCookieheader when a redirect changes origin — the one security-sensitive edge case here is already correct upstream, nothing to duplicate.Where the jar lives
Internals.Sessionis recreated fresh on every request (Resolve.build()constructs a new one each time), so it can't hold cross-request state.Internals.ClientManager(Internals.ClientManager.swift:54) already poolsInternals.Clientinstances, but keys that pool by exact configuration equality, not identity alone (_reusableClientmatches onitem.sessionConfiguration == sessionConfiguration) — two requests sharing the same namedSessioncan still end up with two different pooledInternals.Clientinstances if any session-level property between them differs (Timeout,Proxy, anything).That matters because the cookie jar shouldn't fragment along with client pooling — cookies belong to "this named session" conceptually, the same way
URLSession'sHTTPCookieStoragedoesn't care about per-request connection settings. SoInternals.CookieStorage(afinal class, reference type) does not live as a stored property onInternals.Client. It lives inInternals.ClientManageritself, in a separate table keyed bysessionProviderIDalone — decoupled entirely from the config-equality-keyed client pool. Two differently-configured clients under the same session identity still share one jar.The two integration points
Internals.ClientResponseReceiver.didReceiveHead(task:_:)(
Internals/Sources/Session/Session/Models/Internals.ClientResponseReceiver.swift:86) alreadybuilds the response head from the raw headers and already has
url: String(the request URL,used as RFC 6265's
defaultDomainfallback when aSet-Cookieheader omits its ownDomainattribute) in scope. It gains two more constructor parameters —
cookieStorage: Internals.CookieStorage?(looked up by session identity) and
publicSuffixList: Internals.PublicSuffixList?(resolved perrequest, see below) — and reads
head.headers["Set-Cookie"]there, callingcookieStorage?.store(cookies, forURL: url, publicSuffixList: publicSuffixList).Internals.Session's privateexecute(client:requestConfiguration:cache:logger:)(
Internals/Sources/Session/Session/Internals.Session.swift:64) already has enough in scoperight before
requestConfiguration.build(eventLoop:)— that's where the session'scookieStoragegets looked up and
.cookies(forURL: requestConfiguration.url)folded into aCookieheader on amutated copy of
requestConfiguration, before the request is built. No PSL involvement needed onthis side at all — the public-suffix check only matters when storing an incoming cookie.
Matching semantics (RFC 6265)
Internals.CookieStorage.cookies(forURL:)filters stored cookies by: domain match (exact, or asuffix match against a cookie whose
Domainattribute was set), path match (cookie path is aprefix of the request path), the
secureflag (only included over HTTPS), and expiration (expiredor past-
max-agecookies are dropped, not returned).store(_:forURL:publicSuffixList:)(called from the incomingside) replaces any existing cookie sharing the same name/domain/path, matching RFC 6265's jar-update
semantics, rather than accumulating duplicates.
Merge policy
If a request already carries an explicit
Cookieheader (viaHeadersor any other property), thejar contributes nothing — same "explicit wins over automatic" precedent every other pair of
explicit/automatic behavior in this codebase already follows (an explicit
BaseURL/Proxyalwaysbeats an automatically-resolved one).
Public Suffix List — consumer-supplied, not bundled
Without it, a
Set-Cookie: session=x; Domain=co.ukresponse would be valid for every site under.co.ukunder plain RFC 6265 domain-matching — a "supercookie." Real browsers guard against this with the Public Suffix List (a list of ~9,000 domain suffixes under which unrelated parties can register), rejecting a cookie whoseDomainresolves to a bare public suffix.RequestDL deliberately does not bundle or maintain a PSL snapshot itself — that's a genuine ongoing maintenance burden (the list changes as new gTLDs and dynamic-DNS providers appear) that doesn't belong to an HTTP networking library, the same reasoning that kept hashing out of #197/#203. Nor can it defer to the OS: on Apple platforms
URLSession's protection is backed by a system-level list CFNetwork maintains privately, with no public API for third-party code to query, andswift-corelibs-foundationon Linux has no equivalent at all — deferring to "whatever the platform does" would mean silently weaker protection on Linux than Darwin.So PSL support is consumer-pluggable, via a new property — a global configuration property, same category as
Session(): if not specified, requests simply proceed without the public-suffix check, exactly like an unconfiguredSession()just uses defaults.Populating
make.requestConfiguration.publicSuffixList(notsessionConfiguration) is deliberate: it keeps PSL data out of theEquatablecomparisonInternals.ClientManageruses for pool-reuse, so declaring/omitting/changing it never fragments client pooling or the cookie jar the way a session-level field would.PropertyNode.makebeingasync throwslets file reading and parse-failure surface as a real, catchable error, rather than needing a workaround like the ones hit designing@Endpoint/Configuredearlier.Explicit, visible behavior when omitted: a request without
PSLCookiestill stores incoming cookies — just without the supercookie check. This is stated plainly rather than implied to always be on.Out of scope (v1)
stored for a session) — can be added later without changing this design.
Milestone: 4.6.0
All reactions