github.com/xushiwei/go@v0.0.0-20130601165731-2b9d83f45bc9/src/pkg/net/http/cookie.go (about) 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. 4 5 package http 6 7 import ( 8 "bytes" 9 "fmt" 10 "strconv" 11 "strings" 12 "time" 13 ) 14 15 // This implementation is done according to RFC 6265: 16 // 17 // http://tools.ietf.org/html/rfc6265 18 19 // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an 20 // HTTP response or the Cookie header of an HTTP request. 21 type Cookie struct { 22 Name string 23 Value string 24 Path string 25 Domain string 26 Expires time.Time 27 RawExpires string 28 29 // MaxAge=0 means no 'Max-Age' attribute specified. 30 // MaxAge<0 means delete cookie now, equivalently 'Max-Age: 0' 31 // MaxAge>0 means Max-Age attribute present and given in seconds 32 MaxAge int 33 Secure bool 34 HttpOnly bool 35 Raw string 36 Unparsed []string // Raw text of unparsed attribute-value pairs 37 } 38 39 // readSetCookies parses all "Set-Cookie" values from 40 // the header h and returns the successfully parsed Cookies. 41 func readSetCookies(h Header) []*Cookie { 42 cookies := []*Cookie{} 43 for _, line := range h["Set-Cookie"] { 44 parts := strings.Split(strings.TrimSpace(line), ";") 45 if len(parts) == 1 && parts[0] == "" { 46 continue 47 } 48 parts[0] = strings.TrimSpace(parts[0]) 49 j := strings.Index(parts[0], "=") 50 if j < 0 { 51 continue 52 } 53 name, value := parts[0][:j], parts[0][j+1:] 54 if !isCookieNameValid(name) { 55 continue 56 } 57 value, success := parseCookieValue(value) 58 if !success { 59 continue 60 } 61 c := &Cookie{ 62 Name: name, 63 Value: value, 64 Raw: line, 65 } 66 for i := 1; i < len(parts); i++ { 67 parts[i] = strings.TrimSpace(parts[i]) 68 if len(parts[i]) == 0 { 69 continue 70 } 71 72 attr, val := parts[i], "" 73 if j := strings.Index(attr, "="); j >= 0 { 74 attr, val = attr[:j], attr[j+1:] 75 } 76 lowerAttr := strings.ToLower(attr) 77 parseCookieValueFn := parseCookieValue 78 if lowerAttr == "expires" { 79 parseCookieValueFn = parseCookieExpiresValue 80 } 81 val, success = parseCookieValueFn(val) 82 if !success { 83 c.Unparsed = append(c.Unparsed, parts[i]) 84 continue 85 } 86 switch lowerAttr { 87 case "secure": 88 c.Secure = true 89 continue 90 case "httponly": 91 c.HttpOnly = true 92 continue 93 case "domain": 94 c.Domain = val 95 // TODO: Add domain parsing 96 continue 97 case "max-age": 98 secs, err := strconv.Atoi(val) 99 if err != nil || secs != 0 && val[0] == '0' { 100 break 101 } 102 if secs <= 0 { 103 c.MaxAge = -1 104 } else { 105 c.MaxAge = secs 106 } 107 continue 108 case "expires": 109 c.RawExpires = val 110 exptime, err := time.Parse(time.RFC1123, val) 111 if err != nil { 112 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) 113 if err != nil { 114 c.Expires = time.Time{} 115 break 116 } 117 } 118 c.Expires = exptime.UTC() 119 continue 120 case "path": 121 c.Path = val 122 // TODO: Add path parsing 123 continue 124 } 125 c.Unparsed = append(c.Unparsed, parts[i]) 126 } 127 cookies = append(cookies, c) 128 } 129 return cookies 130 } 131 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()) 135 } 136 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", sanitizeName(c.Name), sanitizeValue(c.Value)) 143 if len(c.Path) > 0 { 144 fmt.Fprintf(&b, "; Path=%s", sanitizeValue(c.Path)) 145 } 146 if len(c.Domain) > 0 { 147 fmt.Fprintf(&b, "; Domain=%s", sanitizeValue(c.Domain)) 148 } 149 if c.Expires.Unix() > 0 { 150 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) 151 } 152 if c.MaxAge > 0 { 153 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) 154 } else if c.MaxAge < 0 { 155 fmt.Fprintf(&b, "; Max-Age=0") 156 } 157 if c.HttpOnly { 158 fmt.Fprintf(&b, "; HttpOnly") 159 } 160 if c.Secure { 161 fmt.Fprintf(&b, "; Secure") 162 } 163 return b.String() 164 } 165 166 // readCookies parses all "Cookie" values from the header h and 167 // returns the successfully parsed Cookies. 168 // 169 // if filter isn't empty, only cookies of that name are returned 170 func readCookies(h Header, filter string) []*Cookie { 171 cookies := []*Cookie{} 172 lines, ok := h["Cookie"] 173 if !ok { 174 return cookies 175 } 176 177 for _, line := range lines { 178 parts := strings.Split(strings.TrimSpace(line), ";") 179 if len(parts) == 1 && parts[0] == "" { 180 continue 181 } 182 // Per-line attributes 183 parsedPairs := 0 184 for i := 0; i < len(parts); i++ { 185 parts[i] = strings.TrimSpace(parts[i]) 186 if len(parts[i]) == 0 { 187 continue 188 } 189 name, val := parts[i], "" 190 if j := strings.Index(name, "="); j >= 0 { 191 name, val = name[:j], name[j+1:] 192 } 193 if !isCookieNameValid(name) { 194 continue 195 } 196 if filter != "" && filter != name { 197 continue 198 } 199 val, success := parseCookieValue(val) 200 if !success { 201 continue 202 } 203 cookies = append(cookies, &Cookie{Name: name, Value: val}) 204 parsedPairs++ 205 } 206 } 207 return cookies 208 } 209 210 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") 211 212 func sanitizeName(n string) string { 213 return cookieNameSanitizer.Replace(n) 214 } 215 216 var cookieValueSanitizer = strings.NewReplacer("\n", " ", "\r", " ", ";", " ") 217 218 func sanitizeValue(v string) string { 219 return cookieValueSanitizer.Replace(v) 220 } 221 222 func unquoteCookieValue(v string) string { 223 if len(v) > 1 && v[0] == '"' && v[len(v)-1] == '"' { 224 return v[1 : len(v)-1] 225 } 226 return v 227 } 228 229 func isCookieByte(c byte) bool { 230 switch { 231 case c == 0x21, 0x23 <= c && c <= 0x2b, 0x2d <= c && c <= 0x3a, 232 0x3c <= c && c <= 0x5b, 0x5d <= c && c <= 0x7e: 233 return true 234 } 235 return false 236 } 237 238 func isCookieExpiresByte(c byte) (ok bool) { 239 return isCookieByte(c) || c == ',' || c == ' ' 240 } 241 242 func parseCookieValue(raw string) (string, bool) { 243 return parseCookieValueUsing(raw, isCookieByte) 244 } 245 246 func parseCookieExpiresValue(raw string) (string, bool) { 247 return parseCookieValueUsing(raw, isCookieExpiresByte) 248 } 249 250 func parseCookieValueUsing(raw string, validByte func(byte) bool) (string, bool) { 251 raw = unquoteCookieValue(raw) 252 for i := 0; i < len(raw); i++ { 253 if !validByte(raw[i]) { 254 return "", false 255 } 256 } 257 return raw, true 258 } 259 260 func isCookieNameValid(raw string) bool { 261 return strings.IndexFunc(raw, isNotToken) < 0 262 }