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