github.com/twelsh-aw/go/src@v0.0.0-20230516233729-a56fe86a7c81/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 return net.ParseIP(host) != nil 366 } 367 368 // defaultPath returns the directory part of a 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 an 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 // RFC 6265 is not super clear here, a sensible interpretation 461 // is that cookies with an IP address in the domain-attribute 462 // are allowed. 463 464 // RFC 6265 section 5.2.3 mandates to strip an optional leading 465 // dot in the domain-attribute before processing the cookie. 466 // 467 // Most browsers don't do that for IP addresses, only curl 468 // version 7.54) and IE (version 11) do not reject a 469 // Set-Cookie: a=1; domain=.127.0.0.1 470 // This leading dot is optional and serves only as hint for 471 // humans to indicate that a cookie with "domain=.bbc.co.uk" 472 // would be sent to every subdomain of bbc.co.uk. 473 // It just doesn't make sense on IP addresses. 474 // The other processing and validation steps in RFC 6265 just 475 // collaps to: 476 if host != domain { 477 return "", false, errIllegalDomain 478 } 479 480 // According to RFC 6265 such cookies should be treated as 481 // domain cookies. 482 // As there are no subdomains of an IP address the treatment 483 // according to RFC 6265 would be exactly the same as that of 484 // a host-only cookie. Contemporary browsers (and curl) do 485 // allows such cookies but treat them as host-only cookies. 486 // So do we as it just doesn't make sense to label them as 487 // domain cookies when there is no domain; the whole notion of 488 // domain cookies requires a domain name to be well defined. 489 return host, true, nil 490 } 491 492 // From here on: If the cookie is valid, it is a domain cookie (with 493 // the one exception of a public suffix below). 494 // See RFC 6265 section 5.2.3. 495 if domain[0] == '.' { 496 domain = domain[1:] 497 } 498 499 if len(domain) == 0 || domain[0] == '.' { 500 // Received either "Domain=." or "Domain=..some.thing", 501 // both are illegal. 502 return "", false, errMalformedDomain 503 } 504 505 domain, isASCII := ascii.ToLower(domain) 506 if !isASCII { 507 // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com" 508 return "", false, errMalformedDomain 509 } 510 511 if domain[len(domain)-1] == '.' { 512 // We received stuff like "Domain=www.example.com.". 513 // Browsers do handle such stuff (actually differently) but 514 // RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in 515 // requiring a reject. 4.1.2.3 is not normative, but 516 // "Domain Matching" (5.1.3) and "Canonicalized Host Names" 517 // (5.1.2) are. 518 return "", false, errMalformedDomain 519 } 520 521 // See RFC 6265 section 5.3 #5. 522 if j.psList != nil { 523 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) { 524 if host == domain { 525 // This is the one exception in which a cookie 526 // with a domain attribute is a host cookie. 527 return host, true, nil 528 } 529 return "", false, errIllegalDomain 530 } 531 } 532 533 // The domain must domain-match host: www.mycompany.com cannot 534 // set cookies for .ourcompetitors.com. 535 if host != domain && !hasDotSuffix(host, domain) { 536 return "", false, errIllegalDomain 537 } 538 539 return domain, false, nil 540 }