github.com/rafaeltorres324/go/src@v0.0.0-20210519164414-9fdf653a9838/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 "log" 9 "net" 10 "net/textproto" 11 "strconv" 12 "strings" 13 "time" 14 ) 15 16 // A Cookie represents an HTTP cookie as sent in the Set-Cookie header of an 17 // HTTP response or the Cookie header of an HTTP request. 18 // 19 // See https://tools.ietf.org/html/rfc6265 for details. 20 type Cookie struct { 21 Name string 22 Value string 23 24 Path string // optional 25 Domain string // optional 26 Expires time.Time // optional 27 RawExpires string // for reading cookies only 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 SameSite SameSite 36 Raw string 37 Unparsed []string // Raw text of unparsed attribute-value pairs 38 } 39 40 // SameSite allows a server to define a cookie attribute making it impossible for 41 // the browser to send this cookie along with cross-site requests. The main 42 // goal is to mitigate the risk of cross-origin information leakage, and provide 43 // some protection against cross-site request forgery attacks. 44 // 45 // See https://tools.ietf.org/html/draft-ietf-httpbis-cookie-same-site-00 for details. 46 type SameSite int 47 48 const ( 49 SameSiteDefaultMode SameSite = iota + 1 50 SameSiteLaxMode 51 SameSiteStrictMode 52 SameSiteNoneMode 53 ) 54 55 // readSetCookies parses all "Set-Cookie" values from 56 // the header h and returns the successfully parsed Cookies. 57 func readSetCookies(h Header) []*Cookie { 58 cookieCount := len(h["Set-Cookie"]) 59 if cookieCount == 0 { 60 return []*Cookie{} 61 } 62 cookies := make([]*Cookie, 0, cookieCount) 63 for _, line := range h["Set-Cookie"] { 64 parts := strings.Split(textproto.TrimString(line), ";") 65 if len(parts) == 1 && parts[0] == "" { 66 continue 67 } 68 parts[0] = textproto.TrimString(parts[0]) 69 j := strings.Index(parts[0], "=") 70 if j < 0 { 71 continue 72 } 73 name, value := parts[0][:j], parts[0][j+1:] 74 if !isCookieNameValid(name) { 75 continue 76 } 77 value, ok := parseCookieValue(value, true) 78 if !ok { 79 continue 80 } 81 c := &Cookie{ 82 Name: name, 83 Value: value, 84 Raw: line, 85 } 86 for i := 1; i < len(parts); i++ { 87 parts[i] = textproto.TrimString(parts[i]) 88 if len(parts[i]) == 0 { 89 continue 90 } 91 92 attr, val := parts[i], "" 93 if j := strings.Index(attr, "="); j >= 0 { 94 attr, val = attr[:j], attr[j+1:] 95 } 96 lowerAttr := strings.ToLower(attr) 97 val, ok = parseCookieValue(val, false) 98 if !ok { 99 c.Unparsed = append(c.Unparsed, parts[i]) 100 continue 101 } 102 switch lowerAttr { 103 case "samesite": 104 lowerVal := strings.ToLower(val) 105 switch lowerVal { 106 case "lax": 107 c.SameSite = SameSiteLaxMode 108 case "strict": 109 c.SameSite = SameSiteStrictMode 110 case "none": 111 c.SameSite = SameSiteNoneMode 112 default: 113 c.SameSite = SameSiteDefaultMode 114 } 115 continue 116 case "secure": 117 c.Secure = true 118 continue 119 case "httponly": 120 c.HttpOnly = true 121 continue 122 case "domain": 123 c.Domain = val 124 continue 125 case "max-age": 126 secs, err := strconv.Atoi(val) 127 if err != nil || secs != 0 && val[0] == '0' { 128 break 129 } 130 if secs <= 0 { 131 secs = -1 132 } 133 c.MaxAge = secs 134 continue 135 case "expires": 136 c.RawExpires = val 137 exptime, err := time.Parse(time.RFC1123, val) 138 if err != nil { 139 exptime, err = time.Parse("Mon, 02-Jan-2006 15:04:05 MST", val) 140 if err != nil { 141 c.Expires = time.Time{} 142 break 143 } 144 } 145 c.Expires = exptime.UTC() 146 continue 147 case "path": 148 c.Path = val 149 continue 150 } 151 c.Unparsed = append(c.Unparsed, parts[i]) 152 } 153 cookies = append(cookies, c) 154 } 155 return cookies 156 } 157 158 // SetCookie adds a Set-Cookie header to the provided ResponseWriter's headers. 159 // The provided cookie must have a valid Name. Invalid cookies may be 160 // silently dropped. 161 func SetCookie(w ResponseWriter, cookie *Cookie) { 162 if v := cookie.String(); v != "" { 163 w.Header().Add("Set-Cookie", v) 164 } 165 } 166 167 // String returns the serialization of the cookie for use in a Cookie 168 // header (if only Name and Value are set) or a Set-Cookie response 169 // header (if other fields are set). 170 // If c is nil or c.Name is invalid, the empty string is returned. 171 func (c *Cookie) String() string { 172 if c == nil || !isCookieNameValid(c.Name) { 173 return "" 174 } 175 // extraCookieLength derived from typical length of cookie attributes 176 // see RFC 6265 Sec 4.1. 177 const extraCookieLength = 110 178 var b strings.Builder 179 b.Grow(len(c.Name) + len(c.Value) + len(c.Domain) + len(c.Path) + extraCookieLength) 180 b.WriteString(c.Name) 181 b.WriteRune('=') 182 b.WriteString(sanitizeCookieValue(c.Value)) 183 184 if len(c.Path) > 0 { 185 b.WriteString("; Path=") 186 b.WriteString(sanitizeCookiePath(c.Path)) 187 } 188 if len(c.Domain) > 0 { 189 if validCookieDomain(c.Domain) { 190 // A c.Domain containing illegal characters is not 191 // sanitized but simply dropped which turns the cookie 192 // into a host-only cookie. A leading dot is okay 193 // but won't be sent. 194 d := c.Domain 195 if d[0] == '.' { 196 d = d[1:] 197 } 198 b.WriteString("; Domain=") 199 b.WriteString(d) 200 } else { 201 log.Printf("net/http: invalid Cookie.Domain %q; dropping domain attribute", c.Domain) 202 } 203 } 204 var buf [len(TimeFormat)]byte 205 if validCookieExpires(c.Expires) { 206 b.WriteString("; Expires=") 207 b.Write(c.Expires.UTC().AppendFormat(buf[:0], TimeFormat)) 208 } 209 if c.MaxAge > 0 { 210 b.WriteString("; Max-Age=") 211 b.Write(strconv.AppendInt(buf[:0], int64(c.MaxAge), 10)) 212 } else if c.MaxAge < 0 { 213 b.WriteString("; Max-Age=0") 214 } 215 if c.HttpOnly { 216 b.WriteString("; HttpOnly") 217 } 218 if c.Secure { 219 b.WriteString("; Secure") 220 } 221 switch c.SameSite { 222 case SameSiteDefaultMode: 223 // Skip, default mode is obtained by not emitting the attribute. 224 case SameSiteNoneMode: 225 b.WriteString("; SameSite=None") 226 case SameSiteLaxMode: 227 b.WriteString("; SameSite=Lax") 228 case SameSiteStrictMode: 229 b.WriteString("; SameSite=Strict") 230 } 231 return b.String() 232 } 233 234 // readCookies parses all "Cookie" values from the header h and 235 // returns the successfully parsed Cookies. 236 // 237 // if filter isn't empty, only cookies of that name are returned 238 func readCookies(h Header, filter string) []*Cookie { 239 lines := h["Cookie"] 240 if len(lines) == 0 { 241 return []*Cookie{} 242 } 243 244 cookies := make([]*Cookie, 0, len(lines)+strings.Count(lines[0], ";")) 245 for _, line := range lines { 246 line = textproto.TrimString(line) 247 248 var part string 249 for len(line) > 0 { // continue since we have rest 250 if splitIndex := strings.Index(line, ";"); splitIndex > 0 { 251 part, line = line[:splitIndex], line[splitIndex+1:] 252 } else { 253 part, line = line, "" 254 } 255 part = textproto.TrimString(part) 256 if len(part) == 0 { 257 continue 258 } 259 name, val := part, "" 260 if j := strings.Index(part, "="); j >= 0 { 261 name, val = name[:j], name[j+1:] 262 } 263 if !isCookieNameValid(name) { 264 continue 265 } 266 if filter != "" && filter != name { 267 continue 268 } 269 val, ok := parseCookieValue(val, true) 270 if !ok { 271 continue 272 } 273 cookies = append(cookies, &Cookie{Name: name, Value: val}) 274 } 275 } 276 return cookies 277 } 278 279 // validCookieDomain reports whether v is a valid cookie domain-value. 280 func validCookieDomain(v string) bool { 281 if isCookieDomainName(v) { 282 return true 283 } 284 if net.ParseIP(v) != nil && !strings.Contains(v, ":") { 285 return true 286 } 287 return false 288 } 289 290 // validCookieExpires reports whether v is a valid cookie expires-value. 291 func validCookieExpires(t time.Time) bool { 292 // IETF RFC 6265 Section 5.1.1.5, the year must not be less than 1601 293 return t.Year() >= 1601 294 } 295 296 // isCookieDomainName reports whether s is a valid domain name or a valid 297 // domain name with a leading dot '.'. It is almost a direct copy of 298 // package net's isDomainName. 299 func isCookieDomainName(s string) bool { 300 if len(s) == 0 { 301 return false 302 } 303 if len(s) > 255 { 304 return false 305 } 306 307 if s[0] == '.' { 308 // A cookie a domain attribute may start with a leading dot. 309 s = s[1:] 310 } 311 last := byte('.') 312 ok := false // Ok once we've seen a letter. 313 partlen := 0 314 for i := 0; i < len(s); i++ { 315 c := s[i] 316 switch { 317 default: 318 return false 319 case 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z': 320 // No '_' allowed here (in contrast to package net). 321 ok = true 322 partlen++ 323 case '0' <= c && c <= '9': 324 // fine 325 partlen++ 326 case c == '-': 327 // Byte before dash cannot be dot. 328 if last == '.' { 329 return false 330 } 331 partlen++ 332 case c == '.': 333 // Byte before dot cannot be dot, dash. 334 if last == '.' || last == '-' { 335 return false 336 } 337 if partlen > 63 || partlen == 0 { 338 return false 339 } 340 partlen = 0 341 } 342 last = c 343 } 344 if last == '-' || partlen > 63 { 345 return false 346 } 347 348 return ok 349 } 350 351 var cookieNameSanitizer = strings.NewReplacer("\n", "-", "\r", "-") 352 353 func sanitizeCookieName(n string) string { 354 return cookieNameSanitizer.Replace(n) 355 } 356 357 // sanitizeCookieValue produces a suitable cookie-value from v. 358 // https://tools.ietf.org/html/rfc6265#section-4.1.1 359 // cookie-value = *cookie-octet / ( DQUOTE *cookie-octet DQUOTE ) 360 // cookie-octet = %x21 / %x23-2B / %x2D-3A / %x3C-5B / %x5D-7E 361 // ; US-ASCII characters excluding CTLs, 362 // ; whitespace DQUOTE, comma, semicolon, 363 // ; and backslash 364 // We loosen this as spaces and commas are common in cookie values 365 // but we produce a quoted cookie-value if and only if v contains 366 // commas or spaces. 367 // See https://golang.org/issue/7243 for the discussion. 368 func sanitizeCookieValue(v string) string { 369 v = sanitizeOrWarn("Cookie.Value", validCookieValueByte, v) 370 if len(v) == 0 { 371 return v 372 } 373 if strings.IndexByte(v, ' ') >= 0 || strings.IndexByte(v, ',') >= 0 { 374 return `"` + v + `"` 375 } 376 return v 377 } 378 379 func validCookieValueByte(b byte) bool { 380 return 0x20 <= b && b < 0x7f && b != '"' && b != ';' && b != '\\' 381 } 382 383 // path-av = "Path=" path-value 384 // path-value = <any CHAR except CTLs or ";"> 385 func sanitizeCookiePath(v string) string { 386 return sanitizeOrWarn("Cookie.Path", validCookiePathByte, v) 387 } 388 389 func validCookiePathByte(b byte) bool { 390 return 0x20 <= b && b < 0x7f && b != ';' 391 } 392 393 func sanitizeOrWarn(fieldName string, valid func(byte) bool, v string) string { 394 ok := true 395 for i := 0; i < len(v); i++ { 396 if valid(v[i]) { 397 continue 398 } 399 log.Printf("net/http: invalid byte %q in %s; dropping invalid bytes", v[i], fieldName) 400 ok = false 401 break 402 } 403 if ok { 404 return v 405 } 406 buf := make([]byte, 0, len(v)) 407 for i := 0; i < len(v); i++ { 408 if b := v[i]; valid(b) { 409 buf = append(buf, b) 410 } 411 } 412 return string(buf) 413 } 414 415 func parseCookieValue(raw string, allowDoubleQuote bool) (string, bool) { 416 // Strip the quotes, if present. 417 if allowDoubleQuote && len(raw) > 1 && raw[0] == '"' && raw[len(raw)-1] == '"' { 418 raw = raw[1 : len(raw)-1] 419 } 420 for i := 0; i < len(raw); i++ { 421 if !validCookieValueByte(raw[i]) { 422 return "", false 423 } 424 } 425 return raw, true 426 } 427 428 func isCookieNameValid(raw string) bool { 429 if raw == "" { 430 return false 431 } 432 return strings.IndexFunc(raw, isNotToken) < 0 433 }