github.com/code-reading/golang@v0.0.0-20220303082512-ba5bc0e589a3/go/src/net/http/cookiejar/jar.go (about) 1 // Copyright 2012 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 cookiejar implements an in-memory RFC 6265-compliant http.CookieJar. 6 package cookiejar 7 8 import ( 9 "errors" 10 "fmt" 11 "net" 12 "net/http" 13 "net/http/internal/ascii" 14 "net/url" 15 "sort" 16 "strings" 17 "sync" 18 "time" 19 ) 20 21 // PublicSuffixList provides the public suffix of a domain. For example: 22 // - the public suffix of "example.com" is "com", 23 // - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and 24 // - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us". 25 // 26 // Implementations of PublicSuffixList must be safe for concurrent use by 27 // multiple goroutines. 28 // 29 // An implementation that always returns "" is valid and may be useful for 30 // testing but it is not secure: it means that the HTTP server for foo.com can 31 // set a cookie for bar.com. 32 // 33 // A public suffix list implementation is in the package 34 // golang.org/x/net/publicsuffix. 35 type PublicSuffixList interface { 36 // PublicSuffix returns the public suffix of domain. 37 // 38 // TODO: specify which of the caller and callee is responsible for IP 39 // addresses, for leading and trailing dots, for case sensitivity, and 40 // for IDN/Punycode. 41 PublicSuffix(domain string) string 42 43 // String returns a description of the source of this public suffix 44 // list. The description will typically contain something like a time 45 // stamp or version number. 46 String() string 47 } 48 49 // Options are the options for creating a new Jar. 50 type Options struct { 51 // PublicSuffixList is the public suffix list that determines whether 52 // an HTTP server can set a cookie for a domain. 53 // 54 // A nil value is valid and may be useful for testing but it is not 55 // secure: it means that the HTTP server for foo.co.uk can set a cookie 56 // for bar.co.uk. 57 PublicSuffixList PublicSuffixList 58 } 59 60 // Jar implements the http.CookieJar interface from the net/http package. 61 type Jar struct { 62 psList PublicSuffixList 63 64 // mu locks the remaining fields. 65 mu sync.Mutex 66 67 // entries is a set of entries, keyed by their eTLD+1 and subkeyed by 68 // their name/domain/path. 69 entries map[string]map[string]entry 70 71 // nextSeqNum is the next sequence number assigned to a new cookie 72 // created SetCookies. 73 nextSeqNum uint64 74 } 75 76 // New returns a new cookie jar. A nil *Options is equivalent to a zero 77 // Options. 78 func New(o *Options) (*Jar, error) { 79 jar := &Jar{ 80 entries: make(map[string]map[string]entry), 81 } 82 if o != nil { 83 jar.psList = o.PublicSuffixList 84 } 85 return jar, nil 86 } 87 88 // entry is the internal representation of a cookie. 89 // 90 // This struct type is not used outside of this package per se, but the exported 91 // fields are those of RFC 6265. 92 type entry struct { 93 Name string 94 Value string 95 Domain string 96 Path string 97 SameSite string 98 Secure bool 99 HttpOnly bool 100 Persistent bool 101 HostOnly bool 102 Expires time.Time 103 Creation time.Time 104 LastAccess time.Time 105 106 // seqNum is a sequence number so that Cookies returns cookies in a 107 // deterministic order, even for cookies that have equal Path length and 108 // equal Creation time. This simplifies testing. 109 seqNum uint64 110 } 111 112 // id returns the domain;path;name triple of e as an id. 113 func (e *entry) id() string { 114 return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name) 115 } 116 117 // shouldSend determines whether e's cookie qualifies to be included in a 118 // request to host/path. It is the caller's responsibility to check if the 119 // cookie is expired. 120 func (e *entry) shouldSend(https bool, host, path string) bool { 121 return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure) 122 } 123 124 // domainMatch implements "domain-match" of RFC 6265 section 5.1.3. 125 func (e *entry) domainMatch(host string) bool { 126 if e.Domain == host { 127 return true 128 } 129 return !e.HostOnly && hasDotSuffix(host, e.Domain) 130 } 131 132 // pathMatch implements "path-match" according to RFC 6265 section 5.1.4. 133 func (e *entry) pathMatch(requestPath string) bool { 134 if requestPath == e.Path { 135 return true 136 } 137 if strings.HasPrefix(requestPath, e.Path) { 138 if e.Path[len(e.Path)-1] == '/' { 139 return true // The "/any/" matches "/any/path" case. 140 } else if requestPath[len(e.Path)] == '/' { 141 return true // The "/any" matches "/any/path" case. 142 } 143 } 144 return false 145 } 146 147 // hasDotSuffix reports whether s ends in "."+suffix. 148 func hasDotSuffix(s, suffix string) bool { 149 return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix 150 } 151 152 // Cookies implements the Cookies method of the http.CookieJar interface. 153 // 154 // It returns an empty slice if the URL's scheme is not HTTP or HTTPS. 155 func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) { 156 return j.cookies(u, time.Now()) 157 } 158 159 // cookies is like Cookies but takes the current time as a parameter. 160 func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) { 161 if u.Scheme != "http" && u.Scheme != "https" { 162 return cookies 163 } 164 host, err := canonicalHost(u.Host) 165 if err != nil { 166 return cookies 167 } 168 key := jarKey(host, j.psList) 169 170 j.mu.Lock() 171 defer j.mu.Unlock() 172 173 submap := j.entries[key] 174 if submap == nil { 175 return cookies 176 } 177 178 https := u.Scheme == "https" 179 path := u.Path 180 if path == "" { 181 path = "/" 182 } 183 184 modified := false 185 var selected []entry 186 for id, e := range submap { 187 if e.Persistent && !e.Expires.After(now) { 188 delete(submap, id) 189 modified = true 190 continue 191 } 192 if !e.shouldSend(https, host, path) { 193 continue 194 } 195 e.LastAccess = now 196 submap[id] = e 197 selected = append(selected, e) 198 modified = true 199 } 200 if modified { 201 if len(submap) == 0 { 202 delete(j.entries, key) 203 } else { 204 j.entries[key] = submap 205 } 206 } 207 208 // sort according to RFC 6265 section 5.4 point 2: by longest 209 // path and then by earliest creation time. 210 sort.Slice(selected, func(i, j int) bool { 211 s := selected 212 if len(s[i].Path) != len(s[j].Path) { 213 return len(s[i].Path) > len(s[j].Path) 214 } 215 if !s[i].Creation.Equal(s[j].Creation) { 216 return s[i].Creation.Before(s[j].Creation) 217 } 218 return s[i].seqNum < s[j].seqNum 219 }) 220 for _, e := range selected { 221 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value}) 222 } 223 224 return cookies 225 } 226 227 // SetCookies implements the SetCookies method of the http.CookieJar interface. 228 // 229 // It does nothing if the URL's scheme is not HTTP or HTTPS. 230 func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) { 231 j.setCookies(u, cookies, time.Now()) 232 } 233 234 // setCookies is like SetCookies but takes the current time as parameter. 235 func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) { 236 if len(cookies) == 0 { 237 return 238 } 239 if u.Scheme != "http" && u.Scheme != "https" { 240 return 241 } 242 host, err := canonicalHost(u.Host) 243 if err != nil { 244 return 245 } 246 key := jarKey(host, j.psList) 247 defPath := defaultPath(u.Path) 248 249 j.mu.Lock() 250 defer j.mu.Unlock() 251 252 submap := j.entries[key] 253 254 modified := false 255 for _, cookie := range cookies { 256 e, remove, err := j.newEntry(cookie, now, defPath, host) 257 if err != nil { 258 continue 259 } 260 id := e.id() 261 if remove { 262 if submap != nil { 263 if _, ok := submap[id]; ok { 264 delete(submap, id) 265 modified = true 266 } 267 } 268 continue 269 } 270 if submap == nil { 271 submap = make(map[string]entry) 272 } 273 274 if old, ok := submap[id]; ok { 275 e.Creation = old.Creation 276 e.seqNum = old.seqNum 277 } else { 278 e.Creation = now 279 e.seqNum = j.nextSeqNum 280 j.nextSeqNum++ 281 } 282 e.LastAccess = now 283 submap[id] = e 284 modified = true 285 } 286 287 if modified { 288 if len(submap) == 0 { 289 delete(j.entries, key) 290 } else { 291 j.entries[key] = submap 292 } 293 } 294 } 295 296 // canonicalHost strips port from host if present and returns the canonicalized 297 // host name. 298 func canonicalHost(host string) (string, error) { 299 var err error 300 if hasPort(host) { 301 host, _, err = net.SplitHostPort(host) 302 if err != nil { 303 return "", err 304 } 305 } 306 if strings.HasSuffix(host, ".") { 307 // Strip trailing dot from fully qualified domain names. 308 host = host[:len(host)-1] 309 } 310 encoded, err := toASCII(host) 311 if err != nil { 312 return "", err 313 } 314 // We know this is ascii, no need to check. 315 lower, _ := ascii.ToLower(encoded) 316 return lower, nil 317 } 318 319 // hasPort reports whether host contains a port number. host may be a host 320 // name, an IPv4 or an IPv6 address. 321 func hasPort(host string) bool { 322 colons := strings.Count(host, ":") 323 if colons == 0 { 324 return false 325 } 326 if colons == 1 { 327 return true 328 } 329 return host[0] == '[' && strings.Contains(host, "]:") 330 } 331 332 // jarKey returns the key to use for a jar. 333 func jarKey(host string, psl PublicSuffixList) string { 334 if isIP(host) { 335 return host 336 } 337 338 var i int 339 if psl == nil { 340 i = strings.LastIndex(host, ".") 341 if i <= 0 { 342 return host 343 } 344 } else { 345 suffix := psl.PublicSuffix(host) 346 if suffix == host { 347 return host 348 } 349 i = len(host) - len(suffix) 350 if i <= 0 || host[i-1] != '.' { 351 // The provided public suffix list psl is broken. 352 // Storing cookies under host is a safe stopgap. 353 return host 354 } 355 // Only len(suffix) is used to determine the jar key from 356 // here on, so it is okay if psl.PublicSuffix("www.buggy.psl") 357 // returns "com" as the jar key is generated from host. 358 } 359 prevDot := strings.LastIndex(host[:i-1], ".") 360 return host[prevDot+1:] 361 } 362 363 // isIP reports whether host is an IP address. 364 func isIP(host string) bool { 365 return net.ParseIP(host) != nil 366 } 367 368 // defaultPath returns the directory part of an URL's path according to 369 // RFC 6265 section 5.1.4. 370 func defaultPath(path string) string { 371 if len(path) == 0 || path[0] != '/' { 372 return "/" // Path is empty or malformed. 373 } 374 375 i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1. 376 if i == 0 { 377 return "/" // Path has the form "/abc". 378 } 379 return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/". 380 } 381 382 // newEntry creates an entry from a http.Cookie c. now is the current time and 383 // is compared to c.Expires to determine deletion of c. defPath and host are the 384 // default-path and the canonical host name of the URL c was received from. 385 // 386 // remove records whether the jar should delete this cookie, as it has already 387 // expired with respect to now. In this case, e may be incomplete, but it will 388 // be valid to call e.id (which depends on e's Name, Domain and Path). 389 // 390 // A malformed c.Domain will result in an error. 391 func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) { 392 e.Name = c.Name 393 394 if c.Path == "" || c.Path[0] != '/' { 395 e.Path = defPath 396 } else { 397 e.Path = c.Path 398 } 399 400 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain) 401 if err != nil { 402 return e, false, err 403 } 404 405 // MaxAge takes precedence over Expires. 406 if c.MaxAge < 0 { 407 return e, true, nil 408 } else if c.MaxAge > 0 { 409 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second) 410 e.Persistent = true 411 } else { 412 if c.Expires.IsZero() { 413 e.Expires = endOfTime 414 e.Persistent = false 415 } else { 416 if !c.Expires.After(now) { 417 return e, true, nil 418 } 419 e.Expires = c.Expires 420 e.Persistent = true 421 } 422 } 423 424 e.Value = c.Value 425 e.Secure = c.Secure 426 e.HttpOnly = c.HttpOnly 427 428 switch c.SameSite { 429 case http.SameSiteDefaultMode: 430 e.SameSite = "SameSite" 431 case http.SameSiteStrictMode: 432 e.SameSite = "SameSite=Strict" 433 case http.SameSiteLaxMode: 434 e.SameSite = "SameSite=Lax" 435 } 436 437 return e, false, nil 438 } 439 440 var ( 441 errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute") 442 errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute") 443 errNoHostname = errors.New("cookiejar: no host name available (IP only)") 444 ) 445 446 // endOfTime is the time when session (non-persistent) cookies expire. 447 // This instant is representable in most date/time formats (not just 448 // Go's time.Time) and should be far enough in the future. 449 var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC) 450 451 // domainAndType determines the cookie's domain and hostOnly attribute. 452 func (j *Jar) domainAndType(host, domain string) (string, bool, error) { 453 if domain == "" { 454 // No domain attribute in the SetCookie header indicates a 455 // host cookie. 456 return host, true, nil 457 } 458 459 if isIP(host) { 460 // According to RFC 6265 domain-matching includes not being 461 // an IP address. 462 // TODO: This might be relaxed as in common browsers. 463 return "", false, errNoHostname 464 } 465 466 // From here on: If the cookie is valid, it is a domain cookie (with 467 // the one exception of a public suffix below). 468 // See RFC 6265 section 5.2.3. 469 if domain[0] == '.' { 470 domain = domain[1:] 471 } 472 473 if len(domain) == 0 || domain[0] == '.' { 474 // Received either "Domain=." or "Domain=..some.thing", 475 // both are illegal. 476 return "", false, errMalformedDomain 477 } 478 479 domain, isASCII := ascii.ToLower(domain) 480 if !isASCII { 481 // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com" 482 return "", false, errMalformedDomain 483 } 484 485 if domain[len(domain)-1] == '.' { 486 // We received stuff like "Domain=www.example.com.". 487 // Browsers do handle such stuff (actually differently) but 488 // RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in 489 // requiring a reject. 4.1.2.3 is not normative, but 490 // "Domain Matching" (5.1.3) and "Canonicalized Host Names" 491 // (5.1.2) are. 492 return "", false, errMalformedDomain 493 } 494 495 // See RFC 6265 section 5.3 #5. 496 if j.psList != nil { 497 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) { 498 if host == domain { 499 // This is the one exception in which a cookie 500 // with a domain attribute is a host cookie. 501 return host, true, nil 502 } 503 return "", false, errIllegalDomain 504 } 505 } 506 507 // The domain must domain-match host: www.mycompany.com cannot 508 // set cookies for .ourcompetitors.com. 509 if host != domain && !hasDotSuffix(host, domain) { 510 return "", false, errIllegalDomain 511 } 512 513 return domain, false, nil 514 }