github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/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 checks whether e's Domain allows sending e back to host. 125 // It differs from "domain-match" of RFC 6265 section 5.1.3 because we treat 126 // a cookie with an IP address in the Domain always as a host cookie. 127 func (e *entry) domainMatch(host string) bool { 128 if e.Domain == host { 129 return true 130 } 131 return !e.HostOnly && hasDotSuffix(host, e.Domain) 132 } 133 134 // pathMatch implements "path-match" according to RFC 6265 section 5.1.4. 135 func (e *entry) pathMatch(requestPath string) bool { 136 if requestPath == e.Path { 137 return true 138 } 139 if strings.HasPrefix(requestPath, e.Path) { 140 if e.Path[len(e.Path)-1] == '/' { 141 return true // The "/any/" matches "/any/path" case. 142 } else if requestPath[len(e.Path)] == '/' { 143 return true // The "/any" matches "/any/path" case. 144 } 145 } 146 return false 147 } 148 149 // hasDotSuffix reports whether s ends in "."+suffix. 150 func hasDotSuffix(s, suffix string) bool { 151 return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix 152 } 153 154 // Cookies implements the Cookies method of the [http.CookieJar] interface. 155 // 156 // It returns an empty slice if the URL's scheme is not HTTP or HTTPS. 157 func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) { 158 return j.cookies(u, time.Now()) 159 } 160 161 // cookies is like Cookies but takes the current time as a parameter. 162 func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) { 163 if u.Scheme != "http" && u.Scheme != "https" { 164 return cookies 165 } 166 host, err := canonicalHost(u.Host) 167 if err != nil { 168 return cookies 169 } 170 key := jarKey(host, j.psList) 171 172 j.mu.Lock() 173 defer j.mu.Unlock() 174 175 submap := j.entries[key] 176 if submap == nil { 177 return cookies 178 } 179 180 https := u.Scheme == "https" 181 path := u.Path 182 if path == "" { 183 path = "/" 184 } 185 186 modified := false 187 var selected []entry 188 for id, e := range submap { 189 if e.Persistent && !e.Expires.After(now) { 190 delete(submap, id) 191 modified = true 192 continue 193 } 194 if !e.shouldSend(https, host, path) { 195 continue 196 } 197 e.LastAccess = now 198 submap[id] = e 199 selected = append(selected, e) 200 modified = true 201 } 202 if modified { 203 if len(submap) == 0 { 204 delete(j.entries, key) 205 } else { 206 j.entries[key] = submap 207 } 208 } 209 210 // sort according to RFC 6265 section 5.4 point 2: by longest 211 // path and then by earliest creation time. 212 sort.Slice(selected, func(i, j int) bool { 213 s := selected 214 if len(s[i].Path) != len(s[j].Path) { 215 return len(s[i].Path) > len(s[j].Path) 216 } 217 if ret := s[i].Creation.Compare(s[j].Creation); ret != 0 { 218 return ret < 0 219 } 220 return s[i].seqNum < s[j].seqNum 221 }) 222 for _, e := range selected { 223 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value}) 224 } 225 226 return cookies 227 } 228 229 // SetCookies implements the SetCookies method of the [http.CookieJar] interface. 230 // 231 // It does nothing if the URL's scheme is not HTTP or HTTPS. 232 func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) { 233 j.setCookies(u, cookies, time.Now()) 234 } 235 236 // setCookies is like SetCookies but takes the current time as parameter. 237 func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) { 238 if len(cookies) == 0 { 239 return 240 } 241 if u.Scheme != "http" && u.Scheme != "https" { 242 return 243 } 244 host, err := canonicalHost(u.Host) 245 if err != nil { 246 return 247 } 248 key := jarKey(host, j.psList) 249 defPath := defaultPath(u.Path) 250 251 j.mu.Lock() 252 defer j.mu.Unlock() 253 254 submap := j.entries[key] 255 256 modified := false 257 for _, cookie := range cookies { 258 e, remove, err := j.newEntry(cookie, now, defPath, host) 259 if err != nil { 260 continue 261 } 262 id := e.id() 263 if remove { 264 if submap != nil { 265 if _, ok := submap[id]; ok { 266 delete(submap, id) 267 modified = true 268 } 269 } 270 continue 271 } 272 if submap == nil { 273 submap = make(map[string]entry) 274 } 275 276 if old, ok := submap[id]; ok { 277 e.Creation = old.Creation 278 e.seqNum = old.seqNum 279 } else { 280 e.Creation = now 281 e.seqNum = j.nextSeqNum 282 j.nextSeqNum++ 283 } 284 e.LastAccess = now 285 submap[id] = e 286 modified = true 287 } 288 289 if modified { 290 if len(submap) == 0 { 291 delete(j.entries, key) 292 } else { 293 j.entries[key] = submap 294 } 295 } 296 } 297 298 // canonicalHost strips port from host if present and returns the canonicalized 299 // host name. 300 func canonicalHost(host string) (string, error) { 301 var err error 302 if hasPort(host) { 303 host, _, err = net.SplitHostPort(host) 304 if err != nil { 305 return "", err 306 } 307 } 308 // Strip trailing dot from fully qualified domain names. 309 host = strings.TrimSuffix(host, ".") 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 if strings.ContainsAny(host, ":%") { 366 // Probable IPv6 address. 367 // Hostnames can't contain : or %, so this is definitely not a valid host. 368 // Treating it as an IP is the more conservative option, and avoids the risk 369 // of interpeting ::1%.www.example.com as a subtomain of www.example.com. 370 return true 371 } 372 return net.ParseIP(host) != nil 373 } 374 375 // defaultPath returns the directory part of a URL's path according to 376 // RFC 6265 section 5.1.4. 377 func defaultPath(path string) string { 378 if len(path) == 0 || path[0] != '/' { 379 return "/" // Path is empty or malformed. 380 } 381 382 i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1. 383 if i == 0 { 384 return "/" // Path has the form "/abc". 385 } 386 return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/". 387 } 388 389 // newEntry creates an entry from an http.Cookie c. now is the current time and 390 // is compared to c.Expires to determine deletion of c. defPath and host are the 391 // default-path and the canonical host name of the URL c was received from. 392 // 393 // remove records whether the jar should delete this cookie, as it has already 394 // expired with respect to now. In this case, e may be incomplete, but it will 395 // be valid to call e.id (which depends on e's Name, Domain and Path). 396 // 397 // A malformed c.Domain will result in an error. 398 func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) { 399 e.Name = c.Name 400 401 if c.Path == "" || c.Path[0] != '/' { 402 e.Path = defPath 403 } else { 404 e.Path = c.Path 405 } 406 407 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain) 408 if err != nil { 409 return e, false, err 410 } 411 412 // MaxAge takes precedence over Expires. 413 if c.MaxAge < 0 { 414 return e, true, nil 415 } else if c.MaxAge > 0 { 416 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second) 417 e.Persistent = true 418 } else { 419 if c.Expires.IsZero() { 420 e.Expires = endOfTime 421 e.Persistent = false 422 } else { 423 if !c.Expires.After(now) { 424 return e, true, nil 425 } 426 e.Expires = c.Expires 427 e.Persistent = true 428 } 429 } 430 431 e.Value = c.Value 432 e.Secure = c.Secure 433 e.HttpOnly = c.HttpOnly 434 435 switch c.SameSite { 436 case http.SameSiteDefaultMode: 437 e.SameSite = "SameSite" 438 case http.SameSiteStrictMode: 439 e.SameSite = "SameSite=Strict" 440 case http.SameSiteLaxMode: 441 e.SameSite = "SameSite=Lax" 442 } 443 444 return e, false, nil 445 } 446 447 var ( 448 errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute") 449 errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute") 450 ) 451 452 // endOfTime is the time when session (non-persistent) cookies expire. 453 // This instant is representable in most date/time formats (not just 454 // Go's time.Time) and should be far enough in the future. 455 var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC) 456 457 // domainAndType determines the cookie's domain and hostOnly attribute. 458 func (j *Jar) domainAndType(host, domain string) (string, bool, error) { 459 if domain == "" { 460 // No domain attribute in the SetCookie header indicates a 461 // host cookie. 462 return host, true, nil 463 } 464 465 if isIP(host) { 466 // RFC 6265 is not super clear here, a sensible interpretation 467 // is that cookies with an IP address in the domain-attribute 468 // are allowed. 469 470 // RFC 6265 section 5.2.3 mandates to strip an optional leading 471 // dot in the domain-attribute before processing the cookie. 472 // 473 // Most browsers don't do that for IP addresses, only curl 474 // (version 7.54) and IE (version 11) do not reject a 475 // Set-Cookie: a=1; domain=.127.0.0.1 476 // This leading dot is optional and serves only as hint for 477 // humans to indicate that a cookie with "domain=.bbc.co.uk" 478 // would be sent to every subdomain of bbc.co.uk. 479 // It just doesn't make sense on IP addresses. 480 // The other processing and validation steps in RFC 6265 just 481 // collapse to: 482 if host != domain { 483 return "", false, errIllegalDomain 484 } 485 486 // According to RFC 6265 such cookies should be treated as 487 // domain cookies. 488 // As there are no subdomains of an IP address the treatment 489 // according to RFC 6265 would be exactly the same as that of 490 // a host-only cookie. Contemporary browsers (and curl) do 491 // allows such cookies but treat them as host-only cookies. 492 // So do we as it just doesn't make sense to label them as 493 // domain cookies when there is no domain; the whole notion of 494 // domain cookies requires a domain name to be well defined. 495 return host, true, nil 496 } 497 498 // From here on: If the cookie is valid, it is a domain cookie (with 499 // the one exception of a public suffix below). 500 // See RFC 6265 section 5.2.3. 501 if domain[0] == '.' { 502 domain = domain[1:] 503 } 504 505 if len(domain) == 0 || domain[0] == '.' { 506 // Received either "Domain=." or "Domain=..some.thing", 507 // both are illegal. 508 return "", false, errMalformedDomain 509 } 510 511 domain, isASCII := ascii.ToLower(domain) 512 if !isASCII { 513 // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com" 514 return "", false, errMalformedDomain 515 } 516 517 if domain[len(domain)-1] == '.' { 518 // We received stuff like "Domain=www.example.com.". 519 // Browsers do handle such stuff (actually differently) but 520 // RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in 521 // requiring a reject. 4.1.2.3 is not normative, but 522 // "Domain Matching" (5.1.3) and "Canonicalized Host Names" 523 // (5.1.2) are. 524 return "", false, errMalformedDomain 525 } 526 527 // See RFC 6265 section 5.3 #5. 528 if j.psList != nil { 529 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) { 530 if host == domain { 531 // This is the one exception in which a cookie 532 // with a domain attribute is a host cookie. 533 return host, true, nil 534 } 535 return "", false, errIllegalDomain 536 } 537 } 538 539 // The domain must domain-match host: www.mycompany.com cannot 540 // set cookies for .ourcompetitors.com. 541 if host != domain && !hasDotSuffix(host, domain) { 542 return "", false, errIllegalDomain 543 } 544 545 return domain, false, nil 546 }