1. 引言
本节为非规范性内容。
存储通常按源分区,以保护用户的安全和隐私。这是正确的 默认设置,但不适合一小类体积庞大、广泛使用、公开分发的 资源(AI 模型、WebAssembly 模块、JavaScript 库、游戏引擎和大型 Web 字体), 无论哪个站点请求这些资源,它们逐字节都是同一个文件。当两个 互不相关的源都依赖同一个 8 GB 模型时,按源分区的存储会迫使 用户将该模型下载并保留两份,这会浪费用户的带宽、存储空间 和电量,也会浪费整个网络的资源。
跨源存储(COS)是一种 内容寻址 缓存,以密码学哈希作为键,而 非 URL,让用户代理能够在选择使用它的各个 源之间共享此类资源的单个存储副本。将资源存储到 COS 始终是 存储源明确选择执行的操作,而用户代理可以拒绝确认资源的存在,即使请求来自 原本获准读取它的源;参见 § 8 隐私 和安全注意事项。
本规范的入口点是 getFileHandle()
方法,通过 crossOriginStorage
暴露:
const hash= { algorithm: 'SHA-256' , value: '8f434346648f6b96df89dda901c5176b10a6d83961dd3c1ac88b59b2dc327aa4' , }; try { const handle= await navigator. crossOriginStorage. getFileHandle( hash); const file= await handle. getFile(); // 对该文件执行某些操作。 } catch ( err) { if ( err. name=== 'NotFoundError' ) { // 跨源存储中不存在(可披露的)此文件;改为从网络获取。 } }
本规范复用 FileSystemFileHandle、
FileSystemWritableFileStream,
以及相关的
基础设施,它们来自文件系统标准 [FS],作用范围限定为一个专用文件系统,该系统
跨源共享,并独立于任何源的私有文件系统或用户可见文件系统。
本规范定义命令式 JavaScript API,以及供宿主
集成使用的共享存储步骤。相关提案将同一底层缓存集成到其他宿主
规范中:HTML 的 crossoriginstorage 属性,用于
link
和
script,
一个
crossOriginStorage JavaScript 导入属性,一个 CSS cross-origin-storage()
<request-url-modifier>,以及一个 crossOriginStorage RequestInit
选项,用于 fetch()。每一项都
在各自的宿主规范中定义;参见 § 9 集成
到其他规范。
2. 概念
2.1. 哈希
- algorithm
-
一个 字符串, 指定由 [WEBCRYPTO] 识别的哈希算法,且不是 "
SHA-1",例 如 "SHA-256"。 - value
-
一个 字符串,其内容为摘要的小写十六进制编码,该摘要由 algorithm 生成。 对于 "
SHA-256",其长度为 64 个字符。
此 algorithm
字典成员的类型是普通的
DOMString,尽管其值域恰好是 [WEBCRYPTO] 的
哈希算法所接受的名称集合。一个 HashAlgorithmIdentifier
是 (object or DOMString),其中的 object 分支,
会被 [WEBCRYPTO] 规范化为 Algorithm
形状的
字典,让调用方能够向
需要额外参数的算法传递这些参数(例如,{name: "HMAC", hash: "SHA-256"})。哈希
算法不接受此类参数,
algorithm 会被存储、
比较并往返传递,作为 内容寻址
键的一部分,而且本规范的
示例中的每次使用都是一个纯字符串;在这里允许任意对象并不会增加任何能力,却会使
相等性 和序列化
变得不必要地复杂。因此,DOMString 是
此字段更严格且更正确的类型。
两个 COS 哈希 值被视为 相等,如果它们的 algorithm 值按照 ASCII 不区分大小写 的方式匹配,而且它们的 value 值完全 相等。
注意: value 已被规范性地规定为小写(见上文),因此比较它就是 普通的 字符串比较。algorithm 没有规范性规定的大小写形式,因此比较时采用 ASCII 不区分大小写 的方式。
一个 内容寻址 存储是指其条目使用以下内容作为键: 相等性,针对一个 COS 哈希:两个文件若具有 相同的字节和相同的哈希算法,就是同一个条目,无论有多少个源 存储了它们,或获取它们时使用了多少个 URL。
2.2. COS 条目
每个用户代理都有一个唯一的 COS 注册表,它是一个 映射,将 COS 哈希 值映射到 COS 条目 结构体, 由使用跨源存储的所有源共享。一个 COS 条目 具有以下 项:
- hash
-
一个 COS 哈希。
- bytes
-
一个 字节序列,其摘要使用 hash 的 algorithm 计算, 等于 hash 的 value。
注意: 只有满足以下条件,才会将条目添加到 COS 注册表: 写入方已提供这些字节,并且 用户代理已验证它们;参见 验证并存储。已请求但 尚未完成的写入在注册表中完全没有对应表示,因此,在读取路径上,进行中的 写入与不存在的写入无法区分。参见 § 8.3 进行中的写入。
- origins
-
一个 列表,包含 源,它们被明确授予跨源读取权限,且独立 于 公共哈希 列表。该授权扩展到每个满足以下条件的源:同站,相对于以下对象中的某个项: 列表,因此 列出
https://a.example也会允许https://www.a.example。初始为 空,它只会 增长。这 是决定谁可以读取条目的三种独立且可叠加的授权之一, 另外两种是其 存储源(始终可读取,同时还包括 那些源, 同站 于它们)和 可全局 披露。注意: 对于一次写入,如果其
origins是显式的空 列表 (origins: []),则其授权与省略以下选项的写入完全相同:origins所授予的权限:该条目的 存储源 以及与之 同站 的源,这是每个条目 都具有的基线(参见 确定 COS 披露)。应用 上限 会将空 列表 转换为 null,因此两种写入在执行以下算法的其余步骤时, 完成创建请求,处于相同的状态。一个 显式 列表 只会增加接收方; 它绝不会对写入方自身的站点隐藏该条目。 - 可全局披露
-
一个 布尔值,初始为 false,记录该条目是否曾被写入, 且请求了 全局("
*")可用性。当为 true 时,任何源都可以读取该条目,只要其请求 通过 § 8.4 可用性门控,也就是说,其 hash 位于 PHL 上,且 未被 GREASE 处理抑制。一旦被 设置,它就一直为 true。注意: 这是一个独立的 项,不同于 origins,以便 后续的 全局可用性请求在现有显式授权之上增加 "
*" 授权, 即 origins 列表。 如果将两者混为一个字段,那么提供了 字节的写入方就可以将列表范围的条目变为 "*" 范围的条目。由于 § 8.4 可用性门控 只适用于 "*" 授权,因此先前能够依据以下对象读取条目的源: 列表 随后就会开始收到 "NotFoundError" 每当其 hash 不 位于 PHL 上 时。保持这些 授权相互独立,使每次可见性变更都严格地只增加权限, 这正是 可升级但绝不可降级 所要求的。 - 存储源
-
一个 集合,包含 源,它们各自都已成功完成 写入 此 条目的字节至少一次。跨页面加载持久保留,并且 随时间 增长;除 § 5.2 逐出 中所述情形外,绝不缩减。属于 存储源 的源始终可以获得该 条目的句柄,通过
getFileHandle(), 不取决于 origins,也不取决于 该条目的 hash 是否 位于公共哈希列表 (PHL)上。 参见 § 3.5.1 原始存储方的访问权限。
用户代理具有关联的 跨源存储队列,它是以下操作的结果: 启动一个新的并行队列。对 COS 注册表 的所有操作都必须 入队 到此队列,以便它们按照 它们 入队的顺序 执行,且不会交错执行。
2.3. 公共哈希列表
确认某个哈希存在于跨源存储中,本身就可能泄露 用户的浏览历史信息(参见 § 8.2 跨站探测)。这种风险 专门针对全局 授权。条目的显式 origins 列表,以及每个条目都具有的同站基线, 每个条目 所携带的这些授权,其披露范围已经由存储源的明确选择限定,而 将条目设为 可全局披露 则可能将它暴露给 Web 上的任何 源。为限制这种 特定风险,可全局披露 的资源只有满足条件才可披露, 通过该 授权,向不属于其 存储源 的源披露,条件是其哈希通过额外的、 独立的门槛:被收录于 公共哈希列表 (PHL),这是一个厂商中立、由实现定义 的允许列表,包含 COS 哈希 值。只有达到类似 k 匿名的 普及度门槛(例如,在至少某个数量的独立 源中出现逐字节相同的内容),哈希才会被纳入公共哈希列表,从而使确认其存在于共享缓存中不会透露任何 单个用户的信息。
一个 COS 哈希 hash 被视为 位于公共 哈希列表上,如果 hash 等于 用户 代理当前快照中的某个条目,该快照来自 公共哈希列表。
获取协议、 更新频率、数据格式、普及度阈值以及治理方式, 针对公共哈希列表,作为本规范的配套产物进行了详细设计, 其思路类似于公共后缀列表,即 [URL] 标准的配套数据文件;参见 本仓库中的 公共哈希列表说明文档。该设计 提议由 WHATWG 治理,效仿公共后缀列表跨厂商、 滚动发布的先例,并采用基于独立佐证的普遍使用程度的准入标准。 这是上述类似 k 匿名门槛的具体实例,只应用一次,以离线方式, 作为哈希准入过程的一部分,因此用户代理绝不会在每次查询时重复执行。上述机制 在本提案之外尚未确立。列表本身的早期非规范性代码原型 目前位于本仓库中,路径为 public-hash-list/implementation/;专用的、 跨厂商仓库仍是 PHL 说明文档中描述的治理目标。本 规范只依赖于某个哈希是否 位于 PHL 上,因此无论 列表的获取机制、治理模型或该设计的其余部分如何演进,它都仍然正确。
3.
此 CrossOriginStorageManager
接口
[Exposed =(Window ,Worker ),SecureContext ]interface {CrossOriginStorageManager Promise <FileSystemFileHandle >getFileHandle (CrossOriginStorageGetFileHandleHash ,hash optional CrossOriginStorageGetFileHandleOptions = {}); };options dictionary {CrossOriginStorageGetFileHandleHash required DOMString ;value required DOMString ; };algorithm dictionary {CrossOriginStorageGetFileHandleOptions boolean =create false ; (DOMString or sequence <DOMString >); };origins interface mixin { [NavigatorCrossOriginStorage SameObject ,SecureContext ]readonly attribute CrossOriginStorageManager crossOriginStorage ; };Navigator includes NavigatorCrossOriginStorage ;WorkerNavigator includes NavigatorCrossOriginStorage ;
每个 Navigator
和 WorkerNavigator
对象都具有关联的 CrossOriginStorageManager
对象。此 crossOriginStorage 获取器
步骤
是返回 this 关联的 CrossOriginStorageManager。
每个 CrossOriginStorageManager
对象都具有关联的 源,其值设置为
this 的 相关设置对象 的 源,设置发生于该对象
创建时。
注意: 从以下对象取得源:相关设置对象,而不依赖于
脚本本身的任何信息,这对 worker 有一个值得明确说明的影响。从
blob: URL 创建的 worker 具有以下 源:创建它的上下文的源,因此它与该源共享完全相同的
跨源存储视图:此类 worker 写入的内容,创建它的页面可以读回,而且
页面存储的内容,worker 可以读取。blob: URL 没有自身独立的源,并且
撤销它不会使 worker 脱离该视图。从 data: URL 创建的 worker
具有一个 不透明源。本规范目前没有定义
getFileHandle()
在 调用 源不透明时的行为;
以下算法中的源规则:验证 COS
请求,只约束
调用方 指定 的源。不透明源没有稳定的标识可用于
存储
源 或 同
站 比较,因此没有任何有意义的权限
可以授予它。
3.1. 此 getFileHandle()
方法
- handle = await navigator . crossOriginStorage .
getFileHandle(hash) -
返回由 hash 标识的文件的句柄,前提是它存在于跨源存储中,且可向 调用源披露。否则,以以下异常拒绝: "
NotFoundError"DOMException。 一个 "NotFoundError" 并不能证明该文件 在物理上不存在于跨源存储中;参见 § 8.4 可用性门控。调用方可以 将其理解为“改为从网络获取此文件”。 - handle = await navigator . crossOriginStorage .
getFileHandle(hash, {create: true }) -
返回一个句柄,可用于将 hash 标识的文件写入 跨源 存储,如果尚不存在条目,则创建新条目,默认仅限同站源访问。 调用方需要通过该句柄的以下方法写入完整的文件内容:
createWritable()方法,无论条目是否已经 存在;用户代理会在此时验证写入字节的哈希是否为 hash, 若非如此,则以 "DataError"DOMException拒绝。这一要求防止 源利用创建请求作为判定 hash 是否已经存在的预言机。 - handle = await navigator . crossOriginStorage .
getFileHandle(hash, {create: true,origins: "*" }) -
与上文相同,此外还会使该条目可向任何满足以下条件的源披露:其请求所针对的 hash 通过 § 8.4 可用性门控,在写入完成后生效。
- handle = await navigator . crossOriginStorage .
getFileHandle(hash, {create: true,origins: ["https://a.example", "https://b.example"] }) -
与上文相同,将披露范围限制为列出的源以及满足以下关系的源:同站,相对于 其中的每个源(此外还包括调用源、任何已经位于 存储源 中的源,以及满足以下关系的源:同站,相对于这些源)。
getFileHandle(hash, options)
方法
的步骤如下:
-
令 result 为 一个新的 promise。
-
如果运行以下算法的结果:检查跨源存储权限, 给定 global, 为 false,则:
-
将一个全局任务排入队列,使用 DOM 操作任务源,给定 global,以 拒绝 result,使用一个 "
NotAllowedError"DOMException。 -
返回 result。
-
-
令 validationFailure 为运行以下算法的结果:验证 COS 请求,给定 hash 和 options。
-
如果 validationFailure 不为 null:
-
将一个全局任务排入队列,使用 DOM 操作任务源,给定 global,以 拒绝 result,使用 validationFailure。
-
返回 result。
-
-
返回 result。
CrossOriginStorageGetFileHandleHash
hash
和一个 CrossOriginStorageGetFileHandleOptions
options,返回 null 或一个 TypeError:
-
如果 hash["
algorithm"] 不是哈希算法 名称,且该名称由 [WEBCRYPTO] 识别,或者它按照 ASCII 不区分大小写 的方式匹配 "SHA-1", 返回一个新的TypeError。注意: SHA-1 不具备抗碰撞性。 由于一个 COS 条目 的键 仅由其 COS 哈希 决定,SHA-1 碰撞就会让一个源的字节被用于响应另一个 源针对具有相同摘要的不同字节所发起的读取。
-
如果 hash["
value"] 不匹配正则 表达式/^[0-9a-f]{64}$/,且 hash["algorithm"] 按照 ASCII 不区分大小写 的方式匹配 "SHA-256",则返回一个新的TypeError。注意: 未来的哈希算法可以定义 不同的预期摘要长度;本 规范只对 "
SHA-256" 作出规范性约束,与它在所有 这些 示例中的使用保持一致。 -
如果 options["
origins"] 存在,并且 不是 "*":-
对于每个 candidate,来自 candidates:
-
令 url 为运行以下算法的结果:基本 URL 解析器,输入为 candidate。
-
如果 url 为失败,则返回一个新的
TypeError。 -
如果 url 的 方案 既不是 "
http" 也不是 "https",或者 url 的 源 不是一个 潜在可信 源,则返回一个新的TypeError。注意: 只有满足以下条件的源, 自身能够调用
getFileHandle(), 授权给它才有意义。该 API 仅暴露于 安全上下文,因此允许https:源和环回http:源,例如http://localhost,并拒绝 其他主机上的http:源、ws:、wss:、ftp:,以及所有满足以下条件的方案: 其源不透明,包括扩展方案,例如chrome-extension:, 也在其中。
-
-
返回 null。
3.2. 读取文件
-
令 entry 为以下 COS 条目,它位于 COS 注册表 中,其 hash 等于 hash(如果存在),否则为 null。
注意: 另一个写入方已经 请求但尚未完成的写入不在 COS 注册表 中,因此 对于此类哈希,entry 为 null,本算法会以完全相同的方式继续执行, 如同处理从未被任何源写入的哈希。本规范允许的每次披露都 由下面的 应用可用性门控 决定;不存在 能够作出响应的更早分支, 先于该步骤。参见 § 8.3 进行中的写入。
-
令 disclosableEntry 为运行以下算法的结果:应用可用性 门控,给定 entry 和 origin。
-
如果 disclosableEntry 为 null:
-
将一个全局任务排入队列,使用 DOM 操作任务源,给定 global,以 拒绝 result,使用一个 "
NotFoundError"DOMException。 -
返回。
-
-
令 handle 为以下操作的结果:创建一个新的
FileSystemFileHandle[FS],其 定位器 寻址 disclosableEntry 的 hash,位于 跨源存储文件系统 中,在 realm 内。 -
将 handle 的 COS 源 设置为 origin。
-
将 handle 的 可读取 设置为 true。
注意: 此句柄已经通过 应用可用性门控,因此调用源 具有 读取该条目字节的权限。
-
将一个全局任务排入队列,使用 DOM 操作任务源,给定 global,以 解决 result,使用 handle。
-
如果 entry 为 null,则返回 null。
注意: 位于 COS 注册表 中的条目 总是已经写入并经过验证,因此不存在 需要本算法考虑的写入未完成状态。对于仅有的写入仍在进行中的哈希, 它不存在于注册表中,传入本算法时为 null,与从未被任何源 写入的哈希相同;参见 § 8.3 进行中的写入。
-
如果 origin 位于 entry 的 存储 源 中,则返回 entry。
注意: 原始存储方,以及任何 自身已成功写入该条目的源, 始终可以将其读回。参见 § 3.5.1 原始存储方的 访问权限。
-
如果 origin 同站 于 entry 的以下对象中的某个项:存储源,则返回 entry。
注意: 每个条目都具有的同站基线 (未提供以下选项的写入所请求的授权:
origins选项)始终成立, 无论任何 origins 列表 或 可全局披露 授权 是否在之后添加。由于它会在 公共哈希列表 门槛之前被检查, 该门槛位于下文,因此同站 源可以读取该条目,无论其 hash 是否 位于 PHL 上。这里的同站 属于同一个信任单元,因此不会跨越站点边界披露任何信息, 该边界由 § 8.2 跨站探测 保护。 -
如果 origin 同站 于 entry 的以下对象中的某个项:origins, 返回 entry。
注意: 位于显式 origins 列表 中的源, 或 同站 于其中某个源的源, 都可以读取该条目,而无需该条目 位于 PHL 上。将每个列出的源扩展到 其站点,与上述同站基线保持一致:同站源属于同一个信任单元,因此指定 一个站点中的一个源就意味着指定其中所有源。存储源作出了有界的披露决定,通过 指定 该源(参见 § 3.5 资源可见性升级 和 § 7 Cross-Origin-Storage-Allow-Origin 标头),而 另外要求 全局普遍使用程度,会使普通的受限共享依赖于无关的、 公开维护的收录机制,而所共享的资源往往是专有资源。在 可全局披露 门槛之前检查这一点,才能防止 后续请求全局可用性的写入 撤销已列出源的访问权限。参见 § 8.2 跨站 探测。
-
如果 entry 的 可全局披露 为 true:
-
如果 entry 的 hash 不 位于 PHL 上,则返回 null。
注意: 公共哈希列表门槛 只适用于全局授权,这是唯一一种 披露本来可能触及 Web 上任何源的情况。对于一个条目,如果它 可全局披露,但其 hash 不 位于 PHL 上,则不会 向仅凭此项授权取得资格的源披露;如果某个源还 出现 于 存储源、显式 origins 列表,或者 任一方的同站集合中,则已在上文返回。
-
如果用户代理选择对此请求应用 GREASE 处理, 则返回 null。
注意: GREASE 处理也只适用于 全局授权。上文检查的授权绝不会 到达此步骤:存储源及其同站源属于同一个信任单元,而 位于显式 origins 列表 中的源,是由 写入方有意指定的,其
Cross-Origin-Storage-Allow-Origin标头已对其授权,也覆盖其 同站源, 同样如此。 -
返回 entry。
-
-
返回 null。
-
返回运行以下算法的结果:确定 COS 披露,给定 entry 和 origin。
3.3. 创建和写入文件
一个 FileSystemFileHandle
若由 完成创建请求 创建,则具有关联的
请求的源,该值是 验证并存储 将会
尝试 升级 条目的 origins 所达到的值,一旦
通过该句柄成功完成写入。
每个 FileSystemFileHandle
若寻址 跨源存储文件系统,还会具有一个
关联的 COS 哈希,即它寻址的 COS 哈希,一个
关联的
COS 源,即 源,该源获得了此句柄,以及一个
关联的
可读取 布尔值,它决定
getFile()
是否可以通过这个特定的
句柄披露所寻址条目的字节。对于由 完成读取请求 返回的句柄,它为 true,因为该句柄已经通过
可用性门控,而对于由以下算法返回的句柄,它为 false:
完成
创建请求,直到 验证并存储
对同一个句柄执行成功。
注意: 句柄寻址的是一个 COS 哈希,而不是一个 COS 条目。完成创建请求 会返回 一个句柄,其哈希在 COS 注册表 中没有条目,并且可能永远不会有,因此不存在 可供它引用的条目,直到 验证并存储 创建一个。
注意: 这是有意作为每个句柄自身的属性。一个
创建请求
无论该哈希的条目是否已经存在,都会返回句柄(参见
完成
创建请求),因此按条目设置的规则会让任何源调用
getFileHandle()
并指定
create:
true,然后立即读取一个它
从未写入的条目,在不满足以下条件的情况下获知其内容:origins、
被收录于 PHL,或 GREASE 处理。本
规范中的每项披露控制都应用于读取路径,因此按条目实施门控会让创建请求
绕过所有这些控制。要求调用方先提供正确的字节,意味着成功
通过创建请求获得的句柄读取内容,不会披露调用方尚未拥有的任何信息。
CrossOriginStorageGetFileHandleOptions
options、一个全局对象
global,以及一个 Realm realm:
-
令 requestedOrigins 为运行以下算法的结果:规范化 请求的源,给定 options["
origins"]。 -
如果 requestedOrigins 是一个 列表,则将 requestedOrigins 设置为运行以下算法的结果: 应用 COS 范围上限,给定 requestedOrigins 和运行以下算法的结果: 取得 COS 范围上限,给定 global。
-
令 handle 为以下操作的结果:创建一个新的
FileSystemFileHandle[FS],其 定位器 寻址 hash,位于 跨源存储文件系统 中,在 realm 内。 -
将 handle 的 COS 哈希 设置为 hash。
-
将 handle 的 请求的源 设置为 requestedOrigins。
-
将 handle 的 可读取 设置为 false。
注意: 本算法不会查询 COS 注册表,也 不会修改它。它不能这样做: 其结果必须独立于 hash 是否已经存储,否则创建 请求就会 自身披露先前的存在状态(参见 § 8.2 跨站探测)。 因此,无论何种情况,handle 都会在完全 相同的条件下返回,无论条目存在、从未被写入,还是正在被其他人写入, 此时都是如此,并且在此调用方提供字节之前,它不附带读取权限。
注意: 由于这里不会注册任何内容, 如果调用方请求句柄后将其放弃, 从未调用
createWritable()或从未关闭由此得到的流, 注册表就会保持与请求之前完全相同。其他源无法观察到该请求 曾经发生,并且任何哈希都不会被固定在占位状态,从而抑制之后对它的读取。 参见 § 8.3 进行中的写入。注意: requestedOrigins 只会被应用, 仅由 验证 并存储,通过 升级资源可见性,一旦字节 已被写入并经过验证。这就 使创建请求必须先提供完整字节,才能扩大可见性;参见 § 3.5 资源可见性升级。
-
将一个全局任务排入队列,使用 DOM 操作任务源,给定 global,以 解决 result,使用 handle。
*"、一个 列表,其中包含 源,或者 null:
-
如果 origins 不 存在,则返回 null。
-
如果 origins 是 "
*",则返回 "*"。 -
令 list 为 « »。
-
返回 list。
注意: 在此处去重,先于任何后续 合并 到 现有条目的操作,可使 origins 保持无重复项,从条目 首次创建时起便如此,因此之后每次 克隆 它时也会保持没有重复项。
当此 FileSystemWritableFileStream
通过调用以下方法获得:
createWritable()
在一个句柄上,该句柄的 定位器
寻址 跨源存储文件系统,而该流被关闭时 [FS],用户代理必须运行
此
验证并存储 步骤,
如下所述,给定该句柄,并在该关闭操作的 promise 被
兑现之前完成。
注意: 无论关闭是如何
触发的,都适用:显式的
close()
调用,或者一个 pipeTo()
调用到达其
源的末尾,默认情况下也会关闭其目标。[STREAMS] 不会区分这两种情况,在
流变为已关闭状态时,本算法也不会。
FileSystemFileHandle
handle,其流已被
关闭、已写入的完整 字节序列 bytes,以及该关闭操作的
相关设置对象 的 源 origin,如下:
-
令 hash 为 handle 的 COS 哈希。
-
令 computedValue 为 bytes 的小写十六进制摘要,计算时 使用以下 算法,其名称由 hash 的 algorithm 指定,依据 [WEBCRYPTO]。
-
如果 computedValue 不完全等于 hash 的 value:
-
拒绝 该关闭操作的 promise,使用一个 "
DataError"DOMException。 -
中止这些步骤。
注意: 失败的写入没有需要清理的 内容。完成创建请求 未注册任何条目, 因此从未产生已验证字节的写入会使 COS 注册表 保持不变,而后续的
getFileHandle()调用若针对同一个哈希,将得到相同的 "NotFoundError" 与从未尝试过该写入时会得到的一样。对于某个源 已经写入的条目,同样不受影响:本算法只会添加字节和授权。 -
-
-
令 entry 为以下 COS 条目,它位于 COS 注册表 中,其 hash 等于 hash(如果存在),否则为 null。
-
如果 entry 为 null:
注意: 这是本 规范中唯一会向以下对象添加条目的步骤: COS 注册表,并且只有在 bytes 已经依据以下值完成验证后才会运行: hash。两个 写入方若都发现该哈希不存在并同时写入,都会到达此处; 其中 第二个入队的会发现第一个创建的条目,并且仅添加自身的源和 下文的授权。它们的字节具有相同的哈希值,因此只存储一次就足够了。
-
将 handle 的 可读取 设置为 true。
注意: 只有此句柄变为 可读取。同一哈希的另一个句柄,若来自 尚未通过它完成写入的另一个创建请求,则仍然不可读取。
-
createWritable()
若在一个 FileSystemFileHandle
上调用,该句柄寻址
跨源存储文件系统,则必须产生一个 FileSystemWritableFileStream
其内容
初始为空,无论以下选项的值是什么:keepExistingData
而且
无论所寻址的 COS
哈希 是否在 COS 注册表 中具有条目。
注意: [FS] 在其他情况下将 keepExistingData
定义为初始化该
流时使用现有文件的内容。在这里遵循该行为,会让调用方成为存储
源,即使它从未拥有这些字节。创建请求会返回句柄,无论条目
是否已经存在,因此调用方可以为另一个源存储的哈希打开可写流,然后关闭它,
而不写入任何内容,并让沿用的字节计算出的哈希与请求值相同。这样就会
授予它读取权限,而 origins、此
公共哈希列表,以及
GREASE 处理 从未被查询:这是一种 可用性
门控 绕过,
由两个各自允许的操作组合而成。同样的理由也使
验证并存储 要求
每个创建请求都提供完整内容。
注意: 因此,未经过任何写入便关闭的可写流
持有空字节序列,它不会
对调用方想要的任何条目计算出所请求的哈希值,因此 验证并存储 会拒绝,
使用一个 "DataError"
DOMException
与处理任何其他不匹配情况相同。若某个实现
在这种情况下改为报告存储或 I/O 失败,就会将验证结果隐藏在
无关错误之后。
文件系统标准
[FS] 尚未定义一个
扩展点,供依赖它的
规范将额外的每次写入验证接入 FileSystemWritableFileStream
的
关闭步骤。在此类挂钩存在之前,本节直接描述所需行为;预计
未来的修订将正式集成到 [FS]。
3.4. 存储获取的资源
以下章节中列出的宿主集成:§ 9 与其他
规范的集成,会存储它们
获取的资源,而不经过一个 FileSystemFileHandle。
预期它们的宿主规范会
运行以下算法,一旦获取的字节已经通过该
集成所要求的完整性检查。
*"、一个 列表,其中包含 字符串,
或者 null origins,以及一个 环境设置对象 settings:
-
令 options 为 «[ "
origins" → origins ]»,如果 origins 不为 null,否则为 «[ ]»。 -
如果运行以下算法的结果:验证 COS 请求,给定 hash 和 options,不为 null, 则返回。
-
令 computedValue 为 bytes 的小写十六进制摘要,计算时 使用以下 算法,其名称由 hash 的 algorithm 指定,依据 [WEBCRYPTO]。
-
如果 computedValue 不完全等于 hash 的 value,则返回。
注意: 集成自身的完整性 元数据可以列出多个摘要,只要匹配 其中任意一个就满足要求。此步骤确保条目使用的键是这些 确切字节所产生的哈希。
-
令 requestedOrigins 为运行以下算法的结果:规范化 请求的源,给定 options["
origins"]。 -
如果 requestedOrigins 是一个 列表:
-
令 internalResponse 为 response 的 内部响应,如果 response 是一个 过滤响应,或者为 response, 在其他情况下。
-
将 requestedOrigins 设置为运行以下算法的结果:应用 COS 范围上限,给定 requestedOrigins 和运行以下算法的结果:解析 COS 范围上限,给定 internalResponse 的 标头列表。
注意: 上限来自所获取的 资源自身的 响应,因此由 提供字节的服务器决定可向哪些源披露这些字节。加载它的
Document的 COS 范围上限 不参与其中:其运营方 不控制该资源,而且 允许它授权接收方,会让任何站点都能宣布另一个站点的资源可共享 给它自行选择的源。用户代理从内部 响应 读取该标头,因此这也适用于不透明的 过滤响应,而且该标头 绝不会暴露给页面。 -
-
令 origin 为 settings 的 源。
-
-
令 entry 为以下 COS 条目,它位于 COS 注册表 中,其 hash 等于 hash(如果存在),否则为 null。
-
如果 entry 为 null:
注意: 如同 验证并存储, 条目只有在 bytes 已经验证后才会创建, 验证依据为 hash,而集成的
integrity检查已经完成此事。一个getFileHandle()写入方若针对同一哈希,且仍在 进行写入,则不受影响:它没有持有任何注册表状态,而且它自身的 验证并存储 会敲定, 正常完成,找到此条目并向其添加自身的源和授权。 -
运行 升级资源可见性,给定 entry 和 requestedOrigins。
-
注意: 如同 验证并存储,存储始终要求
提供完整字节,包括当
条目已经处于 "written" 状态时,因此不会透露先前的存在状态。集成的
查找,
在其网络获取之前进行,是一种读取,受 可用性门控 以及
§ 8.2 跨站探测 中的探测注意事项约束。
3.5. 资源可见性升级
一个 COS 条目 的可见性可以 升级,但绝不可降级。
*"、一个 列表,其中包含
源,或者 null requestedOrigins:
-
如果 requestedOrigins 为 null,则返回。
注意: 省略
origins只会请求同站 可用性,而每个条目都已具备这一点(参见 确定 COS 披露),因此 没有需要添加的内容。 -
如果 requestedOrigins 是 "
*":-
将 entry 的 可全局披露 设置为 true。
-
返回。
注意: 这会添加全局授权;它 不会改动 entry 的 origins。一个条目 如果已具有显式的 origins 列表,就会保留它,因此每个 源,位于 该 列表 中,都能保持其与 PHL 无关的访问权限,即使条目随后变为 可全局披露。这就是 两种授权被存储的关键原因, 分开存储;参见 可全局披露。
-
-
对于每个 candidateOrigin,来自 requestedOrigins:
注意: 升级只会向以下对象添加内容:origins,或者设置 可全局披露
为 true。如果写入方请求一个 列表,则会扩大条目的可见范围,即使该条目已经
可全局披露,如同对尚未如此的条目扩大可见范围
一样,因为两种授权
相互独立:新列出的源会获得与 PHL 无关的访问权限,即使
"*" 授权仍然有效。
注意: 任何站点,包括原始 存储方之外的站点,都可以扩大条目的 origins,只要 它同时提供计算出的哈希等于条目的以下值的字节:hash。这是有意为之: 任何已经拥有某个哈希所对应正确字节的站点,根据这一构造,就具有同等的权威性, 与原始存储方一样,能够确定该哈希表示什么。
3.5.1. 原始存储方的访问权限
一个 源,如果已成功完成 写入 一个 COS 条目(也
就是,任何 源,位于该条目的 存储源 中),就可以
始终在之后获得
该条目的句柄,通过 getFileHandle(),
不取决于该条目的
origins 值,也
不取决于其 hash 是否 位于 PHL 上。
这与 Cache API 的模型一致,在该模型中,源始终可以访问自身存储的内容。
4. 跨源存储文件系统
此 跨源存储文件系统 是一个 文件系统根,不同于任何 源的 存储桶文件系统 或本地文件系统访问根,其 条目 与以下对象一一对应:COS 注册表 的 COS 条目 项。
此 跨源存储文件系统 不使用 [FS] 通常采用的每次调用
权限检查
模型:每个 FileSystemFileHandle
从它获得时,都已经得到完整授权,依据
§ 3.2 读取文件 或 § 3.3 创建和
写入文件,并且发生在将其返回给脚本之前,因此调用
getFile()
或 createWritable()
在此类句柄上,
绝不会触发额外的权限提示。
因此,在此类句柄上调用 queryPermission() 和 requestPermission() 时,绝不得
报告
"prompt",因为不会出现任何提示。它们必须报告 "denied",对于写入模式,
如果该句柄
不是通过 创建请求 获得的,并且
"granted"
在其他情况下。
createWritable()
若在一个 FileSystemFileHandle
上调用,该句柄寻址
跨源存储文件系统,且并非
通过一个
创建请求 获得,则必须以一个 "NotAllowedError"
DOMException
拒绝,
而不创建一个 FileSystemWritableFileStream。
注意: 写入是句柄确实可能
缺少的一项能力。只有创建请求会产生一个
可以通过它写入的句柄,因此对任何其他句柄的写入模式报告 "granted"
都会声称具备一项能力,而 createWritable()
会拒绝提供它。读取仍为
"granted",因为持有可读句柄的调用方可以通过它读取。请求权限无法
改变
任一答案,因为没有可显示的提示,也没有可记录授权的状态。
调用 getFile()
在一个 FileSystemFileHandle
上,该句柄寻址
跨源存储文件系统,且其 可读取 为 false,则必须拒绝,
使用一个 "NotAllowedError"
DOMException。
这涵盖两种不同的情况:
-
来自 创建请求 的句柄,尚未通过它完成写入, 写入 仍未完成,其寻址的哈希在 COS 注册表 中没有条目。不存在已验证的字节可供 返回,而且调用方不得将自身未完成的写入观察为 文件的真实内容。
-
来自创建请求的句柄,其哈希已被某个 其他 源写入。 已验证的 字节存在,而此调用方没有提供它们,也没有通过 可用性门控,因此它不得获知该条目的任何信息, 甚至不能 知道它是否存在。
注意: 两种情况的拒绝完全相同,因此它
不会披露任何信息。这就是为什么
"NotAllowedError"
在这里是安全的,尽管读取路径会刻意避免它:此句柄是
调用方自身的,由其自身的创建请求获得,而且句柄无法在任何
其他源中反序列化(参见 § 4.1 转移句柄)。因此,该答案
绝不会跨越源边界,而且
相对于注册表内容保持恒定。对照 § 8.3
进行中的写入,在那里,
读取路径上不同的错误 会 跨越该边界。
一旦句柄的 可读取 为 true,getFile()
在其上调用时,就会返回一个 File
其内容是所寻址条目的 bytes;这
正是 [FS]
已经定义的行为,针对一个 文件
条目,其
二进制数据 就是这些字节。
一个 FileSystemFileHandle
若寻址 跨源存储文件系统,则具有一个
文件系统定位器,其 路径 是一个 列表,包含
单个
字符串,即其 COS 哈希 的 value,并且其
根
是此 跨源存储文件系统。
注意: [FS] 将 name
定义为句柄的
定位器路径中的最后一个路径分量,因此这也确定了 name:
它就是该
哈希。条目没有自身独立的名称,而哈希是它唯一的标识,因此
报告它能够保留空字符串会丢弃的信息。
条目没有名称,也没有包含它的目录,因此 [FS] 的大部分结构性接口 都没有作用对象。标识是例外,而且它是内容寻址条目 始终能够回答的一个问题:此 COS 注册表 最多保存一个 COS 条目,对应每个 COS 哈希,因此两个 句柄寻址同一个条目,当且仅当它们的哈希相等。
isSameEntry()
若在一个 FileSystemFileHandle
上调用,该句柄寻址
跨源存储文件系统,且参数
也寻址该系统,并且是
由相同的 源 获得的,则必须返回 true,如果两个句柄的
COS 哈希 项 相等,否则返回 false。
注意: 拒绝这种比较会丢弃用户 代理已经掌握的信息。
isSameEntry()
若在一个 FileSystemFileHandle
上调用,该句柄寻址
跨源存储文件系统,且参数
并非由调用 源 获得,或者
它寻址的文件系统不是此 跨源存储文件系统,则必须拒绝,使用
一个 "NotAllowedError"
DOMException。
对于寻址其他文件系统的句柄,例如
由 getDirectory()
返回的句柄,则保留 [FS] 为它定义的行为,当调用时使用一个
FileSystemFileHandle
寻址 跨源存储文件系统 作为参数。
注意: 在这种情况下返回 false,会断言这 两个句柄寻址不同的条目, 而这是对调用源无权检查的句柄作出的声明。拒绝只表示 无法进行该比较。
move() 和 remove() 若在一个
FileSystemFileHandle
上调用,该句柄寻址 跨源存储文件系统,则必须以一个
"NotAllowedError"
DOMException
拒绝,
并且必须使任何所寻址的条目保持不变。
注意: 一个条目由其以下集合中的每个源共享:存储 源,因此执行一个 移除操作会让一个站点破坏其他站点所依赖的数据;删除操作属于 逐出 和用户自身的存储控制。重命名或更改父目录没有任何 作用对象,因为条目以其 COS 哈希 命名,而且没有包含它的目录。
注意: 这两个方法都未定义于 [FS] 或文件系统
访问 API 中,截至撰写本文时
(WICG/file-system-access#214),
因此错误名称沿用 removeEntry(),
该操作是 [FS] 确实
为删除条目定义的操作:当读写访问权限
未被授予时,它会使用访问结果的错误名称拒绝。在这里,拒绝是准确的行为:这些操作有意义,但
被拒绝执行。
createSyncAccessHandle()
在这里无需额外规则:其步骤会以一个
"InvalidStateError"
DOMException
拒绝,只要句柄不在一个 存储桶文件系统 中,
而此 跨源存储文件系统 是一个 文件系统根,不同于任何源的文件系统根,
因此 [FS] 已经确定了
拒绝行为及其错误名称。
注意: 这也是本规范自身会
希望得到的结果。一个
FileSystemSyncAccessHandle
会向调用方提供可写文件描述符,这将允许它
更改条目的 bytes,
使其不再匹配 COS 哈希,这些字节原本
以该哈希为键存储,
而可向其披露该条目的所有其他源,读取的都是这些相同字节。
4.1. 转移句柄
一个 FileSystemFileHandle
是一个 可序列化对象 [FS],因此寻址一个
COS 条目 的句柄可以传递给另一个 环境设置对象,例如使用
postMessage(message, options)。
这是获得此类句柄的第二种方式,另一种是
getFileHandle(),
因此这里也对它加以约束。
此 序列化步骤,针对一个 FileSystemFileHandle
寻址
跨源存储文件系统,给定
value 和 serialized,还包括:
-
将 serialized.[[COSHash]] 设置为 value 的 COS 哈希。
-
将 serialized.[[COSOrigin]] 设置为 value 的 COS 源。
-
将 serialized.[[COSMayRead]] 设置为 value 的 可读取。
此 反序列化步骤,给定 serialized、 value 和 realm,还包括:
-
如果 serialized.[[COSOrigin]] 不 同源 于 realm 的 环境设置对象 的 源,则抛出一个 "
DataCloneError"DOMException。 -
将 value 的 COS 哈希 设置为 serialized.[[COSHash]]。
-
将 value 的 COS 源 设置为 serialized.[[COSOrigin]]。
-
将 value 的 可读取 设置为 serialized.[[COSMayRead]]。
注意: 如果没有 同 源 检查,转移就会一次性绕过 本规范中的每项披露控制。一个句柄,若其 可读取 为 true, 则已经 通过 应用可用性门控,针对请求它的源 而言;将其发送给另一个 源,会将该条目的字节交给该源,而无需 origins、 PHL 收录情况 或 GREASE 处理 针对它进行任何评估。这 与文件系统标准现有的句柄处理方式一致,句柄同样只有 对一个 同源 接收方才有用。
注意: 可读取 会随 句柄一同传递,因此尚未通过其完成写入的创建请求句柄,在 转移后仍不可读取, 而读取请求句柄会保持可读取,无需再次门控。在接收时再次门控,会让 调用方能够通过反复转移同一个 句柄来探测可用性。
5. 存储管理
5.1. 存储限制
用户代理必须限制字节总数,对于一个 源 可以向以下对象贡献的字节:
COS 注册表,通过 成功的写入,以
防止单个源
用大量数据填满缓存,试图逐出其他源的条目。具体限制
由实现定义。如果某个源的写入将超出其
限制,用户代理必须
拒绝 该
写入,使用一个 "QuotaExceededError"
DOMException
并且应记录一条警告到
控制台。
注意: 由于条目采用 内容寻址,如果一个源 反复写入相同的 字节,使用相同的哈希,则不会在首次成功写入之外消耗额外配额;参见 § 2.2 COS 条目。
用户代理还具有一个 由实现定义 的 源列表最大长度,它是一个
正整数,用于限制多少个 源,一个 origins 列表 可以
包含。该限制会在一个 列表
首次被提供时执行(参见 验证
COS 请求),
也会在其随后合并到现有条目时执行(参见
升级资源可见性),从而使单次调用和
随着时间推移的多次调用的累积效果,都不能使条目的 origins 无
限增长。除了限制内存使用,这还能防止一个 列表,其中列出源,被用作
一个
未声明的 "*" 替代项;参见 § 8.2 跨站探测。
在上述两个位置,超出此限制时的处理方式不同,因为这两次调用 发生于操作中非常不同的阶段:
-
在 验证 COS 请求 中,会在任何 文件被获取、计算哈希或 写入之前检查限制。超出限制的调用方会立即得到一个 "
TypeError" 并且不会发生其他任何事情, 与任何其他格式错误的origins值受到相同处理。 -
在 升级资源可见性 中,该限制只有在 累积效果下才能达到,累积的是 各次相互独立的写入调用请求的源,这些调用可能来自互不相关的源,跨越 很长一段时间,而且只有在本次调用的字节已经计算哈希、 经过验证并持久存储后才会检查。在那个阶段拒绝写入,会因为一个无关的记录管理限制,丢弃一次成功的、 已经验证且可能体积非常大的写入,而且 甚至不能将其归因于单个调用方的错误。因此,写入仍然 成功,只有超出容量的源会从以下对象中被丢弃:origins,并附带一条 控制台警告。这与现有的以下处理方式一致: 从宽松变为更受限 的请求:一个 源 请求 如果不能被完全满足,就会被静默限制,而携带它的写入仍然 成功。与 § 8.4 可用性门控 在其他位置的规定一致, 在本规范中,一个 调用方没有可靠的方法能够事后确认其请求的 源 是否 确实被添加:后续的
getFileHandle()调用,以该 源发起,即使该源已被添加,也仍可能失败(例如,因为条目已被 逐出,或者用户代理对该请求进行了节流),因此不得将其结果视为确认, 无论是肯定还是否定。
5.2. 逐出
本节为非规范性内容。
在存储空间紧张时,用户代理可以逐出 条目,从 COS 注册表 中, 例如采用最近最少使用策略,综合各个 存储源,这些源曾 最近访问过各个条目。预期用户代理会提供设置界面,通过它, 用户可以检查存储了哪些文件、哪些源访问过每个文件,并手动 删除条目或清除所有跨源存储数据。
当用户清除某个源的站点数据时,用户代理应从每个以下集合中移除该源: 存储 源,只要该源出现在其中。如果移除之后,一个 COS 条目 的 存储 源 为空,用户代理可以考虑删除该条目。
5.3. 手动添加的条目
本节为非规范性内容。
上述设置界面可以让用户将磁盘上已有的文件直接添加到
跨源存储(例如,独立于任何网站下载的 AI 模型),
而无需任何脚本调用 getFileHandle()。
此类条目
没有发起请求的 源 可供将写入归因于它,这引出了两个问题,命令式
的
API 本身无法回答:
-
谁是 原始存储方,就以下章节而言: § 3.5.1 原始存储方的访问权限?没有任何源:该条目的 存储源 初始为空。 因此,没有任何源能够豁免于 常规的 可用性门控 和 origins 检查,这些检查 原本适用于每个发起请求的源。这是正确的结果,因为没有任何源 真正写入过这些字节。
-
此类条目是否应像省略以下选项的普通写入一样,限制为仅限同站:
origins时的行为?不应如此:“同站”只有在 相对于发起请求的源时才有意义,而手动添加没有这样的源,因此不存在可供“同站” 指代的站点。预期用户代理会将此类条目设为 可全局 披露, 同时保持其显式 origins 列表 为空,因为手动 预置文件只有 在实现其目的时才有意义(让用户尚未访问的站点复用用户已经拥有的文件, 位于磁盘上的文件),而这要求其他源确实能够发现它。用户代理的设置界面还可以 让用户选择更小的范围。
以这种方式添加之后,条目在其他方面无法区分
于网站通过以下方法写入的条目:getFileHandle():
相同的
可用性门控 和 可见性
升级 规则适用于它,与具有相同授权的任何其他条目完全相同。
这包括 位于 PHL 上 的检查,
每当该条目最终变为
可全局披露 时,而这正是手动添加的默认行为。
5.4. 来源元数据
本节为非规范性内容。
一个 COS 条目 有意不记录 任何关于其字节来自何处的信息。标识仅为 hash:相同的字节 就是同一个条目,无论有多少个源存储过 它们,或者获取它们时使用了多少个 URL(参见 § 2.2 COS 条目)。URL 是一个 属性,属于一次 写入方的获取操作,因此没有非任意的方式可以为一个条目选择一个 URL, 如果它由多个源写入;URL 无法像字节那样被验证(参见 § 8.1 资源完整性); 而且它携带的用户浏览信息远多于粗粒度的 源 值,这些值位于 存储 源 中,因为路径或查询字符串可以指明特定的文档、 账户或会话。因此,在条目上记录它,会让高熵信号绕过 披露限制,这些限制由 origins 和 § 8.4 可用性 门控 负责实施。
尽管如此,想要这类信息的动机确实存在:用户在以下位置查看一个数 GB 的文件时:
§ 5.2 逐出 中描述的设置界面,以及开发者在调试为什么一个
getFileHandle()
调用未命中时,都希望知道文件
来自何处。用户代理可以通过保留实现私有的来源记录来满足这种需求,
将其保存在条目旁边(例如,其每个 存储源 获取
这些
字节时使用的 URL,以及时间),前提是将该记录视为用户代理状态,保存在
条目之外:
-
它绝不会暴露给脚本。本规范中的任何机制都不会披露它,而且之后添加的任何机制 也不应如此:无论
getFileHandle()还是其FileSystemFileHandle返回值,都不应透露此类记录是否存在,更不用说其 内容。特别是,发起请求的 源 必须无法观察到并非由它 自身产生的记录。 -
它只会通过可信的非 Web 界面呈现:用户代理自身的设置和存储 检查界面、其开发者工具,以及(当用户代理具备扩展平台时) 需要用户明确授予权限才能使用的扩展 API,与其他 暴露浏览历史的 API 受到同等对待。参见 浏览器扩展集成点说明文档,位于 此 仓库中,了解正在考虑的扩展接口。
-
它未经验证,呈现时也应明确如此。其中的 URL 是由写入这些 字节的源作出的声明。只有 hash 能保证内容(参见 § 8.1 资源完整性)。两个源可能记录不同的 URL, 对应同一个条目,而且任意一方 都可能不准确或有意误导。
-
它与条目以及其归属源共享生命周期。当 条目被逐出或删除时,它会被丢弃,而可归因于某个源的部分会被丢弃,当 该源从以下集合中被移除时:存储源,包括用户清除 该 源的站点数据时(参见 § 5.2 逐出)。
由于此类记录对网页内容不可见,用户代理是否保留记录,以及保留多少 内容,完全由实现决定。本规范中的任何机制都不依赖于它,而且其 存在与否无法被站点检测到。
6. 权限策略集成
本规范定义一个 策略控制的特性 [permissions-policy],其标识符为
字符串 "cross-origin-storage"。其默认允许列表
为 self。
[permissions-policy] 定义了 允许使用,适用于 Document,
但
CrossOriginStorageManager
也暴露于 WorkerGlobalScope,
而它没有
Document
属于自身。因此,检查针对调用方的 全局对象 来表述:一个
worker 全局对象会解析到这些 Document,
它们传递性地拥有该对象,并且其中每一个
都必须允许该特性。
-
如果 global 是一个
Window对象,则:-
令 document 为 global 的 关联的 Document。
-
如果 document 为 null,则返回 false。
-
如果 document 不 允许使用 此 "
cross-origin-storage" 策略控制的特性, 则返回 false。 -
返回 true。
-
-
如果 global 是一个
WorkerGlobalScope对象,则:-
令 owners 为 global 的 所有者集合。
-
如果 owners 为 空,则返回 false。
-
对于每个 owner,来自 owners:
-
如果 owner 是一个
Document并且 owner 不 允许使用 此 "cross-origin-storage" 策略控制的特性, 则返回 false。 -
如果 owner 是一个
WorkerGlobalScope对象,并且运行以下算法的结果: 检查跨源 存储权限,给定 owner,为 false,则返回 false。
-
-
返回 true。
-
-
返回 false。
一个 全局对象,如果本算法对它返回 false,就会导致每次
getFileHandle()
从它发起的调用被拒绝,使用一个
"NotAllowedError"
DOMException,
在任何哈希验证、注册表查找或写入
发生之前。
注意: 通过以下对象解析:所有者集合,才能防止该特性的
禁用机制徒有其名。从 blob: URL 创建的 worker 运行于创建它的页面的
源 中,并与该页面共享完全相同的跨源存储视图(参见
§ 3 CrossOriginStorageManager 接口),因此,一个
仅检查全局对象自身的
Document
的检查,会在每个 worker 中因没有检查对象而自动满足,而将调用移入
new Worker(URL.createObjectURL(…)) 就会逃避
cross-origin-storage=() 策略,
无需改变其他任何方面。
注意: 一个 ServiceWorkerGlobalScope
的
所有者集合 为空,因此本
算法在那里返回 false。这源于以下概念的含义:所有者集合 是一种
存活关系,在 worker 创建时填充,并用于判断该 worker
是否仍被积极需要。Service Worker 有意不与任何以下对象建立此类关系:Document:
其注册会被持久保存,用户代理响应事件启动全局对象,并且它能够
在没有任何 Service Worker 客户端 的情况下运行。调用了
register()
的客户端,因此在注册时可以确定,但
到全局对象运行时通常已经不存在,而且一个正在运行的 Service Worker 会服务多个客户端,
它们的策略不一定一致。
注意: 要覆盖 Service Worker,需要捕获一份策略,
而这项工作属于
[permissions-policy] 和 [service-workers]。所采用的形式,
本规范倾向于从以下来源派生 Service Worker 的策略:
其自身脚本响应上的 Permissions-Policy 标头,如同其策略容器
已经携带该响应的 CSP:它仍受该源控制,而且会在
每次更新检查时重新评估。对发起注册的客户端策略取快照,会使禁用选择
变得过时,因为移除该特性
从标头中移除,并不会生效,直到该注册恰好发生更新。在此类
模型存在之前,失败时拒绝访问是唯一无法被用来逃避禁用选择的答案。
注意: 一个 SharedWorkerGlobalScope
的
所有者集合 可以包含多个
Document,
它们具有不同的策略,而且会在 worker 的生命周期中增加和失去所有者。
要求每个所有者都允许该特性,意味着一个不允许它的所有者就会禁用
整个共享 worker 的跨源存储,而且结果可能在两次
getFileHandle()
调用之间改变。另一种方式,只要求一个
允许它的所有者,会让任何允许该特性的文档代表
不允许它的文档重新启用该特性。
7. 此 Cross-Origin-Storage-Allow-Origin 标头
一个 COS 条目,如果其 origins 列表
非空,则可向那些源披露,而这些源
不受写入源控制,但该 列表
由脚本声明(通过
origins)
或由标记声明,而两者都可以被攻击者设置,
只要该攻击者已经实现脚本或标记注入。为了将该声明绑定到运营方,
即其字节正被披露的源的运营方,并采用注入内容无法伪造的形式,本
规范定义一个响应标头,用于限定此类声明可以指定哪些源。
此 Cross-Origin-Storage-Allow-Origin
响应标头携带一个
COS 范围上限:一个
集合,
其中包含 源,写入可以在非同站的
origins 列表 中指定这些源。谁
提供字节,谁就发送该标头,因此从以下来源读取它:
此 响应,它是被披露字节的来源:
-
对于一次
getFileHandle()写入,其字节可能生成于 脚本中,没有自身的来源 响应,则使用此 响应,它被用于 创建调用方的 全局对象 的Document, 或者,对于 worker,使用这些对象的响应:Document, 它们拥有该 worker(参见 取得 COS 范围上限)。 -
对于宿主集成,它是此 响应,来自所获取的资源,其字节被存储 (参见 将获取的资源存储到 跨源存储)。这是资源的服务器,它 不必是以下对象的服务器:
Document, 该对象加载此资源。
声明的 列表 会 取交集,与该上限(参见 应用 COS 范围上限),因此上限只会缩小声明的范围。
注意: 该标头是一项授权,来自
提供字节的一方。它指定的源,
即最终读取方,不会被征询,也无需发送任何内容:列出的源只会在之后参与,
通过调用 getFileHandle()
来请求此 COS 哈希,如同任何其他
读取方。这与 Access-Control-Allow-Origin 的方向相反 [FETCH],在后者中,
持有资源的服务器选择允许给定源读取它。这里,字节的提供方
限定谁能够在之后从跨源存储读取这些字节,而写入
页面上的注入内容无法扩大该范围,因为它无法设置提供方的响应标头。
注意: 该标头只适用于 列表 形式。此
同站默认形式没有指定跨源
接收方,因此无需授权,而 "*" 形式则在读取路径上受以下机制管理:
可用性门控,依据 公共哈希列表,而一个
每用户秘密永远无法通过它,因此也不需要写入时的上限。要求为
"*" 提供标头,还会破坏零配置的全局共享用例,却不会封堵任何
公共哈希列表尚未封堵的情况。
-
令 value 为以下操作的结果:获取、解码并拆分 "
Cross-Origin-Storage-Allow-Origin",从 headers 中。 -
如果 value 为 null,则返回一个空 集合。
-
令 ceiling 为一个空 集合。
-
对于每个 token,来自 value:
-
去除首尾的 ASCII 空白,针对 token。
-
如果 token 是空字符串,则 继续。
-
令 url 为运行以下算法的结果:基本 URL 解析器,输入为 token。
-
如果 url 为失败、url 的 方案 既不是 "
http" 也不是 "https",或者 url 的 源 不是一个 潜在可信源, 则返回一个空 集合。注意: 这包括一个 token 为 "
*" 的情况,它不会解析为源。一个Cross-Origin-Storage-Allow-Origin标头只能枚举有限集合,其中包含 具体的 源;"*" 不是允许的值,因为“任何源”这样的上限 会让 注入内容声明任意接收方,从而破坏该标头的全部 目的。 无界的 披露只能通过 "*" origins 形式实现,而它 受到门控,依据 此 公共哈希列表,并且绝不会查询此 标头。格式错误或包含 "*" 的标头 不会授予任何权限,与缺少标头完全相同;失败时拒绝授权。
-
-
返回 ceiling。
注意: 获取、解码并拆分 会在 U+002C (,) 处拆分,
因此标头
值中的源以逗号分隔,与 Timing-Allow-Origin 一致 [RESOURCE-TIMING],也符合通用
约定,针对
列表值 HTTP 响应标头,尽管同类的 crossoriginstorage HTML 内容
属性采用空格分隔,以匹配其宿主语法。两者都解析为相同的
源 集合。
-
如果 global 是一个
Window对象,则:-
令 document 为 global 的 关联的 Document。
-
如果 document 为 null,则返回一个空 集合。
-
返回 document 的 COS 范围上限。
-
-
如果 global 是一个
WorkerGlobalScope对象,则:-
令 owners 为 global 的 所有者集合。
-
令 ceiling 为 null。
-
对于每个 owner,来自 owners:
-
令 ownerCeiling 为一个空 集合。
-
如果 owner 是一个
Document, 则将 ownerCeiling 设置为 owner 的 COS 范围上限。 -
否则,如果 owner 是一个
WorkerGlobalScope对象,则将 ownerCeiling 设置为以下结果: 运行 取得 COS 范围上限, 给定 owner。 -
如果 ceiling 为 null,则将 ceiling 设置为 ownerCeiling; 否则,将 ceiling 设置为 交集,取自 ceiling 和 ownerCeiling。
-
-
返回 ceiling。
-
-
返回一个空 集合。
每个 Document
都具有一个 COS 范围上限,它是一个 集合,其中包含 源,
在文档创建时设置为运行以下算法的结果:解析 COS 范围上限,
给定此
标头列表,来自此 响应,它被用于
创建该文档。
注意: 对 worker 的以下集合取交集:所有者集合,与以下算法一致:
检查跨源存储权限:一个源
只有在每个拥有该 worker 的
Document,
按传递关系,都已授权它时,才会位于该 worker 的上限中。由于一个 blob: worker 与创建它的页面共享
源 和完全相同的跨源存储视图(参见
§ 3 CrossOriginStorageManager 接口),一个
仅检查 worker 自身的上限
对每个 worker 都会为空,而将列表范围的写入移入
new Worker(URL.createObjectURL(…)) 会去掉页面授权的接收方;
对所有者的上限取交集,可以保留该授权,同时不允许任何单个
所有者扩大它。
填充一个 Document
的
COS
范围上限,依据其 响应,
需要
一个挂钩,位于 [HTML] 的文档
创建步骤中,如同 [permissions-policy]
和 [CSP] 会被
在响应处理时填充。在该挂钩存在之前,本节直接描述
所需行为;预期未来的修订会正式集成它,同时还有
[FS] 挂钩,见 § 3.3 创建和写入文件。宿主集成不需要此类
挂钩:它们
传递所获取的 响应,给 将获取的资源存储到跨源
存储,该算法读取
其中的上限。
8. 隐私和安全注意事项
本节为非规范性内容,使用 RFC 2119 关键字的部分除外;另请参见 安全和隐私调查问卷,位于 仓库中。
8.1. 资源完整性
每个 COS 条目 的键及其
字节验证的依据都是密码学哈希(参见
§ 3.3 创建和写入文件)。因此,站点可以确定,
一个文件,通过以下方法获得:
getFileHandle(),
具有完全相同的字节,与本来会获得的字节一致,
如果自行获取 hash。开发者无法枚举跨源存储的内容,也无法
在尚不知道文件哈希的情况下访问它。
一个 COS 哈希 不是秘密。它是一个 内容标识符,任何人都能轻易计算, 只要已经拥有该文件,而且对于那些广泛分发的 资源,本规范所针对的资源,它通常是公开信息,往往与模型或库的 发布一同公布。因此,知道哈希足以查找条目,但不会授予访问权限, 仅凭哈希本身不会:查找是否成功,完全由 origins 和 § 8.4 可用性门控 决定。开发者不得将未公开的哈希 视为访问控制机制;一个 对特定文件感兴趣的攻击者不需要猜测其哈希,只需要获得文件 本身(参见 § 8.2 跨站探测)。
8.2. 跨站探测
如果资源只被少量站点使用,那么能够获知该资源
存在于跨源存储中的攻击者,就可以推断用户很可能访问过其中某个站点。由于
getFileHandle()
是获知存在状态的机制,因此每次调用
实际上都是一次探测。本规范中的两种独立机制限制了一次探测能够
获知的信息:
-
origins(参见 § 3.5 资源可见性升级)让存储 源能够限制 披露范围,只向选定的源集合,以及满足以下关系的源披露:同站 于它们,因此专有资源或普及度较低的资源 完全不必 可被全局探测。
-
§ 8.4 可用性门控 还会阻止披露一个 "
*" 范围的资源,针对 不属于其以下集合的请求源:存储源,除非该哈希 位于 PHL 上, 因而,使用以下设置存储的资源:origins: "*", 并不会自动变得可被 Web 上每个源探测。此门槛只适用于 "*" 范围的 条目;对于列表范围和同站范围的条目,第一个要点中的限制是唯一的 门槛,因为存储源已经明确限定了这些条目的披露范围。
第一个要点只有在“选定的源集合”显著小于整个 Web 时才成立。
以下选项的形式本身:origins,
没有任何机制阻止调用方
枚举数量极大的源(例如,根据公开的
热门站点排名构造的列表),这在功能上将近似于全局披露,同时绕过
只有 "*" 才要求的有意且明确的启用选择。此 源列表最大
长度(参见
§ 5.1 存储限制)对此加以约束:一个足够小、能够容纳真实
多站点资产用例的限制
(由同一方控制的少量相关源),但又远不足以有意义地近似
“每个源”,可以防止受限源形式的 origins 被用作
未声明的 "*" 替代项。
用户代理还应实施速率限制或以其他方式节流重复的
getFileHandle()
调用,来自单个源,并且可以应用
设备上的启发式规则(例如,检测针对某些哈希的请求模式,这些哈希似乎
按用户生成),以识别并阻止探测尝试,无论被探测的
哈希是否 位于 PHL 上。
getFileHandle()
调用,因为一个
可通过脚本控制的集成同样可以轻易地在循环中发起它们。
8.3. 进行中的写入
对于每个源,已请求但未完成的写入必须无法区分于 从未被写入过的哈希。本规范通过结构设计实现这一点:一个 COS 条目 只会被添加到 COS 注册表,通过 验证并存储 或 将获取的资源存储到跨源 存储,两种情况都只会在完整字节 已经依据哈希验证之后发生。完成创建请求 既不读取也不写入 注册表。因此 不存在可供读取观察到的占位状态,读取路径上也不存在能够 提前响应的分支,先于 应用可用性门控 作出响应。
另一种方式,在创建请求发起时注册占位项,并拒绝并发的 读取,使用可区分的错误,会让调用方获知 某个 源正在写入一个 给定哈希。那单个比特听起来微不足道,但并非如此。它会可被获取:
-
由任何源获取,包括在该条目上没有任何形式授权的源,因为占位项 不携带授权,而且读取会在以下算法之前得到响应:确定 COS 披露 运行之前;
-
针对任何哈希,包括不 位于 PHL 上 且永远不可能被收录的哈希,因此攻击者可以 使用从每用户值派生的哈希,这正是以下章节所针对的模式:§ 8.6 指纹识别, 该章节旨在检测这种模式;
-
无需 GREASE 处理,它只适用于到达门控 步骤的披露, 因此该比特将没有噪声;
因此,嵌入两个站点的追踪器可以在第一个站点创建任意宽度的标识符,并 在第二个站点将其读回,无需存储成本,同时绕过 origins、此 公共哈希 列表 和 GREASE 处理。占位项还会成为拒绝服务 攻击途径:固定一个确实广泛使用的资源的哈希,会抑制所有其他源的读取, 只要该占位项仍然存在。
注意: 为此付出的代价是,一个调用方无法
发现另一个调用方已经在下载
大型文件,从而避免启动一次重复下载。这确实有代价,而且这正是
本规范早期草案在此处使用可区分错误的原因。这种能力
可以在最重要的场景下恢复,即同一个源内,使用
Web Locks、BroadcastChannel,
或共享 worker,
这些机制都不会披露
任何跨越源边界的信息。跨源节省大多是理论上的:两个互不相关的
源必须在同一时刻竞争同一个哈希,而且一个双方都尚未
写入的条目,无论如何都不能向另一方披露,直到它 位于 PHL 上。
注意: "NotAllowedError"
仍然是以下方法的响应:getFile(),
在一个
由创建请求获得、尚未通过其完成写入的句柄上调用时(参见 § 4 此
跨源存储文件系统)。该答案
相对于注册表保持恒定,且只会到达发起请求的源,因为
句柄无法跨源反序列化(参见 § 4.1 转移
句柄),因此它不是一个信息通道。
8.4. 可用性门控
一次 getFileHandle()
读取,其发起源不属于
存储
源,是否成功始终取决于请求源是否被
允许 读取该条目,由 origins 决定。对于 "*" 范围的条目,还适用第二个
独立问题:用户代理是否愿意 披露该条目
存在这一事实,由 PHL
收录情况 和 GREASE 处理 决定。两者都必须
对此类条目成立;对于列表范围或同站范围的条目,只适用第一个问题。
规范性算法见 § 3.2 读取文件。一个 "NotFoundError"
因此并不是
资源不存在的证据:它可能意味着资源不存在、请求
源超出范围、"*" 范围资源的哈希尚未(或永远不会)位于 PHL 上,或者
GREASE 处理抑制了一次真阳性结果。开发者不得将 "NotFoundError"
视为
比“回退到网络”更具体的任何含义。
注意: 在支持第三方 cookie 的用户代理中,
"*" 授权让追踪器关联一个
用户跨站访问的能力,不会超过第三方 cookie 已有的能力。此 PHL 本身
并不能限制这种情况:攻击者可以用各个单独
符合 PHL 准入条件的资源构造跨站标识符,通过选择写入哪个子集。阻止第三方
cookie 的用户代理仍然可以提供 "*" 授权,只要它限制跨站披露,例如采用
较小的跨站查找预算,以顶层站点为键,并由
页面上的所有框架共享,计数在一个时间窗口内跨重新加载持久保留,而且使已写入的条目
只有在用户手势之后才能通过 "*" 授权使用。README 的
隐私注意事项 描述了这些缓解措施。如同
GREASE 处理,它们只适用于仅凭以下授权取得资格的源:
可全局披露 授权。存储
源、位于
显式 origins 列表 中的源,以及
满足以下关系的源:同站 于任一方,都可以读取条目,
一旦 确定 COS
披露 返回该条目,无需用户手势,也
不受查找预算限制。
8.5. GREASE 处理
用户代理可以应用 GREASE 处理(生成随机扩展并保持 可扩展性):偶尔作出响应,仿佛 一个可披露的条目不存在,即使 § 8.4 可用性门控 原本会 允许披露。这会添加噪声,使站点更难区分真实的不存在 与出于隐私考虑的假阴性,类似于以下机制采用的技术: UA 客户端 提示。
GREASE 处理只适用于仅凭以下授权取得资格的源:可全局 披露 授权,与以下机制实施门控的情况相同:公共哈希列表(参见 确定 COS 披露)。一个 用户代理不得将其应用于 存储源、位于显式 origins 列表 中的源,或者 满足以下关系的源:同站 于任一方。
应用 GREASE 处理的用户代理,必须作出与条目大小相称的判断, 针对有关条目。对于小条目,回退到网络获取的成本较低,偶尔出现 假阴性是合理的隐私权衡。用户代理不得对以下条目应用 GREASE 处理: 其大小使无谓的重新下载与所获得的隐私收益明显不成比例, (例如,GB 级的 AI 模型权重),因为那里的假阴性会迫使一次完整的、 可观察到的且成本高昂的重新下载。
8.6. 指纹识别
攻击者能够从探测跨源存储中提取的信息,受限于普及程度, 即被探测资源的普及程度。获知用户拥有一个非常广泛使用的资源,例如常见的 AI 模型 或广泛使用的 JavaScript 库,只会透露用户访问过其中某个(可能非常 众多的)使用它的站点。获知用户拥有罕见或唯一的资源,则会透露多得多的信息。 预期用户代理会应用设备上的启发式规则,检测那些哈希看起来 被刻意设置为每用户唯一的资源(例如,某个哈希只曾被单个 站点写入,或者请求频率异常高),并将此类模式视为探测尝试, 无论其名义上的 PHL 状态如何。
9. 与其他规范的集成
本节为非规范性内容。
此处定义的 COS 注册表
还可以通过四种宿主集成访问,而无需
经过 getFileHandle()
直接调用它。每种
集成都在各自的宿主规范中定义。本规范定义共享的
底层概念(COS 哈希、COS 条目、可用性门控,以及
此 公共哈希
列表)和此 将获取的资源存储到跨源
存储 步骤,
预期这些集成将基于它构建。
-
HTML:一个
crossoriginstorage属性,用于link和script元素,这些元素已经 携带integrity[SRI],已向 WHATWG 提议,见 whatwg/html#12770。 -
JavaScript:一个宿主定义的
crossOriginStorage导入属性,可以 一同使用,与integrity,基于 导入属性 提案构建,已向 WHATWG 提议,见 whatwg/html#12771。 -
CSS:一个
cross-origin-storage()<request-url-modifier>, 可与integrity()一同使用, 已向 CSS 工作组提议,见 w3c/csswg-drafts#14056。 -
Fetch:一个
crossOriginStorageRequestInit选项,可一同使用,与integrity[FETCH],已向 WHATWG 提议,见 whatwg/fetch#1954。 这一项没有宿主定义的目标,而且一个 COS 条目 存储 bytes,仅存储字节, 因此该规范定义此Response, 它由 COS 注册表 提供。预期的 形式是一个成功响应:其状态为 200,状态消息为OK,而其 主体是一个流,内容为 bytes。其Content-Type标头是 MIME 类型, 由 调用方声明,并且不携带其他标头,因为没有发生网络获取。
仅作说明,以下展示同一个全局共享资源如何选择使用跨源存储, 分别通过这四种形式:
< script src = "popular-library.js" integrity = "sha256-abc123..." crossoriginstorage = "*" ></ script >
import datafrom "popular-resource.ext" with { integrity: "sha256-abc123..." , crossOriginStorage: "*" , };
@font-face { font-family : "Popular Font" ; src : url ( "popular-font.woff2" integrity("sha256-abc123..." ) cross-origin-storage ( *)); }
const response= await fetch( "popular-resource.ext" , { integrity: "sha256-abc123..." , crossOriginStorage: "*" , });
这些片段仅用于说明。本规范并未定义其中任何语法。
每种形式的权威语法,包括受限源列表的写法,
都属于各自的宿主规范。这些写法有意
不同:一个 RequestInit 成员可以接受 sequence<DOMString>,与
origins
完全匹配,而一个属性值、一个
导入属性值和一个 CSS 修饰符参数只能携带文本。
预期这四种形式共享以下处理模型:首先查询 COS
注册表,寻找匹配且可披露的条目,
然后才回退到网络获取,并且
将成功获取且通过完整性验证的资源存储到 COS 注册表
中,以供复用,
由其他源复用,如同 getFileHandle()
所做的一样。存储步骤
是 将获取的资源存储到跨源
存储,每个集成运行它时,都会使用
获取的 响应。因此,受限源值会被以下标头限定:
该资源响应上的 Cross-Origin-Storage-Allow-Origin 标头,它由
提供字节的服务器发送(参见 § 7 Cross-Origin-Storage-Allow-Origin
标头)。关于
这些集成的讨论,应位于将承载它们的宿主规范的跟踪系统中,
而不是本仓库的问题跟踪系统。
致谢
衷心感谢 Tab Atkins-Bittner、Yash Raj Bharti 和 Joshua Lochner 提供的宝贵反馈,以及 Kenji Baheux 和 Kevin Moore 提供的宝贵启发或想法。
本规范包含以以下内容为范本的材料: 文件系统,它依据以下许可证提供: W3C 软件 和文档许可证。