github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/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 "log" 11 "net" 12 "strconv" 13 "strings" 14 "time" 15 ) 16 17 // This implementation is done according to RFC 6265: 18 // 19 // http://tools.ietf.org/html/rfc6265 20 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 30 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 39 } 40 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 49 } 50 parts[0] = strings.TrimSpace(parts[0]) 51 j := strings.Index(parts[0], "=") 52 if j < 0 { 53 continue 54 } 55 name, value := parts[0][:j], parts[0][j+1:] 56 if !isCookieNameValid(name) { 57 continue 58 } 59 value, success := parseCookieValue(value) 60 if !success { 61 continue 62 } 63 c := &Cookie{ 64 Name: name, 65 Value: value, 66 Raw: line, 67 } 68 for i := 1; i < len(parts); i++ { 69 parts[i] = strings.TrimSpace(parts[i]) 70 if len(parts[i]) == 0 { 71 continue 72 } 73 74 attr, val := parts[i], "" 75 if j := strings.Index(attr, "="); j >= 0 { 76 attr, val = attr[:j], attr[j+1:] 77 } 78 lowerAttr := strings.ToLower(attr) 79 val, success = parseCookieValue(val) 80 if !success { 81 c.Unparsed = append(c.Unparsed, parts[i]) 82 continue 83 } 84 switch lowerAttr { 85 case "secure": 86 c.Secure = true 87 continue 88 case "httponly": 89 c.HttpOnly = true 90 continue 91 case "domain": 92 c.Domain = val 93 continue 94 case "max-age": 95 secs, err := strconv.Atoi(val) 96 if err != nil || secs != 0 && val[0] == '0' { 97 break 98 } 99 if secs <= 0 { 100 c.MaxAge = -1 101 } else { 102 c.MaxAge = secs 103 } 104 continue 105 case "expires": 106 c.RawExpires = val 107 exptime, err := time.Parse(time.RFC1123, val) 108 if err != nil { 109 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) 110 if err != nil { 111 c.Expires = time.Time{} 112 break 113 } 114 } 115 c.Expires = exptime.UTC() 116 continue 117 case "path": 118 c.Path = val 119 continue 120 } 121 c.Unparsed = append(c.Unparsed, parts[i]) 122 } 123 cookies = append(cookies, c) 124 } 125 return cookies 126 } 127 128 // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. 129 func SetCookie(w ResponseWriter, cookie *Cookie) { 130 w.Header().Add("Set-Cookie", cookie.String()) 131 } 132 133 // String returns the serialization of the cookie for use in a Cookie 134 // header (if only Name and Value are set) or a Set-Cookie response 135 // header (if other fields are set). 136 func (c *Cookie) String() string { 137 var b bytes.Buffer 138 fmt.Fprintf(&b, "%s=%s", sanitizeCookieName(c.Name), sanitizeCookieValue(c.Value)) 139 if len(c.Path) > 0 { 140 fmt.Fprintf(&b, "; Path=%s", sanitizeCookiePath(c.Path)) 141 } 142 if len(c.Domain) > 0 { 143 if validCookieDomain(c.Domain) { 144 // A c.Domain containing illegal characters is not 145 // sanitized but simply dropped which turns the cookie 146 // into a host-only cookie. A leading dot is okay 147 // but won't be sent. 148 d := c.Domain 149 if d[0] == '.' { 150 d = d[1:] 151 } 152 fmt.Fprintf(&b, "; Domain=%s", d) 153 } else { 154 log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", 155 c.Domain) 156 } 157 } 158 if c.Expires.Unix() > 0 { 159 fmt.Fprintf(&b, "; Expires=%s", c.Expires.UTC().Format(time.RFC1123)) 160 } 161 if c.MaxAge > 0 { 162 fmt.Fprintf(&b, "; Max-Age=%d", c.MaxAge) 163 } else if c.MaxAge < 0 { 164 fmt.Fprintf(&b, "; Max-Age=0") 165 } 166 if c.HttpOnly { 167 fmt.Fprintf(&b, "; HttpOnly") 168 } 169 if c.Secure { 170 fmt.Fprintf(&b, "; Secure") 171 } 172 return b.String() 173 } 174 175 // readCookies parses all "Cookie" values from the header h and 176 // returns the successfully parsed Cookies. 177 // 178 // if filter isn't empty, only cookies of that name are returned 179 func readCookies(h Header, filter string) []*Cookie { 180 cookies := []*Cookie{} 181 lines, ok := h["Cookie"] 182 if !ok { 183 return cookies 184 } 185 186 for _, line := range lines { 187 parts := strings.Split(strings.TrimSpace(line), ";") 188 if len(parts) == 1 && parts[0] == "" { 189 continue 190 } 191 // Per-line attributes 192 parsedPairs := 0 193 for i := 0; i < len(parts); i++ { 194 parts[i] = strings.TrimSpace(parts[i]) 195 if len(parts[i]) == 0 { 196 continue 197 } 198 name, val := parts[i], "" 199 if j := strings.Index(name, "="); j >= 0 { 200 name, val = name[:j], name[j+1:] 201 } 202 if !isCookieNameValid(name) { 203 continue 204 } 205 if filter != "" && filter != name { 206 continue 207 } 208 val, success := parseCookieValue(val) 209 if !success { 210 continue 211 } 212 cookies = append(cookies, &Cookie{Name: name, Value: val}) 213 parsedPairs++ 214 } 215 } 216 return cookies 217 } 218 219 // validCookieDomain returns wheter v is a valid cookie domain-value. 220 func validCookieDomain(v string) bool { 221 if isCookieDomainName(v) { 222 return true 223 } 224 if net.ParseIP(v) != nil && !strings.Contains(v, ":") { 225 return true 226 } 227 return false 228 } 229 230 // isCookieDomainName returns whether s is a valid domain name or a valid 231 // domain name with a leading dot '.'. It is almost a direct copy of 232 // package net's isDomainName. 233 func isCookieDomainName(s string) bool { 234 if len(s) == 0 { 235 return false 236 } 237 if len(s) > 255 { 238 return false 239 } 240 241 if s[0] == '.' { 242 // A cookie a domain attribute may start with a leading dot. 243 s = s[1:] 244 } 245 last := byte('.') 246 ok := false // Ok once we've seen a letter. 247 partlen := 0 248 for i := 0; i < len(s); i++ { 249 c := s[i] 250 switch { 251 default: 252 return false 253 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': 254 // No '_' allowed here (in contrast to package net). 255 ok = true 256 partlen++ 257 case '0' <= c && c <= '9': 258 // fine 259 partlen++ 260 case c == '-': 261 // Byte before dash cannot be dot. 262 if last == '.' { 263 return false 264 } 265 partlen++ 266 case c == '.': 267 // Byte before dot cannot be dot, dash. 268 if last == '.' || last == '-' { 269 return false 270 } 271 if partlen > 63 || partlen == 0 { 272 return false 273 } 274 partlen = 0 275 } 276 last = c 277 } 278 if last == '-' || partlen > 63 { 279 return false 280 } 281 282 return ok 283 } 284 285 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") 286 287 func sanitizeCookieName(n string) string { 288 return cookieNameSanitizer.Replace(n) 289 } 290 291 // http://tools.ietf.org/html/rfc6265#section-4.1.1 292 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) 293 // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 294 // ; US-ASCII characters excluding CTLs, 295 // ; whitespace DQUOTE, comma, semicolon, 296 // ; and backslash 297 // We loosen this as spaces and commas are common in cookie values 298 // but we produce a quoted cookie-value in when value starts or ends 299 // with a comma or space. 300 // See http://golang.org/issue/7243 for the discussion. 301 func sanitizeCookieValue(v string) string { 302 v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) 303 if len(v) == 0 { 304 return v 305 } 306 if v[0] == ' ' || v[0] == ',' || v[len(v)-1] == ' ' || v[len(v)-1] == ',' { 307 return `"` + v + `"` 308 } 309 return v 310 } 311 312 func validCookieValueByte(b byte) bool { 313 return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' 314 } 315 316 // path-av = "Path=" path-value 317 // path-value = <any CHAR except CTLs or ";"> 318 func sanitizeCookiePath(v string) string { 319 return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) 320 } 321 322 func validCookiePathByte(b byte) bool { 323 return 0x20 <= b && b < 0x7f && b != ';' 324 } 325 326 func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { 327 ok := true 328 for i := 0; i < len(v); i++ { 329 if valid(v[i]) { 330 continue 331 } 332 log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) 333 ok = false 334 break 335 } 336 if ok { 337 return v 338 } 339 buf := make([]byte, 0, len(v)) 340 for i := 0; i < len(v); i++ { 341 if b := v[i]; valid(b) { 342 buf = append(buf, b) 343 } 344 } 345 return string(buf) 346 } 347 348 func parseCookieValue(raw string) (string, bool) { 349 // Strip the quotes, if present. 350 if len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { 351 raw = raw[1 : len(raw)-1] 352 } 353 for i := 0; i < len(raw); i++ { 354 if !validCookieValueByte(raw[i]) { 355 return "", false 356 } 357 } 358 return raw, true 359 } 360 361 func isCookieNameValid(raw string) bool { 362 return strings.IndexFunc(raw, isNotToken) < 0 363 }