...
Run Format

Source file src/net/http/cookiejar/jar.go

Documentation: net/http/cookiejar

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

View as plain text