From 7f28ba642961920fa81d8541fcaf97aabcb6c877 Mon Sep 17 00:00:00 2001 From: Matt Reynolds Date: Fri, 4 Aug 2023 15:43:44 -0700 Subject: [PATCH 1/6] Add steps for parsing the blocklist and testing blocklistedness --- index.bs | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) diff --git a/index.bs b/index.bs index 5659eef..d5c612b 100644 --- a/index.bs +++ b/index.bs @@ -631,6 +631,7 @@ run the following steps in parallel: |enumerationResult|. 2. Let |devices| be a new empty {{Array}}. 3. For each |device| in |enumerationResult|: + 1. If |device| is [=blocklisted=], [=iteration/continue=]. 1. If this is the first call to this method, check permissions for |device| with |storage|. 2. Search for an element |allowedDevice| in @@ -676,6 +677,7 @@ steps in parallel: 4. Set |status|.{{PermissionStatus/state}} to "ask". 5. Enumerate all devices attached to the system. Let this result be |enumerationResult|. + 1. Remove devices from |enumerationResult| if they are [=blocklisted=]. 6. Remove devices from |enumerationResult| if they do not match a device filter in |options|.{{USBPermissionDescriptor/filters}}. 7. Remove devices from |enumerationResult| if they match a device filter @@ -2300,6 +2302,85 @@ slots described in the following table: 1. Return wMaxPacketSize of |endpointDescriptor|. +# The USB Blocklist # {#blocklist} + + + // USBBlocklistEntry is never exposed. + [Exposed=()] + dictionary USBBlocklistEntry { + required unsigned short idVendor; + required unsigned short idProduct; + required unsigned short bcdDevice; + }; + + +This specification relies on a blocklist.txt +file in this repository to restrict the set of devices a website can access. + +The result of parsing the blocklist at a {{URL}} |url| is a +{{Promise}} that resolves to a [=list=] of {{USBBlocklistEntry}} objects, +produced by the following algorithm: + + 1. Let |promise| be a [=a new promise=]. + 1. Let |request| be a new [=request=] with associated [=request/URL=] |url|. + 1. Let |processResponse| be an algorithm accepting a [=response=] |response| + with the following steps: + 1. Let |body| be the associated [=response/body=] for |response|. + 1. If |body| is null, [=resolve=] |promise| with an empty + [=list=] and abort these steps. + 1. Let |contents| be the result of running [=utf-8 decode=] with |body|. + 1. Let |lines| be the result of invoking + {{String/split(separator, limit)}} on |contents| with separator + '\n'. + 1. Let |blocklist| be an empty [=list=]. + 1. [=list/For each=] |line| of |lines|: + 1. Let |commentBegin| be the result of invoking + |line|.{{String/indexOf()}} with '#'. + 1. If |commentBegin| is not -1, set |line| to the result of invoking + |line|.{{String/substring()}} with 0 and |commentBegin|. + 1. Set |line| to the result of |line|.{{String/trimEnd()}}. + 1. Let |components| be the result of invoking + {{String/split(separator, limit)}} on |line| with separator + ':'. + 1. If the [=list/size=] of |components| is not 2 or 3, + [=iteration/continue=]. + 1. Let |idVendor| be the result of invoking + {{Number/parseInt(string, radix)}} with |components|[0] and 16, or + [=iteration/continue=] if a {{TypeError}} is thrown. + 1. Let |idProduct| be the result of invoking + {{Number/parseInt(string, radix)}} with |components|[1] and 16, or + [=iteration/continue=] if a {{TypeError}} is thrown. + 1. If the [=list/size=] of |components| is: + * 2: Let |bcdDevice| be 0xFFFF. + * 3: Let |bcdDevice| be the result of invoking + {{Number/parseInt(string, radix)}} with |components|[2] and 16, + or [=iteration/continue=] if a {{TypeError}} is thrown. + 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, + |idProduct|, and |bcdDevice| to |blocklist|. + 1. [=Resolve=] |promise| with |blocklist|. + 1. [=Fetch=] with |request| and |processResponse|. + 1. Return |promise|. + +The USB blocklist is the value of the resolved promise returned by +[=parsing the blocklist=] at https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt. +The UA should re-fetch the blocklist periodically, but it’s unspecified how +often. + +A {{USBDevice}} |device| is blocklisted if the following steps return +"blocked": + + 1. [=list/For each=] |entry| of the [=USB blocklist=]: + 1. If |device|.{{USBDevice/vendorId}} is not equal to + |entry|.{{USBBlocklistEntry/idVendor}}, [=iteration/continue=]. + 1. If |device|.{{USBDevice/productId}} is not equal to + |entry|.{{USBBlocklistEntry/idProduct}}, [=iteration/continue=]. + 1. Let |bcdDevice| be |device|.{{USBDevice/deviceVersionMajor}} << 8 + + |device|.{{USBDevice/deviceVersionMinor}} << 4 + + |device|.{{USBDevice/deviceVersionSubminor}}. + 1. If |bcdDevice| is less than or equal to + |entry|.{{USBBlocklistEntry/bcdDevice}}, return "blocked". + 1. Return "not blocked". + # Integrations # {#integrations}

Permissions Policy

@@ -2561,4 +2642,7 @@ spec:infra; type:dfn; for:list; text:for each spec:infra; type:dfn; for:list; text:append spec:infra; type:dfn; for:list; text:empty spec:infra; type:dfn; for:list; text:size +spec:fetch; type:dfn; for:/; text:request +spec:fetch; type:dfn; for:/; text:response +spec:url; type:interface; text:URL From 00ee63227884b7da460198348919274e6d9007cc Mon Sep 17 00:00:00 2001 From: Matt Reynolds Date: Fri, 4 Aug 2023 15:55:41 -0700 Subject: [PATCH 2/6] Remove Exposed=() and change invocation style --- index.bs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/index.bs b/index.bs index d5c612b..4da4833 100644 --- a/index.bs +++ b/index.bs @@ -2306,7 +2306,6 @@ slots described in the following table: // USBBlocklistEntry is never exposed. - [Exposed=()] dictionary USBBlocklistEntry { required unsigned short idVendor; required unsigned short idProduct; @@ -2340,21 +2339,22 @@ produced by the following algorithm: |line|.{{String/substring()}} with 0 and |commentBegin|. 1. Set |line| to the result of |line|.{{String/trimEnd()}}. 1. Let |components| be the result of invoking - {{String/split(separator, limit)}} on |line| with separator + |line|.{{String/split(separator, limit)}} with separator <code>':'</code>. 1. If the [=list/size=] of |components| is not 2 or 3, [=iteration/continue=]. 1. Let |idVendor| be the result of invoking - {{Number/parseInt(string, radix)}} with |components|[0] and 16, or - [=iteration/continue=] if a {{TypeError}} is thrown. + {{Number}}.{{Number/parseInt(string, radix)}} with |components|[0] + and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. 1. Let |idProduct| be the result of invoking - {{Number/parseInt(string, radix)}} with |components|[1] and 16, or - [=iteration/continue=] if a {{TypeError}} is thrown. + {{Number}}.{{Number/parseInt(string, radix)}} with |components|[1] + and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. 1. If the [=list/size=] of |components| is: * 2: Let |bcdDevice| be <code>0xFFFF</code>. * 3: Let |bcdDevice| be the result of invoking - {{Number/parseInt(string, radix)}} with |components|[2] and 16, - or [=iteration/continue=] if a {{TypeError}} is thrown. + {{Number}}.{{Number/parseInt(string, radix)}} with + |components|[2] and 16, or [=iteration/continue=] if a + {{TypeError}} is thrown. 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, |idProduct|, and |bcdDevice| to |blocklist|. 1. [=Resolve=] |promise| with |blocklist|. From 528592ac3651ea5b602e524e92008ec9a0e4b35c Mon Sep 17 00:00:00 2001 From: Matt Reynolds <mattreynolds@google.com> Date: Tue, 10 Oct 2023 14:15:33 -0700 Subject: [PATCH 3/6] simplify blocklist parsing steps --- index.bs | 86 ++++++++++++++++++++++++-------------------------------- 1 file changed, 37 insertions(+), 49 deletions(-) diff --git a/index.bs b/index.bs index 4da4833..b68e004 100644 --- a/index.bs +++ b/index.bs @@ -2316,53 +2316,43 @@ slots</a> described in the following table: This specification relies on a <a href="https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt">blocklist.txt</a> file in this repository to restrict the set of devices a website can access. -The result of <dfn>parsing the blocklist</dfn> at a {{URL}} |url| is a -{{Promise}} that resolves to a [=list=] of {{USBBlocklistEntry}} objects, -produced by the following algorithm: - - 1. Let |promise| be a [=a new promise=]. - 1. Let |request| be a new [=request=] with associated [=request/URL=] |url|. - 1. Let |processResponse| be an algorithm accepting a [=response=] |response| - with the following steps: - 1. Let |body| be the associated [=response/body=] for |response|. - 1. If |body| is <code>null</code>, [=resolve=] |promise| with an empty - [=list=] and abort these steps. - 1. Let |contents| be the result of running [=utf-8 decode=] with |body|. - 1. Let |lines| be the result of invoking - {{String/split(separator, limit)}} on |contents| with separator - <code>'\n'</code>. - 1. Let |blocklist| be an empty [=list=]. - 1. [=list/For each=] |line| of |lines|: - 1. Let |commentBegin| be the result of invoking - |line|.{{String/indexOf()}} with <code>'#'</code>. - 1. If |commentBegin| is not -1, set |line| to the result of invoking - |line|.{{String/substring()}} with 0 and |commentBegin|. - 1. Set |line| to the result of |line|.{{String/trimEnd()}}. - 1. Let |components| be the result of invoking - |line|.{{String/split(separator, limit)}} with separator - <code>':'</code>. - 1. If the [=list/size=] of |components| is not 2 or 3, - [=iteration/continue=]. - 1. Let |idVendor| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with |components|[0] - and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. - 1. Let |idProduct| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with |components|[1] - and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. - 1. If the [=list/size=] of |components| is: - * 2: Let |bcdDevice| be <code>0xFFFF</code>. - * 3: Let |bcdDevice| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with - |components|[2] and 16, or [=iteration/continue=] if a - {{TypeError}} is thrown. - 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, - |idProduct|, and |bcdDevice| to |blocklist|. - 1. [=Resolve=] |promise| with |blocklist|. - 1. [=Fetch=] with |request| and |processResponse|. - 1. Return |promise|. - -The <dfn>USB blocklist</dfn> is the value of the resolved promise returned by -[=parsing the blocklist=] at <a href="https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt">https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt</a>. +The result of <dfn>parsing the blocklist</dfn> at a {{URL}} |url| is a [=list=] +of {{USBBlocklistEntry}} objects produced by the following algorithm: + + 1. Fetch |url| and let |contents| be its body, decoded as UTF-8. + 1. Let |lines| be the result of invoking + {{String/split(separator, limit)}} on |contents| with separator + <code>'\n'</code>. + 1. Let |blocklist| be an empty [=list=]. + 1. [=list/For each=] |line| of |lines|: + 1. Let |commentBegin| be the result of invoking + |line|.{{String/indexOf()}} with <code>'#'</code>. + 1. If |commentBegin| is not -1, set |line| to the result of invoking + |line|.{{String/substring()}} with 0 and |commentBegin|. + 1. Set |line| to the result of |line|.{{String/trimEnd()}}. + 1. Let |components| be the result of invoking + |line|.{{String/split(separator, limit)}} with separator + <code>':'</code>. + 1. If the [=list/size=] of |components| is not 2 or 3, + [=iteration/continue=]. + 1. Let |idVendor| be the result of invoking + {{Number}}.{{Number/parseInt(string, radix)}} with |components|[0] + and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. + 1. Let |idProduct| be the result of invoking + {{Number}}.{{Number/parseInt(string, radix)}} with |components|[1] + and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. + 1. If the [=list/size=] of |components| is: + * 2: Let |bcdDevice| be <code>0xFFFF</code>. + * 3: Let |bcdDevice| be the result of invoking + {{Number}}.{{Number/parseInt(string, radix)}} with + |components|[2] and 16, or [=iteration/continue=] if a + {{TypeError}} is thrown. + 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, + |idProduct|, and |bcdDevice| to |blocklist|. + 1. Return |blocklist|. + +The <dfn>USB blocklist</dfn> is the result of [=parsing the blocklist=] at +<a href="https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt">https://raw.githubusercontent.com/WICG/webusb/main/blocklist.txt</a>. The UA should re-fetch the blocklist periodically, but it’s unspecified how often. @@ -2642,7 +2632,5 @@ spec:infra; type:dfn; for:list; text:for each spec:infra; type:dfn; for:list; text:append spec:infra; type:dfn; for:list; text:empty spec:infra; type:dfn; for:list; text:size -spec:fetch; type:dfn; for:/; text:request -spec:fetch; type:dfn; for:/; text:response spec:url; type:interface; text:URL </pre> From 39c50be91321815137c65a82eafb26e39546c6d3 Mon Sep 17 00:00:00 2001 From: Matt Reynolds <mattreynolds@google.com> Date: Tue, 10 Oct 2023 15:39:51 -0700 Subject: [PATCH 4/6] Replace ECMAscript String with infra string algorithms --- index.bs | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/index.bs b/index.bs index b68e004..f402248 100644 --- a/index.bs +++ b/index.bs @@ -2320,19 +2320,17 @@ The result of <dfn>parsing the blocklist</dfn> at a {{URL}} |url| is a [=list=] of {{USBBlocklistEntry}} objects produced by the following algorithm: 1. Fetch |url| and let |contents| be its body, decoded as UTF-8. - 1. Let |lines| be the result of invoking - {{String/split(separator, limit)}} on |contents| with separator - <code>'\n'</code>. + 1. Let |lines| be the result of [=strictly splitting=] |contents| starting + from the beginning of |contents| on code point <code>'\n'</code>. 1. Let |blocklist| be an empty [=list=]. 1. [=list/For each=] |line| of |lines|: - 1. Let |commentBegin| be the result of invoking - |line|.{{String/indexOf()}} with <code>'#'</code>. - 1. If |commentBegin| is not -1, set |line| to the result of invoking - |line|.{{String/substring()}} with 0 and |commentBegin|. - 1. Set |line| to the result of |line|.{{String/trimEnd()}}. - 1. Let |components| be the result of invoking - |line|.{{String/split(separator, limit)}} with separator - <code>':'</code>. + 1. Set |line| to the result of [=collecting a sequence of code points=] + not equal to <code>'#'</code> from |line| starting from the beginning + of |line|. + 1. Set |line| to the result of [=stripping leading and trailing ASCII + whitespace=] from |line|. + 1. Let |components| be the result of [=strictly splitting=] |line| + starting from the beginning of |line| on code point <code>':'</code>. 1. If the [=list/size=] of |components| is not 2 or 3, [=iteration/continue=]. 1. Let |idVendor| be the result of invoking From 0c7ace03c4d37c8df6512d13bcd6ee65f92908f1 Mon Sep 17 00:00:00 2001 From: Matt Reynolds <mattreynolds@google.com> Date: Tue, 10 Oct 2023 15:44:28 -0700 Subject: [PATCH 5/6] Replace Number.parseInt --- index.bs | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/index.bs b/index.bs index f402248..a215983 100644 --- a/index.bs +++ b/index.bs @@ -2333,18 +2333,14 @@ of {{USBBlocklistEntry}} objects produced by the following algorithm: starting from the beginning of |line| on code point <code>':'</code>. 1. If the [=list/size=] of |components| is not 2 or 3, [=iteration/continue=]. - 1. Let |idVendor| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with |components|[0] - and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. - 1. Let |idProduct| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with |components|[1] - and 16, or [=iteration/continue=] if a {{TypeError}} is thrown. + 1. Let |idVendor| be the result of interpreting |components|[0] as a + hexadecimal number. + 1. Let |idProduct| be the result of interpreting |components|[1] as a + hexadecimal number. 1. If the [=list/size=] of |components| is: * 2: Let |bcdDevice| be <code>0xFFFF</code>. - * 3: Let |bcdDevice| be the result of invoking - {{Number}}.{{Number/parseInt(string, radix)}} with - |components|[2] and 16, or [=iteration/continue=] if a - {{TypeError}} is thrown. + * 3: Let |bcdDevice| be the result of interpreting |components|[2] as + a hexadecimal number. 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, |idProduct|, and |bcdDevice| to |blocklist|. 1. Return |blocklist|. From a256a3a02ca29ea4974604a98caac8a0211c88b2 Mon Sep 17 00:00:00 2001 From: Matt Reynolds <mattreynolds@google.com> Date: Wed, 11 Oct 2023 16:55:19 -0700 Subject: [PATCH 6/6] set bcdDevice default value --- index.bs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/index.bs b/index.bs index a215983..80bacb6 100644 --- a/index.bs +++ b/index.bs @@ -2337,10 +2337,9 @@ of {{USBBlocklistEntry}} objects produced by the following algorithm: hexadecimal number. 1. Let |idProduct| be the result of interpreting |components|[1] as a hexadecimal number. - 1. If the [=list/size=] of |components| is: - * 2: Let |bcdDevice| be <code>0xFFFF</code>. - * 3: Let |bcdDevice| be the result of interpreting |components|[2] as - a hexadecimal number. + 1. Let |bcdDevice| be <code>0xFFFF</code>. + 1. If the [=list/size=] of |components| is 3, set |bcdDevice| to the + result of interpreting |components|[2] as a hexadecimal number. 1. [=list/Append=] a new {{USBBlocklistEntry}} with |idVendor|, |idProduct|, and |bcdDevice| to |blocklist|. 1. Return |blocklist|.