...

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		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