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 }