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