Ensure errors are handled or logged
if err != nil {
1// Copyright 2012 The Go Authors. All rights reserved.2// Use of this source code is governed by a BSD-style3// license that can be found in the LICENSE file.45// Package cookiejar implements an in-memory [RFC 6265]-compliant [http.CookieJar].6//7// [RFC 6265]: https://www.rfc-editor.org/info/rfc62658package cookiejar910import (11 "cmp"12 "errors"13 "fmt"14 "net"15 "net/http"16 "net/http/internal/ascii"17 "net/netip"18 "net/url"19 "slices"20 "strings"21 "sync"22 "time"23)2425// PublicSuffixList provides the public suffix of a domain. For example:26// - the public suffix of "example.com" is "com",27// - the public suffix of "foo1.foo2.foo3.co.uk" is "co.uk", and28// - the public suffix of "bar.pvt.k12.ma.us" is "pvt.k12.ma.us".29//30// Implementations of PublicSuffixList must be safe for concurrent use by31// multiple goroutines.32//33// An implementation that always returns "" is valid and may be useful for34// testing but it is not secure: it means that the HTTP server for foo.com can35// set a cookie for bar.com.36//37// A public suffix list implementation is in the package38// [golang.org/x/net/publicsuffix].39type PublicSuffixList interface {40 // PublicSuffix returns the public suffix of domain.41 //42 // TODO: specify which of the caller and callee is responsible for IP43 // addresses, for leading and trailing dots, for case sensitivity, and44 // for IDN/Punycode.45 PublicSuffix(domain string) string4647 // String returns a description of the source of this public suffix48 // list. The description will typically contain something like a time49 // stamp or version number.50 String() string51}5253// Options are the options for creating a new [Jar].54type Options struct {55 // PublicSuffixList is the public suffix list that determines whether56 // an HTTP server can set a cookie for a domain.57 //58 // A nil value is valid and may be useful for testing but it is not59 // secure: it means that the HTTP server for foo.co.uk can set a cookie60 // for bar.co.uk.61 PublicSuffixList PublicSuffixList62}6364// Jar implements the [net/http.CookieJar] interface.65type Jar struct {66 psList PublicSuffixList6768 // mu locks the remaining fields.69 mu sync.Mutex7071 // entries is a set of entries, keyed by their eTLD+1 and subkeyed by72 // their name/domain/path.73 entries map[string]map[string]entry7475 // nextSeqNum is the next sequence number assigned to a new cookie76 // created SetCookies.77 nextSeqNum uint6478}7980// New returns a new cookie jar. A nil [*Options] is equivalent to a zero81// Options.82func New(o *Options) (*Jar, error) {83 jar := &Jar{84 entries: make(map[string]map[string]entry),85 }86 if o != nil {87 jar.psList = o.PublicSuffixList88 }89 return jar, nil90}9192// entry is the internal representation of a cookie.93//94// This struct type is not used outside of this package per se, but the exported95// fields are those of RFC 6265.96type entry struct {97 Name string98 Value string99 Quoted bool100 Domain string101 Path string102 SameSite string103 Secure bool104 HttpOnly bool105 Persistent bool106 HostOnly bool107 Expires time.Time108 Creation time.Time109 LastAccess time.Time110111 // seqNum is a sequence number so that Cookies returns cookies in a112 // deterministic order, even for cookies that have equal Path length and113 // equal Creation time. This simplifies testing.114 seqNum uint64115}116117// id returns the domain;path;name triple of e as an id.118func (e *entry) id() string {119 return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)120}121122// shouldSend determines whether e's cookie qualifies to be included in a123// request to host/path. It is the caller's responsibility to check if the124// cookie is expired.125func (e *entry) shouldSend(https bool, host, path string) bool {126 return e.domainMatch(host) && e.pathMatch(path) && e.secureMatch(https)127}128129// domainMatch checks whether e's Domain allows sending e back to host.130// It differs from "domain-match" of RFC 6265 section 5.1.3 because we treat131// a cookie with an IP address in the Domain always as a host cookie.132func (e *entry) domainMatch(host string) bool {133 if e.Domain == host {134 return true135 }136 return !e.HostOnly && hasDotSuffix(host, e.Domain)137}138139// pathMatch implements "path-match" according to RFC 6265 section 5.1.4.140func (e *entry) pathMatch(requestPath string) bool {141 if requestPath == e.Path {142 return true143 }144 if strings.HasPrefix(requestPath, e.Path) {145 if e.Path[len(e.Path)-1] == '/' {146 return true // The "/any/" matches "/any/path" case.147 } else if requestPath[len(e.Path)] == '/' {148 return true // The "/any" matches "/any/path" case.149 }150 }151 return false152}153154// secureMatch checks whether a cookie should be sent based on the protocol155// and the Secure flag. Localhost is considered a secure origin regardless156// of protocol, matching browser behavior.157func (e *entry) secureMatch(https bool) bool {158 if !e.Secure {159 // Cookies not marked secure are always sent.160 return true161 }162 // Everything below is about cookies marked secure.163 if https {164 // HTTPS request matches secure cookies.165 return true166 }167 // Consider localhost to be secure like browsers.168 if isLocalhost(e.Domain) {169 return true170 }171 ip, err := netip.ParseAddr(e.Domain)172 if err == nil && ip.IsLoopback() {173 return true174 }175 return false176}177178func isLocalhost(host string) bool {179 host = strings.TrimSuffix(host, ".")180 if idx := strings.LastIndex(host, "."); idx >= 0 {181 host = host[idx+1:]182 }183 return ascii.EqualFold(host, "localhost")184}185186// hasDotSuffix reports whether s ends in "."+suffix.187func hasDotSuffix(s, suffix string) bool {188 return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix189}190191// Cookies implements the Cookies method of the [http.CookieJar] interface.192//193// It returns an empty slice if the URL's scheme is not HTTP or HTTPS.194func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {195 return j.cookies(u, time.Now())196}197198// cookies is like Cookies but takes the current time as a parameter.199func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {200 if u.Scheme != "http" && u.Scheme != "https" {201 return cookies202 }203 host, err := canonicalHost(u.Host)204 if err != nil {205 return cookies206 }207 key := jarKey(host, j.psList)208209 j.mu.Lock()210 defer j.mu.Unlock()211212 submap := j.entries[key]213 if submap == nil {214 return cookies215 }216217 https := u.Scheme == "https"218 path := u.Path219 if path == "" {220 path = "/"221 }222223 modified := false224 var selected []entry225 for id, e := range submap {226 if e.Persistent && !e.Expires.After(now) {227 delete(submap, id)228 modified = true229 continue230 }231 if !e.shouldSend(https, host, path) {232 continue233 }234 e.LastAccess = now235 submap[id] = e236 selected = append(selected, e)237 modified = true238 }239 if modified {240 if len(submap) == 0 {241 delete(j.entries, key)242 } else {243 j.entries[key] = submap244 }245 }246247 // sort according to RFC 6265 section 5.4 point 2: by longest248 // path and then by earliest creation time.249 slices.SortFunc(selected, func(a, b entry) int {250 if r := cmp.Compare(b.Path, a.Path); r != 0 {251 return r252 }253 if r := a.Creation.Compare(b.Creation); r != 0 {254 return r255 }256 return cmp.Compare(a.seqNum, b.seqNum)257 })258 for _, e := range selected {259 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value, Quoted: e.Quoted})260 }261262 return cookies263}264265// SetCookies implements the SetCookies method of the [http.CookieJar] interface.266//267// It does nothing if the URL's scheme is not HTTP or HTTPS.268func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {269 j.setCookies(u, cookies, time.Now())270}271272// setCookies is like SetCookies but takes the current time as parameter.273func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {274 if len(cookies) == 0 {275 return276 }277 if u.Scheme != "http" && u.Scheme != "https" {278 return279 }280 host, err := canonicalHost(u.Host)281 if err != nil {282 return283 }284 key := jarKey(host, j.psList)285 defPath := defaultPath(u.Path)286287 j.mu.Lock()288 defer j.mu.Unlock()289290 submap := j.entries[key]291292 modified := false293 for _, cookie := range cookies {294 e, remove, err := j.newEntry(cookie, now, defPath, host)295 if err != nil {296 continue297 }298 id := e.id()299 if remove {300 if submap != nil {301 if _, ok := submap[id]; ok {302 delete(submap, id)303 modified = true304 }305 }306 continue307 }308 if submap == nil {309 submap = make(map[string]entry)310 }311312 if old, ok := submap[id]; ok {313 e.Creation = old.Creation314 e.seqNum = old.seqNum315 } else {316 e.Creation = now317 e.seqNum = j.nextSeqNum318 j.nextSeqNum++319 }320 e.LastAccess = now321 submap[id] = e322 modified = true323 }324325 if modified {326 if len(submap) == 0 {327 delete(j.entries, key)328 } else {329 j.entries[key] = submap330 }331 }332}333334// canonicalHost strips port from host if present and returns the canonicalized335// host name.336func canonicalHost(host string) (string, error) {337 var err error338 if hasPort(host) {339 host, _, err = net.SplitHostPort(host)340 if err != nil {341 return "", err342 }343 }344 // Strip trailing dot from fully qualified domain names.345 host = strings.TrimSuffix(host, ".")346 encoded, err := toASCII(host)347 if err != nil {348 return "", err349 }350 // We know this is ascii, no need to check.351 lower, _ := ascii.ToLower(encoded)352 return lower, nil353}354355// hasPort reports whether host contains a port number. host may be a host356// name, an IPv4 or an IPv6 address.357func hasPort(host string) bool {358 colons := strings.Count(host, ":")359 if colons == 0 {360 return false361 }362 if colons == 1 {363 return true364 }365 return host[0] == '[' && strings.Contains(host, "]:")366}367368// jarKey returns the key to use for a jar.369func jarKey(host string, psl PublicSuffixList) string {370 if isIP(host) {371 return host372 }373374 var i int375 if psl == nil {376 i = strings.LastIndex(host, ".")377 if i <= 0 {378 return host379 }380 } else {381 suffix := psl.PublicSuffix(host)382 if suffix == host {383 return host384 }385 i = len(host) - len(suffix)386 if i <= 0 || host[i-1] != '.' {387 // The provided public suffix list psl is broken.388 // Storing cookies under host is a safe stopgap.389 return host390 }391 // Only len(suffix) is used to determine the jar key from392 // here on, so it is okay if psl.PublicSuffix("www.buggy.psl")393 // returns "com" as the jar key is generated from host.394 }395 prevDot := strings.LastIndex(host[:i-1], ".")396 return host[prevDot+1:]397}398399// isIP reports whether host is an IP address.400func isIP(host string) bool {401 if strings.ContainsAny(host, ":%") {402 // Probable IPv6 address.403 // Hostnames can't contain : or %, so this is definitely not a valid host.404 // Treating it as an IP is the more conservative option, and avoids the risk405 // of interpreting ::1%.www.example.com as a subdomain of www.example.com.406 return true407 }408 return net.ParseIP(host) != nil409}410411// defaultPath returns the directory part of a URL's path according to412// RFC 6265 section 5.1.4.413func defaultPath(path string) string {414 if len(path) == 0 || path[0] != '/' {415 return "/" // Path is empty or malformed.416 }417418 i := strings.LastIndex(path, "/") // Path starts with "/", so i != -1.419 if i == 0 {420 return "/" // Path has the form "/abc".421 }422 return path[:i] // Path is either of form "/abc/xyz" or "/abc/xyz/".423}424425// newEntry creates an entry from an http.Cookie c. now is the current time and426// is compared to c.Expires to determine deletion of c. defPath and host are the427// default-path and the canonical host name of the URL c was received from.428//429// remove records whether the jar should delete this cookie, as it has already430// expired with respect to now. In this case, e may be incomplete, but it will431// be valid to call e.id (which depends on e's Name, Domain and Path).432//433// A malformed c.Domain will result in an error.434func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {435 e.Name = c.Name436437 if c.Path == "" || c.Path[0] != '/' {438 e.Path = defPath439 } else {440 e.Path = c.Path441 }442443 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)444 if err != nil {445 return e, false, err446 }447448 // MaxAge takes precedence over Expires.449 if c.MaxAge < 0 {450 return e, true, nil451 } else if c.MaxAge > 0 {452 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)453 e.Persistent = true454 } else {455 if c.Expires.IsZero() {456 e.Expires = endOfTime457 e.Persistent = false458 } else {459 if !c.Expires.After(now) {460 return e, true, nil461 }462 e.Expires = c.Expires463 e.Persistent = true464 }465 }466467 e.Value = c.Value468 e.Quoted = c.Quoted469 e.Secure = c.Secure470 e.HttpOnly = c.HttpOnly471472 switch c.SameSite {473 case http.SameSiteDefaultMode:474 e.SameSite = "SameSite"475 case http.SameSiteStrictMode:476 e.SameSite = "SameSite=Strict"477 case http.SameSiteLaxMode:478 e.SameSite = "SameSite=Lax"479 }480481 return e, false, nil482}483484var (485 errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute")486 errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")487)488489// endOfTime is the time when session (non-persistent) cookies expire.490// This instant is representable in most date/time formats (not just491// Go's time.Time) and should be far enough in the future.492var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)493494// domainAndType determines the cookie's domain and hostOnly attribute.495func (j *Jar) domainAndType(host, domain string) (string, bool, error) {496 if domain == "" {497 // No domain attribute in the SetCookie header indicates a498 // host cookie.499 return host, true, nil500 }501502 if isIP(host) {503 // RFC 6265 is not super clear here, a sensible interpretation504 // is that cookies with an IP address in the domain-attribute505 // are allowed.506507 // RFC 6265 section 5.2.3 mandates to strip an optional leading508 // dot in the domain-attribute before processing the cookie.509 //510 // Most browsers don't do that for IP addresses, only curl511 // (version 7.54) and IE (version 11) do not reject a512 // Set-Cookie: a=1; domain=.127.0.0.1513 // This leading dot is optional and serves only as hint for514 // humans to indicate that a cookie with "domain=.bbc.co.uk"515 // would be sent to every subdomain of bbc.co.uk.516 // It just doesn't make sense on IP addresses.517 // The other processing and validation steps in RFC 6265 just518 // collapse to:519 if host != domain {520 return "", false, errIllegalDomain521 }522523 // According to RFC 6265 such cookies should be treated as524 // domain cookies.525 // As there are no subdomains of an IP address the treatment526 // according to RFC 6265 would be exactly the same as that of527 // a host-only cookie. Contemporary browsers (and curl) do528 // allows such cookies but treat them as host-only cookies.529 // So do we as it just doesn't make sense to label them as530 // domain cookies when there is no domain; the whole notion of531 // domain cookies requires a domain name to be well defined.532 return host, true, nil533 }534535 // From here on: If the cookie is valid, it is a domain cookie (with536 // the one exception of a public suffix below).537 // See RFC 6265 section 5.2.3.538 domain = strings.TrimPrefix(domain, ".")539540 if len(domain) == 0 || domain[0] == '.' {541 // Received either "Domain=." or "Domain=..some.thing",542 // both are illegal.543 return "", false, errMalformedDomain544 }545546 domain, isASCII := ascii.ToLower(domain)547 if !isASCII {548 // Received non-ASCII domain, e.g. "perché.com" instead of "xn--perch-fsa.com"549 return "", false, errMalformedDomain550 }551552 if domain[len(domain)-1] == '.' {553 // We received stuff like "Domain=www.example.com.".554 // Browsers do handle such stuff (actually differently) but555 // RFC 6265 seems to be clear here (e.g. section 4.1.2.3) in556 // requiring a reject. 4.1.2.3 is not normative, but557 // "Domain Matching" (5.1.3) and "Canonicalized Host Names"558 // (5.1.2) are.559 return "", false, errMalformedDomain560 }561562 // See RFC 6265 section 5.3 #5.563 if j.psList != nil {564 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {565 if host == domain {566 // This is the one exception in which a cookie567 // with a domain attribute is a host cookie.568 return host, true, nil569 }570 return "", false, errIllegalDomain571 }572 }573574 // The domain must domain-match host: www.mycompany.com cannot575 // set cookies for .ourcompetitors.com.576 if host != domain && !hasDotSuffix(host, domain) {577 return "", false, errIllegalDomain578 }579580 return domain, false, nil581}
Same data, no extra tab — call code_get_file + code_get_findings over MCP from Claude/Cursor/Copilot.