From 96f7cf5f5e9686c0676f00b43a4078943a322d71 Mon Sep 17 00:00:00 2001 From: James M Snell Date: Mon, 20 Jul 2026 17:21:06 -0700 Subject: [PATCH] Add getStructured/setStructured to Headers Adds new APIs to the Headers class for getting/setting structured header fields. --- fetch.bs | 760 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 760 insertions(+) diff --git a/fetch.bs b/fetch.bs index fa6deae20..41e30620f 100755 --- a/fetch.bs +++ b/fetch.bs @@ -23,6 +23,15 @@ urlPrefix:https://httpwg.org/specs/rfc9651.html#;type:dfn;spec:rfc9651 url:text-parse;text:parsing structured fields url:;text:structured header url:token;text:structured field token + url:integer;text:structured field integer + url:decimal;text:structured field decimal + url:string;text:structured field string + url:boolean;text:structured field boolean + url:byte-sequence;text:structured field byte sequence + url:date;text:structured field date + url:displaystring;text:structured field display string + url:inner-list;text:structured field inner list + url:param;text:structured field parameters urlPrefix:https://httpwg.org/specs/rfc9110.html#;type:dfn;spec:http url:method.overview;text:method @@ -7897,6 +7906,16 @@ fetch("/music/pk/altes-kamuffel.flac") +

StructuredFieldType enum

+ +
+enum StructuredFieldType { "item", "list", "dictionary" };
+
+ +

The {{StructuredFieldType}} enum identifies the top-level type of a +structured field value as defined in [[RFC9651]]. + +

Headers class

@@ -7912,6 +7931,8 @@ interface Headers {
   sequence<ByteString> getSetCookie();
   boolean has(ByteString name);
   undefined set(ByteString name, ByteString value);
+  any getStructured(ByteString name, StructuredFieldType type);
+  undefined setStructured(ByteString name, StructuredFieldType type, any value);
   iterable<ByteString, ByteString>;
 };
 
@@ -7970,6 +7991,93 @@ new Headers(meta2);

Replaces the value of the first header whose name is name with value and removes any remaining headers whose name is name. +

result = headers . getStructured(name, type) +
+

Parses the value of the header named name as a structured field + ([[RFC9651]]) of the given type and returns the result as a + JavaScript value. + +

Returns null if the header is not present or its value fails to parse as + the specified structured field type. + +

The type must be one of "item", + "list", or "dictionary". + +

Structured field values are converted to JavaScript as follows: + integers, decimals, and dates become numbers (dates as seconds since the + Unix epoch), strings and tokens both become strings, booleans become + booleans, and byte sequences become {{Uint8Array}}. Items are returned as + objects with value and params properties. Inner + lists are returned as objects with items and params + properties. Lists are returned as arrays. Dictionaries and parameters are + returned as {{Map}} objects. + +

Tokens are represented as plain strings, following the same + pattern as {{DOMString}}, {{USVString}}, and {{ByteString}} — they are a + constrained profile of string rather than a separate type. When serializing + via {{Headers/setStructured()}}, strings that conform to + structured field token syntax are serialized as tokens; all other + strings are serialized as quoted structured field strings. + +

+

+// Priority: u=3, i
+const p = headers.getStructured("Priority", "dictionary");
+// p is Map { "u" => { value: 3, params: Map {} },
+//             "i" => { value: true, params: Map {} } }
+const urgency = p?.get("u")?.value ?? 3;
+
+// Cache-Status: cdn; hit; ttl=3600
+const cs = headers.getStructured("Cache-Status", "list");
+const cacheName = cs?.[0]?.value;           // "cdn" (token, as a string)
+const ttl = cs?.[0]?.params.get("ttl");     // 3600
+
+// Sec-Purpose: prefetch
+const purpose = headers.getStructured("Sec-Purpose", "item");
+if (purpose?.value === "prefetch") { /* ... */ }  // direct string comparison
+
+
+ +
headers . setStructured(name, type, value) +
+

Serializes value as a structured field ([[RFC9651]]) of the + given type and replaces the header named name with the + result. + +

Throws a {{TypeError}} if value cannot be serialized as the + specified structured field type or if the headers are immutable. + +

For serialization, numbers serialize as integers or decimals (determined + by whether the number is an integer), booleans serialize as structured field + booleans, {{Uint8Array}} serializes as a byte sequence, and strings + serialize as tokens if they conform to structured field token syntax + or as quoted structured field strings otherwise. + +

+

+// Set Priority header — plain object, no params needed
+headers.setStructured("Priority", "dictionary", {
+  u: { value: 0 },
+  i: { value: true }
+});
+// Result: Priority: u=0, i
+
+// Token values are just strings
+headers.setStructured("Cache-Status", "list", [
+  { value: "MyProxy", params: { hit: true, ttl: 7200 } }
+]);
+// Result: Cache-Status: MyProxy;hit;ttl=7200
+// "MyProxy" matches token syntax, so it serializes unquoted.
+// params accepts a plain object, Map, or sequence of pairs.
+
+// Map and sequence forms also work (same as HeadersInit):
+headers.setStructured("Priority", "dictionary", new Map([
+  ["u", { value: 0 }],
+  ["i", { value: true }]
+]));
+
+
+
for(const [name, value] of headers)

headers can be iterated over. @@ -8075,6 +8183,587 @@ from a {{Headers}} object (headers), run these steps:

This is called when headers are modified by unprivileged code. +


+ +

Structured field conversion

+ +

The following algorithms convert between JavaScript values and the +abstract data types defined in [[RFC9651]]. They are used by +{{Headers/getStructured()}} and {{Headers/setStructured()}}. + +

+

To +convert a structured field bare item to a JavaScript value +given a bare item bareItem: + +

    +
  1. If bareItem is a structured field integer, + then return bareItem's value as a Number. + +

  2. If bareItem is a structured field decimal, + then return bareItem's value as a Number. + +

  3. If bareItem is a structured field string, + then return bareItem's value as a String. + +

  4. If bareItem is a structured field token, + then return bareItem's value as a String. + +

  5. If bareItem is a structured field boolean, + then return bareItem's value as a Boolean. + +

  6. If bareItem is a structured field byte sequence, + then return a new {{Uint8Array}} in the current realm whose + contents are bareItem's byte sequence. + +

  7. If bareItem is a structured field date, + then return bareItem's value (seconds since the Unix epoch) + as a Number. + +

  8. If bareItem is a structured field display string, + then return bareItem's value as a String. +

+ +

Both structured field tokens and +structured field strings are returned as JavaScript Strings. +Tokens are a constrained profile of string — they conform to the +token syntax defined in [[RFC9651]] +(Section 3.3.4) +— and are represented as plain strings rather than a distinct type, +following the pattern of {{DOMString}}, {{USVString}}, and {{ByteString}}. +

+ +
+

To +convert structured field parameters to a JavaScript Map +given structured field parameters params: + +

    +
  1. Let result be a new {{Map}} object in the + current realm. + +

  2. +

    For each keybareItem of + params: + +

      +
    1. Let jsValue be the result of + converting a structured field bare item to a JavaScript value + given bareItem. + +

    2. Perform %Map.prototype.set% + on result with arguments key and jsValue. +

    + +
  3. Return result. +

+
+ +
+

To +convert a structured field item to a JavaScript object +given a structured field item sfItem: + +

    +
  1. Let value be the result of + converting a structured field bare item to a JavaScript value + given sfItem's bare item. + +

  2. Let params be the result of + converting structured field parameters to a JavaScript Map + given sfItem's structured field parameters. + +

  3. Let result be + OrdinaryObjectCreate({{%Object.prototype%}}). + +

  4. Perform + CreateDataPropertyOrThrow(result, + "value", value). + +

  5. Perform + CreateDataPropertyOrThrow(result, + "params", params). + +

  6. Return result. +

+ +

The returned object has the shape +{ value: bareItem, params: Map }. +

+ +
+

To +convert a structured field inner list to a JavaScript object +given a structured field inner list innerList: + +

    +
  1. Let items be an empty list. + +

  2. +

    For each sfItem of innerList's members: + +

      +
    1. Let converted be the result of + converting a structured field item to a JavaScript object + given sfItem. + +

    2. Append converted to items. +

    + +
  3. Let params be the result of + converting structured field parameters to a JavaScript Map + given innerList's structured field parameters. + +

  4. Let jsArray be + CreateArrayFromList(items). + +

  5. Let result be + OrdinaryObjectCreate({{%Object.prototype%}}). + +

  6. Perform + CreateDataPropertyOrThrow(result, + "items", jsArray). + +

  7. Perform + CreateDataPropertyOrThrow(result, + "params", params). + +

  8. Return result. +

+ +

The returned object has the shape +{ items: [{ value, params }, ...], params: Map }. +An inner list is distinguished from an item by the presence of +items rather than value. +

+ +
+

To +convert a structured field member to a JavaScript value +given a structured field member member: + +

    +
  1. If member is a structured field item, return + the result of converting a structured field item to a JavaScript object + given member. + +

  2. If member is a structured field inner list, return + the result of converting a structured field inner list to a JavaScript object + given member. +

+
+ +
+

To +convert a structured field to a JavaScript value +given a structured field value sfValue and a +{{StructuredFieldType}} type: + +

    +
  1. +

    If type is "item": + +

      +
    1. Return the result of + converting a structured field item to a JavaScript object + given sfValue. +

    + +
  2. +

    If type is "list": + +

      +
    1. Let result be an empty list. + +

    2. +

      For each member of sfValue's members: + +

        +
      1. Let converted be the result of + converting a structured field member to a JavaScript value + given member. + +

      2. Append converted to result. +

      + +
    3. Return + CreateArrayFromList(result). +

    + +
  3. +

    If type is "dictionary": + +

      +
    1. Let result be a new {{Map}} object in the + current realm. + +

    2. +

      For each keymember of + sfValue's members (in order): + +

        +
      1. Let converted be the result of + converting a structured field member to a JavaScript value + given member. + +

      2. Perform %Map.prototype.set% + on result with arguments key and converted. +

      + +
    3. Return result. +

    +
+
+ +
+ +

The following algorithms convert JavaScript values to structured field +values for use with {{Headers/setStructured()}}. + +

+

To +convert a JavaScript value to a structured field bare item +given a JavaScript value jsValue: + +

    +
  1. If Type(jsValue) + is Boolean, return a structured field boolean with jsValue's value. + +

  2. +

    If Type(jsValue) + is Number: + +

      +
    1. If jsValue is NaN, +∞, or −∞, + then throw a {{TypeError}}. + +

    2. If jsValue is an + integral number, + return a structured field integer with jsValue's value. + +

    3. Otherwise, return a structured field decimal with + jsValue's value. +

    + +
  3. +

    If Type(jsValue) + is String: + +

      +
    1. If jsValue conforms to the structured field token + syntax defined in [[RFC9651]] + (Section 3.3.4) + — i.e., it matches the production + ( ALPHA / "*" ) *( tchar / ":" / "/" ) + — then return a structured field token with + jsValue's value. + +

    2. Otherwise, return a structured field string with + jsValue's value. +

    + +

    Strings that match token syntax are serialized as + unquoted tokens; all other strings are serialized as quoted structured + field strings. This follows the pattern of treating tokens as a + constrained profile of string, similar to {{DOMString}} vs + {{ByteString}}. + +

  4. If jsValue is a {{Uint8Array}}, return a + structured field byte sequence whose contents are a copy of + jsValue's buffer. + +

  5. Throw a {{TypeError}}. +

+
+ +
+

To +convert a JavaScript value to structured field parameters +given a JavaScript value jsParams: + +

    +
  1. Let params be an empty ordered map of + structured field parameters. + +

  2. If jsParams is undefined or null, then return + params. + +

  3. +

    If jsParams is a {{Map}}: + +

      +
    1. +

      For each keyjsValue of jsParams + (in insertion order): + +

        +
      1. If key is not a String, then throw a + {{TypeError}}. + +

      2. Let bareItem be the result of + converting a JavaScript value to a structured field bare item + given jsValue. + +

      3. Set params[key] to bareItem. +

      + +
    2. Return params. +

    + +
  4. +

    If jsParams is a sequence: + +

      +
    1. +

      For each pair of jsParams: + +

        +
      1. If pair's size is not 2, then + throw a {{TypeError}}. + +

      2. Let bareItem be the result of + converting a JavaScript value to a structured field bare item + given pair[1]. + +

      3. Set params[pair[0]] to bareItem. +

      + +
    2. Return params. +

    + +
  5. +

    Otherwise, jsParams is a record: + +

      +
    1. +

      For each keyjsValue of + jsParams: + +

        +
      1. Let bareItem be the result of + converting a JavaScript value to a structured field bare item + given jsValue. + +

      2. Set params[key] to bareItem. +

      + +
    2. Return params. +

    +
+ +

This accepts the same input shapes as {{HeadersInit}}: a +{{Map}}, a sequence of key-value pairs, or a plain object (record). +Additionally, undefined or null is accepted and treated as empty +parameters, allowing the params property to be omitted from +item and inner list objects passed to {{Headers/setStructured()}}. +

+ +
+

To +convert a JavaScript object to a structured field item +given a JavaScript value jsObj: + +

    +
  1. If Type(jsObj) + is not Object, or jsObj does not have a property "value", + then throw a {{TypeError}}. + +

  2. Let bareItem be the result of + converting a JavaScript value to a structured field bare item + given jsObj["value"]. + +

  3. Let jsParams be jsObj["params"] + if jsObj has a "params" property, or undefined + otherwise. + +

  4. Let params be the result of + converting a JavaScript value to structured field parameters + given jsParams. + +

  5. Return a structured field item whose bare item is + bareItem and whose structured field parameters are + params. +

+
+ +
+

To +convert a JavaScript object to a structured field inner list +given a JavaScript value jsObj: + +

    +
  1. If Type(jsObj) + is not Object, or jsObj does not have a property "items", + then throw a {{TypeError}}. + +

  2. Let jsItems be jsObj["items"]. + +

  3. If jsItems is not iterable, then throw a + {{TypeError}}. + +

  4. Let members be an empty list. + +

  5. +

    For each jsItem of jsItems: + +

      +
    1. Let sfItem be the result of + converting a JavaScript object to a structured field item + given jsItem. + +

    2. Append sfItem to members. +

    + +
  6. Let jsParams be jsObj["params"] + if jsObj has a "params" property, or undefined + otherwise. + +

  7. Let params be the result of + converting a JavaScript value to structured field parameters + given jsParams. + +

  8. Return a structured field inner list whose members are + members and whose structured field parameters are + params. +

+
+ +
+

To +convert a JavaScript object to a structured field member +given a JavaScript value jsObj: + +

    +
  1. If Type(jsObj) + is Object and jsObj has a property "items", + return the result of + converting a JavaScript object to a structured field inner list + given jsObj. + +

  2. Otherwise, return the result of + converting a JavaScript object to a structured field item + given jsObj. +

+
+ +
+

To +convert a JavaScript value to a structured field +given a JavaScript value jsValue and a {{StructuredFieldType}} +type: + +

    +
  1. +

    If type is "item": + +

      +
    1. Return the result of + converting a JavaScript object to a structured field item + given jsValue. +

    + +
  2. +

    If type is "list": + +

      +
    1. If jsValue is not iterable, then + throw a {{TypeError}}. + +

    2. Let members be an empty list. + +

    3. +

      For each jsItem of jsValue: + +

        +
      1. Let member be the result of + converting a JavaScript object to a structured field member + given jsItem. + +

      2. Append member to members. +

      + +
    4. Return a structured field list whose members are members. +

    + +
  3. +

    If type is "dictionary": + +

      +
    1. Let dict be an empty structured field dictionary. + +

    2. +

      If jsValue is a {{Map}}: + +

        +
      1. +

        For each keyjsMember of jsValue + (in insertion order): + +

          +
        1. If key is not a String, then throw a + {{TypeError}}. + +

        2. Let member be the result of + converting a JavaScript object to a structured field member + given jsMember. + +

        3. Set dict[key] to member. +

        +
      + +
    3. +

      Otherwise, if jsValue is a sequence: + +

        +
      1. +

        For each pair of jsValue: + +

          +
        1. If pair's size is not 2, then + throw a {{TypeError}}. + +

        2. Let member be the result of + converting a JavaScript object to a structured field member + given pair[1]. + +

        3. Set dict[pair[0]] to member. +

        +
      + +
    4. +

      Otherwise, if Type(jsValue) + is Object: + +

        +
      1. +

        For each keyjsMember of + jsValue: + +

          +
        1. Let member be the result of + converting a JavaScript object to a structured field member + given jsMember. + +

        2. Set dict[key] to member. +

        +
      + +
    5. Otherwise, throw a {{TypeError}}. + +

    6. Return dict. +

    +
+ +

Like {{HeadersInit}}, dictionaries accept a {{Map}}, a +sequence of key-value pairs, or a plain object (record). This allows the +ergonomic form +{ u: { value: 3 }, i: { value: true } } +in addition to +new Map([["u", { value: 3 }], ["i", { value: true }]]). +

+ +
+

The new Headers(init) @@ -8174,6 +8863,77 @@ method steps are:

+
+

The getStructured(name, type) +method steps are: + +

    +
  1. If name is not a header name, then throw + a {{TypeError}}. + +

  2. Let sfValue be the result of getting + a structured field value given name and type from + this's header list. + +

  3. If sfValue is null, then return null. + +

  4. Return the result of converting a structured field to a + JavaScript value given sfValue and type. +

+
+ +

Parsing structured fields and converting the result to +JavaScript objects is entirely optional. Implementations that do not +support structured field parsing are fully compliant with this +specification by having get a structured field value +return null. Returning null matches the behavior when the header is absent or +malformed, ensuring no feature-detection breakage. The presence of the +{{Headers/getStructured()}} method itself is required. + +

+

The setStructured(name, type, value) +method steps are: + +

    +
  1. If name is not a header name, then throw + a {{TypeError}}. + +

  2. If this's guard is "immutable", + then throw a {{TypeError}}. + +

  3. Let sfValue be the result of + converting a JavaScript value to a structured field given + value and type. + +

  4. Let serialized be the result of + serializing structured fields on sfValue. + +

  5. If serialization fails, then throw a {{TypeError}}. + +

  6. If validating (name, serialized) + for this returns false, then return. + +

  7. If this's guard is + "request-no-cors" and (name, serialized) + is not a no-CORS-safelisted request-header, then return. + +

  8. Set (name, serialized) + in this's header list. + +

  9. If this's guard is + "request-no-cors", then remove privileged + no-CORS request-headers from this. +

+
+ +

Serializing structured fields is entirely optional. +Implementations that do not support structured field serialization are +fully compliant with this specification. The minimum conformance +requirement is that {{Headers/setStructured()}} does not throw for valid +inputs — an implementation that silently ignores the call is compliant. +Whether to actually serialize and set the header is an implementation +decision. +

The value pairs to iterate over are the return value of running sort and combine with this's header list.