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