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