1 // Copyright 2009 The Go Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style
3 // license that can be found in the LICENSE file.
17 // This implementation is done according to RFC 6265:
19 // http://tools.ietf.org/html/rfc6265
21 // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an
22 // HTTP response or the Cookie header of an HTTP request.
31 // MaxAge=0 means no 'Max-Age' attribute specified.
32 // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0'
33 // MaxAge>0 means Max-Age attribute present and given in seconds
38 Unparsed
[]string // Raw text of unparsed attribute-value pairs
41 // readSetCookies parses all "Set-Cookie" values from
42 // the header h and returns the successfully parsed Cookies.
43 func readSetCookies(h Header
) []*Cookie
{
44 cookies
:= []*Cookie
{}
45 for _
, line
:= range h
["Set-Cookie"] {
46 parts
:= strings
.Split(strings
.TrimSpace(line
), ";")
47 if len(parts
) == 1 && parts
[0] == "" {
50 parts
[0] = strings
.TrimSpace(parts
[0])
51 j
:= strings
.Index(parts
[0], "=")
55 name
, value
:= parts
[0][:j
], parts
[0][j
+1:]
56 if !isCookieNameValid(name
) {
59 value
, success
:= parseCookieValue(value
)
68 for i
:= 1; i
< len(parts
); i
++ {
69 parts
[i
] = strings
.TrimSpace(parts
[i
])
70 if len(parts
[i
]) == 0 {
74 attr
, val
:= parts
[i
], ""
75 if j
:= strings
.Index(attr
, "="); j
>= 0 {
76 attr
, val
= attr
[:j
], attr
[j
+1:]
78 lowerAttr
:= strings
.ToLower(attr
)
79 parseCookieValueFn
:= parseCookieValue
80 if lowerAttr
== "expires" {
81 parseCookieValueFn
= parseCookieExpiresValue
83 val
, success
= parseCookieValueFn(val
)
85 c
.Unparsed
= append(c
.Unparsed
, parts
[i
])
99 secs
, err
:= strconv
.Atoi(val
)
100 if err
!= nil || secs
!= 0 && val
[0] == '0' {
111 exptime
, err
:= time
.Parse(time
.RFC1123
, val
)
113 exptime
, err
= time
.Parse("Mon, 02-Jan-2006 15:04:05 MST", val
)
115 c
.Expires
= time
.Time
{}
119 c
.Expires
= exptime
.UTC()
125 c
.Unparsed
= append(c
.Unparsed
, parts
[i
])
127 cookies
= append(cookies
, c
)
132 // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers.
133 func SetCookie(w ResponseWriter
, cookie
*Cookie
) {
134 w
.Header().Add("Set-Cookie", cookie
.String())
137 // String returns the serialization of the cookie for use in a Cookie
138 // header (if only Name and Value are set) or a Set-Cookie response
139 // header (if other fields are set).
140 func (c
*Cookie
) String() string {
142 fmt
.Fprintf(&b
, "%s=%s", sanitizeCookieName(c
.Name
), sanitizeCookieValue(c
.Value
))
144 fmt
.Fprintf(&b
, "; Path=%s", sanitizeCookiePath(c
.Path
))
146 if len(c
.Domain
) > 0 {
147 if validCookieDomain(c
.Domain
) {
148 // A c.Domain containing illegal characters is not
149 // sanitized but simply dropped which turns the cookie
150 // into a host-only cookie. A leading dot is okay
151 // but won't be sent.
156 fmt
.Fprintf(&b
, "; Domain=%s", d
)
158 log
.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute",
162 if c
.Expires
.Unix() > 0 {
163 fmt
.Fprintf(&b
, "; Expires=%s", c
.Expires
.UTC().Format(time
.RFC1123
))
166 fmt
.Fprintf(&b
, "; Max-Age=%d", c
.MaxAge
)
167 } else if c
.MaxAge
< 0 {
168 fmt
.Fprintf(&b
, "; Max-Age=0")
171 fmt
.Fprintf(&b
, "; HttpOnly")
174 fmt
.Fprintf(&b
, "; Secure")
179 // readCookies parses all "Cookie" values from the header h and
180 // returns the successfully parsed Cookies.
182 // if filter isn't empty, only cookies of that name are returned
183 func readCookies(h Header
, filter
string) []*Cookie
{
184 cookies
:= []*Cookie
{}
185 lines
, ok
:= h
["Cookie"]
190 for _
, line
:= range lines
{
191 parts
:= strings
.Split(strings
.TrimSpace(line
), ";")
192 if len(parts
) == 1 && parts
[0] == "" {
195 // Per-line attributes
197 for i
:= 0; i
< len(parts
); i
++ {
198 parts
[i
] = strings
.TrimSpace(parts
[i
])
199 if len(parts
[i
]) == 0 {
202 name
, val
:= parts
[i
], ""
203 if j
:= strings
.Index(name
, "="); j
>= 0 {
204 name
, val
= name
[:j
], name
[j
+1:]
206 if !isCookieNameValid(name
) {
209 if filter
!= "" && filter
!= name
{
212 val
, success
:= parseCookieValue(val
)
216 cookies
= append(cookies
, &Cookie
{Name
: name
, Value
: val
})
223 // validCookieDomain returns wheter v is a valid cookie domain-value.
224 func validCookieDomain(v
string) bool {
225 if isCookieDomainName(v
) {
228 if net
.ParseIP(v
) != nil && !strings
.Contains(v
, ":") {
234 // isCookieDomainName returns whether s is a valid domain name or a valid
235 // domain name with a leading dot '.'. It is almost a direct copy of
236 // package net's isDomainName.
237 func isCookieDomainName(s
string) bool {
246 // A cookie a domain attribute may start with a leading dot.
250 ok
:= false // Ok once we've seen a letter.
252 for i
:= 0; i
< len(s
); i
++ {
257 case 'a' <= c
&& c
<= 'z' ||
'A' <= c
&& c
<= 'Z':
258 // No '_' allowed here (in contrast to package net).
261 case '0' <= c
&& c
<= '9':
265 // Byte before dash cannot be dot.
271 // Byte before dot cannot be dot, dash.
272 if last
== '.' || last
== '-' {
275 if partlen
> 63 || partlen
== 0 {
282 if last
== '-' || partlen
> 63 {
289 var cookieNameSanitizer
= strings
.NewReplacer("\n", "-", "\r", "-")
291 func sanitizeCookieName(n
string) string {
292 return cookieNameSanitizer
.Replace(n
)
295 // http://tools.ietf.org/html/rfc6265#section-4.1.1
296 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE )
297 // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E
298 // ; US-ASCII characters excluding CTLs,
299 // ; whitespace DQUOTE, comma, semicolon,
301 func sanitizeCookieValue(v
string) string {
302 return sanitizeOrWarn("Cookie.Value", validCookieValueByte
, v
)
305 func validCookieValueByte(b
byte) bool {
306 return 0x20 < b
&& b
< 0x7f && b
!= '"' && b
!= ',' && b
!= ';' && b
!= '\\'
309 // path-av = "Path=" path-value
310 // path-value = <any CHAR except CTLs or ";">
311 func sanitizeCookiePath(v
string) string {
312 return sanitizeOrWarn("Cookie.Path", validCookiePathByte
, v
)
315 func validCookiePathByte(b
byte) bool {
316 return 0x20 <= b
&& b
< 0x7f && b
!= ';'
319 func sanitizeOrWarn(fieldName
string, valid
func(byte) bool, v
string) string {
321 for i
:= 0; i
< len(v
); i
++ {
325 log
.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v
[i
], fieldName
)
332 buf
:= make([]byte, 0, len(v
))
333 for i
:= 0; i
< len(v
); i
++ {
334 if b
:= v
[i
]; valid(b
) {
341 func unquoteCookieValue(v
string) string {
342 if len(v
) > 1 && v
[0] == '"' && v
[len(v
)-1] == '"' {
343 return v
[1 : len(v
)-1]
348 func isCookieByte(c
byte) bool {
350 case c
== 0x21, 0x23 <= c
&& c
<= 0x2b, 0x2d <= c
&& c
<= 0x3a,
351 0x3c <= c
&& c
<= 0x5b, 0x5d <= c
&& c
<= 0x7e:
357 func isCookieExpiresByte(c
byte) (ok
bool) {
358 return isCookieByte(c
) || c
== ',' || c
== ' '
361 func parseCookieValue(raw
string) (string, bool) {
362 return parseCookieValueUsing(raw
, isCookieByte
)
365 func parseCookieExpiresValue(raw
string) (string, bool) {
366 return parseCookieValueUsing(raw
, isCookieExpiresByte
)
369 func parseCookieValueUsing(raw
string, validByte
func(byte) bool) (string, bool) {
370 raw
= unquoteCookieValue(raw
)
371 for i
:= 0; i
< len(raw
); i
++ {
372 if !validByte(raw
[i
]) {
379 func isCookieNameValid(raw
string) bool {
380 return strings
.IndexFunc(raw
, isNotToken
) < 0