github.com/ader1990/go@v0.0.0-20140630135419-8c24447fa791/src/pkg/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/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  // code.google.com/p/go.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  // byPathLength is a []entry sort.Interface that sorts according to RFC 6265
   151  // section 5.4 point 2: by longest path and then by earliest creation time.
   152  type byPathLength []entry
   153  
   154  func (s byPathLength) Len() int { return len(s) }
   155  
   156  func (s byPathLength) Less(i, j int) bool {
   157  	if len(s[i].Path) != len(s[j].Path) {
   158  		return len(s[i].Path) > len(s[j].Path)
   159  	}
   160  	if !s[i].Creation.Equal(s[j].Creation) {
   161  		return s[i].Creation.Before(s[j].Creation)
   162  	}
   163  	return s[i].seqNum < s[j].seqNum
   164  }
   165  
   166  func (s byPathLength) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
   167  
   168  // Cookies implements the Cookies method of the http.CookieJar interface.
   169  //
   170  // It returns an empty slice if the URL's scheme is not HTTP or HTTPS.
   171  func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
   172  	return j.cookies(u, time.Now())
   173  }
   174  
   175  // cookies is like Cookies but takes the current time as a parameter.
   176  func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
   177  	if u.Scheme != "http" && u.Scheme != "https" {
   178  		return cookies
   179  	}
   180  	host, err := canonicalHost(u.Host)
   181  	if err != nil {
   182  		return cookies
   183  	}
   184  	key := jarKey(host, j.psList)
   185  
   186  	j.mu.Lock()
   187  	defer j.mu.Unlock()
   188  
   189  	submap := j.entries[key]
   190  	if submap == nil {
   191  		return cookies
   192  	}
   193  
   194  	https := u.Scheme == "https"
   195  	path := u.Path
   196  	if path == "" {
   197  		path = "/"
   198  	}
   199  
   200  	modified := false
   201  	var selected []entry
   202  	for id, e := range submap {
   203  		if e.Persistent && !e.Expires.After(now) {
   204  			delete(submap, id)
   205  			modified = true
   206  			continue
   207  		}
   208  		if !e.shouldSend(https, host, path) {
   209  			continue
   210  		}
   211  		e.LastAccess = now
   212  		submap[id] = e
   213  		selected = append(selected, e)
   214  		modified = true
   215  	}
   216  	if modified {
   217  		if len(submap) == 0 {
   218  			delete(j.entries, key)
   219  		} else {
   220  			j.entries[key] = submap
   221  		}
   222  	}
   223  
   224  	sort.Sort(byPathLength(selected))
   225  	for _, e := range selected {
   226  		cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
   227  	}
   228  
   229  	return cookies
   230  }
   231  
   232  // SetCookies implements the SetCookies method of the http.CookieJar interface.
   233  //
   234  // It does nothing if the URL's scheme is not HTTP or HTTPS.
   235  func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
   236  	j.setCookies(u, cookies, time.Now())
   237  }
   238  
   239  // setCookies is like SetCookies but takes the current time as parameter.
   240  func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
   241  	if len(cookies) == 0 {
   242  		return
   243  	}
   244  	if u.Scheme != "http" && u.Scheme != "https" {
   245  		return
   246  	}
   247  	host, err := canonicalHost(u.Host)
   248  	if err != nil {
   249  		return
   250  	}
   251  	key := jarKey(host, j.psList)
   252  	defPath := defaultPath(u.Path)
   253  
   254  	j.mu.Lock()
   255  	defer j.mu.Unlock()
   256  
   257  	submap := j.entries[key]
   258  
   259  	modified := false
   260  	for _, cookie := range cookies {
   261  		e, remove, err := j.newEntry(cookie, now, defPath, host)
   262  		if err != nil {
   263  			continue
   264  		}
   265  		id := e.id()
   266  		if remove {
   267  			if submap != nil {
   268  				if _, ok := submap[id]; ok {
   269  					delete(submap, id)
   270  					modified = true
   271  				}
   272  			}
   273  			continue
   274  		}
   275  		if submap == nil {
   276  			submap = make(map[string]entry)
   277  		}
   278  
   279  		if old, ok := submap[id]; ok {
   280  			e.Creation = old.Creation
   281  			e.seqNum = old.seqNum
   282  		} else {
   283  			e.Creation = now
   284  			e.seqNum = j.nextSeqNum
   285  			j.nextSeqNum++
   286  		}
   287  		e.LastAccess = now
   288  		submap[id] = e
   289  		modified = true
   290  	}
   291  
   292  	if modified {
   293  		if len(submap) == 0 {
   294  			delete(j.entries, key)
   295  		} else {
   296  			j.entries[key] = submap
   297  		}
   298  	}
   299  }
   300  
   301  // canonicalHost strips port from host if present and returns the canonicalized
   302  // host name.
   303  func canonicalHost(host string) (string, error) {
   304  	var err error
   305  	host = strings.ToLower(host)
   306  	if hasPort(host) {
   307  		host, _, err = net.SplitHostPort(host)
   308  		if err != nil {
   309  			return "", err
   310  		}
   311  	}
   312  	if strings.HasSuffix(host, ".") {
   313  		// Strip trailing dot from fully qualified domain names.
   314  		host = host[:len(host)-1]
   315  	}
   316  	return toASCII(host)
   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 == -1 {
   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  	}
   356  	prevDot := strings.LastIndex(host[:i-1], ".")
   357  	return host[prevDot+1:]
   358  }
   359  
   360  // isIP reports whether host is an IP address.
   361  func isIP(host string) bool {
   362  	return net.ParseIP(host) != nil
   363  }
   364  
   365  // defaultPath returns the directory part of an URL's path according to
   366  // RFC 6265 section 5.1.4.
   367  func defaultPath(path string) string {
   368  	if len(path) == 0 || path[0] != '/' {
   369  		return "/" // Path is empty or malformed.
   370  	}
   371  
   372  	i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
   373  	if i == 0 {
   374  		return "/" // Path has the form "/abc".
   375  	}
   376  	return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
   377  }
   378  
   379  // newEntry creates an entry from a http.Cookie c. now is the current time and
   380  // is compared to c.Expires to determine deletion of c. defPath and host are the
   381  // default-path and the canonical host name of the URL c was received from.
   382  //
   383  // remove records whether the jar should delete this cookie, as it has already
   384  // expired with respect to now. In this case, e may be incomplete, but it will
   385  // be valid to call e.id (which depends on e's Name, Domain and Path).
   386  //
   387  // A malformed c.Domain will result in an error.
   388  func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
   389  	e.Name = c.Name
   390  
   391  	if c.Path == "" || c.Path[0] != '/' {
   392  		e.Path = defPath
   393  	} else {
   394  		e.Path = c.Path
   395  	}
   396  
   397  	e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
   398  	if err != nil {
   399  		return e, false, err
   400  	}
   401  
   402  	// MaxAge takes precedence over Expires.
   403  	if c.MaxAge < 0 {
   404  		return e, true, nil
   405  	} else if c.MaxAge > 0 {
   406  		e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
   407  		e.Persistent = true
   408  	} else {
   409  		if c.Expires.IsZero() {
   410  			e.Expires = endOfTime
   411  			e.Persistent = false
   412  		} else {
   413  			if !c.Expires.After(now) {
   414  				return e, true, nil
   415  			}
   416  			e.Expires = c.Expires
   417  			e.Persistent = true
   418  		}
   419  	}
   420  
   421  	e.Value = c.Value
   422  	e.Secure = c.Secure
   423  	e.HttpOnly = c.HttpOnly
   424  
   425  	return e, false, nil
   426  }
   427  
   428  var (
   429  	errIllegalDomain   = errors.New("cookiejar: illegal cookie domain attribute")
   430  	errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
   431  	errNoHostname      = errors.New("cookiejar: no host name available (IP only)")
   432  )
   433  
   434  // endOfTime is the time when session (non-persistent) cookies expire.
   435  // This instant is representable in most date/time formats (not just
   436  // Go's time.Time) and should be far enough in the future.
   437  var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
   438  
   439  // domainAndType determines the cookie's domain and hostOnly attribute.
   440  func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
   441  	if domain == "" {
   442  		// No domain attribute in the SetCookie header indicates a
   443  		// host cookie.
   444  		return host, true, nil
   445  	}
   446  
   447  	if isIP(host) {
   448  		// According to RFC 6265 domain-matching includes not being
   449  		// an IP address.
   450  		// TODO: This might be relaxed as in common browsers.
   451  		return "", false, errNoHostname
   452  	}
   453  
   454  	// From here on: If the cookie is valid, it is a domain cookie (with
   455  	// the one exception of a public suffix below).
   456  	// See RFC 6265 section 5.2.3.
   457  	if domain[0] == '.' {
   458  		domain = domain[1:]
   459  	}
   460  
   461  	if len(domain) == 0 || domain[0] == '.' {
   462  		// Received either "Domain=." or "Domain=..some.thing",
   463  		// both are illegal.
   464  		return "", false, errMalformedDomain
   465  	}
   466  	domain = strings.ToLower(domain)
   467  
   468  	if domain[len(domain)-1] == '.' {
   469  		// We received stuff like "Domain=www.example.com.".
   470  		// Browsers do handle such stuff (actually differently) but
   471  		// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
   472  		// requiring a reject.  4.1.2.3 is not normative, but
   473  		// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
   474  		// (5.1.2) are.
   475  		return "", false, errMalformedDomain
   476  	}
   477  
   478  	// See RFC 6265 section 5.3 #5.
   479  	if j.psList != nil {
   480  		if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
   481  			if host == domain {
   482  				// This is the one exception in which a cookie
   483  				// with a domain attribute is a host cookie.
   484  				return host, true, nil
   485  			}
   486  			return "", false, errIllegalDomain
   487  		}
   488  	}
   489  
   490  	// The domain must domain-match host: www.mycompany.com cannot
   491  	// set cookies for .ourcompetitors.com.
   492  	if host != domain && !hasDotSuffix(host, domain) {
   493  		return "", false, errIllegalDomain
   494  	}
   495  
   496  	return domain, false, nil
   497  }