...
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  	Secure     bool
    97  	HttpOnly   bool
    98  	Persistent bool
    99  	HostOnly   bool
   100  	Expires    time.Time
   101  	Creation   time.Time
   102  	LastAccess time.Time
   103  
   104  	// seqNum is a sequence number so that Cookies returns cookies in a
   105  	// deterministic order, even for cookies that have equal Path length and
   106  	// equal Creation time. This simplifies testing.
   107  	seqNum uint64
   108  }
   109  
   110  // id returns the domain;path;name triple of e as an id.
   111  func (e *entry) id() string {
   112  	return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
   113  }
   114  
   115  // shouldSend determines whether e's cookie qualifies to be included in a
   116  // request to host/path. It is the caller's responsibility to check if the
   117  // cookie is expired.
   118  func (e *entry) shouldSend(https bool, host, path string) bool {
   119  	return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
   120  }
   121  
   122  // domainMatch implements "domain-match" of RFC 6265 section 5.1.3.
   123  func (e *entry) domainMatch(host string) bool {
   124  	if e.Domain == host {
   125  		return true
   126  	}
   127  	return !e.HostOnly && hasDotSuffix(host, e.Domain)
   128  }
   129  
   130  // pathMatch implements "path-match" according to RFC 6265 section 5.1.4.
   131  func (e *entry) pathMatch(requestPath string) bool {
   132  	if requestPath == e.Path {
   133  		return true
   134  	}
   135  	if strings.HasPrefix(requestPath, e.Path) {
   136  		if e.Path[len(e.Path)-1] == '/' {
   137  			return true // The "/any/" matches "/any/path" case.
   138  		} else if requestPath[len(e.Path)] == '/' {
   139  			return true // The "/any" matches "/any/path" case.
   140  		}
   141  	}
   142  	return false
   143  }
   144  
   145  // hasDotSuffix reports whether s ends in "."+suffix.
   146  func hasDotSuffix(s, suffix string) bool {
   147  	return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
   148  }
   149  
   150  // Cookies implements the Cookies method of the http.CookieJar interface.
   151  //
   152  // It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
   153  func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
   154  	return j.cookies(u, time.Now())
   155  }
   156  
   157  // cookies is like Cookies but takes the current time as a parameter.
   158  func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
   159  	if u.Scheme != "http" && u.Scheme != "https" {
   160  		return cookies
   161  	}
   162  	host, err := canonicalHost(u.Host)
   163  	if err != nil {
   164  		return cookies
   165  	}
   166  	key := jarKey(host, j.psList)
   167  
   168  	j.mu.Lock()
   169  	defer j.mu.Unlock()
   170  
   171  	submap := j.entries[key]
   172  	if submap == nil {
   173  		return cookies
   174  	}
   175  
   176  	https := u.Scheme == "https"
   177  	path := u.Path
   178  	if path == "" {
   179  		path = "/"
   180  	}
   181  
   182  	modified := false
   183  	var selected []entry
   184  	for id, e := range submap {
   185  		if e.Persistent && !e.Expires.After(now) {
   186  			delete(submap, id)
   187  			modified = true
   188  			continue
   189  		}
   190  		if !e.shouldSend(https, host, path) {
   191  			continue
   192  		}
   193  		e.LastAccess = now
   194  		submap[id] = e
   195  		selected = append(selected, e)
   196  		modified = true
   197  	}
   198  	if modified {
   199  		if len(submap) == 0 {
   200  			delete(j.entries, key)
   201  		} else {
   202  			j.entries[key] = submap
   203  		}
   204  	}
   205  
   206  	// sort according to RFC 6265 section 5.4 point 2: by longest
   207  	// path and then by earliest creation time.
   208  	sort.Slice(selected, func(i, j int) bool {
   209  		s := selected
   210  		if len(s[i].Path) != len(s[j].Path) {
   211  			return len(s[i].Path) > len(s[j].Path)
   212  		}
   213  		if !s[i].Creation.Equal(s[j].Creation) {
   214  			return s[i].Creation.Before(s[j].Creation)
   215  		}
   216  		return s[i].seqNum < s[j].seqNum
   217  	})
   218  	for _, e := range selected {
   219  		cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
   220  	}
   221  
   222  	return cookies
   223  }
   224  
   225  // SetCookies implements the SetCookies method of the http.CookieJar interface.
   226  //
   227  // It does nothing if the URL's scheme is not HTTP or HTTPS.
   228  func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
   229  	j.setCookies(u, cookies, time.Now())
   230  }
   231  
   232  // setCookies is like SetCookies but takes the current time as parameter.
   233  func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
   234  	if len(cookies) == 0 {
   235  		return
   236  	}
   237  	if u.Scheme != "http" && u.Scheme != "https" {
   238  		return
   239  	}
   240  	host, err := canonicalHost(u.Host)
   241  	if err != nil {
   242  		return
   243  	}
   244  	key := jarKey(host, j.psList)
   245  	defPath := defaultPath(u.Path)
   246  
   247  	j.mu.Lock()
   248  	defer j.mu.Unlock()
   249  
   250  	submap := j.entries[key]
   251  
   252  	modified := false
   253  	for _, cookie := range cookies {
   254  		e, remove, err := j.newEntry(cookie, now, defPath, host)
   255  		if err != nil {
   256  			continue
   257  		}
   258  		id := e.id()
   259  		if remove {
   260  			if submap != nil {
   261  				if _, ok := submap[id]; ok {
   262  					delete(submap, id)
   263  					modified = true
   264  				}
   265  			}
   266  			continue
   267  		}
   268  		if submap == nil {
   269  			submap = make(map[string]entry)
   270  		}
   271  
   272  		if old, ok := submap[id]; ok {
   273  			e.Creation = old.Creation
   274  			e.seqNum = old.seqNum
   275  		} else {
   276  			e.Creation = now
   277  			e.seqNum = j.nextSeqNum
   278  			j.nextSeqNum++
   279  		}
   280  		e.LastAccess = now
   281  		submap[id] = e
   282  		modified = true
   283  	}
   284  
   285  	if modified {
   286  		if len(submap) == 0 {
   287  			delete(j.entries, key)
   288  		} else {
   289  			j.entries[key] = submap
   290  		}
   291  	}
   292  }
   293  
   294  // canonicalHost strips port from host if present and returns the canonicalized
   295  // host name.
   296  func canonicalHost(host string) (string, error) {
   297  	var err error
   298  	host = strings.ToLower(host)
   299  	if hasPort(host) {
   300  		host, _, err = net.SplitHostPort(host)
   301  		if err != nil {
   302  			return "", err
   303  		}
   304  	}
   305  	if strings.HasSuffix(host, ".") {
   306  		// Strip trailing dot from fully qualified domain names.
   307  		host = host[:len(host)-1]
   308  	}
   309  	return toASCII(host)
   310  }
   311  
   312  // hasPort reports whether host contains a port number. host may be a host
   313  // name, an IPv4 or an IPv6 address.
   314  func hasPort(host string) bool {
   315  	colons := strings.Count(host, ":")
   316  	if colons == 0 {
   317  		return false
   318  	}
   319  	if colons == 1 {
   320  		return true
   321  	}
   322  	return host[0] == '[' && strings.Contains(host, "]:")
   323  }
   324  
   325  // jarKey returns the key to use for a jar.
   326  func jarKey(host string, psl PublicSuffixList) string {
   327  	if isIP(host) {
   328  		return host
   329  	}
   330  
   331  	var i int
   332  	if psl == nil {
   333  		i = strings.LastIndex(host, ".")
   334  		if i <= 0 {
   335  			return host
   336  		}
   337  	} else {
   338  		suffix := psl.PublicSuffix(host)
   339  		if suffix == host {
   340  			return host
   341  		}
   342  		i = len(host) - len(suffix)
   343  		if i <= 0 || host[i-1] != '.' {
   344  			// The provided public suffix list psl is broken.
   345  			// Storing cookies under host is a safe stopgap.
   346  			return host
   347  		}
   348  		// Only len(suffix) is used to determine the jar key from
   349  		// here on, so it is okay if psl.PublicSuffix("www.buggy.psl")
   350  		// returns "com" as the jar key is generated from host.
   351  	}
   352  	prevDot := strings.LastIndex(host[:i-1], ".")
   353  	return host[prevDot+1:]
   354  }
   355  
   356  // isIP reports whether host is an IP address.
   357  func isIP(host string) bool {
   358  	return net.ParseIP(host) != nil
   359  }
   360  
   361  // defaultPath returns the directory part of an URL's path according to
   362  // RFC 6265 section 5.1.4.
   363  func defaultPath(path string) string {
   364  	if len(path) == 0 || path[0] != '/' {
   365  		return "/" // Path is empty or malformed.
   366  	}
   367  
   368  	i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
   369  	if i == 0 {
   370  		return "/" // Path has the form "/abc".
   371  	}
   372  	return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
   373  }
   374  
   375  // newEntry creates an entry from a http.Cookie c. now is the current time and
   376  // is compared to c.Expires to determine deletion of c. defPath and host are the
   377  // default-path and the canonical host name of the URL c was received from.
   378  //
   379  // remove records whether the jar should delete this cookie, as it has already
   380  // expired with respect to now. In this case, e may be incomplete, but it will
   381  // be valid to call e.id (which depends on e's Name, Domain and Path).
   382  //
   383  // A malformed c.Domain will result in an error.
   384  func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
   385  	e.Name = c.Name
   386  
   387  	if c.Path == "" || c.Path[0] != '/' {
   388  		e.Path = defPath
   389  	} else {
   390  		e.Path = c.Path
   391  	}
   392  
   393  	e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
   394  	if err != nil {
   395  		return e, false, err
   396  	}
   397  
   398  	// MaxAge takes precedence over Expires.
   399  	if c.MaxAge < 0 {
   400  		return e, true, nil
   401  	} else if c.MaxAge > 0 {
   402  		e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
   403  		e.Persistent = true
   404  	} else {
   405  		if c.Expires.IsZero() {
   406  			e.Expires = endOfTime
   407  			e.Persistent = false
   408  		} else {
   409  			if !c.Expires.After(now) {
   410  				return e, true, nil
   411  			}
   412  			e.Expires = c.Expires
   413  			e.Persistent = true
   414  		}
   415  	}
   416  
   417  	e.Value = c.Value
   418  	e.Secure = c.Secure
   419  	e.HttpOnly = c.HttpOnly
   420  
   421  	return e, false, nil
   422  }
   423  
   424  var (
   425  	errIllegalDomain   = errors.New("cookiejar: illegal cookie domain attribute")
   426  	errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
   427  	errNoHostname      = errors.New("cookiejar: no host name available (IP only)")
   428  )
   429  
   430  // endOfTime is the time when session (non-persistent) cookies expire.
   431  // This instant is representable in most date/time formats (not just
   432  // Go's time.Time) and should be far enough in the future.
   433  var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
   434  
   435  // domainAndType determines the cookie's domain and hostOnly attribute.
   436  func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
   437  	if domain == "" {
   438  		// No domain attribute in the SetCookie header indicates a
   439  		// host cookie.
   440  		return host, true, nil
   441  	}
   442  
   443  	if isIP(host) {
   444  		// According to RFC 6265 domain-matching includes not being
   445  		// an IP address.
   446  		// TODO: This might be relaxed as in common browsers.
   447  		return "", false, errNoHostname
   448  	}
   449  
   450  	// From here on: If the cookie is valid, it is a domain cookie (with
   451  	// the one exception of a public suffix below).
   452  	// See RFC 6265 section 5.2.3.
   453  	if domain[0] == '.' {
   454  		domain = domain[1:]
   455  	}
   456  
   457  	if len(domain) == 0 || domain[0] == '.' {
   458  		// Received either "Domain=." or "Domain=..some.thing",
   459  		// both are illegal.
   460  		return "", false, errMalformedDomain
   461  	}
   462  	domain = strings.ToLower(domain)
   463  
   464  	if domain[len(domain)-1] == '.' {
   465  		// We received stuff like "Domain=www.example.com.".
   466  		// Browsers do handle such stuff (actually differently) but
   467  		// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
   468  		// requiring a reject.  4.1.2.3 is not normative, but
   469  		// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
   470  		// (5.1.2) are.
   471  		return "", false, errMalformedDomain
   472  	}
   473  
   474  	// See RFC 6265 section 5.3 #5.
   475  	if j.psList != nil {
   476  		if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
   477  			if host == domain {
   478  				// This is the one exception in which a cookie
   479  				// with a domain attribute is a host cookie.
   480  				return host, true, nil
   481  			}
   482  			return "", false, errIllegalDomain
   483  		}
   484  	}
   485  
   486  	// The domain must domain-match host: www.mycompany.com cannot
   487  	// set cookies for .ourcompetitors.com.
   488  	if host != domain && !hasDotSuffix(host, domain) {
   489  		return "", false, errIllegalDomain
   490  	}
   491  
   492  	return domain, false, nil
   493  }
   494  

View as plain text