libgo: Merge from revision 18783:00cce3a34d7e of master library.
[official-gcc.git] / libgo / go / net / http / cookie.go
bloba1759214f3827c394441f43e18a7af433f22a390
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.
5 package http
7 import (
8 "bytes"
9 "fmt"
10 "log"
11 "net"
12 "strconv"
13 "strings"
14 "time"
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.
23 type Cookie struct {
24 Name string
25 Value string
26 Path string
27 Domain string
28 Expires time.Time
29 RawExpires string
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
34 MaxAge int
35 Secure bool
36 HttpOnly bool
37 Raw string
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] == "" {
48 continue
50 parts[0] = strings.TrimSpace(parts[0])
51 j := strings.Index(parts[0], "=")
52 if j < 0 {
53 continue
55 name, value := parts[0][:j], parts[0][j+1:]
56 if !isCookieNameValid(name) {
57 continue
59 value, success := parseCookieValue(value)
60 if !success {
61 continue
63 c := &Cookie{
64 Name: name,
65 Value: value,
66 Raw: line,
68 for i := 1; i < len(parts); i++ {
69 parts[i] = strings.TrimSpace(parts[i])
70 if len(parts[i]) == 0 {
71 continue
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)
84 if !success {
85 c.Unparsed = append(c.Unparsed, parts[i])
86 continue
88 switch lowerAttr {
89 case "secure":
90 c.Secure = true
91 continue
92 case "httponly":
93 c.HttpOnly = true
94 continue
95 case "domain":
96 c.Domain = val
97 continue
98 case "max-age":
99 secs, err := strconv.Atoi(val)
100 if err != nil || secs != 0 && val[0] == '0' {
101 break
103 if secs <= 0 {
104 c.MaxAge = -1
105 } else {
106 c.MaxAge = secs
108 continue
109 case "expires":
110 c.RawExpires = val
111 exptime, err := time.Parse(time.RFC1123, val)
112 if err != nil {
113 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val)
114 if err != nil {
115 c.Expires = time.Time{}
116 break
119 c.Expires = exptime.UTC()
120 continue
121 case "path":
122 c.Path = val
123 continue
125 c.Unparsed = append(c.Unparsed, parts[i])
127 cookies = append(cookies, c)
129 return cookies
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 {
141 var b bytes.Buffer
142 fmt.Fprintf(&b, "%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value))
143 if len(c.Path) > 0 {
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.
152 d := c.Domain
153 if d[0] == '.' {
154 d = d[1:]
156 fmt.Fprintf(&b, "; Domain=%s", d)
157 } else {
158 log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute",
159 c.Domain)
162 if c.Expires.Unix() > 0 {
163 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123))
165 if c.MaxAge > 0 {
166 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge)
167 } else if c.MaxAge < 0 {
168 fmt.Fprintf(&b, "; Max-Age=0")
170 if c.HttpOnly {
171 fmt.Fprintf(&b, "; HttpOnly")
173 if c.Secure {
174 fmt.Fprintf(&b, "; Secure")
176 return b.String()
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"]
186 if !ok {
187 return cookies
190 for _, line := range lines {
191 parts := strings.Split(strings.TrimSpace(line), ";")
192 if len(parts) == 1 && parts[0] == "" {
193 continue
195 // Per-line attributes
196 parsedPairs := 0
197 for i := 0; i < len(parts); i++ {
198 parts[i] = strings.TrimSpace(parts[i])
199 if len(parts[i]) == 0 {
200 continue
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) {
207 continue
209 if filter != "" && filter != name {
210 continue
212 val, success := parseCookieValue(val)
213 if !success {
214 continue
216 cookies = append(cookies, &Cookie{Name: name, Value: val})
217 parsedPairs++
220 return cookies
223 // validCookieDomain returns wheter v is a valid cookie domain-value.
224 func validCookieDomain(v string) bool {
225 if isCookieDomainName(v) {
226 return true
228 if net.ParseIP(v) != nil && !strings.Contains(v, ":") {
229 return true
231 return false
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 {
238 if len(s) == 0 {
239 return false
241 if len(s) > 255 {
242 return false
245 if s[0] == '.' {
246 // A cookie a domain attribute may start with a leading dot.
247 s = s[1:]
249 last := byte('.')
250 ok := false // Ok once we've seen a letter.
251 partlen := 0
252 for i := 0; i < len(s); i++ {
253 c := s[i]
254 switch {
255 default:
256 return false
257 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z':
258 // No '_' allowed here (in contrast to package net).
259 ok = true
260 partlen++
261 case '0' <= c && c <= '9':
262 // fine
263 partlen++
264 case c == '-':
265 // Byte before dash cannot be dot.
266 if last == '.' {
267 return false
269 partlen++
270 case c == '.':
271 // Byte before dot cannot be dot, dash.
272 if last == '.' || last == '-' {
273 return false
275 if partlen > 63 || partlen == 0 {
276 return false
278 partlen = 0
280 last = c
282 if last == '-' || partlen > 63 {
283 return false
286 return ok
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,
300 // ; and backslash
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 {
320 ok := true
321 for i := 0; i < len(v); i++ {
322 if valid(v[i]) {
323 continue
325 log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName)
326 ok = false
327 break
329 if ok {
330 return v
332 buf := make([]byte, 0, len(v))
333 for i := 0; i < len(v); i++ {
334 if b := v[i]; valid(b) {
335 buf = append(buf, b)
338 return string(buf)
341 func unquoteCookieValue(v string) string {
342 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' {
343 return v[1 : len(v)-1]
345 return v
348 func isCookieByte(c byte) bool {
349 switch {
350 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a,
351 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e:
352 return true
354 return false
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]) {
373 return "", false
376 return raw, true
379 func isCookieNameValid(raw string) bool {
380 return strings.IndexFunc(raw, isNotToken) < 0