...
Run Format

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

     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 == -1 {
   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		}
   349		prevDot := strings.LastIndex(host[:i-1], ".")
   350		return host[prevDot+1:]
   351	}
   352	
   353	// isIP reports whether host is an IP address.
   354	func isIP(host string) bool {
   355		return net.ParseIP(host) != nil
   356	}
   357	
   358	// defaultPath returns the directory part of an URL's path according to
   359	// RFC 6265 section 5.1.4.
   360	func defaultPath(path string) string {
   361		if len(path) == 0 || path[0] != '/' {
   362			return "/" // Path is empty or malformed.
   363		}
   364	
   365		i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.
   366		if i == 0 {
   367			return "/" // Path has the form "/abc".
   368		}
   369		return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".
   370	}
   371	
   372	// newEntry creates an entry from a http.Cookie c. now is the current time and
   373	// is compared to c.Expires to determine deletion of c. defPath and host are the
   374	// default-path and the canonical host name of the URL c was received from.
   375	//
   376	// remove records whether the jar should delete this cookie, as it has already
   377	// expired with respect to now. In this case, e may be incomplete, but it will
   378	// be valid to call e.id (which depends on e's Name, Domain and Path).
   379	//
   380	// A malformed c.Domain will result in an error.
   381	func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
   382		e.Name = c.Name
   383	
   384		if c.Path == "" || c.Path[0] != '/' {
   385			e.Path = defPath
   386		} else {
   387			e.Path = c.Path
   388		}
   389	
   390		e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
   391		if err != nil {
   392			return e, false, err
   393		}
   394	
   395		// MaxAge takes precedence over Expires.
   396		if c.MaxAge < 0 {
   397			return e, true, nil
   398		} else if c.MaxAge > 0 {
   399			e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
   400			e.Persistent = true
   401		} else {
   402			if c.Expires.IsZero() {
   403				e.Expires = endOfTime
   404				e.Persistent = false
   405			} else {
   406				if !c.Expires.After(now) {
   407					return e, true, nil
   408				}
   409				e.Expires = c.Expires
   410				e.Persistent = true
   411			}
   412		}
   413	
   414		e.Value = c.Value
   415		e.Secure = c.Secure
   416		e.HttpOnly = c.HttpOnly
   417	
   418		return e, false, nil
   419	}
   420	
   421	var (
   422		errIllegalDomain   = errors.New("cookiejar: illegal cookie domain attribute")
   423		errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
   424		errNoHostname      = errors.New("cookiejar: no host name available (IP only)")
   425	)
   426	
   427	// endOfTime is the time when session (non-persistent) cookies expire.
   428	// This instant is representable in most date/time formats (not just
   429	// Go's time.Time) and should be far enough in the future.
   430	var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
   431	
   432	// domainAndType determines the cookie's domain and hostOnly attribute.
   433	func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
   434		if domain == "" {
   435			// No domain attribute in the SetCookie header indicates a
   436			// host cookie.
   437			return host, true, nil
   438		}
   439	
   440		if isIP(host) {
   441			// According to RFC 6265 domain-matching includes not being
   442			// an IP address.
   443			// TODO: This might be relaxed as in common browsers.
   444			return "", false, errNoHostname
   445		}
   446	
   447		// From here on: If the cookie is valid, it is a domain cookie (with
   448		// the one exception of a public suffix below).
   449		// See RFC 6265 section 5.2.3.
   450		if domain[0] == '.' {
   451			domain = domain[1:]
   452		}
   453	
   454		if len(domain) == 0 || domain[0] == '.' {
   455			// Received either "Domain=." or "Domain=..some.thing",
   456			// both are illegal.
   457			return "", false, errMalformedDomain
   458		}
   459		domain = strings.ToLower(domain)
   460	
   461		if domain[len(domain)-1] == '.' {
   462			// We received stuff like "Domain=www.example.com.".
   463			// Browsers do handle such stuff (actually differently) but
   464			// RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in
   465			// requiring a reject.  4.1.2.3 is not normative, but
   466			// "Domain Matching" (5.1.3) and "Canonicalized Host Names"
   467			// (5.1.2) are.
   468			return "", false, errMalformedDomain
   469		}
   470	
   471		// See RFC 6265 section 5.3 #5.
   472		if j.psList != nil {
   473			if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
   474				if host == domain {
   475					// This is the one exception in which a cookie
   476					// with a domain attribute is a host cookie.
   477					return host, true, nil
   478				}
   479				return "", false, errIllegalDomain
   480			}
   481		}
   482	
   483		// The domain must domain-match host: www.mycompany.com cannot
   484		// set cookies for .ourcompetitors.com.
   485		if host != domain && !hasDotSuffix(host, domain) {
   486			return "", false, errIllegalDomain
   487		}
   488	
   489		return domain, false, nil
   490	}
   491	

View as plain text