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.
5 // Package cookiejar implements an in-memory RFC 6265-compliant http.CookieJar.
13 "net/http/internal/ascii"
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".
26 // Implementations of PublicSuffixList must be safe for concurrent use by
27 // multiple goroutines.
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.
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.
38 // TODO: specify which of the caller and callee is responsible for IP
39 // addresses, for leading and trailing dots, for case sensitivity, and
41 PublicSuffix(domain string) string
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.
49 // Options are the options for creating a new Jar.
51 // PublicSuffixList is the public suffix list that determines whether
52 // an HTTP server can set a cookie for a domain.
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
57 PublicSuffixList PublicSuffixList
60 // Jar implements the http.CookieJar interface from the net/http package.
62 psList PublicSuffixList
64 // mu locks the remaining fields.
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
71 // nextSeqNum is the next sequence number assigned to a new cookie
72 // created SetCookies.
76 // New returns a new cookie jar. A nil *Options is equivalent to a zero
78 func New(o *Options) (*Jar, error) {
80 entries: make(map[string]map[string]entry),
83 jar.psList = o.PublicSuffixList
88 // entry is the internal representation of a cookie.
90 // This struct type is not used outside of this package per se, but the exported
91 // fields are those of RFC 6265.
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.
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)
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)
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 {
131 return !e.HostOnly && hasDotSuffix(host, e.Domain)
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 {
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.
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
154 // Cookies implements the Cookies method of the http.CookieJar interface.
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())
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" {
166 host, err := canonicalHost(u.Host)
170 key := jarKey(host, j.psList)
175 submap := j.entries[key]
180 https := u.Scheme == "https"
188 for id, e := range submap {
189 if e.Persistent && !e.Expires.After(now) {
194 if !e.shouldSend(https, host, path) {
199 selected = append(selected, e)
203 if len(submap) == 0 {
204 delete(j.entries, key)
206 j.entries[key] = submap
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 {
214 if len(s[i].Path) != len(s[j].Path) {
215 return len(s[i].Path) > len(s[j].Path)
217 if ret := s[i].Creation.Compare(s[j].Creation); ret != 0 {
220 return s[i].seqNum < s[j].seqNum
222 for _, e := range selected {
223 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
229 // SetCookies implements the SetCookies method of the http.CookieJar interface.
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())
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 {
241 if u.Scheme != "http" && u.Scheme != "https" {
244 host, err := canonicalHost(u.Host)
248 key := jarKey(host, j.psList)
249 defPath := defaultPath(u.Path)
254 submap := j.entries[key]
257 for _, cookie := range cookies {
258 e, remove, err := j.newEntry(cookie, now, defPath, host)
265 if _, ok := submap[id]; ok {
273 submap = make(map[string]entry)
276 if old, ok := submap[id]; ok {
277 e.Creation = old.Creation
278 e.seqNum = old.seqNum
281 e.seqNum = j.nextSeqNum
290 if len(submap) == 0 {
291 delete(j.entries, key)
293 j.entries[key] = submap
298 // canonicalHost strips port from host if present and returns the canonicalized
300 func canonicalHost(host string) (string, error) {
303 host, _, err = net.SplitHostPort(host)
308 // Strip trailing dot from fully qualified domain names.
309 host = strings.TrimSuffix(host, ".")
310 encoded, err := toASCII(host)
314 // We know this is ascii, no need to check.
315 lower, _ := ascii.ToLower(encoded)
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, ":")
329 return host[0] == '[' && strings.Contains(host, "]:")
332 // jarKey returns the key to use for a jar.
333 func jarKey(host string, psl PublicSuffixList) string {
340 i = strings.LastIndex(host, ".")
345 suffix := psl.PublicSuffix(host)
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.
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.
359 prevDot := strings.LastIndex(host[:i-1], ".")
360 return host[prevDot+1:]
363 // isIP reports whether host is an IP address.
364 func isIP(host string) bool {
365 return net.ParseIP(host) != nil
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.
375 i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
377 return "/" // Path has the form "/abc".
379 return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
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.
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).
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) {
394 if c.Path == "" || c.Path[0] != '/' {
400 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
405 // MaxAge takes precedence over Expires.
408 } else if c.MaxAge > 0 {
409 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
412 if c.Expires.IsZero() {
413 e.Expires = endOfTime
416 if !c.Expires.After(now) {
419 e.Expires = c.Expires
426 e.HttpOnly = c.HttpOnly
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"
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)")
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)
451 // domainAndType determines the cookie's domain and hostOnly attribute.
452 func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
454 // No domain attribute in the SetCookie header indicates a
456 return host, true, nil
460 // RFC 6265 is not super clear here, a sensible interpretation
461 // is that cookies with an IP address in the domain-attribute
464 // RFC 6265 section 5.2.3 mandates to strip an optional leading
465 // dot in the domain-attribute before processing the cookie.
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
477 return "", false, errIllegalDomain
480 // According to RFC 6265 such cookies should be treated as
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
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] == '.' {
499 if len(domain) == 0 || domain[0] == '.' {
500 // Received either "Domain=." or "Domain=..some.thing",
502 return "", false, errMalformedDomain
505 domain, isASCII := ascii.ToLower(domain)
507 // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com"
508 return "", false, errMalformedDomain
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"
518 return "", false, errMalformedDomain
521 // See RFC 6265 section 5.3 #5.
523 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
525 // This is the one exception in which a cookie
526 // with a domain attribute is a host cookie.
527 return host, true, nil
529 return "", false, errIllegalDomain
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
539 return domain, false, nil