From 6ebacb6553f77fef5a7d29d6e7131bb92f9146ba Mon Sep 17 00:00:00 2001 From: Lola Date: Wed, 10 Jan 2024 17:16:47 +0000 Subject: [PATCH] Add support for handling cookies (#501) * add cookie events, command & result references * add command & result definitions -wip * rm ref to events * Reorganize into a new module named "Storage" * Add storage partition keys to capabilities * Support cookies with multiple partition keys * Remove modifications to capabilities processing * Relocate type definitions * Implement commands * Reformat according to document convention * Rename key in PartitionKey type * Duplicate field definitions * Add reminder to editors * Define type for sameSite value * Include partition key in response to GetCookies * Tolerate additional fields * Include partition key in response to DeleteCookies * Reference recently-aligned WebDriver concepts * Require specification of source origin * Normalize algorithm return type * Support specification of store w/browsing context * Correct CDDL errors * Rename command parameters * Make "filter" argument optional * Clarify "get the cookie store" algorithm * Define a named type for each kind of partition key * Update index.bs Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> * Update index.bs Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> * Update index.bs Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> * Update index.bs Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> * Update index.bs Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> * Do not infer sourceOrigin; allow UAs to require it * Correct algorithm names * Add explanations for userContext and sourceOrigin * Use a string type * Incorporate feedback on partition definitions * Build a new map when expanding partition specifier * Correct algorithm invocation * Add guidance on the contents of data structures * Update index.bs Co-authored-by: jgraham * Update index.bs Co-authored-by: jgraham * Update index.bs Co-authored-by: jgraham * Correct formatting issues * Correct formatting issue * Update index.bs Co-authored-by: Henrik Skupin * Update index.bs Co-authored-by: Henrik Skupin * Update index.bs Co-authored-by: Henrik Skupin * Relocate issue reference * Modify visual presentation of table * Reference algorithm by its full name --------- Co-authored-by: Mike Pennisi Co-authored-by: Maksim Sadym <69349599+sadym-chromium@users.noreply.github.com> Co-authored-by: jgraham Co-authored-by: Henrik Skupin --- index.bs | 499 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 494 insertions(+), 5 deletions(-) diff --git a/index.bs b/index.bs index 10442191..616d147c 100644 --- a/index.bs +++ b/index.bs @@ -37,6 +37,14 @@ spec: RFC6455; urlPrefix: https://tools.ietf.org/html/rfc6455 spec: RFC8610; urlPrefix: https://tools.ietf.org/html/rfc8610 type: dfn text: match a CDDL specification; url: appendix-C +spec: RFC6265; urlPrefix: https://httpwg.org/specs/rfc6265.html + type: dfn + text: Cookie; url: section-5.3 + text: Cookie store; url: section-5.3 +spec: RFC6265bis; urlPrefix: https://tools.ietf.org/html/draft-ietf-httpbis-rfc6265bis-05 + type: dfn + text: Lax; url: section-4.1.2.7 + text: Strict; url: section-4.1.2.7 spec: WEBDRIVER; urlPrefix: https://w3c.github.io/webdriver/ type: dfn text: WebDriver new session algorithm; url: dfn-webdriver-new-session-algorithm @@ -46,6 +54,15 @@ spec: WEBDRIVER; urlPrefix: https://w3c.github.io/webdriver/ text: additional capability deserialization algorithm; url: dfn-additional-capability-deserialization-algorithm text: capability name; url: dfn-capability-name text: close the session; url: dfn-close-the-session + text: cookie domain; url: dfn-cookie-domain + text: cookie expiry time; url: dfn-cookie-expiry-time + text: cookie HTTP only; url: dfn-cookie-http-only + text: cookie name; url: dfn-cookie-name + text: cookie path; url: dfn-cookie-path + text: cookie same site; url: dfn-cookie-same-site + text: cookie secure only; url: dfn-cookie-secure-only + text: cookie value; url: dfn-cookie-value + text: create a cookie; url: dfn-creating-a-cookie text: create a session; url: dfn-create-a-session text: dispatch actions; url: dfn-dispatch-actions text: dispatch tick actions; url: dfn-dispatch-tick-actions @@ -84,6 +101,7 @@ spec: WEBDRIVER; urlPrefix: https://w3c.github.io/webdriver/ text: session; url: dfn-sessions text: set a property; url: dfn-set-a-property text: success; url: dfn-success + text: table for cookie conversion; url: dfn-table-for-cookie-conversion text: try; url: dfn-try text: trying; url: dfn-try text: unable to capture screen; url: dfn-unable-to-capture-screen @@ -263,6 +281,29 @@ spec: UNICODE; urlPrefix: https://www.unicode.org/versions/Unicode15.0.0/ var { color: #cd5c5c } + +/** + * Emulate the appearace of the so-called "simple" table provided by ReSpec, as + * used in WebDriver Classic. + */ +table.respec-simple { + border-spacing: 0; + border-collapse: collapse; + border-bottom: 3px solid #005a9c; +} +table.respec-simple th { + background: #005a9c; + color: #fff; + padding: 3px 5px; + text-align: left; +} +table.respec-simple tr:nth-child(2n) { + background: #f0f6ff; +} +table.respec-simple td { + padding: 3px 10px; + border-top: 1px solid #ddd; +} # Introduction # {#intro} @@ -357,7 +398,8 @@ CommandData = ( InputCommand // NetworkCommand // ScriptCommand // - SessionCommand + SessionCommand // + StorageCommand ) EmptyParams = { @@ -395,7 +437,8 @@ ResultData = ( EmptyResult / NetworkResult / ScriptResult / - SessionResult + SessionResult / + StorageResult ) EmptyResult = { @@ -549,8 +592,17 @@ with the following additional codes:
no such script
Tried to remove an unknown [=preload script=]. +
no such storage partition +
Tried to access data in a non-existent storage partition. +
unable to close browser
Tried to close the browser, but failed to do so. + +
unable to set cookie +
Tried to create a cookie, but the user agent rejected it. + +
underspecified storage partition +
Tried to interact with data in a storage partition which was not adequately specified.
@@ -566,9 +618,12 @@ ErrorCode = "invalid argument" /
             "no such node" /
             "no such request" /
             "no such script" /
+            "no such storage partition" /
             "session not created" /
             "unable to capture screen" /
             "unable to close browser" /
+            "unable to set cookie" /
+            "underspecified storage partition" /
             "unknown command" /
             "unknown error" /
             "unsupported operation"
@@ -1978,6 +2033,9 @@ implicitly set when the context is created. For browsing contexts with an
 associated WebDriver [=window handle=] the [=/browsing context id=] must be the
 same as the [=window handle=].
 
+Each [=/browsing context=] also has an associated storage partition,
+which is the [=storage partition=] it uses to persist data.
+
 
To get a browsing context given |context id|: @@ -4522,6 +4580,13 @@ Note: this takes a [=byte sequence=] and returns a network.BytesValue + +network.SameSite = "strict" / "lax" / "none" + + network.Cookie = { name: text, value: network.BytesValue, @@ -4530,7 +4595,7 @@ network.Cookie = { size: js-uint, httpOnly: bool, secure: bool, - sameSite: "strict" / "lax" / "none", + sameSite: network.SameSite, ? expiry: js-uint, };
@@ -4876,7 +4941,7 @@ To get the request data given |request|: 1. If |name| is a [=byte-case-insensitive=] match for "Cookie" then: - 1. For each |cookie| in the user agent's cookie store that are included in + 1. For each |cookie| in the user agent's [=cookie store=] that are included in |request|: Note: [[COOKIES]] defines some baseline requirements for which cookies in @@ -5041,6 +5106,10 @@ To get the response data given |response|: [=Remote end definition=]
+
 network.SetCookieHeader = {
     name: text,
     value: network.BytesValue,
@@ -5049,7 +5118,7 @@ network.SetCookieHeader = {
     ? expiry: text,
     ? maxAge: js-int,
     ? path: text,
-    ? sameSite: "strict" / "lax" / "none",
+    ? sameSite: network.SameSite,
     ? secure: bool,
 }
 
@@ -9061,6 +9130,426 @@ worker=] algorithm: +## The storage Module ## {#module-storage} + +The storage module contains functionality and +events related to storage. + +A storage partition is a namespace within which the user agent may +organize persistent data such as [=cookies=] and local storage. + +A storage partition key is a [=/map=] which uniquely identifies a [=storage partition=]. + +### Definition ### {#module-storage-definition} + +[=Remote end definition=] + +
+StorageCommand = (
+  storage.DeleteCookies //
+  storage.GetCookies //
+  storage.SetCookie
+)
+
+ +[=Local end definition=] + +
+StorageResult = (
+  storage.DeleteCookiesResult /
+  storage.GetCookiesResult /
+  storage.SetCookieResult
+)
+
+ +### Types ### {#module-storage-types} + +#### The storage.PartitionKey Type #### {#type-storage-PartitionKey} + +[=Local end definition=] + +
+storage.PartitionKey = {
+  ? userContext: text,
+  ? sourceOrigin: text,
+  Extensible,
+}
+
+ +The storage.PartitionKey type represents a [=storage partition key=]. + +The following table of standard storage partition key attributes +enumerates attributes with well-known meanings which a [=remote end=] may +choose to support. An implementation may define additional [=extension storage +partition key attributes=]. + + + + + +
Attribute + Definition +
"userContext" + A user context id +
"sourceOrigin" + The [=serialization of an origin|serialization of the origin=] of resources that can access the storage partition +
+ +Issue(w3c/webdriver-bidi#570): User contexts are in the process of being +specified. + +[=Remote ends=] may support any number of extension storage partition key +attributes. In order to avoid conflicts with other implementations, these +attributes must begin with a unique identifier for the vendor and user-agent +followed by U+003A (:). + +A [=remote end=] has a [=/map=] of default values for storage partition +key attributes which contains zero or more entries. Each key must be a +member of the [=table of standard storage partition key attributes=] where the +[=storage partition key=] corresponds to a standard storage partition, or an +[=extension storage partition key attribute=] where it does not, and the values +represent the default value of that partition key that will be used when the user +doesn't provide an explicit value. The precise entries are +implementation-defined and are determined by the storage partitioning adopted +by the implementation. + +A [=remote end=] has a [=/list=] of required partition key +attributes which contains zero or more entries. Each key must be a member +of the [=table of standard storage partition key attributes=] where the +[=storage partition key=] corresponds to a standard storage partition, or an +[=extension storage partition key attribute=] where it does not. The precise +entries are implementation-defined and are determined by the storage +partitioning adopted by the implementation. This list includes only partition +keys for which no default is available. As such the list must not share any +entries with the keys of [=default values for storage partition key +attributes=]. + +
+To expand a storage partition spec given |partition spec|: + +1. If |partition spec| is null: + + 1. Set |partition spec| to an empty [=/map=]. + +1. Otherwise, if |partition spec|["type"] is "context": + + 1. Let |browsing context| be the result of [=trying=] to [=get a browsing context=] given |partition spec|["context"]. + + 1. Let |partition key| be the [=storage partition key|key=] of |browsing context|'s [=associated storage partition=]. + + 1. Return [=success=] with data |partition key|. + +1. Let |partition key| be an empty [=/map=]. + +1. For each |name| → |default value| in the [=default values for storage partition key attributes=]: + + 1. Let |value| be |partition spec|[|name|] if it [=map/exists=] or |default value| otherwise. + + 1. [=map/Set=] |partition key|[|name|] to |value|. + +1. For each |name| in the remote end's [=required partition key attributes=]: + + 1. If |partition spec|[|name|] [=map/exists=]: + + 1. [=map/Set=] |partition key|][|name|] to |partition spec|[|name|]. + + 1. Otherwise: + + 1. Return [=error=] with [=error code=] [=underspecified storage partition=]. + +1. Return [=success=] with data |partition key|. + +
+ +
+To get the cookie store given |storage partition key|: + +1. If |storage partition key| uniquely identifies an extant [=storage partition=]: + + 1. Let |store| be the [=cookie store=] of that [=storage partition=]. + + 1. Return [=success=] with data |store|. + +1. Return [=error=] with [=error code=] [=no such storage partition=]. + +
+ +
+To match cookie given |stored cookie| and |filter|: + +1. For each |name| → |value| in |filter|: + + 1. If |name| is "value": + + 1. Set |value| to [=deserialize protocol bytes=] with |value|. + + 1. Let |field name| be the field name corresponding to the JSON key |name| in + the [=table for cookie conversion=]. + + 1. If |stored cookie|[|field name|] does not equal |value|: + + 1. Return false. + +1. Return true. + +
+ +
+To get matching cookies given |cookie store| and |filter|: + +1. Let |cookies| be a new list. + +1. For each |stored cookie| in |cookie store|: + + 1. If [=match cookie=] with |stored cookie| and |filter| is true: + + 1. Append the result of [=serialize cookie=] given |stored cookie| to |cookies|. + +1. Return |cookies|. + +
+ +### Commands ### {#module-storage-commands} + +#### The storage.getCookies Command #### {#command-storage-getCookies} + +The storage.getCookies command retrieves zero or more [=cookies=] which [=match cookie|match=] a set of provided parameters. + +
+
Command Type
+
+
+        storage.GetCookies = (
+          method: "storage.getCookies",
+          params: storage.GetCookiesParameters
+        )
+
+        
+        storage.CookieFilter = {
+          ? name: text,
+          ? value: network.BytesValue,
+          ? domain: text,
+          ? path: text,
+          ? size: js-uint,
+          ? httpOnly: bool,
+          ? secure: bool,
+          ? sameSite: network.SameSite,
+          ? expiry: js-uint,
+          Extensible,
+        }
+
+        storage.BrowsingContextPartitionDescriptor = {
+          type: "context",
+          context: browsingContext.BrowsingContext
+        }
+
+        storage.StorageKeyPartitionDescriptor = {
+          type: "storageKey",
+          ? userContext: text,
+          ? sourceOrigin: text,
+          Extensible,
+        }
+
+        storage.PartitionDescriptor = (
+          storage.BrowsingContextPartitionDescriptor /
+          storage.StorageKeyPartitionDescriptor
+        )
+
+        storage.GetCookiesParameters = {
+          ? filter: storage.CookieFilter,
+          ? partition: storage.PartitionDescriptor,
+        }
+    
+
+
Result Type
+
+
+      storage.GetCookiesResult = {
+        cookies: [*network.Cookie],
+        partitionKey: storage.PartitionKey,
+      }
+    
+
+
+ +
+The [=remote end steps=] with session and |command parameters| are: + +1. Let |filter| be the value of the filter field of |command parameters| + if it is present or an empty [=/map=] if it isn't. + +1. Let |partition spec| be the value of the partition field of + |command parameters| if it is present or null if it isn't. + +1. Let |partition key| be the result of [=trying=] to [=expand a storage partition spec=] with |partition spec|. + +1. Let |store| be the result of [=trying=] to [=get the cookie store=] with + |partition key|. + +1. Let |cookies| be the result of [=get matching cookies=] with |store| and |filter|. + +1. Let |body| be a [=/map=] matching the storage.GetCookiesResult production, + with the cookies field set to |cookies| and the partitionKey + field set to |partition key|. + +1. Return [=success=] with data |body|. + +
+ +#### The storage.setCookie Command #### {#command-storage-setCookie} + +The storage.setCookie command creates a new [=cookie=] in a cookie store, replacing any cookie in that store which matches according to [[COOKIES]]. + +
+
Command Type
+
+
+        storage.SetCookie = (
+          method: "storage.setCookie",
+          params: storage.SetCookieParameters,
+        )
+
+        
+        storage.PartialCookie = {
+          name: text,
+          value: network.BytesValue,
+          domain: text,
+          ? path: text,
+          ? httpOnly: bool,
+          ? secure: bool,
+          ? sameSite: network.SameSite,
+          ? expiry: js-uint,
+          Extensible,
+        }
+
+        storage.SetCookieParameters = {
+          cookie: storage.PartialCookie,
+          ? partition: storage.PartitionDescriptor,
+        }
+    
+
+
Result Type
+
+
+      storage.SetCookieResult = {
+        partitionKey: storage.PartitionKey
+      }
+    
+
+
+ +
+The [=remote end steps=] with session and |command parameters| are: + +1. Let |cookie spec| be the value of the cookie field of |command parameters|. + +1. Let |partition spec| be the value of the partition field of + |command parameters| if it is present or null if it isn't. + +1. Let |partition key| be the result of [=trying=] to [=expand a storage partition spec=] with |partition spec|. + +1. Let |store| be the result of [=trying=] to [=get the cookie store=] with + |partition key|. + +1. Let |deserialized value| be [=deserialize protocol bytes=] with + |cookie spec|["value"]. + +1. [=Create a cookie=] in |store| using [=cookie name=] |cookie spec|["name"], + [=cookie value=] |deserialized value|, [=cookie domain=] + |cookie spec|["domain"], and an attribute-value list of the + following cookie concepts listed in the [=table for cookie conversion=]: + +
+
[=Cookie path=] +

|cookie spec|["path"] if it exists, otherwise "/". + +

[=Cookie secure only=] +

|cookie spec|["secure"] if it exists, otherwise false. + +

[=Cookie HTTP only=] +

|cookie spec|["httpOnly"] if it exists, otherwise false. + +

[=Cookie expiry time=] +

|cookie spec|["expiry"] if it exists, otherwise leave unset to + indicate that this is a session cookie. + +

[=Cookie same site=] +

|cookie spec|["sameSite"] if it exists, otherwise leave unset to + indicate that no same site policy is defined. +

+ + If this step is aborted without inserting a cookie into the cookie store, return + [=error=] with [=error code=] [=unable to set cookie=]. + +1. Let |body| be a [=/map=] matching the storage.SetCookieResult production, + with the partitionKey field set to |partition key|. + +1. Return [=success=] with data |body|. + +
+ +#### The storage.deleteCookies Command #### {#command-storage-deleteCookies} + +The storage.deleteCookies command removes zero or more [=cookies=] which [=match cookie|match=] a set of provided parameters. + +
+
Command Type
+
+
+        storage.DeleteCookies = (
+          method: "storage.deleteCookies",
+          params: storage.DeleteCookiesParameters,
+        )
+
+        storage.DeleteCookiesParameters = {
+          ? filter: storage.CookieFilter,
+          ? partition: storage.PartitionDescriptor,
+        }
+    
+
+
Result Type
+
+
+        storage.DeleteCookiesResult = {
+          partitionKey: storage.PartitionKey
+        }
+     
+
+
+ +
+The [=remote end steps=] with session and |command parameters| are: + +1. Let |filter| be the value of the filter field of |command parameters| + if it is present or an empty [=/map=] if it isn't. + +1. Let |partition spec| be the value of the partition field of + |command parameters| if it is present or null if it isn't. + +1. Let |partition key| be the result of [=trying=] to [=expand a storage partition spec=] with |partition spec|. + +1. Let |store| be the result of [=trying=] to [=get the cookie store=] with + |partition key|. + +1. Let |cookies| be the result of [=get matching cookies=] with |store| and |filter|. + +1. For each |cookie| in |cookies|: + + 1. Remove |cookie| from |store|. + +1. Let |body| be a [=/map=] matching the storage.DeleteCookiesResult production, + with the partitionKey field set to |partition key|. + +1. Return [=success=] with data |body|. + +
+ ## The log Module ## {#module-log} The log module contains functionality and events