HTML5/webstorage

From HTML5 Chinese Interest Group Wiki
Jump to: navigation, search

Web Storage

编辑草案 2011-11-28

最近发布版本(英):
http://www.w3.org/TR/webstorage/

最近编辑草案(英):
http://dev.w3.org/html5/webstorage/

过往的版本(英):
http://www.w3.org/TR/2011/WD-webstorage-20110208/
http://www.w3.org/TR/2009/WD-webstorage-20091222/
http://www.w3.org/TR/2009/WD-webstorage-20091029/
http://www.w3.org/TR/2009/WD-webstorage-20090423/

编辑:Ian Hickson(ian@hixie.ch), Google, Inc.

版权声明(英)
Copyright © 2011 W3C® (MIT, ERCIM, Keio), All Rights Reserved. W3C liability, trademark and document use rules apply.
The bulk of the text of this specification is also available in the WHATWG Web Applications 1.0 specification, under a license that permits reuse of the specification text.


摘要

这是一个定义如何在Web客户端里永久保存键值对数据的API规范。

该文档的状态描述

昝略

问题

实现者们认为,通过存储互斥锁(storage mutex)来回避条件竞争(race conditions)会造成较高的效能负担,这比数据被破坏还要严重。我们正在积极探求无需为同源脚本上锁(per-origin script lock)的其他方案。(如果审阅者对此有任何想法或建议,请务必在前一个章节提到的邮箱中提出)

了解相关问题的讨论可以移步至此:


目录

  1. 介绍
  2. 一致性要求
    1. 依赖关系
  3. 术语
  4. API
    1. Storage接口
    2. sessionStorage特性
    3. localStorage特性
      1. 安全性
    4. storage事件
      1. 事件定义
    5. 线程
  5. 磁盘空间
  6. 隐私
    1. 用户跟踪
    2. 数据敏感度
  7. 安全
    1. DNS欺骗攻击
    2. 跨目录攻击
    3. 实现中的风险
  • 引用
  • 致谢

介绍

这个章节是非规范性的

本规范介绍了两个客户端结构化数据存储的相关机制,他们类似HTTP会话(session)cookies。#COOKIES

第一个机制的构想,来自于这样的情形:用户无法在多个窗口中同时进行各自的交易,只能同时进行一笔交易。

Cookies没有很好的解决这个问题。打个比方,我们现在想买一张机票,但同时打开了两个相同的购票页面。如果网站通过cookies追踪用户的购票结果,这时,如果我们分别在这两个网页里完成各自的购票手续,那么实际上我们买到了两张票,但在购票过程中,对于另外一个页面的购票情况,用户并不知情。

本规范会介绍sessionStorage特性的接口定义,以解决这个问题。网站可以将数据添加到会话存储中,并且这些数据可以被该网页打开的任何同源网页所访问。

例如,有一个网页,用户可以通过复选框勾选是否购买保险:
<label>
  <input type="checkbox" onchange="sessionStorage.insurance = checked ? 'true' : ''">
  I want insurance on this trip.
</label>

网页的后半部分可以通过脚本判断用户是否做了勾选:

if (sessionStorage) { ... }
如果用户已经打开了多个该网站的页面,那么每个页面都会有各自独立的会话存储对象的拷贝。

有的情况下,我们希望数据可以横跨多个页面,并在会话结束后依然持续有效。特别是当Web应用需要存储上兆字节的数据的时候,比如在本地保存一份完整的文档或保存用户的整个收件箱。我们为此设计了第二个机制。

同样的,cookies不适合处理这种问题,因为每个发出去的请求都会附带cookies中的数据。

localStorage特性的接口定义就是为访问本地存储区域而生的。

比如example.com可以在其网站的任何地方,使用下面这段代码告诉某个用户,他一共访问了多少次这个网站的页面。
<p>
  You have viewed this page
  <span id="count">an untold number of</span>
  time(s).
</p>
<script>
  if (!localStorage.pageLoadCount)
    localStorage.pageLoadCount = 0;
  localStorage.pageLoadCount = parseInt(localStorage.pageLoadCount) + 1;
  document.getElementById('count').textContent = localStorage.pageLoadCount;
</script>

每个网站的存储区域是相互独立的。

一致性要求

该规范中的所有的图表、实例和注意事项都是非规范性的,同时这也是全部的非规范性内容。也就是说,除此之外的内容都是规范性的。

规范性内容中的关键字:“必须”、“禁止”、“要求的”、“应该”、“不应该”、“推荐的”、“可以”、“可选的”都是RFC2119中的描述性解释(to be interpreted as described in RFC2119)。为了保障阅读的流畅性,该规范中对这些关键字不会做格式上的强调。#RFC2119

有算法要求的部分全部使用了“必须”、“应该”、“可以”等关键字进行描述。

部分一致性要求是对特性、方法或对象的要求,这些要求会被解释为用户代理(user agent)的要求。

算法和步骤方面的一致性要求可以通过任何方式进行实现,只要运行结果相同即可。(规范里的算法定义只是为了易于遵循,而非性能最优)

规范中唯一的一致性的类就是用户代理。

用户代理在实现上可以针对无约束的用户输入进行特殊限制。比如为了防御服务攻击、为了保护过多内存 被占用、为了在有局限性的平台上工作等。

依赖关系

该规范依赖于以下几个的基础规范

HTML
很多基础的HTML概念都在这个规范中。#HTML
WebIDL
本规范中有关接口定义(IDL)的内容皆遵循WebIDL规范的语法。#WEBIDL

术语

在“一个Foo对象”这句话中,Foo实际上是一个接口,更准确的描述应该是“一个实现Foo接口的对象”。

文中的DOM是指通过脚本操作Web应用的API集合,但并不代表DOM Core规范中定义的Document对象或任何Note等对象确实存在。#DOMCORE

一个接口定义语言特性就是getting时读取值,setting时写入值。

因为JavaScript一词更被人们熟知,所以我们用JavaScript代指ECMA262,而没有使用官方称谓ECMAScript。#ECMA262

API

Storage接口

interface Storage {
  readonly attribute unsigned long length;
  DOMString? key(unsigned long index);
  getter DOMString getItem(DOMString key);
  setter creator void setItem(DOMString key, DOMString value);
  deleter void removeItem(DOMString key);
  void clear();
};

每个Storage对象都提供键-值对式的访问方式。我们有时称之为条目。键和值都是字符串类型,包括空字符串在内的任何字符串都是有效的。

每个Storage都关联一个键-值对列表,该列表会由下面的章节sessionStorage特性和localStorage特性给出具体的定义。实现Storage接口的多个的独立对象可以同时关联相同的键-值对列表。

length特性必须返回与该对象相关联的那个列表中,当前键-值对的数量。

key(n)方法必须返回该列表中第n个条目的关键字。关键字的排列顺序由用户代理自行确定,但在条目数量不变的情况下必须保持固定。(因此,添加或删除一个关键字可能导致键的排列顺序发生变化,但是改变某个关键字所对应的值时则不会)如果n大于等于键-值对的总数量,那么此方法必须返回null。

Storage对象中支持的属性名包括了其相关联的键-值对列表中的每个关键字。

getItem(key)方法必须返回关联关键字key的当前值。如果该对象关联的列表中不存在关键字key,则必须返回null。

setItem(key, value)方法必须首先检查是否关键字key存在于相关联的列表中。

如果不存在,那么一个新的键-值对会被添加到列表中,其关键字为key,其对应的值为value

如果关键字key存在,那么必须将其对应的值更新为value

如果无法为其设置新的值,那么该方法必须抛出一个QuotaExceededError异常。(可能导致赋值失败的情况包括:用户关闭了该网站的存储功能、存储已被占满等)

removeItem(key)方法必须使得在相关联列表中,关键字为key的键-值对被移除。如果当前没有关键字为key的条目,则该方法不会做任何处理。

setItem()方法和removeItem()方法必须是单步操作,且对失败的操作友好。在失败的情形下,该方法不会做任何处理。也就是说,修改数据存储区域的行为要么成功,要么不造成任何改变。

clear()方法必须是单步操作,且将该对象相关联的键-值对列表清空。如果该列表本身就是空列表,则该方法不会做任何处理。

注:当setItem()方法、removeItem()方法和clear()方法被调用时,能够访问其相同数据的其它Document对象会触发相应的事件,该事件被下面的章节sessionStorage特性和localStorage特性所定义。
注:该规范并不要求上述方法在执行时等待数据被物理写入磁盘,仅通过脚本访问到的键-值对符合要求即可。

sessionStorage特性

[NoInterfaceObject]
interface WindowSessionStorage {
  readonly attribute Storage sessionStorage;
};
Window implements WindowSessionStorage;

sessionStorage特性代表了当前顶级浏览上下文指定的存储区域。

每个顶层浏览上下文都有唯一的一套会话存储区域,其中每个源有一个会话存储区域。

用户代理不得令浏览上下文的会话存储区域的数据过期,但是在用户请求删除区域的数据、或当用户代理检测到存储空间受到限制、或出于一些安全性的考虑,可以丢掉数据。用户代理应该始终避免在能够访问到数据的脚本运行时删除这些数据。当一个顶级浏览上下文被销毁时(并且永久无法被用户访问时),会话存储区域中的数据可以随之丢掉。同时本规范中的API也没有机会继续获取这些数据了。

注:浏览上下文的生命周期可以跟用户代理的实际进程没有关系,因为一些用户代理是可以在重启时恢复上一次的会话的。

当一个新的Document在一个顶级浏览上下文中被创建时,用户代理必须检查该顶级浏览上下文在当前文档的源下,是否具有会话存储区域。如果有,那么这个存储区域就是这个Document分配的会话存储区域;如果没有,则会为该文档的源创建一个新的存储区域,并作为该Document分配的会话存储区域。一个Document分配的会话存储区域在其生命周期内是不会改变的——甚至在一个嵌套的浏览上下文(比如iframe)被移动到另一个父浏览上下文的时候。

如果一个Document分配的会话存储区域存在,则sessionStorage特性必须返回一个关联着其存储区域的Storage对象,反之则返回null。每个Document对象必然以WindowsessionStorage特性的形式拥有一个相对独立的对象。

当一个顶级浏览上下文通过克隆已有的浏览上下文的方式被创建时,这个新的浏览上下文必须从该源的同一个会话存储区域开始。但是这两个集合必须基于这一点从此各自独立,不再相互产生任何影响。

当一个顶级浏览上下文通过在已有的浏览上下文中运行脚本、或点击一个已有浏览上下文的链接、或通过其它方式在跟某个特定的Document中被创建时,这个Document的这个源的会话存储区域会伴随新浏览上下文的创建而被复制入内。基于这一点,两个会话存储区域必须从此各自独立,不再互相产生任何影响。

setItem()方法、removeItem()方法和clear()方法被一个关联着某会话存储区域的Storage对象x所调用的时候,如果该方法令数据发生了改变,则对于除x之外的所有Document对象中关联着相同存储区域的Window对象的sessionStorage特性的Storage对象,均会触发storage事件,详见下面的描述。

localStorage特性

[NoInterfaceObject]
interface WindowLocalStorage {
  readonly attribute Storage localStorage;
};
Window implements WindowLocalStorage;

localStorage对象为每个源提供了一个Storage对象。

用户代理必须具有一个本地存储区域的集合,每个源占一个存储区域。

用户代理只能在出于安全性或用户要求的情况下丢掉过期。用户代理应该始终避免在能够访问到数据的脚本运行时删除这些数据。

localStorage特性被访问时,用户代理必须通过下列Storage对象初始化步骤

  1. 在返回一个Storage对象时,若该操作违背了某些决策(比如用户代理禁用当前页面的数据访问),则用户代理可以抛出SecurityError异常。
  2. 如果Document的源不是一个 访问协议/主机/端口 的组合,那么抛出SecurityError异常。并且终止这些步骤。
  3. 确认该用户代理是否已经为该被访问其特性的Window对象的Document所在的源分配了本地存储区域,如果尚未分配,则为这个源创建一个新的存储区域。
  4. 返回关联着该源的本地存储区域的Storage对象。每个Document对象的WindowlocalStorage特性必须具有相互独立的对象。

setItem()方法、removeItem()方法和clear()方法被一个关联着某本地存储区域的Storage对象x所调用的时候,如果该方法令数据发生了改变,则对于除x之外的所有Document对象中关联着相同存储区域的Window对象的localStorage特性的Storage对象,均会触发storage事件,详见下面的描述。

一个代表localStorage特性的Storage对象的属性不论被怎样检索、返回、设置或删除,是否作为直接属性访问,当确认属性是否存在时,当属性枚举时,当决定存在的属性数量时,或作为Storage接口定义的方法或特性的一部分执行时,用户代理必须首先获得存储的互斥锁。

安全性

无论一个Storage对象的任何成员在被localStorage特性访问时,如果运行该命令的脚本的有效源和提供访问localStorage特性的Window对象的Document的源不匹配时,用户代理必须抛出SecurityError异常。

注:换句话说,Storage对象在document.domain特性被使用时会部分失效。

storage事件

正如之前两个章节(会话存储和本地存储)描述的,当一个存储区域被改动时,就会触发storage事件。

当改动发生时,用户代理必须在每个有一个Storage对象被影响到了的Document对象的Window对象中排队触发一个名为storage的事件。该事件套用了StorageEvent接口,且无法冒泡,也无法取消。

注:注意:上述要求包含非活跃的Document对象,但这些事件会被循环事件忽略,直到Document被再次激活时才触发。

这个任务的任务来源是DOM操作的任务来源。(?)

如果事件是因调用setItem()方法或removeItem()方法而被触发的,那么该事件必须将其key特性初始化为该方法涉及的关键字;将其oldValue特性初始化为改动之前的值,如果该关键字是新创建的,那么就初始化为null;将其newValue特性初始化为改动之后的新的值,如果该关键字被删掉了,那么就初始化为null。

反之,如果事件是因点用clear()方法而被触发的,那么该事件必须将其keyoldValuenewValue特性均初始化为null。

另外,事件必须将其url特性初始化为被改动的Storage对象所在的文档的地址;将其storageArea特性初始化为目标DocumentWindow里同样代表这个被改动的Storage对象的Storage对象(比如会话的或本地的)。

事件定义

[Constructor(DOMString type, optional StorageEventInit eventInitDict)]
interface StorageEvent : Event {
  readonly attribute DOMString key;
  readonly attribute DOMString? oldValue;
  readonly attribute DOMString? newValue;
  readonly attribute DOMString url;
  readonly attribute Storage? storageArea;
};

dictionary StorageEventInit : EventInit {
  DOMString key;
  DOMString? oldValue;
  DOMString? newValue;
  DOMString url;
  Storage? storageArea;
};

key特性必须返回其初始化后的值。当对象被创建的时候,这个属性必须初始化为空字符串,它代表了被改动的关键字。

oldValue特性必须返回其初始化后的值。当对象被创建的时候,这个属性必须初始化为null,它代表了被改动的关键字在改动之前的值。

newValue特性必须返回其初始化后的值。当对象被创建的时候,这个属性必须初始化为null,它代表了被改动的关键字在改动之后的值。

url特性必须返回其初始化后的值。当对象被创建的时候,这个属性必须初始化为空字符串,它代表了被改动的文档的地址。

storageArea特性必须返回其初始化后的值。当对象被创建的时候,这个属性必须初始化为null,它代表了被影响的Storage对象。

线程

因为存储互斥锁的使用,本地存储区域可以在并行执行但却无法相互判断的多个浏览上下文的脚本中被同步访问。

因此,在一段脚本执行的同时,其不可预测的改变是不能发生在Storage对象的length特性及其每个变量的属性值上的。

磁盘空间

用户代理必须对允许存储区域所占据的本地空间进行限制。

用户代理应该防范一个网站的数据被存储在不同附属源下,比如a1.example.com、a2.example.com、a3.example.com的存储限制和example.com的存储限制是相互不制约的。

当配额达到时,用户代理可以经过用户的确认,允许用户授予这个网站更大的空间。举个例子,这可以使得用户可以在这个网站上创建更多的文档并存在用户的电脑中。

用户代理应该允许用户查看每个域名分别占用了多少存储空间。

我们推荐每个源的存储空间限制在5MB以内,并欢迎实施中的反馈,这些反馈将在未来用于改进这些建议。

隐私

用户追踪

第三方广告商(或任何可以获取内容并分发到多个站点的实体)为了高精准度的广告,都可以追踪用户在多个会话中的信息,然后建立一个档案,记录该用户的兴趣,并通过一个唯一标识存储在其本地存储区域中。一个站点结合了用户的真实身份(比如一个要求身份验证的电子商务网站),就可以比纯粹的匿名用法更精准的定位目标个体。

以下是一些可以用来降低用户追踪风险的技巧:

阻止三方存储
用户代理可以对访问localStorage对象的行为进行限制,只有跟顶级浏览上下文同域的脚本才能够访问,比如拒绝不同域iframe中的脚本访问其页面的API。
存储数据的过期时间
用户代理可以,通过用户的设置,在一段时间后自动删除已存储的数据。
比如,一个用户代理可以设置为把三方本地存储区域限制在当前会话内有效,用户一旦关闭所有的浏览上下文,那么数据就会被删除。
这可以限制一个站点追踪用户的能力,这样一个站点只能在用户通过该网站身份认证的情况下,才可能跨域多个会话追踪用户(比如购买或日志记录)。
尽管如此,作为一种长期存储机制,这样的做法降低了API的实用性。如果用户不完全理解数据过期的实现方式,那么这些数据就随时处于风险之中。
像对待cookies一样对待持久性存储
当用户尝试通过清除cookies但同时不清除本地存储区域,以此保护他们的隐私时,网站可以通过cookies和本地存储区域这两者之间相互恢复备份的策略进行阻止和防护。所以用户代理有必要向用户展示完整清除这些数据的界面,同时帮助用户理解。
特定于网站的访问本地存储区域的白名单
用户代理不需要限制网站访问会话存数区域,但是可以限制网站访问本地存储区域。
数据存储的起源追踪
用户代理可以记录哪些站点的源可以存储包括内容来自第三方源的数据。
如果这些信息可以用作展示当前永久性存储的视图,那么我们可以让用户决定哪些部分的永久性存储是可以被调整的。再结合黑名单的功能(“删除这些数据并且从此阻止该域名存储数据”),用户可以限制对他信任的站点的持久性存储的使用。
共享黑名单
用户代理可以允许用户共享他们的持久性存储的域名黑名单。
这将会促成大家共同交流,一起保护大家的隐私。

当然这些建议只是回避一些琐碎的用户追踪的情况。他们并不会完全阻止用户追踪。在一个域名下,一个网站还是可以在单个会话中持续追踪用户,并且可以通过任何站内获取的内容(姓名、信用卡号、地址)对用户身份进行识别,并向三方传递所有信息。如果一个三方合作希望跨越多个站点获取信息建立用户配置,同样可以做到。

尽管如此,用户追踪甚至在某种程度上不需要跟任何用户代理合作。比如通过URL进行会话识别,这个技术已经很常见了,并且无毒无害。这一信息也可以分享给其它网站,使用访问者的IP地址以及其它用户自定义的数据(比如用户代理的页头信息和配置信息),将相互独立的会话结合到同一个用户配置当中。

数据敏感度

用户代理应该对持久性存储的数据有潜在的敏感度。在这套机制下,它很可能会用来存储电子邮件、日历行程、健康记录、或其它机密档案。

最后,用户代理应该确保当删除数据的时候,可以迅速的把数据从底层删除。

安全

DNS欺骗攻击

因为潜在的DNS欺骗攻击,我们无法保证一个声明自己属于某个域名的主机是真正来自那个域名的。为了回避这个问题,页面可以使用安全传输层协议(TLS),使用了安全传输层协议的页面可以确定只有特定用户、以该用户的名义工作的软件、以及其它使用安全传输层协议且被认证为来自相同域名的页面,可以访问他们的存储区域。

跨目录攻击

享有同一个主机名的不同作者,比如内容承载在geocities.com上的所有用户,共享一个本地存储对象。没有通过路径进行访问限制的特性。因此不推荐共享主机上的作者们使用这一特性,它可能会使得其它作者读取或复写你的数据。

注:即使存在一个严格限制路径的特性,常用的DOM脚本安全模型依然可以绕过保护措施,通过任何路径访问数据。

实现风险

在实现这些永久性存储特性后,这两个风险使得恶意网站可以从其它域名读取信息并写入自己的域名。

使得三方网站读取到本不支持被他们读取的数据被称作信息泄露。比如一个用户的购物心愿单在另一个域名下可以被当做广告目标;或一个用户存储在某文字处理网站撰写的机密文档可以被竞争公司的网站查阅。

使得三方网站向其它域名的持久性存储区域写入数据可能导致信息欺骗,这是相当危险的。比如,一个恶意网站可以添加一件商品到用户的心愿单中;或一个恶意网站通过在会话中设置一个已知的用户身份ID来追踪用户在该受害网站的行为。

因此,严格遵循本规范的源模型描述对用户安全是非常重要的。

引用

所有引用如果未标明“非正式”字样的,都是正式的

COOKIES

HTTP State Management Mechanism, A. Barth. IETF. (英文)

DOMCORE

Web DOM Core, A. van Kesteren. W3C. (英文)

ECMA262

ECMAScript Language Specification. ECMA. (英文)

HTML

HTML, I. Hickson. WHATWG. (英文)

RFC2119

Key words for use in RFCs to Indicate Requirement Levels, S. Bradner. IETF. (英文)

WEBIDL

Web IDL, C. McCormack. W3C. (英文)

致谢

完整的致谢列表,请参阅 HTML 规范 #HTML