Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cookie规范(RFC 6265)翻译 #4

Open
renaesop opened this issue May 20, 2016 · 0 comments
Open

cookie规范(RFC 6265)翻译 #4

renaesop opened this issue May 20, 2016 · 0 comments

Comments

@renaesop
Copy link
Owner

renaesop commented May 20, 2016

RFC 6265 要点翻译

1.简介

本文档定义了HTTP Cookie以及HTTP头的Set-Cookie字段。通过使用Set-Cookie头,一个HTTP服务器可以传递name/value键值对以及相对应的元数据(所谓的cookies)到user agent。当user agent向服务器发送后续请求时,user agent会根据元数据和其他信息来决定是否要在Cookie头中返回name/value键值对。

虽然表面上看起来很简单, 但时cookies有很多复杂的地方。例如,服务器在向user agent发送cookie时,对每个cookie会设定一个作用域。 作用域制定了user agent回传cookie的规则:cookie需要回传的最大期限,需要回传cookie到哪些服务器,以及需要应用到哪些模式的URI上。

由于历史原因,cookie包含许多在安全性和隐私上不恰当的地方。例如,服务器可以指定一个给出的cookie字段需要“安全的”连接,但是安全属性并没有保证在存在网络中间人攻击时cookie的完整性。相似的是,给定host的cookies将会被这个host上的所有端口共享,尽管通常来说,浏览器所用的“同源策略”会将从不同端口上取回的东西孤立开来。

这份标准有两类受众:会生产cookie的web服务器的开发者,以及会消费cookie的user agent的开发者。

为了最大化在user agent中的通用性,web服务器在生成cookie时应该把他们自身限制为一个在第4章定义的良好的实现者。

User agent必须实现比第5章中定义的更加宽松的规则,以达到最大化和现有的不符合第4章定义的良好实现者的服务器的互通性。

这份文档说明了在互联网上经常被使用的头的句法和语义。特别地,这份文档并没有创造新的句法和语义。对cookie生成的推荐标准在第4章提供,表述了一些现有服务器行为的子集,在第5章中表述了一些今天并不被推荐的句法和语义,更加宽松的cookie处理算法。某些现存软件的实现和推荐的协议有一些重大的不同,这份文档也包含了一份解释这些不同的内容。

在这份文档之前,至少存在着三份不同的cookie描述:所谓的“Netscape cookie 标准”,RFC 2109, RFC2965。然而,这些文档都没有描述Cookie和Set-Cookie头是如何在互联网上被使用的。根据之前IETF的HTTP状态管理机制的标准,这份文档请求下列操作:

[]将RFC2109的状态改为Historic(已经被RFC2965废止)
[]将RFC2965的状态改为Historic
[]指定RFC2965已经被这份文档废止

特别的是,通过将RFC2965移到Historic并且将其废止,这份文档反对使用Cookie2和Set-Cookie2头。

2.约定

2.1. 一致标准

此处略去几百字

2.2. 句法注解

本文档使用扩充巴科斯范式(ABNF),在RFC5234中有注释。

下列在RFC5234中定义的核心规则被引用,ALPHA(字母),CR(回车),CRLF(CR LF),CTLs(控制字符),DIGIT(数字0-9),DQUOTE(双引号),HEXDIG(十六进制元素 0-9/A-F/a-f),LF(换行符),NUL(空八比特),OCTET(除了NUL以外的所有八比特串),SP(空格),HTAB(水平制表符),CHAR(任意ascii码字符),VCHAR(任意可见的ascii码字符),以及WSP(空白符)。

OWS(可选空白符)规则被用在0个或更多的线性空白符可能出现的场合:

OWS = *([obs-fold]WSP)

obs-fold = CRLF

OWS应该要么不产生要么就产生为一个单独的SP字符。

2.3. 术语

下列术语:user agent,client,server,proxy,origin server(源服务器)和HTTP/1.1标准(RFC2616,1.3节)中的含义相同。

术语request-host是指host的名字,也就是已经被user agent所知的,对user agent来说发送HTTP请求的目的地,或者接收HTTP响应的来源。(也就是发送相应的HTTP请求的host名字)。

术语request-uri已经在RFC2616的5.1.2节中定义。

两个八比特的序列被称为大小写不敏感地相同,当且仅当他们在RFC4790中定义的大小写映射关系被满足时成立。

术语字符串意思是一个非NUL的八位bit序列。

3.概述

这一节概括了一种源服务器将状态信息传递给user agent的方式,也包含了一种user agent将状态信息回传服务器的方式。

为了存储状态,源服务器在HTTP响应中包含了一个Set-Cookie头。在后续的请求中,user agent将回传一个Cookie请求头到源服务器。Cookie头包含了user agent在前面Set-Cookie头中包含的cookie。源服务器可以选择忽略Cookie头或将Cookie用于应用所定义的目的。

源服务器可以在任何响应中发送Set-Cookie响应头。user agent可以在响应码为1xx的请求中忽略Set-Cookie,但必须在除此以外的任何种类响应中处理Set-Cookie(包括响应码为4xx和5xx的响应)。源服务器可以在单个请求的响应中包含多个Set-Cookie字段。Cookie或者Set-Cookie的出现不会阻止存储和复用HTTP请求的缓存。

源服务器不应该把多个Set-Cookie字段打包到单个HTTP头中。通常打包HTTP头的字段可能会更改Set-Cookie字段的语义,因为%x2c(",")字符被Set-Cookie使用,从而在这种打包方式中存在冲突。

3.1. 示例

使用Set-Cookie头,服务器可以向在一个HTTP响应中user agent发送一条短字符串,这条字符串会在未来符合cookie作用域的HTTP请求中回传给服务器。例如,服务器可以给user agent发送一个名叫“SID”的“session标识符”,值为 31d4d96e407aad42。user agent会在后续的请求中回传这个session标识符以及其值。

== Server -> User Agent ==

Set-Cookie: SID=31d4d96e407aad42

== User Agent -> Server ==

Cookie: SID=31d4d96e407aad42

服务器可以使用Path和Domain属性变更cookie的作用域。例如,服务器可以委托user agent在每个path每个example.com的子域都返回cookie。

 == Server -> User Agent ==

Set-Cookie:SID=31d4d96e407aad42;Path=/;Domain=example.com

 == User Agent -> Server ==

Cookie: SID=31d4d96e407aad42

就如下一个例子中展示的那样,服务器可以在user agent中存储多个cookie。例如,服务器可以通过返回两个Set-Cookie字段,实现既存储一个session标识符,又存储用户的偏好语言。值得注意的是,服务器用Secure和HttpOnly属性来对更加敏感的session标识符提供额外的安全保护(见4.1.2.)

 == Server -> User Agent ==

Set-Cookie: SID=31d4d96e407aad42; Path=/; Secure; HttpOnly
Set-Cookie: lang=en-US; Path=/; Domain=example.com

 == User Agent -> Server ==

Cookie: SID=31d4d96e407aad42; lang=en-US

注意上面的Cookie头包含了两个cookie,一个名叫SID,另一个为lang。如果服务器希望cookie在user agent的多个“会话“(sessions,例如,user agent重启之后)中持续存在,服务器可以在Expires属性中指定一个过期时间。注意,如果user agent的cookie存储超过它的定额或者用户手动删除了cookie的话,user agent可能会在过期时间到达之前删除cookie。

 == Server -> User Agent ==

Set-Cookie: lang=en-US; Expires=Wed, 09 Jun 2021 10:18:14 GMT

 == User Agent -> Server ==

Cookie: SID=31d4d96e407aad42; lang=en-US

最后,为了移除一个cookie,服务器要返回一个把过期时间设置在过去的Set-Cookie字段。服务器只有在Set-Cookie头中Path和Domain属性与创建cookie时相符时,才能成功删除cookie。

== Server -> User Agent ==

Set-Cookie: lang=; Expires=Sun, 06 Nov 1994 08:49:37 GMT

== User Agent -> Server ==

Cookie: SID=31d4d96e407aad42

4. 服务端的要求

本节描述了“表现良好”的Cookie和Set-Cookie头的句法和语义。

4.1. Set-Cookie

HTTP响应头中的Set-Cookie被用于从服务器向user agent发送cookie。

4.1.1 句法

不正式地说,Set-Cookie响应头包含了名字叫做“Set-Cookie”并跟着的一个“:”以及一个cookie。每个cookie由一个name-value键值对打头,后面跟着0个或者多个attribute-value键值对。服务器不应该发送未能遵从下列语法的Set-Cookie头。

 set-cookie-header = "Set-Cookie:" SP set-cookie-string  
                    ;Set-Cookie: 之后必须有空格,空格之后才是  
                    ;具体的set-cookie-string    

 set-cookie-string = cookie-pair *( ";" SP cookie-av )  
                    ;cookie-pair以及每个cookie-av  
                    ;之间分隔符都是";" SP(也就是分号加空格)  

 cookie-pair       = cookie-name "=" cookie-value   

 cookie-name       = token  
                    ;token表示的是除了分隔符和CTLs以外的ASCII字符  
                    ;分隔符包括:  
                    ;小中大尖括号 "("|")"|"[]"|"]"|"{"|"}"|"<"|">"  
                    ;空格和水平制表符 SP | HT  
                    ;逗号分号冒号引号问号等号 ","|";"|":"|"?"|"="|"\""  
                    ;斜线: "\" | "/"  
                    ;@: "@"  

 cookie-value      = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )

 cookie-octet      = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E  
                       ; ASCII字符中除了CTRL(控制字符, 空白符  
                       ; 双引号, 逗号, 分号, 反斜线(\)  

 token             = <token, defined in [RFC2616], Section 2.2>  

 cookie-av         = expires-av / max-age-av / domain-av /  
                     path-av / secure-av / httponly-av /  
                     extension-av  

 expires-av        = "Expires=" sane-cookie-date  

 sane-cookie-date  = <rfc1123-date, defined in [RFC2616], Section 3.3.1>  

 max-age-av        = "Max-Age=" non-zero-digit *DIGIT  
                       ; In practice, both expires-av and max-age-av  
                       ; are limited to dates representable by the  
                       ; user agent.  

 non-zero-digit    = %x31-39  
                       ; digits 1 through 9  

 domain-av         = "Domain=" domain-value  

 domain-value      = <subdomain>  
                       ; defined in [RFC1034], Section 3.5, as  
                       ; enhanced by [RFC1123], Section 2.1  

 path-av           = "Path=" path-value  

 path-value        = <any CHAR except CTLs or ";">  

 secure-av         = "Secure"  

 httponly-av       = "HttpOnly"  

 extension-av      = <any CHAR except CTLs or ";">  

注意上面的参考文档中某些条目使用了一些和这份文档(ABNF,RFC5234)不同的语法标记。

这份文档没有定义任何关于cookie-value的语义。

为了最大化和user agent的兼容性,服务器在要使用一些任意的数据作为cookie-value时,应该将数据编码,例如使用Base64(RFC4648)。

set-cookie-string中由cookie-av项贡献的部分是被大家熟知的属性。为了最大化和user agent的兼容性,服务器不应该产生在set-cookie-string中有两个属性相同的名字的cookie。(关于user agent如何处理这种情况,见5.3节)

服务器不应该在同一个响应中包含超过一个具有相同cookie-name的Set-Cookie字段。(关于如何处理这种情况,见5.2节)

如果一个服务器向user agent并发地发送了多条包含Set-Cookie头的响应(例如,当在多个sockets上和user agent通信时),这些响应将会创造一个“竞争条件”,最终会导致不可预期的后果。

注意:一些现有的user agent对两位数的年份有不同的处理。为了避免兼容性问题,服务器应该使用要求四位数年份的RFC1123定义的日期格式。

注意:一些user agent会用32位的UNIX time_t来存储和处理日期。time_t相关的库的bug可能会导致这些user agent在2038年之后错误地处理日期。

4.1.2. 句法(非正式)

本节描述了简化的关于Set-Cookie头的语义。这些语义对于要理解最常见的服务器上cookie用法已经足够详细。全部地语义在第5节描述。

当user agent接收到一个Set-Cookie头时,user agent会将cookie及其属性一起存储。随后,当user agent发起HTTP请求时,user agent会在Cookie头中包含合适的并且没有过期的cookie。

如果user agent接收到了一个和某个现有cookie的cookie-name、domain-value和path-value都相同的新cookie,现有的那个cookie将会被驱逐,取而代之的是那个新cookie。注意服务器可以通过向user agent发送一个拥有值为过去某一时刻的Expires属性的新cookie,来删除一个cookie。

除非cookie的属性额外指定,cookie将只会回传到源服务器(例如,不会回传到任何子域上),并且cookie将会在当前会话结束时过期(会话由user agent自己定义)。user agent会忽略未被识别的cookie属性(但不会忽略整个cookie)。

4.1.2.1. Expires属性

Expires属性指明了cookie的最大生命周期,形式为cookie过期的时刻。user agent并不被要求在设定的时间之前保留cookie。实际上,user agent经常由于存储压力或者隐私上的考虑驱逐了cookie。

4.1.2.2. Max-Age属性

Max-Age属性指明了cookie的最大生命周期,形式为cookie过期之前的具体秒数。user agent并不被要求在这段指定的时长内保留cookie。实际上,user agent经常由于存储压力或者隐私上的考虑驱逐了cookie。

注意:某些现有的user agent并不支持Max-Age属性。不支持Max-Age属性的user agent将会直接忽略。

如果cookie既有Max-Age也有Expires属性,Max-Age属性将会有更高的优先级,并且控制cookie的过期时间。如果一个cookie既没有Max-Age也没有Expires属性,user agent将会在本次会话(会话由user agent定义)结束之前保留这个cookie。

4.1.2.3. Domain属性

Domain属性指明了cookie会被发送到哪些host。例如,如果某cookie的Domain属性的值为"example.com",user agent将会在向example.com,www.example.com以及www.corp.example.com(注意,最前面的%x2E("."), 如果出现,将会被忽略,尽管它除了出现在末尾以外都是非法的)发送HTTP请求时,在Cookie头中包含该cookie。如果服务器漏掉了这个Domain属性,user agent只会向源服务器返回cookie。

警告:某些现存的user agent会将不存在Domain属性时,错误地假设为Domain属性存在,并且值为当前的host name。例如,如果example.com返回了一个没有Domain属性的Set-Cookie头,这些user agent将会错误地也向www.example.com发送cookie。

user agent将会拒绝cookie,除非Domain属性为cookie指定的作用域会包含源服务器。例如,user agent将会接受一段来自“foo.example.com”的Domain属性为"example.com"或者"foo.example.com"的cookie,但是user agent不会接受Domain属性为"bas.example.com"或者"baz.foo.example.com"的cookie。

注意:出于安全的原因,许多user agent被设定为拒绝Domain属性对应为"公共结尾"的cookie。例如,一些user agent将会拒绝Domain属性为"com"或"co.uk"等。(详见5.3节)

4.1.2.4. Path属性

每个cookie的作用域被限定到了由path组成集合中,由Path属性控制。如果服务器没有提供Path属性,user agent将会使用当前的require-uri中path元素的“目录”作为默认值(更多细节详见5.1.4节)

user agent会在一次HTTP请求中包含该cookie,条件是require-uri中路径的部分匹配Path属性(或者是Path的子目录),其中%x2F("/")被解释为路径分隔符。

虽然这看起来对分隔同一host中不同路径的cookie十分实用,但是Path属性不能作安全的凭据。(见第8节)

4.1.2.5.Secure属性

Secure属性将cookie的作用域限定到“安全的”传输途径(“安全的”是有user agent所定义的)。当一个cookie拥有Secure属性时,user agent只有在请求是从一个安全的传输途径(典型的是以TLS方式传输HTTP,也就是HTTPS,RFC2818)传输时,才会发送该cookie。

尽管这看起来对保护cookie以免受中间人攻击很有用,但是Secure属性只在机密性上保护了cookie。一个网络中间攻击者可以通过非安全的传输途径覆盖该cookie,从而破坏其完整性。(详见8.6节)

4.1.2.6. HttpOnly属性

HttpOnly属性将cookie的作用域限制到HTTP请求中。尤其是,这个属性委托user agent在提供非HTTP方式访问cookie时(例如,浏览器提供给脚本访问cookie的接口),忽略该cookie。

4.2. Cookie

4.2.1. 句法

user agent会将存储的cookie放在Cookie头中发送给源服务器。如果服务器遵从4.1中的要求(并且user agent遵从第5节的要求),user agent将会发送符合下述语法的Cookie头部:

cookie-header = "Cookie:" OWS cookie-string OWS  

cookie-string = cookie-pair *( ";" SP cookie-pair )  

4.2.2. 语义

每个cookie键值对都表述了一个被user agent保存的cookie。cookie键值对包括从Set-Cookie头中接收到的的cookie-name和cookie-value。

注意cookie的属性没有被返回。尤其是,服务器不能单靠Cookie头部就能确定,什么时候cookie会过期,cookie对哪些host有效,对什么路径有效,还有cookie是否设置了Secure或者HttpOnly属性。

每个单独的cookie的语义没有在该文档中定义。服务器被期望以应用相关地语义来填充cookie。

虽然cookie在Cookie头中被线性地序列化,但是服务器不应该依赖序列化的顺序。尤其是,当Cookie头中包含了两个具有相同名字的cookie时(例如,被设置成不同Path或者Domain属性但拥有相同名字的cookie),服务器不应该依赖这些cookie在头部中出现的顺序。

5. User Agent的要求

本节用具体地细节说明了Cookie和Set-Cookie头部,使得实现这些要求的user agent可以和现存的服务器(即使那些不满足第4节中要求的)进行互操作。

5.1. 子元素算法

本节定义了user agent所用的用于处理Cookie和Set-Cookie头部子元素得一些算法。

5.1.1. 日期

user agent必须使用一个和下述算法等价的算法来实现解析cookie-date。注意下述的各种被定义为算法一部分的boolean-flag(例如,found-time,found-day-of-month, found-month,found-year)最初状态是“没有设定”)。

1.使用下述算法,将cookie-date分割成date-token

cookie-date     = *delimiter date-token-list *delimiter  
date-token-list = date-token *( 1*delimiter date-token )  
                  ;date-token由终结符分隔  
date-token      = 1*non-delimiter  

delimiter       = %x09 / %x20-2F / %x3B-40 / %x5B-60 / %x7B-7E  

non-delimiter   = %x00-08 / %x0A-1F / DIGIT / ":" / ALPHA /%x7F-FF  
                  ;此处的非终结符指的就是非ascii,以及ascii码中的不可见字符  
                  ;以及字母和数字,以及:  
                  ;也就是说非终结符在可见ascii字符中只包括数字字母和冒号  

non-digit       = %x00-2F / %x3A-FF  

day-of-month    = 1*2DIGIT ( non-digit *OCTET )  
month           = ( "jan" / "feb" / "mar" / "apr" /  
                       "may" / "jun" / "jul" / "aug" /  
                       "sep" / "oct" / "nov" / "dec" ) *OCTET  

year            = 2*4DIGIT ( non-digit *OCTET )  
time            = hms-time ( non-digit *OCTET )   
                ;可以看出时间之后必须要有一个数字来分隔  
                ;但是再往后是什么都无所谓了  
hms-time        = time-field ":" time-field ":" time-field  
time-field      = 1*2DIGIT  

2.按照下述顺序出列每个在cookie-date中出现的token:

1. 如果found-time的flag还没有设定并且token匹配了time产生式,设定found-time的flag,并且相应地  设定hour、minute、second为token所表示的值。跳过下面剩余的步骤,并继续处理下一个  date-token。  
2. 如果found-day-of-month的flag还没有被设定,并且token匹配了day-of-month的产生式,设定  found-day-of-month的flag,并且相应地设定day-of-month为token所表示的值。跳过下面剩余的步骤,  并继续处理下一个date-token。
3. 如果found-month的flag还没有被设定,并且token匹配了month的产生式,设定found-month的flag,  并且相应地设定month为token所表示的值。跳过下面剩余的步骤,并继续处理下一个date-token。  
4. 如果found-year的flag还没有被设定,并且token匹配了year的产生式,设定found-year的flag,  并且相应地设定year为token所表示的值。跳过下面剩余的步骤,并继续处理下一个date-token。

3.如果year的值大于70且小于等于99,在year的值的基础上增加1900.

4.如果year的值大于0且小于等于69,在year值得基础上增加2000.

  1. 注意:某些现有的user agent会用不同的方式解析两位数的年份。

5.在下列情况中,终止这些步骤并且解析cookie-date以失败告终:

[*] 至少在found-day-of-month,found-month,found-year,或者found-time的flag中有一个没有设定

[*] day-of-month的值小于1或者大于31

[*] year的值小于1601

[*] hour的值大于23

[*] minute的值大于59

[*] second的值大于59

(注意在这个句法中闰秒不能被体现)

6.将解析出的cookie-date转换为UTC时间。如果这个日期不存在,终止算法并且以失败告终。

7.将转换出的时间作为算法的结果返回。

5.1.1. 规范化host名

一个规范的host名是通过下述算法生成的字符串:

1. 将host名转换为独立域名标签序列.  
2. 将每个不属于LDH非保留字标签的标签,转换为A标签详见[RFC5890] 2.3.2.1),或者转换为“punycode 标签”(RFC3490的第四节所定义的“TOASCII”方法).  
3. 将最终的标签序列拼接,中间以%x2E(".")分隔.  

5.1.3. Domain匹配

只有在满足下列条件之一时:

A. domain 字符串和给定的字符串相同。(注意两者会被规范化为
小写之后进行比较)  
B. 当满足下列所有条件时:  
    [*] domain字符串是给定字符串的一个尾部子串  
    [*] 给定字符串不包含在domain字符串中的最后一位字符是%X2E(".")  
    [*] 该字符串是一个host name(也就是说,不是一个ip地址)

给定的字符串才匹配给定的domain字符串的domain。

注意此处Domain是指hostname,是不包含端口号的

5.1.4. 路径和路径匹配

user agent必须使用和下述算法等价的算法来计算cookie的默认路径:

1. 将uri-path设定为require-uri中的path部分,如果path不存在, 则设为空。  
例如,如果request-uri刚好包含了path(以及可选的query string),那么  
uri-path就是该path(不包括%X3F("?")字符或者query string),而当request-uri  
包含一个完整的绝对路径时,uri-path就是URI的path元素。  

2. 如果uri-path为空,或者uri-path的第一位不是%X2F("/")字符,输出%x2F并且  
跳过剩余的步骤。  

3. 如果uri-path只包含不超过一个%x2F("/"),输出%x2F并且  
跳过剩余的步骤。  

4. 输出uri-path从左边数第一位到最后一个%x2F("/")之间的字符串,但是结果不  
包含最后一个%x2F("/")

至少满足下面的一个条件时,request-path才匹配cookie-path的路径:

[*] cookie-path和request-path相等  
[*] cookie-path是request-path的前缀,并且cookie-path的最后一位是%x2F("/")  
[*] cookie-path是request-path的前缀,并且第一个不包含在cookie-path的
字符是%x2F("/")

5.2. Set-Cookie头

当user agent在一次HTTP响应中接收到一个Set-Cookie字段时,user agent可能会完全会略Set-Cookie字段。例如,user agent可能希望禁止“第三方”cookie(见7.3节)。

如果user agent没有完全忽略Set-Cookie字段,那么user agent必须解析将Set-Cookie头中每个字段的值作为cookie-string解析。(下面将定义)

注意:下述算法比4.1节中的语法更加宽松。例如,该算法会将cookie的name-value的头尾空白符
移除(但保留中间的空白符),不过4.1节中的语法禁止了这些地方的空白符。user agent使用这个算法来和哪些不符合第四节中的推荐的服务器进行互操作。

user agent必须使用和下面所述的算法等价的算法来解析set-cookie-string:

1. 如果set-cookie-string包含%x3B(";"), 那么name-value-pair由set-cookie-string  
开头到第一个%x3B(“;”)组成,但不包含%x3B,并且unparsed-attributes由剩下的set-cookie-string  组成(包括%x3B); 否则,name-value-pair就是set-cookie-string,并且  
unparsed-attributes时空串。  

2. 如果name-value-pair缺少%x3D("=")字符,那么完全忽略set-cookie-string。      
3. name字符串(可能为空)由name-value-pair的开头,到第一个%x3D("=")组成,但不包含%x3D,  同时value(可能为空)由第一个%x3D之后的所有字符组成。    

4. 移除name和value的开头和结尾处的所有WSP(空白符)。    

5. 如果name为空,完全忽略set-cookie-string。    

6. cookie-name就是name字符串,cookie-value就是value字符串。  

user agent必须使用和下面所述的算法等价的算法来解析unparsed-attributes:

1. 如果unparsed-attributes为空,跳过剩下的这些步骤。    

2. 丢弃unparsed-attributes的第一个字符串(也就是%x3B)。    

3. 如果剩下的unparsed-attributes中包含%x3B(“;”),那么消耗从开头到第一个%x3B  
的字符串(但不包含);否则,消耗剩下的所有unparsed-attributes。之后,令  
这个步骤中消耗的字符串为cookie-av。  
4. 如果cookie-av包含一个%x3D(“=”),那么attribute-name(可能为空)由开头  
到第一个%x3D组成,并且不包含%x3D,同时attribute-name(可能为空)为第一个  
%x3D之后的字符串;否则,attribute-name的值为整个cookie-av,并且attribute-value  
为空。  

5. 移除attribute-name和attribute-value的首尾空格。    

6. 根据下面子章节所述的要求解析attribute-name和attribute-value(注意,未被  
识别的attribute-name将会被忽略)。  

7. 回到算法的第一步。

当user agent完成对set-cookie-string的解析时,就说user agent从require-uri中获取到了
名字为cookie-name,值为cookie-value,以及属性为cookie-attribute-list的cookie。(由接收到cookie所触发的额外的要求详见5.3节)

5.2.1. Expires属性

如果attribute-name大小写不敏感地匹配了字符串“Expires”,user agent必须按照下列步骤
处理cookie-av。

令expiry-time的值为将attribute-value按cookie-date(见5.1.1节)解析后的值。

如果attribute-value没有成功地解析成一个cookie date,那么忽略这个cookie-av。

如果expiry-time晚于user agent可以表达的最晚的时间,user agent可以将expiry-time
替换为user agent可以表达的最晚的时间。

如果expiry-time早于user agent可以表达的最早的时间,user agent可以将expiry-time
替换为能表达的最早的时间。

向cookie-attribute-list追加一个属性名为Expires,属性值为expiry-time
的属性。

5.2.2. Max-Age属性

如果attribute-name大小写不敏感地匹配了字符串“Max-Age”,user agent必须按照下列步骤
处理cookie-av。

如果attribute-value的第一个字符不是DIGIT(数字)或者“-”,那么忽略cookie-av。

如果attribute-value剩余的字符中包含一个非DIGIT(非数字)字符,那么忽略这个cookie-av。

令delta-seconds为attribute-value转换为整数之后的值。

如果delta-seconds小于或等于0,令expiry-time为最早可以表达的日期和时间。否则,
令expiry-time为当前的日期和时间加上delta-seconds的秒数。

向cookie-attribute-list追加一个属性名为Max-Age,属性值为expiry-time
的属性。

5.2.3. Domain属性

如果attribute-name大小写不敏感地匹配了字符串“Domain”,user agent必须按照下列步骤
处理cookie-av。

如果attribute-value是空值,那么行为是未知的。然而,user agent应该忽略整条cookie-av。

如果attribute-value的第一个字符是%x2E("."),令cookie-domain为除去attribute-value第一个%x2E(".")之后的值;否则,令cookie-domain的值为整个attribute-value。

将cookie-domain转换为小写。

向cookie-attribute-list追加一个属性名为Domain,属性值为cookie-domain
的属性。

5.2.4. Path属性

如果attribute-name大小写不敏感地匹配了字符串“Path”,user agent必须按照下列步骤
处理cookie-av。

如果attribute-value时空值或者attribute-value的第一个字符不是%x2F(“/”),
那么令cookie-path为默认路径;否则,令cookie-path为整个attribute-value。

向cookie-attribute-list追加一个属性名为Path,属性值为cookie-path
的属性。

5.2.5. Secure属性

如果attribute-name大小写不敏感地匹配了字符串“Secure”,user agent必须向cookie-attribute-list追加一个属性名为Secure,属性值为空的属性。

5.2.5. HttpOnly属性

如果attribute-name大小写不敏感地匹配了字符串“HttpOnly”,user agent必须向cookie-attribute-list追加一个属性名为HttpOnly,属性值为空的属性。

5.3. 存储模型

user agent的每个cookie会存储下列所述的字段:name,value,expiry-time,domain,
path,creation-time,last-access-time,persistent-flag,host-only-flag,
secure-only-flag,以及http-only-flag。

当user agent从一个request-uri接受了一个拥有名叫cookie-name,值为cookie-value,
以及属性为cookie-attribute-list的cookie时,user agent必须像下面这样处理cookie:

1. user agent可能完全忽略某个接收到的cookie。例如,user agent可能希望禁止掉来自  
第三方的cookie,或者user agent不希望存储超过某个大小的cookie。   

2. 新建一个名字为cookie-name,值为cookie-value的新cookie。将creation-time和  
last-access-time设定为当前日期和时间。  

3. 如果cookie-attribute-list包含一个属性名为"Max-Age"的属性,那么将cookie  
的presistent-flag设为true,将cookie的expiry-time设定为cookie-attribute-list中  
最后一个属性名为Max-Age的属性的属性值; 

否则,如果cookie-attribute-list中包含一个属性名为“Expires”的属性  (并且不包含“Max-Age”属性) ,那么将cookie的presistent-flag设定为true,并且将cookie的expiry-time  
设定为cookie-attribute-list中最后一个属性名为Expires的属性值;    

否则,将cookie的presistent-flag属性设为false,并且将cookie的expiry-time设定为  
能表达的最远的日期。  

4. 如果cookie-attribute-list包含一个属性名为“Domain”的属性,令domain-attribute  
为cookie-attribute-list中最后一个属性名为Domain的属性值;否则,令domain-attribute为空串。    

5. 如果user agent被配置为拒绝“公共后缀”,并且domain-attribute的值为某个  
公共后缀时:如果domain-attribute和规范化后的request-host相同的话,令domain-  
attribute属性为空串;否则,忽略整个cookie病跳过这些步骤。  

> 注意: “公共后缀”是一个由公开注册机构控制的域名,比如说“com”,"co.uk",以及
> "pvt.k12.wy.us"。一个步骤对于预防从attacker.com,通过设置一个Domain属性
> 为"com"的cookie,来破坏example.com的cookie完整性很重要。不幸的是,
> 公共后缀(著称的注册商控制的域名)的集合随时间发生着变化。如果可能的话,user agent
> 应该用一份最新的公共后缀列表,例如有Mozillas维护的一份列表(https://publicsuffix.org/)。  

6. 如果domain-attribute非空:如果规范化之后的request-host不匹配domain-attribute  
中的域名,那么完全忽略掉cookie并且终止这些步骤;否则,将cookie的host-only-flag  
设定为false,并且将cookie的domain设定为domain-attribute。    
否则:将cookie的host-only-flag设定为true,并且将domain设定为规范化之后的request-host。    

7. 如果cookie-attribute-list包含一个属性名为“Path”的属性,将cookie的path  
属性设定为cookie-attribute-list中最后一个属性名为“path”的属性值;否则,将  
cookie的path设定为request-uri的默认路径。(5.1.4节)    

8. 如果cookie-attribute-list包含一个属性名为“Secure”的属性时,将cookie的  
Secure-only-flag设定为true;否则,设为false。    

9. 如果cookie-attribute-list包含一个属性名为“HttpOnly”的属性时,将cookie的  
http-only-flag设定为true;否则,设为false。  

10. 如果cookie是从非HTTP的API传入的,并且设定了cookie的http-only-flag,那么  
终止这些步骤并且完全忽略该cookie。  

11. 如果cookie的存储中已经包含了一个和新建的cookie的name、domain、path  
都相同的cookie,那么:a. 令old-cookie为现存的domain、name、path都相同的  
cookie(注意这个算法保持了至多只有一个这样cookie的不变性);b. 如果新创建的  
cookie是从一个非HTTP的API接收到的并且old-cookie的http-only-flag已经设定,  
那么终止这些步骤并且完全忽略这个新创建的cookie;c. 将新创建的cookie的creation-time  
更新为old-cookie的creation-time;d. 将old-cookie从cookie 存储中移除。    

12. 将新创建的cookie插入到cookie存储中。

当cookie的过期时间是在过去时,这个cookie就是“过期的”。

user agent必须从cookie存储中清除掉所有的过期cookie,条件为在任何时刻,cookie
存储中存在一个过期的cookie时。

在任何时刻,user agent都可能会从cookie存储中“移除过量的cookie”,条件为共享
同一个domain字段的cookie的数量超过了预设的边界值(例如50个cookie)。

在任何时刻,user agent都可能会从cookie存储中“移除过量的cookie”,条件为所有cookie的总数量超过了预设的边界值(例如3000个cookie)。

当user agent从cookie存储中移除过量的cookie时,user agent必须按下面的优先级
清除cookie:

  1. 过期的cookie
  2. 共享一个Domain的cookie数目超过其他cookie预设的一个数量(?)时
  3. 所有的cookie

如果两个cookie具有相同的移除优先级,那么user agent必须先移除last-access
更早的那个cookie。

当“当前会话结束”(由user agent定义)时,user agent必须从cookie存储中
移除所有persisitent-flag属性为false的cookie。

5.4. Cookie头

user agent将会在HTTP请求的Cookie头中包含存储的cookie。

当user agent产生一个HTTP请求时,user agent禁止产生多余一个的Cookie头字段。

user agent可能会在HTTP中删除Cookie头部。例如,user agent可能希望禁止
发送从第三方请求中获取到的cookie。(见7.1节)

如果user agnet已经把一个Cookie头部字段添加到HTTP头部中,user agent必须把cookie-string(由下面定义)当做这个头部字段的值发送。

user agent必须用一个等价于下面所述的算法来从cookie存储中结合request-uri,来
计算“cookie-string”:

1. 令cookie-list为cookie存储中符合下面所有要求的cookie的集合:
    [*] 要么,cookie的host-only-flag为true,并且规范化后的request-host和
        cookie的域名相同;或者,cookie的host-only-flag为false,并且规范化后
        的request-host和cookie的domain匹配;  
    [*] request-uri的path部分和cookie的path部分相匹配;  
    [*] 如果cookie的secure-only-flag为true, 那么request-uri的scheme
    必须表示一个“安全的”协议(由user agent定义)。  
    > 注意:“安全的”协议不是由本文档定义的。典型的情况是,user agent会将使用
    安全传输的协议认为是安全的,比如SSL或者TLS。例如,大多user agent会将“https”
    看作是表示安全协议的scheme。  
    [*] 如果cookie的http-only-flag被设定为true,那么这个cookie将会在为“非HTTP”
    接口(由user agent定义)生产cookie-string时被忽略。   

2. user agent应该按照下列顺序对cookie-list进行排序:
    [*] 拥有更长的path的cookie将会排在拥有更短的path的cookie前面。  
    [*] 当cookie拥有相同的path字段长度时,拥有更早creation-time的cookie将会
    被排在更晚creation-time的cookie前面。  
    > 注意:不是所有的user agent都按这个吮吸排列cookie-list,但是这个顺序
    反应了在撰写本文档时以及历史上,现有的服务器(错误地)所依赖的顺序的经验。    

 3. 将cookie-list中每个cookie的last-access-time更新为当前的时刻。  

 4. 将cookie-list序列化为一个cookie-string,需要按照下列顺序处理每个cookie:
    1. 输出cookie的名字,%x3D("=")字符,以及cookie的值.
    2. 如果在cookie-list中还存在未处理的cookie,输出字符%x3B以及%x20("; ").
 >注意:除了它的名字,cookie-string其实是一个八bit序列,而不是字符序列。为了将
 cookie-string(或者其中的元素)转换为字符序列(例如,为了展示给用户),user agent
 可能希望尝试使用UTF-8编码(RFC3629)来解码这个八bit序列。但是,这个解码可能会失败,因为
 不是所有的八bit序列都是合法的UTF-8.

6. 实现上的考虑

6.1. 限制

实际的user agtn实现在他们能存储的cookie的数目和大小上有限制。常见的user agent
应该提供下述的最小容量:
[] 每个cookie至少4096bytes(以cookie的name,value和attribute之和衡量)
[
] 每个domain至少50个cookie
[*] 至少总共3000个cookie

服务器应该使用尽可能小和尽可能小的cookie来防止达到这些实现上的限制,以及最小化
网络带宽的需求,因为Cookie头会在每一个请求中都被包含。

如果user agent未能在Cookie头中返回一个或者多个cookie,服务器应该优雅地处理
这种情况,因为user agent随时可能会按照用户的要求移除任何cookie。

6.2. API

Cookie和Set-Cookie头使用一个这么难懂的句法的原因是,许多平台(包括服务器和
user agent)都提供了一个基于字符串的有关cookie的API,这就要求应用层的开发者
自己生成和解析Cookie和Set-Cookie头,这导致许多程序员错误地实现了,最终导致
不兼容问题。

为了替代提供基于字符串的cookie相关API的做法,平台最好提供更加语义化的API。推荐
详细的API设计超过了本文档的范畴,但是显然接收一个抽象的"Date"对象而不是一段序列化的
date string会有清晰的好处。

6.3. IDNA依赖与迁移

IDNA2008(RFC5890)取代了IDNA2003(RFC3490)。然而,在两份说明中存在差异,
因此处理(比如,转换)从一个标准下注册的域名标签到另一个存在差异。IDNA2003存在的
过渡时期会有一定的时常。user agent应该实现IDNA2008(RFC5890)并且可能会实现
UTS46或者RFC5895以加快IDNA的过渡。如果user agent没有实现IDNA2008,那它应该
实现IDNA2003(RFC3490)。

7. 隐私的考虑

cookie因为允许服务器追踪用户饱受批评。例如,很多“web分析”公司使用cookie来识别
用户是否回到了网站或者访问了另一个站点。虽然cookie不是服务器唯一可以用来追踪跨
HTTP请求的手段,但是cookie促进了追踪,因为他们在user agent的会话之间可以一直存在,
并且可以被多个host共享。

7.1. 第三方cookie

特别令人担忧的是所谓的“第三方”cookie。在渲染一个HMTL文档时,user agent经常从其他
服务器请求资源(例如广告网络)。这些第三方服务器可以使用cookie来跟踪用户,尽管用户
没有直接访问他们的服务器。例如,如果一个用户访问了一个带有第三方内容的网站,之后用户
浏览另一个包含这个内容的网站时,第三方可以跨域两个站点追踪用户。

一些user agent限制了第三方cookie的行为。例如,其中一些user agent拒绝在向第三方的请求中
发送Cookie头部。另外一些则是拒绝处理第三方请求的响应中的Set-Cookie头部。user agent在
处理的第三方cookie策略方面有很多不同。本文档允许user agent在很大范围内尝试第三方cookie的策略,来满足用户对隐私和兼容性的需求。然而,本文档并不偏向于任何特别的第三方cookie策略。

禁止第三方cookie的策略,当服务器尝试绕过这个追踪用户的限制来实现他们隐私追踪时,是无效的。
尤其是,两个协作的服务器经常通过动态url而不是cookie添加识别信息,来追踪用户的时候。

7.2. 用户控制

user agent应该提供一种给用户管理存储在cookie存储中cookie的机制。例如,user agent可能
让用户删除一段时间内的所有cookie或者和某个domain相关的所有cookie另外,许多user agent
都包含了一个让用户检查存储在cookie store中的cookie的界面。

user agent应该提供一种可以让用户禁用cookie的机制。当cookie被禁用时,user agent禁止在
发出的HTTP请求中包含Cookie头部,并且user agent处理收到的HTTP响应中的Set-Cookie头部。

一些user agent提供了阻止cookie跨session存储的选项。当这个被配置到的时候,user agent
必须将所有收到的cookie当做persistent-flag设定为false进行对待。一些流行的user agent
通过“匿名浏览模式”来开放这个功能。

某些user agent提供了允许用户自己写入cookie的能力。在大多数常见的场景中,这会产生大量的
对话框。然而,尽管如此,某些看中隐私的用户认为这个功能很有用。

7.3. 过期时间

虽然服务器可以将cookie的过期时间设定到一个遥远的未来,但是绝大多数user agent实际上并不会
将cookie保留几十年。与其选择无厘头的很长的国务时间,服务器应该,基于实际目的来选择一个合适的cookie过期时间,以提高用户的隐私性。例如,一个典型的cookie标识符应该设置成过期时间为
两周比较合理。

8. 安全考虑

8.1. 概述

cookie可能有很多安全陷阱。本节概述了几个比较显著的问题。

尤其是,cookie鼓励开发者依赖Ambient Authority做认证,往往提供了被攻击的弱点,比如说跨站请求伪造(CSRF)。同时,当在cookie中存储session标识符时,开发者往往也留下了session fixation的隐患。

传输层加密,例如使用HTTPS,并不足以防御网络攻击者获得或者更改受攻击者的cookie,因为cookie协议本身有很多脆弱性(见下面的“弱机密性”和“弱完整性”)。另外,默认情况下,cookie不能从网络攻击者那儿获得完整性和机密性,甚至在使用HTTPS时。

8.2. 环境授权(Ambient Authority)

使用cookie来认证用户的服务器可能会承受安全上的脆弱性,因为一些user agent允许远程组织从
user agent发送HTTP请求(例如,通过HTTP重定向或者HTML表单)。当处理这些请求时,user agent会附上这些cookie,尽管远程组织不知道cookie的内容,但是也潜在地允许远程组织利用在
没有防备性的服务器的授权。

尽管这个安全担忧有过许多名字(比如,跨站请求伪造,confused deputy),这个问题起源于将cookie作为一种环境授权(Ambient Authority)。cookie鼓励服务器的管理员将名称(用URL的形式)和授权(cookie)中分离。结果是,user agent可能会向攻击者指定的地址进行授权,可能导致服务器和客户端承受由攻击者指定的动作,因为他们曾经被用户授权。

服务器管理员可能会考虑通过将URL作为授权表,将名字(URL)和授权绑在一起,来作为取代使用cookie作为授权的手段。取代将secret存在cookie的是,这个方法将secret存在URL中,要求远程
实体自己提供授权。虽然这个方法不是万能药,审慎的运用这个原则可以获得更好的安全性。

8.3. 明文

除非以安全的途径传输(例如TLS),在Cookie和Set-Cookie字头是以明文传输的。

  1. 这些头部中所有传输的敏感信息都曝光给了窃听者。
  2. 一个怀有恶意的中间人可以更改任一方向中的头部,带来不可预知的后果。
  3. 一个怀有恶意的客户端可以在传输之前更改Cookie头部,带来不可预知的后果。

当传输到user agent时(就算在安全的隧道发送cookie也是),服务器应该加密并签名cookie的内容(使用任何服务器想使用的格式)。然而,加密并签名的cookie内容并没有预防攻击者将cookie从一个user agent转移到另一个,或者之后重放cookie进行攻击。

签名和加密每个cookie的内容之外,要求一个更高安全等级的服务器应该只在一个安全的隧道中
使用Cookie和Set-Cookie头。当使用安全渠道的cookie时,服务器应该设定每个cookie的Secure
属性。如果服务器没有设置Secure属性,由安全隧道提供的保护将会很大程度上的没有意义。

例如,考虑一个webmail服务器,将session标识符存在一个cookie钟,并且通过HTTPS访问。如果
服务器没有在它的cookie上设定安全属性,一个活跃的网络攻击者将可以拦截任何由user agent发出的HTTP请求并且将其请求重定向到向HTTP上的webmail服务器。就算webmail服务器没有监听HTTP连接,user agent也会在该请求中包含cookie。这个活跃的网络攻击者拦截这些cookie,并且向服务器重放攻击,之后获取到用户的邮件内容。如果,取而代之,服务器在cookie中设定了Secure属性,
user agent将会在明文请求中包含这个cookie。

8.4. session标识符

服务器一般在cookie中存储一个nonce(或者session标识符)来代替,直接在cookie中存储session信息(可能会被攻击者获得或者重放)。当服务器收到一个拥有nonce的HTTP请求时,服务器会把nonce当做key查找和cookie相关联的状态信息。

使用session标识符限制了攻击者可以造成的危害,如果攻击者拿到了cookie的内容,因为nonce是唯一一个和服务器交互的有用信息(不像非nonce得cookie内容,其本身就是敏感的)。而且,使用单个nonce避免了攻击者将两个交互中的内容混杂起来,造成服务器不可预期的行为。

使用session标识符也不是完全没有风险。例如,服务器应该避免“session fixation”造成的易受攻击性。固化session攻击以下面三个步骤进行。首先,攻击者将session标识符从他的user agent中
移植到受害者的user agent中。第二,受害者使用这个session和服务器交互,可能会用用户信息填充这个session标识符。第三,攻击者直接使用这个session标识符和服务器交互,可能会获得授权信息或者机密信息。

8.5. 弱机密性

cookie没有提供端口隔离。如果一个cookie在一个端口上是可读的,那么这个cookie对另一个运行在同一个服务器上不同端口的服务来说也是可读的。如果一个cookie在一个端口上是可写的,那么这个cookie对另一个运行在同一个服务器上不同端口的服务来说也是可写的。出于这个原因,服务器不应该
在具有相同host的不同端口上运行相互不信任的服务,并且用cookie来存储安全敏感信息。

cookie没有提供协议带来的隔离性。虽然绝大多数通常都使用http和HTTPS协议,给定host的cookie也可能被其他协议获取到,例如ftp和gopher。虽然缺少协议隔离性,对从非HTTP的API获取cookie的权限是显著的,但是实际上由协议导致的隔离性缺失表现在了他们需要自己处理cookie(例如,考虑通过HTTP取回一个使用gopher协议的URI)。

cookie往往也没有提供基于path的隔离性。虽然网络层的协议并没有将存储在一个path的协议发到另一个,但是某些user agent通过非HTTP的api暴露了这些cookie,例如HTML的document.cookie API。因为其中的某些user agent(例如,浏览器)并没有将从不同path获得的资源隔离起来,从一个path取回的某个资源也可能可以拿到存储到另一个path的cookie。

8.6. 弱完整性

cookie没有为兄弟域名(极其子域名)提供完整性保障。例如,考虑foo.example.com和bar.example.com。foo.example.com服务器可以设置一个Domain属性为"example.com"的cookie(可能覆盖一个现有的由bar.example.com设置的"example.com"的cookie)。并且user agent会在想bar.example.com的HTTP请求中包含这个cookie。在最坏的情况下,bar.example.com将不能把这个cookie和一个自己设定cookie区分开来。foo.example.com服务器可能会利用这个能力来发起对bar.example.com的攻击。

尽管Set-Cookie头支持Path属性,path属性也没有提供任何完整性保证,因为user agent将会接受一个Set-Cookie头部的任意路径。例如,一个对 http://example.com/foo/bar 的HTTP响应,可以设置一个Path属性为"/qux"的cookie。因此,服务器不应该在相同host上的不同path运行两个相互不信任的服务,并且使用cookie来存储安全敏感信息。

某个活动的网络攻击者也可以向发送到 https://example.com/ 的请求中的Cookie头部注入cookie,方法是仿造一个 http://example.com/ 的响应并且注入一个Set-Cookie头。位于example.com的HTTPS服务器将不能分辨gaicooie是否是他自己在HTTPS响应中设置的cookie。一个活跃的网络攻击者可能会通过这个机制来攻击example.com,即使example.com只使用了HTTPS。

译者注:例如,用户直接敲了一个HTTP站点,www.baidu.com,在重完成定向到HTTPS之前,中间人可以伪造跳转,比如返回一个302跳转到 https://www.baidu.com 同时中间人设定一个cookie

服务器可以通过加密和签名他们的cookie来缓和这些攻击。然而,使用密码学并没有完全缓解这个问题,因为一个攻击者可以重放TA从真实的example.com服务器中的sessuib获取到的cookie,以导致不可预期的后果。

最后,一个攻击者可以通过存储大量cookie来强迫user agent删除掉cookie。一旦user agent达到了它的存储限制,user agent会被迫驱除掉某些cookie。服务器不应该依赖user agent对cookie的保留。

8.7. 依赖DNS

cookie依赖DNS系统来提供安全性。如果DNS部分或全部受损,cookie协议可能会无法提供应用所要求的安全属性

9. IANA考虑

永久的消息头部注册(RFC3864)已经被下列登记项目更新。

9.1. Cookie

头部名称: Cookie

应用协议: http

状态: 标准

作者/变化控制者: IETF

标准文档: 本文档(5.4节)

9.2. Set-Cookie

头部名称: Set-Cookie

应用协议: http

状态: 标准

作者/变化控制者: IETF

标准文档: 本文档(5.2节)

9.3. Cookie2

头部名称: Cookie2

应用协议: http

状态: 已淘汰

作者/变化控制者: IETF

标准文档: RFC2965

9.4. Set-Cookie2

头部名称: Set-Cookie2

应用协议: http

状态: 已淘汰

作者/变化控制者: IETF

标准文档: RFC2965

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant