Skip to content
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

Integrate with new draft cookie spec (draft-annevk-johannhof-httpbis-cookies/00+ε) #1807

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 147 additions & 33 deletions fetch.bs
Original file line number Diff line number Diff line change
Expand Up @@ -54,10 +54,28 @@ urlPrefix:https://tc39.es/ecma262/#;type:dfn;spec:ecma-262
url:realm;text:realm
url:sec-list-and-record-specification-type;text:Record
url:current-realm;text:current realm

urlPrefix:https://www.ietf.org/archive/id/draft-annevk-johannhof-httpbis-cookies-00.html#;type:dfn;spec:cookies
url:name-cookie-store-and-limits;text:cookie store
url:name-parse-and-store-a-cookie;text:parse and store a cookie
url:name-parse-a-cookie;text:parse a cookie
url:name-store-a-cookie;text:store a cookie
url:name-retrieve-cookies;text:retrieve cookies
url:name-serialize-cookies;text:serialize cookies

<!-- TODO: pending HTML changes- ancestor enum (https://github.com/whatwg/html/pull/10559), has storage access bit, initiator origin plumbing -->
urlPrefix:https://html.spec.whatwg.org#;type:dfn;spec:html
url:TODO;text:ancestry;for:environment
url:TODO;text:has storage access;for:environment
</pre>

<pre class=biblio>
{
"COOKIES": {
"authors": ["Johann Hofmann", "Anne Van Kesteren"],
"href": "https://www.ietf.org/archive/id/draft-annevk-johannhof-httpbis-cookies-00.html",
"title": "Cookies: HTTP State Management Mechanism"
},
"HTTP": {
"aliasOf": "RFC9110"
},
Expand Down Expand Up @@ -1938,6 +1956,10 @@ not always relevant and might require different behavior.
"<code>client</code>" or an <a for=/>origin</a>. Unless stated otherwise it is
"<code>client</code>".

<p>A <a for=/>request</a> has an associated
<dfn export for=request id=concept-request-navigation-initiator-origin>navigation initiator origin</dfn>, which is
an <a for=/>origin</a> or null. Unless stated otherwise it is null.

<p class=note>"<code>client</code>" is changed to an <a for=/>origin</a> during
<a lt=fetch for=/>fetching</a>. It provides a convenient way for standards to not have to set
<a for=/>request</a>'s <a for=request>origin</a>.
Expand Down Expand Up @@ -2226,31 +2248,38 @@ or "<code>object</code>".
<hr>

<div algorithm>
<p>A <a for=/>request</a> <var>request</var> has a
<dfn for=request id=concept-request-tainted-origin>redirect-tainted origin</dfn> if these steps
return true:
<p>A <a for=/>request</a> has a <dfn for=request id=concept-request-redirect-taint>redirect-taint</dfn>,
which is "<code>None</code>", "<code>Cross-Origin</code>", or "<code>Cross-Site</code>".
<p>To get <a for=/>request</a> <var>request</var>'s <a>redirect-taint</a>:

<ol>
<li><p><a for=/>Assert</a>: <var>request</var>'s <a for=request>origin</a> is not
"<code>client</code>".

<li><p>Let <var>lastURL</var> be null.

<li><p>Let <var>crossOriginTaint</var> be "<code>None</code>".

<li>
<p><a for=list>For each</a> <var>url</var> of <var>request</var>'s <a for=request>URL list</a>:

<ol>
<li><p>If <var>lastURL</var> is null, then set <var>lastURL</var> to <var>url</var> and
<a for=iteration>continue</a>.

<li><p>If <var>url</var>'s <a for=url>origin</a> is not <a for=/>same site</a> with
<var>lastURL</var>'s <a for=url>origin</a> and <var>request</var>'s <a for=request>origin</a> is
not <a for=/>same site</a> with <var>lastURL</var>'s <a for=url>origin</a>, then return "<code>Cross-Site</code>".

<li><p>If <var>url</var>'s <a for=url>origin</a> is not <a>same origin</a> with
<var>lastURL</var>'s <a for=url>origin</a> and <var>request</var>'s <a for=request>origin</a> is
not <a>same origin</a> with <var>lastURL</var>'s <a for=url>origin</a>, then return true.
not <a>same origin</a> with <var>lastURL</var>'s <a for=url>origin</a>,
then let <var>crossOriginTaint</var> be "<code>Cross-Origin</code>"..

<li>Set <var>lastURL</var> to <var>url</var>.
</ol>

<li>Return false.
<li>Return <var>crossOriginTaint</var>.
</ol>
</div>

Expand All @@ -2262,8 +2291,8 @@ run these steps:
<li><p><a for=/>Assert</a>: <var>request</var>'s <a for=request>origin</a> is not
"<code>client</code>".

<li><p>If <var>request</var> has a <a for=request>redirect-tainted origin</a>, then return
"<code>null</code>".
<li><p>If <var>request</var>'s <a for=request>redirect-taint</a> is not "<code>None</code>",
then return "<code>null</code>".

<li><p>Return <var>request</var>'s <a for=request>origin</a>,
<a lt="ASCII serialization of an origin">serialized</a>.
Expand Down Expand Up @@ -2372,8 +2401,8 @@ source of security bugs. Please seek security review for features that deal with
"<a for="embedder policy value"><code>credentialless</code></a>", then return true.</p>

<li><p>If <var>request</var>'s <a for=request>origin</a> is <a>same origin</a> with
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>origin</a> and <var>request</var>
does not have a <a for=request>redirect-tainted origin</a>, then return true.</p>
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>origin</a> and <var>request</var>'s
<a for=request>redirect-taint</a> is not "<code>None</code>", then return true.</p>

<li><p>Return false.</p>
</ol>
Expand Down Expand Up @@ -2489,6 +2518,9 @@ this is also tracked internally using the request's <a for=request>timing allow
<p>A <a for=/>response</a> has an associated <dfn for=response>has-cross-origin-redirects</dfn>
(a boolean), which is initially false.

<p>A <a for=/>response</a> has an associated <dfn for=response>has-cross-site-redirects</dfn>
(a boolean), which is initially false.

<hr>

<p>A <dfn export id=concept-network-error>network error</dfn> is a <a for=/>response</a> whose
Expand Down Expand Up @@ -2710,7 +2742,6 @@ manually. [[!HTML]]
</ol>
</div>


<h3 id=authentication-entries>Authentication entries</h3>

<p>An <dfn export>authentication entry</dfn> and a <dfn export>proxy-authentication entry</dfn> are
Expand Down Expand Up @@ -3292,6 +3323,105 @@ through TLS using ALPN. The protocol cannot be spoofed through HTTP requests in

<h2 id=http-extensions>HTTP extensions</h2>

<h3 id=cookie-header>`<code>Cookie</code>` header</h3>

<p>The `<dfn export http-header id=http-cookie><code>Cookie</code></dfn>`
request <a for=/>header</a> allows the request to carry locally stored state, such as user credentials.

<div algorithm>
<p>To <dfn id=append-a-request-cookie-header>append a request `<code>Cookie</code>` header</dfn>,
given a <a for=/>request</a> <var>request</var>, run these steps:
<ol>
<li><p>Let |sameSite| be the result of [=determining the same-site mode=] for <var>request</var>.
<li><p>Let |isSecure| be false.
<li><p>If <var>request</var>'s <a for=request>client</a> is a <a>secure context</a>, then set |isSecure| to true.
<li><p>Let |httpOnlyAllowed| be true.
<p class=note>Fetch implies that the request is http-only, as opposed to document.cookie
<li><p>Let |partitionKey| be the result of [=computing the cookie partition key=] for <var>request</var>.
<li><p>Let |partitionedContext| be the result of [=determining the partitioned context state=] for |request|.
<li><p>Let |cookies| be the result of running <a>retrieve cookies</a> given
|isSecure|,
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>host</a>,
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>path</a>,
|httpOnlyAllowed|,
|sameSite|,
|partitionKey|
and |partitionedContext|.

<p class=note>It is expected that the cookie store returns an ordered list of cookies
<li>If |cookies| <a for="list">is empty</a>, then return.
<li>Let |value| be the result of running <a>serialize cookies</a> given |cookies|.
<li><a for="header list">Append</a> (`<code>Cookie</code>`, <var>value</var>) to <var>request</var>'s <a for=request>header list</a>.
</ol>
</div>

<div algorithm>
<p>To <dfn id=parse-and-store-response-cookie-headers>parse and store response `<code>Set-Cookie</code>` headers</dfn>,
given a <a for=/>request</a> <var>request</var> and a <a for=/>response</a> <var>response</var>, run these steps:
<ol>
<li><p>Let |allowNonHostOnlyCookieForPublicSuffix| be false.
<li><p>Let |isSecure| be false.
<li><p>If <var>request</var>'s <a for=request>client</a> is a <a>secure context</a>, then set |isSecure| to true.
<li><p>Let |httpOnlyAllowed| be true.
<p class=note>Fetch implies that the request is http-only, as opposed to document.cookie
<li><p>Let |partitionKey| be the result of [=computing the cookie partition key=] for <var>request</var>.
<li><p>Let |partitionedContext| be the result of [=determining the partitioned context state=] for |request|.
<li><p>Let |sameSiteStrictOrLaxAllowed| be true if the result of [=determine the same-site mode=] for |request| is "<code>StrictOrLess</code>", and false otherwise.
<li><p><a for=list>For each</a> <var>header</var> of <var>response</var>'s <a for=response>header list</a>:
<ol>
<li><p>If <var>header</var>'s <a for=header>name</a> is not a <a>byte-case-insensitive</a> match for `<code>Set-Cookie</code>`, then <a for=iteration>continue</a>.
<li><p><a>Parse and store a cookie</a> given
<var>header</var>'s <a for=header>value</a>,
|isSecure|,
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>host</a>,
<var>request</var>'s <a for=request>current URL</a>'s <a for=url>path</a>,
|httpOnlyAllowed|,
|allowNonHostOnlyCookieForPublicSuffix|,
|sameSiteStrictOrLaxAllowed|,
|partitionKey|
and |partitionedContext|.
</ol>
</ol>
</div>

<div algorithm>
<p>To <dfn>determine the same-site mode</dfn> for a given <a for=/>request</a> <var>request</var>, run these steps:
<ol>
<li><p><a for=/>Assert</a>: <var>request</var>'s <a for=request>method</a> is "GET" or "POST".
<li><p>If <var>request</var>'s <a for=request>navigation initiator origin</a> is not null and is not <a for=/>same site</a> to <var>request</var>'s <a for=request>URL</a>'s <a for=url>origin</a>, return "<code>UnsetOrLess</code>".
<li><p>If <var>request</var>'s <a for=request>method</a> is "GET" and
<var>request</var>'s <a for=request>destination</a> is "document", return "<code>LaxOrLess</code>".
<li><p>If <var>request</var>'s <a for=request>client</a>'s <a for=environment>ancestry</a> is "<code>cross-site</code>", return "<code>UnsetOrLess</code>".
<li><p>If <var>request</var>'s <a for=request>redirect-taint</a> is "<code>Cross-Site</code>", return "<code>UnsetOrLess</code>".
<li><p>Return "StrictOrLess".
</ol>
</div>


<div algorithm>
<p>To <dfn>compute the cookie partition key</dfn> for a given <a for=/>request</a> <var>request</var>, run these steps:
<ol>
<li><p>Let <var>topLevelOrigin</var> be <var>request</var>'s <a for=request>client</a>'s
<a for="environment">top-level origin</a>.

<li><p>Let <var>topLevelSite</var> be the result of <a lt="obtain a site">obtaining a site</a>,
given <var>topLevelOrigin</var>.

<li><p>Let <var>crossSiteAncestors</var> be <var>request</var>'s <a for=request>client</a>'s <a for=environment>cross site ancestry</a>.

<li><p>Return (<var>topLevelSite</var>, <var>crossSiteAncestors</var>).
</ol>
</div>

<div algorithm>
<p>To <dfn>determine the partitioned context state</dfn> for a given <a for=/>request</a> <var>request</var>, run these steps:
<ol>
<li><p>If <var>request</var>'s <a for=request>client</a>'s <a for=environment>cross site ancestry</a> is false, return false.
<li><p>If <var>request</var>'s <a for=request>client</a>'s [=environment/has storage access=] is true, return false.
<li><p>Return true.
</ol>
</div>

<h3 id=origin-header>`<code>Origin</code>` header</h3>

<p>The `<dfn export http-header id=http-origin><code>Origin</code></dfn>`
Expand Down Expand Up @@ -4680,9 +4810,12 @@ steps:
<!-- If you are ever tempted to move this around, carefully consider responses from about URLs,
blob URLs, service workers, HTTP cache, HTTP network, etc. -->

<li><p>If <var>request</var> has a <a for=request>redirect-tainted origin</a>, then set
<li><p>If <var>request</var>'s <a for=request>redirect-taint</a> is not "<code>None</code>", then set
<var>internalResponse</var>'s <a for=response>has-cross-origin-redirects</a> to true.

<li><p>If <var>request</var>'s <a for=request>redirect-taint</a> is "<code>Cross-Site</code>", then set
<var>internalResponse</var>'s <a for=response>has-cross-site-redirects</a> to true.

<li><p>If <var>request</var>'s <a for=request>timing allow failed flag</a> is unset, then set
<var>internalResponse</var>'s <a for=response>timing allow passed flag</a>.

Expand Down Expand Up @@ -5710,21 +5843,9 @@ run these steps:
<p>If <var>includeCredentials</var> is true, then:

<ol>
<li>
<p>If the user agent is not configured to block cookies for <var>httpRequest</var> (see
<a href=https://httpwg.org/specs/rfc6265.html#privacy-considerations>section 7</a> of
[[!COOKIES]]), then:

<ol>
<li><p>Let <var>cookies</var> be the result of running the "cookie-string" algorithm (see
<a href=https://httpwg.org/specs/rfc6265.html#cookie>section 5.4</a> of
[[!COOKIES]]) with the user agent's cookie store and <var>httpRequest</var>'s
<a for=request>current URL</a>.
<p class=note>This permits some implementations to choose to not support cookies for some or all <var>httpRequest</var>s.
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"This" here refers to the word "should" in the line below? I'm not a huge fan of that, both because it's a bit subtle and also because it doesn't account for the opposite case: All major browsers have settings / overrides that will allow cross-site cookies to be sent, so I think it would be preferable to insert a step that allows user agents to make an additional implementation defined choice about whether to include cookies or not. It might have to live in the append a request Cookie header algorithm.

Obviously this should be reserved for truly implementation-specific settings and anything else should be standardized here.


<li>If <var>cookies</var> is not the empty string, then <a for="header list">append</a>
(`<code>Cookie</code>`, <var>cookies</var>) to <var>httpRequest</var>'s
<a for=request>header list</a>.
</ol>
<li><p>The user agent should <a>append a request `<code>Cookie</code>` header</a> for <var>httpRequest</var>.

<li>
<p>If <var>httpRequest</var>'s <a for=request>header list</a>
Expand Down Expand Up @@ -6288,14 +6409,7 @@ optional boolean <var>forceNewConnection</var> (default false), run these steps:
<li><p>Set <var>response</var>'s <a for=response>body</a> to a new <a for=/>body</a> whose
<a for=body>stream</a> is <var>stream</var>.

<li><p tracking-vector>If <var>includeCredentials</var> is true and the user agent is not
configured to block cookies for <var>request</var> (see
<a href=https://httpwg.org/specs/rfc6265.html#privacy-considerations>section 7</a> of
[[!COOKIES]]), then run the "set-cookie-string" parsing algorithm (see
<a href=https://httpwg.org/specs/rfc6265.html#set-cookie>section 5.2</a> of [[!COOKIES]]) on the
<a for=header>value</a> of each <a for=/>header</a> whose <a for=header>name</a> is a
<a>byte-case-insensitive</a> match for `<code>Set-Cookie</code>` in <var>response</var>'s
<a for=response>header list</a>, if any, and <var>request</var>'s <a for=request>current URL</a>.
<li><p tracking-vector>If <var>includeCredentials</var> is true, the user agent should <a>parse and store response `<code>Set-Cookie</code>` headers</a> given <var>request</var> and <var>response</var>.

<li>
<p>Run these steps <a>in parallel</a>:
Expand Down