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