Source file src/pkg/net/http/cookiejar/jar.go
1
2
3
4
5
6 package cookiejar
7
8 import (
9 "errors"
10 "fmt"
11 "net"
12 "net/http"
13 "net/url"
14 "sort"
15 "strings"
16 "sync"
17 "time"
18 )
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34 type PublicSuffixList interface {
35
36
37
38
39
40 PublicSuffix(domain string) string
41
42
43
44
45 String() string
46 }
47
48
49 type Options struct {
50
51
52
53
54
55
56 PublicSuffixList PublicSuffixList
57 }
58
59
60 type Jar struct {
61 psList PublicSuffixList
62
63
64 mu sync.Mutex
65
66
67
68 entries map[string]map[string]entry
69
70
71
72 nextSeqNum uint64
73 }
74
75
76
77 func New(o *Options) (*Jar, error) {
78 jar := &Jar{
79 entries: make(map[string]map[string]entry),
80 }
81 if o != nil {
82 jar.psList = o.PublicSuffixList
83 }
84 return jar, nil
85 }
86
87
88
89
90
91 type entry struct {
92 Name string
93 Value string
94 Domain string
95 Path string
96 SameSite string
97 Secure bool
98 HttpOnly bool
99 Persistent bool
100 HostOnly bool
101 Expires time.Time
102 Creation time.Time
103 LastAccess time.Time
104
105
106
107
108 seqNum uint64
109 }
110
111
112 func (e *entry) id() string {
113 return fmt.Sprintf("%s;%s;%s", e.Domain, e.Path, e.Name)
114 }
115
116
117
118
119 func (e *entry) shouldSend(https bool, host, path string) bool {
120 return e.domainMatch(host) && e.pathMatch(path) && (https || !e.Secure)
121 }
122
123
124 func (e *entry) domainMatch(host string) bool {
125 if e.Domain == host {
126 return true
127 }
128 return !e.HostOnly && hasDotSuffix(host, e.Domain)
129 }
130
131
132 func (e *entry) pathMatch(requestPath string) bool {
133 if requestPath == e.Path {
134 return true
135 }
136 if strings.HasPrefix(requestPath, e.Path) {
137 if e.Path[len(e.Path)-1] == '/' {
138 return true
139 } else if requestPath[len(e.Path)] == '/' {
140 return true
141 }
142 }
143 return false
144 }
145
146
147 func hasDotSuffix(s, suffix string) bool {
148 return len(s) > len(suffix) && s[len(s)-len(suffix)-1] == '.' && s[len(s)-len(suffix):] == suffix
149 }
150
151
152
153
154 func (j *Jar) Cookies(u *url.URL) (cookies []*http.Cookie) {
155 return j.cookies(u, time.Now())
156 }
157
158
159 func (j *Jar) cookies(u *url.URL, now time.Time) (cookies []*http.Cookie) {
160 if u.Scheme != "http" && u.Scheme != "https" {
161 return cookies
162 }
163 host, err := canonicalHost(u.Host)
164 if err != nil {
165 return cookies
166 }
167 key := jarKey(host, j.psList)
168
169 j.mu.Lock()
170 defer j.mu.Unlock()
171
172 submap := j.entries[key]
173 if submap == nil {
174 return cookies
175 }
176
177 https := u.Scheme == "https"
178 path := u.Path
179 if path == "" {
180 path = "/"
181 }
182
183 modified := false
184 var selected []entry
185 for id, e := range submap {
186 if e.Persistent && !e.Expires.After(now) {
187 delete(submap, id)
188 modified = true
189 continue
190 }
191 if !e.shouldSend(https, host, path) {
192 continue
193 }
194 e.LastAccess = now
195 submap[id] = e
196 selected = append(selected, e)
197 modified = true
198 }
199 if modified {
200 if len(submap) == 0 {
201 delete(j.entries, key)
202 } else {
203 j.entries[key] = submap
204 }
205 }
206
207
208
209 sort.Slice(selected, func(i, j int) bool {
210 s := selected
211 if len(s[i].Path) != len(s[j].Path) {
212 return len(s[i].Path) > len(s[j].Path)
213 }
214 if !s[i].Creation.Equal(s[j].Creation) {
215 return s[i].Creation.Before(s[j].Creation)
216 }
217 return s[i].seqNum < s[j].seqNum
218 })
219 for _, e := range selected {
220 cookies = append(cookies, &http.Cookie{Name: e.Name, Value: e.Value})
221 }
222
223 return cookies
224 }
225
226
227
228
229 func (j *Jar) SetCookies(u *url.URL, cookies []*http.Cookie) {
230 j.setCookies(u, cookies, time.Now())
231 }
232
233
234 func (j *Jar) setCookies(u *url.URL, cookies []*http.Cookie, now time.Time) {
235 if len(cookies) == 0 {
236 return
237 }
238 if u.Scheme != "http" && u.Scheme != "https" {
239 return
240 }
241 host, err := canonicalHost(u.Host)
242 if err != nil {
243 return
244 }
245 key := jarKey(host, j.psList)
246 defPath := defaultPath(u.Path)
247
248 j.mu.Lock()
249 defer j.mu.Unlock()
250
251 submap := j.entries[key]
252
253 modified := false
254 for _, cookie := range cookies {
255 e, remove, err := j.newEntry(cookie, now, defPath, host)
256 if err != nil {
257 continue
258 }
259 id := e.id()
260 if remove {
261 if submap != nil {
262 if _, ok := submap[id]; ok {
263 delete(submap, id)
264 modified = true
265 }
266 }
267 continue
268 }
269 if submap == nil {
270 submap = make(map[string]entry)
271 }
272
273 if old, ok := submap[id]; ok {
274 e.Creation = old.Creation
275 e.seqNum = old.seqNum
276 } else {
277 e.Creation = now
278 e.seqNum = j.nextSeqNum
279 j.nextSeqNum++
280 }
281 e.LastAccess = now
282 submap[id] = e
283 modified = true
284 }
285
286 if modified {
287 if len(submap) == 0 {
288 delete(j.entries, key)
289 } else {
290 j.entries[key] = submap
291 }
292 }
293 }
294
295
296
297 func canonicalHost(host string) (string, error) {
298 var err error
299 host = strings.ToLower(host)
300 if hasPort(host) {
301 host, _, err = net.SplitHostPort(host)
302 if err != nil {
303 return "", err
304 }
305 }
306 if strings.HasSuffix(host, ".") {
307
308 host = host[:len(host)-1]
309 }
310 return toASCII(host)
311 }
312
313
314
315 func hasPort(host string) bool {
316 colons := strings.Count(host, ":")
317 if colons == 0 {
318 return false
319 }
320 if colons == 1 {
321 return true
322 }
323 return host[0] == '[' && strings.Contains(host, "]:")
324 }
325
326
327 func jarKey(host string, psl PublicSuffixList) string {
328 if isIP(host) {
329 return host
330 }
331
332 var i int
333 if psl == nil {
334 i = strings.LastIndex(host, ".")
335 if i <= 0 {
336 return host
337 }
338 } else {
339 suffix := psl.PublicSuffix(host)
340 if suffix == host {
341 return host
342 }
343 i = len(host) - len(suffix)
344 if i <= 0 || host[i-1] != '.' {
345
346
347 return host
348 }
349
350
351
352 }
353 prevDot := strings.LastIndex(host[:i-1], ".")
354 return host[prevDot+1:]
355 }
356
357
358 func isIP(host string) bool {
359 return net.ParseIP(host) != nil
360 }
361
362
363
364 func defaultPath(path string) string {
365 if len(path) == 0 || path[0] != '/' {
366 return "/"
367 }
368
369 i := strings.LastIndex(path, "/")
370 if i == 0 {
371 return "/"
372 }
373 return path[:i]
374 }
375
376
377
378
379
380
381
382
383
384
385 func (j *Jar) newEntry(c *http.Cookie, now time.Time, defPath, host string) (e entry, remove bool, err error) {
386 e.Name = c.Name
387
388 if c.Path == "" || c.Path[0] != '/' {
389 e.Path = defPath
390 } else {
391 e.Path = c.Path
392 }
393
394 e.Domain, e.HostOnly, err = j.domainAndType(host, c.Domain)
395 if err != nil {
396 return e, false, err
397 }
398
399
400 if c.MaxAge < 0 {
401 return e, true, nil
402 } else if c.MaxAge > 0 {
403 e.Expires = now.Add(time.Duration(c.MaxAge) * time.Second)
404 e.Persistent = true
405 } else {
406 if c.Expires.IsZero() {
407 e.Expires = endOfTime
408 e.Persistent = false
409 } else {
410 if !c.Expires.After(now) {
411 return e, true, nil
412 }
413 e.Expires = c.Expires
414 e.Persistent = true
415 }
416 }
417
418 e.Value = c.Value
419 e.Secure = c.Secure
420 e.HttpOnly = c.HttpOnly
421
422 switch c.SameSite {
423 case http.SameSiteDefaultMode:
424 e.SameSite = "SameSite"
425 case http.SameSiteStrictMode:
426 e.SameSite = "SameSite=Strict"
427 case http.SameSiteLaxMode:
428 e.SameSite = "SameSite=Lax"
429 }
430
431 return e, false, nil
432 }
433
434 var (
435 errIllegalDomain = errors.New("cookiejar: illegal cookie domain attribute")
436 errMalformedDomain = errors.New("cookiejar: malformed cookie domain attribute")
437 errNoHostname = errors.New("cookiejar: no host name available (IP only)")
438 )
439
440
441
442
443 var endOfTime = time.Date(9999, 12, 31, 23, 59, 59, 0, time.UTC)
444
445
446 func (j *Jar) domainAndType(host, domain string) (string, bool, error) {
447 if domain == "" {
448
449
450 return host, true, nil
451 }
452
453 if isIP(host) {
454
455
456
457 return "", false, errNoHostname
458 }
459
460
461
462
463 if domain[0] == '.' {
464 domain = domain[1:]
465 }
466
467 if len(domain) == 0 || domain[0] == '.' {
468
469
470 return "", false, errMalformedDomain
471 }
472 domain = strings.ToLower(domain)
473
474 if domain[len(domain)-1] == '.' {
475
476
477
478
479
480
481 return "", false, errMalformedDomain
482 }
483
484
485 if j.psList != nil {
486 if ps := j.psList.PublicSuffix(domain); ps != "" && !hasDotSuffix(domain, ps) {
487 if host == domain {
488
489
490 return host, true, nil
491 }
492 return "", false, errIllegalDomain
493 }
494 }
495
496
497
498 if host != domain && !hasDotSuffix(host, domain) {
499 return "", false, errIllegalDomain
500 }
501
502 return domain, false, nil
503 }
504
View as plain text