Source file
src/net/http/cookie_test.go
1
2
3
4
5 package http
6
7 import (
8 "encoding/json"
9 "errors"
10 "fmt"
11 "log"
12 "os"
13 "reflect"
14 "slices"
15 "strings"
16 "testing"
17 "time"
18 )
19
20 var writeSetCookiesTests = []struct {
21 Cookie *Cookie
22 Raw string
23 }{
24 {
25 &Cookie{Name: "cookie-1", Value: "v$1"},
26 "cookie-1=v$1",
27 },
28 {
29 &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600},
30 "cookie-2=two; Max-Age=3600",
31 },
32 {
33 &Cookie{Name: "cookie-3", Value: "three", Domain: ".example.com"},
34 "cookie-3=three; Domain=example.com",
35 },
36 {
37 &Cookie{Name: "cookie-4", Value: "four", Path: "/restricted/"},
38 "cookie-4=four; Path=/restricted/",
39 },
40 {
41 &Cookie{Name: "cookie-5", Value: "five", Domain: "wrong;bad.abc"},
42 "cookie-5=five",
43 },
44 {
45 &Cookie{Name: "cookie-6", Value: "six", Domain: "bad-.abc"},
46 "cookie-6=six",
47 },
48 {
49 &Cookie{Name: "cookie-7", Value: "seven", Domain: "127.0.0.1"},
50 "cookie-7=seven; Domain=127.0.0.1",
51 },
52 {
53 &Cookie{Name: "cookie-8", Value: "eight", Domain: "::1"},
54 "cookie-8=eight",
55 },
56 {
57 &Cookie{Name: "cookie-9", Value: "expiring", Expires: time.Unix(1257894000, 0)},
58 "cookie-9=expiring; Expires=Tue, 10 Nov 2009 23:00:00 GMT",
59 },
60
61 {
62 &Cookie{Name: "cookie-10", Value: "expiring-1601", Expires: time.Date(1601, 1, 1, 1, 1, 1, 1, time.UTC)},
63 "cookie-10=expiring-1601; Expires=Mon, 01 Jan 1601 01:01:01 GMT",
64 },
65 {
66 &Cookie{Name: "cookie-11", Value: "invalid-expiry", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)},
67 "cookie-11=invalid-expiry",
68 },
69 {
70 &Cookie{Name: "cookie-12", Value: "samesite-default", SameSite: SameSiteDefaultMode},
71 "cookie-12=samesite-default",
72 },
73 {
74 &Cookie{Name: "cookie-13", Value: "samesite-lax", SameSite: SameSiteLaxMode},
75 "cookie-13=samesite-lax; SameSite=Lax",
76 },
77 {
78 &Cookie{Name: "cookie-14", Value: "samesite-strict", SameSite: SameSiteStrictMode},
79 "cookie-14=samesite-strict; SameSite=Strict",
80 },
81 {
82 &Cookie{Name: "cookie-15", Value: "samesite-none", SameSite: SameSiteNoneMode},
83 "cookie-15=samesite-none; SameSite=None",
84 },
85 {
86 &Cookie{Name: "cookie-16", Value: "partitioned", SameSite: SameSiteNoneMode, Secure: true, Path: "/", Partitioned: true},
87 "cookie-16=partitioned; Path=/; Secure; SameSite=None; Partitioned",
88 },
89
90
91 {
92 &Cookie{Name: "special-1", Value: "a z"},
93 `special-1="a z"`,
94 },
95 {
96 &Cookie{Name: "special-2", Value: " z"},
97 `special-2=" z"`,
98 },
99 {
100 &Cookie{Name: "special-3", Value: "a "},
101 `special-3="a "`,
102 },
103 {
104 &Cookie{Name: "special-4", Value: " "},
105 `special-4=" "`,
106 },
107 {
108 &Cookie{Name: "special-5", Value: "a,z"},
109 `special-5="a,z"`,
110 },
111 {
112 &Cookie{Name: "special-6", Value: ",z"},
113 `special-6=",z"`,
114 },
115 {
116 &Cookie{Name: "special-7", Value: "a,"},
117 `special-7="a,"`,
118 },
119 {
120 &Cookie{Name: "special-8", Value: ","},
121 `special-8=","`,
122 },
123 {
124 &Cookie{Name: "empty-value", Value: ""},
125 `empty-value=`,
126 },
127 {
128 nil,
129 ``,
130 },
131 {
132 &Cookie{Name: ""},
133 ``,
134 },
135 {
136 &Cookie{Name: "\t"},
137 ``,
138 },
139 {
140 &Cookie{Name: "\r"},
141 ``,
142 },
143 {
144 &Cookie{Name: "a\nb", Value: "v"},
145 ``,
146 },
147 {
148 &Cookie{Name: "a\nb", Value: "v"},
149 ``,
150 },
151 {
152 &Cookie{Name: "a\rb", Value: "v"},
153 ``,
154 },
155
156 {
157 &Cookie{Name: "cookie", Value: "quoted", Quoted: true},
158 `cookie="quoted"`,
159 },
160 {
161 &Cookie{Name: "cookie", Value: "quoted with spaces", Quoted: true},
162 `cookie="quoted with spaces"`,
163 },
164 {
165 &Cookie{Name: "cookie", Value: "quoted,with,commas", Quoted: true},
166 `cookie="quoted,with,commas"`,
167 },
168 }
169
170 func TestWriteSetCookies(t *testing.T) {
171 defer log.SetOutput(os.Stderr)
172 var logbuf strings.Builder
173 log.SetOutput(&logbuf)
174
175 for i, tt := range writeSetCookiesTests {
176 if g, e := tt.Cookie.String(), tt.Raw; g != e {
177 t.Errorf("Test %d, expecting:\n%s\nGot:\n%s\n", i, e, g)
178 }
179 }
180
181 if got, sub := logbuf.String(), "dropping domain attribute"; !strings.Contains(got, sub) {
182 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
183 }
184 }
185
186 type headerOnlyResponseWriter Header
187
188 func (ho headerOnlyResponseWriter) Header() Header {
189 return Header(ho)
190 }
191
192 func (ho headerOnlyResponseWriter) Write([]byte) (int, error) {
193 panic("NOIMPL")
194 }
195
196 func (ho headerOnlyResponseWriter) WriteHeader(int) {
197 panic("NOIMPL")
198 }
199
200 func TestSetCookie(t *testing.T) {
201 m := make(Header)
202 SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-1", Value: "one", Path: "/restricted/"})
203 SetCookie(headerOnlyResponseWriter(m), &Cookie{Name: "cookie-2", Value: "two", MaxAge: 3600})
204 if l := len(m["Set-Cookie"]); l != 2 {
205 t.Fatalf("expected %d cookies, got %d", 2, l)
206 }
207 if g, e := m["Set-Cookie"][0], "cookie-1=one; Path=/restricted/"; g != e {
208 t.Errorf("cookie #1: want %q, got %q", e, g)
209 }
210 if g, e := m["Set-Cookie"][1], "cookie-2=two; Max-Age=3600"; g != e {
211 t.Errorf("cookie #2: want %q, got %q", e, g)
212 }
213 }
214
215 var addCookieTests = []struct {
216 Cookies []*Cookie
217 Raw string
218 }{
219 {
220 []*Cookie{},
221 "",
222 },
223 {
224 []*Cookie{{Name: "cookie-1", Value: "v$1"}},
225 "cookie-1=v$1",
226 },
227 {
228 []*Cookie{
229 {Name: "cookie-1", Value: "v$1"},
230 {Name: "cookie-2", Value: "v$2"},
231 {Name: "cookie-3", Value: "v$3"},
232 },
233 "cookie-1=v$1; cookie-2=v$2; cookie-3=v$3",
234 },
235
236 {
237 []*Cookie{
238 {Name: "cookie-1", Value: "quoted", Quoted: true},
239 {Name: "cookie-2", Value: "quoted with spaces", Quoted: true},
240 {Name: "cookie-3", Value: "quoted,with,commas", Quoted: true},
241 },
242 `cookie-1="quoted"; cookie-2="quoted with spaces"; cookie-3="quoted,with,commas"`,
243 },
244 }
245
246 func TestAddCookie(t *testing.T) {
247 for i, tt := range addCookieTests {
248 req, _ := NewRequest("GET", "http://example.com/", nil)
249 for _, c := range tt.Cookies {
250 req.AddCookie(c)
251 }
252 if g := req.Header.Get("Cookie"); g != tt.Raw {
253 t.Errorf("Test %d:\nwant: %s\n got: %s\n", i, tt.Raw, g)
254 }
255 }
256 }
257
258 var readSetCookiesTests = []struct {
259 header Header
260 cookies []*Cookie
261 godebug string
262 }{
263 {
264 header: Header{"Set-Cookie": {"Cookie-1=v$1"}},
265 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"}},
266 },
267 {
268 header: Header{"Set-Cookie": {"NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly"}},
269 cookies: []*Cookie{{
270 Name: "NID",
271 Value: "99=YsDT5i3E-CXax-",
272 Path: "/",
273 Domain: ".google.ch",
274 HttpOnly: true,
275 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
276 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
277 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
278 }},
279 },
280 {
281 header: Header{"Set-Cookie": {".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly"}},
282 cookies: []*Cookie{{
283 Name: ".ASPXAUTH",
284 Value: "7E3AA",
285 Path: "/",
286 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
287 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
288 HttpOnly: true,
289 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
290 }},
291 },
292 {
293 header: Header{"Set-Cookie": {"ASP.NET_SessionId=foo; path=/; HttpOnly"}},
294 cookies: []*Cookie{{
295 Name: "ASP.NET_SessionId",
296 Value: "foo",
297 Path: "/",
298 HttpOnly: true,
299 Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
300 }},
301 },
302 {
303 header: Header{"Set-Cookie": {"samesitedefault=foo; SameSite"}},
304 cookies: []*Cookie{{
305 Name: "samesitedefault",
306 Value: "foo",
307 SameSite: SameSiteDefaultMode,
308 Raw: "samesitedefault=foo; SameSite",
309 }},
310 },
311 {
312 header: Header{"Set-Cookie": {"samesiteinvalidisdefault=foo; SameSite=invalid"}},
313 cookies: []*Cookie{{
314 Name: "samesiteinvalidisdefault",
315 Value: "foo",
316 SameSite: SameSiteDefaultMode,
317 Raw: "samesiteinvalidisdefault=foo; SameSite=invalid",
318 }},
319 },
320 {
321 header: Header{"Set-Cookie": {"samesitelax=foo; SameSite=Lax"}},
322 cookies: []*Cookie{{
323 Name: "samesitelax",
324 Value: "foo",
325 SameSite: SameSiteLaxMode,
326 Raw: "samesitelax=foo; SameSite=Lax",
327 }},
328 },
329 {
330 header: Header{"Set-Cookie": {"samesitestrict=foo; SameSite=Strict"}},
331 cookies: []*Cookie{{
332 Name: "samesitestrict",
333 Value: "foo",
334 SameSite: SameSiteStrictMode,
335 Raw: "samesitestrict=foo; SameSite=Strict",
336 }},
337 },
338 {
339 header: Header{"Set-Cookie": {"samesitenone=foo; SameSite=None"}},
340 cookies: []*Cookie{{
341 Name: "samesitenone",
342 Value: "foo",
343 SameSite: SameSiteNoneMode,
344 Raw: "samesitenone=foo; SameSite=None",
345 }},
346 },
347
348
349 {
350 header: Header{"Set-Cookie": {`special-1=a z`}},
351 cookies: []*Cookie{{Name: "special-1", Value: "a z", Raw: `special-1=a z`}},
352 },
353 {
354 header: Header{"Set-Cookie": {`special-2=" z"`}},
355 cookies: []*Cookie{{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`}},
356 },
357 {
358 header: Header{"Set-Cookie": {`special-3="a "`}},
359 cookies: []*Cookie{{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`}},
360 },
361 {
362 header: Header{"Set-Cookie": {`special-4=" "`}},
363 cookies: []*Cookie{{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`}},
364 },
365 {
366 header: Header{"Set-Cookie": {`special-5=a,z`}},
367 cookies: []*Cookie{{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`}},
368 },
369 {
370 header: Header{"Set-Cookie": {`special-6=",z"`}},
371 cookies: []*Cookie{{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`}},
372 },
373 {
374 header: Header{"Set-Cookie": {`special-7=a,`}},
375 cookies: []*Cookie{{Name: "special-7", Value: "a,", Raw: `special-7=a,`}},
376 },
377 {
378 header: Header{"Set-Cookie": {`special-8=","`}},
379 cookies: []*Cookie{{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`}},
380 },
381
382
383 {
384 header: Header{"Set-Cookie": {`special-9 =","`}},
385 cookies: []*Cookie{{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`}},
386 },
387
388 {
389 header: Header{"Set-Cookie": {`cookie="quoted"`}},
390 cookies: []*Cookie{{Name: "cookie", Value: "quoted", Quoted: true, Raw: `cookie="quoted"`}},
391 },
392 {
393 header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
394 cookies: []*Cookie{},
395 },
396 {
397 header: Header{"Set-Cookie": slices.Repeat([]string{"a="}, 10)},
398 cookies: []*Cookie{},
399 godebug: "httpcookiemaxnum=5",
400 },
401 {
402 header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
403 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
404 godebug: "httpcookiemaxnum=0",
405 },
406 {
407 header: Header{"Set-Cookie": strings.Split(strings.Repeat(";a=", defaultCookieMaxNum+1)[1:], ";")},
408 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false, Raw: "a="}}, defaultCookieMaxNum+1),
409 godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
410 },
411
412
413
414
415
416
417 }
418
419 func toJSON(v any) string {
420 b, err := json.Marshal(v)
421 if err != nil {
422 return fmt.Sprintf("%#v", v)
423 }
424 return string(b)
425 }
426
427 func TestReadSetCookies(t *testing.T) {
428 for i, tt := range readSetCookiesTests {
429 t.Setenv("GODEBUG", tt.godebug)
430 for n := 0; n < 2; n++ {
431 c := readSetCookies(tt.header)
432 if !reflect.DeepEqual(c, tt.cookies) {
433 t.Errorf("#%d readSetCookies: have\n%s\nwant\n%s\n", i, toJSON(c), toJSON(tt.cookies))
434 }
435 }
436 }
437 }
438
439 var readCookiesTests = []struct {
440 header Header
441 filter string
442 cookies []*Cookie
443 godebug string
444 }{
445 {
446 header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
447 filter: "",
448 cookies: []*Cookie{
449 {Name: "Cookie-1", Value: "v$1"},
450 {Name: "c2", Value: "v2"},
451 },
452 },
453 {
454 header: Header{"Cookie": {"Cookie-1=v$1", "c2=v2"}},
455 filter: "c2",
456 cookies: []*Cookie{
457 {Name: "c2", Value: "v2"},
458 },
459 },
460 {
461 header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
462 filter: "",
463 cookies: []*Cookie{
464 {Name: "Cookie-1", Value: "v$1"},
465 {Name: "c2", Value: "v2"},
466 },
467 },
468 {
469 header: Header{"Cookie": {"Cookie-1=v$1; c2=v2"}},
470 filter: "c2",
471 cookies: []*Cookie{
472 {Name: "c2", Value: "v2"},
473 },
474 },
475 {
476 header: Header{"Cookie": {`Cookie-1="v$1"; c2="v2"`}},
477 filter: "",
478 cookies: []*Cookie{
479 {Name: "Cookie-1", Value: "v$1", Quoted: true},
480 {Name: "c2", Value: "v2", Quoted: true},
481 },
482 },
483 {
484 header: Header{"Cookie": {`Cookie-1="v$1"; c2=v2;`}},
485 filter: "",
486 cookies: []*Cookie{
487 {Name: "Cookie-1", Value: "v$1", Quoted: true},
488 {Name: "c2", Value: "v2"},
489 },
490 },
491 {
492 header: Header{"Cookie": {``}},
493 filter: "",
494 cookies: []*Cookie{},
495 },
496
497
498 {
499 header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
500 cookies: []*Cookie{},
501 },
502 {
503 header: Header{"Cookie": slices.Repeat([]string{"a="}, 10)},
504 cookies: []*Cookie{},
505 godebug: "httpcookiemaxnum=5",
506 },
507 {
508 header: Header{"Cookie": {strings.Repeat(";a=", defaultCookieMaxNum+1)[1:]}},
509 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
510 godebug: "httpcookiemaxnum=0",
511 },
512 {
513 header: Header{"Cookie": slices.Repeat([]string{"a="}, defaultCookieMaxNum+1)},
514 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
515 godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
516 },
517 }
518
519 func TestReadCookies(t *testing.T) {
520 for i, tt := range readCookiesTests {
521 t.Setenv("GODEBUG", tt.godebug)
522 for n := 0; n < 2; n++ {
523 c := readCookies(tt.header, tt.filter)
524 if !reflect.DeepEqual(c, tt.cookies) {
525 t.Errorf("#%d readCookies:\nhave: %s\nwant: %s\n", i, toJSON(c), toJSON(tt.cookies))
526 }
527 }
528 }
529 }
530
531 func TestSetCookieDoubleQuotes(t *testing.T) {
532 res := &Response{Header: Header{}}
533 res.Header.Add("Set-Cookie", `quoted0=none; max-age=30`)
534 res.Header.Add("Set-Cookie", `quoted1="cookieValue"; max-age=31`)
535 res.Header.Add("Set-Cookie", `quoted2=cookieAV; max-age="32"`)
536 res.Header.Add("Set-Cookie", `quoted3="both"; max-age="33"`)
537 got := res.Cookies()
538 want := []*Cookie{
539 {Name: "quoted0", Value: "none", MaxAge: 30},
540 {Name: "quoted1", Value: "cookieValue", MaxAge: 31},
541 {Name: "quoted2", Value: "cookieAV"},
542 {Name: "quoted3", Value: "both"},
543 }
544 if len(got) != len(want) {
545 t.Fatalf("got %d cookies, want %d", len(got), len(want))
546 }
547 for i, w := range want {
548 g := got[i]
549 if g.Name != w.Name || g.Value != w.Value || g.MaxAge != w.MaxAge {
550 t.Errorf("cookie #%d:\ngot %v\nwant %v", i, g, w)
551 }
552 }
553 }
554
555 func TestCookieSanitizeValue(t *testing.T) {
556 defer log.SetOutput(os.Stderr)
557 var logbuf strings.Builder
558 log.SetOutput(&logbuf)
559
560 tests := []struct {
561 in string
562 quoted bool
563 want string
564 }{
565 {"foo", false, "foo"},
566 {"foo;bar", false, "foobar"},
567 {"foo\\bar", false, "foobar"},
568 {"foo\"bar", false, "foobar"},
569 {"\x00\x7e\x7f\x80", false, "\x7e"},
570 {`withquotes`, true, `"withquotes"`},
571 {`"withquotes"`, true, `"withquotes"`},
572 {"a z", false, `"a z"`},
573 {" z", false, `" z"`},
574 {"a ", false, `"a "`},
575 {"a,z", false, `"a,z"`},
576 {",z", false, `",z"`},
577 {"a,", false, `"a,"`},
578 }
579 for _, tt := range tests {
580 if got := sanitizeCookieValue(tt.in, tt.quoted); got != tt.want {
581 t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want)
582 }
583 }
584
585 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
586 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
587 }
588 }
589
590 func TestCookieSanitizePath(t *testing.T) {
591 defer log.SetOutput(os.Stderr)
592 var logbuf strings.Builder
593 log.SetOutput(&logbuf)
594
595 tests := []struct {
596 in, want string
597 }{
598 {"/path", "/path"},
599 {"/path with space/", "/path with space/"},
600 {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"},
601 }
602 for _, tt := range tests {
603 if got := sanitizeCookiePath(tt.in); got != tt.want {
604 t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want)
605 }
606 }
607
608 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
609 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
610 }
611 }
612
613 func TestCookieValid(t *testing.T) {
614 tests := []struct {
615 cookie *Cookie
616 valid bool
617 }{
618 {nil, false},
619 {&Cookie{Name: ""}, false},
620 {&Cookie{Name: "invalid-value", Value: "foo\"bar"}, false},
621 {&Cookie{Name: "invalid-path", Path: "/foo;bar/"}, false},
622 {&Cookie{Name: "invalid-secure-for-partitioned", Value: "foo", Path: "/", Secure: false, Partitioned: true}, false},
623 {&Cookie{Name: "invalid-domain", Domain: "example.com:80"}, false},
624 {&Cookie{Name: "invalid-expiry", Value: "", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)}, false},
625 {&Cookie{Name: "valid-empty"}, true},
626 {&Cookie{Name: "valid-expires", Value: "foo", Path: "/bar", Domain: "example.com", Expires: time.Unix(0, 0)}, true},
627 {&Cookie{Name: "valid-max-age", Value: "foo", Path: "/bar", Domain: "example.com", MaxAge: 60}, true},
628 {&Cookie{Name: "valid-all-fields", Value: "foo", Path: "/bar", Domain: "example.com", Expires: time.Unix(0, 0), MaxAge: 0}, true},
629 {&Cookie{Name: "valid-partitioned", Value: "foo", Path: "/", Secure: true, Partitioned: true}, true},
630 }
631
632 for _, tt := range tests {
633 err := tt.cookie.Valid()
634 if err != nil && tt.valid {
635 t.Errorf("%#v.Valid() returned error %v; want nil", tt.cookie, err)
636 }
637 if err == nil && !tt.valid {
638 t.Errorf("%#v.Valid() returned nil; want error", tt.cookie)
639 }
640 }
641 }
642
643 func BenchmarkCookieString(b *testing.B) {
644 const wantCookieString = `cookie-9=i3e01nf61b6t23bvfmplnanol3; Path=/restricted/; Domain=example.com; Expires=Tue, 10 Nov 2009 23:00:00 GMT; Max-Age=3600`
645 c := &Cookie{
646 Name: "cookie-9",
647 Value: "i3e01nf61b6t23bvfmplnanol3",
648 Expires: time.Unix(1257894000, 0),
649 Path: "/restricted/",
650 Domain: ".example.com",
651 MaxAge: 3600,
652 }
653 var benchmarkCookieString string
654 b.ReportAllocs()
655 b.ResetTimer()
656 for i := 0; i < b.N; i++ {
657 benchmarkCookieString = c.String()
658 }
659 if have, want := benchmarkCookieString, wantCookieString; have != want {
660 b.Fatalf("Have: %v Want: %v", have, want)
661 }
662 }
663
664 func BenchmarkReadSetCookies(b *testing.B) {
665 header := Header{
666 "Set-Cookie": {
667 "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
668 ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
669 },
670 }
671 wantCookies := []*Cookie{
672 {
673 Name: "NID",
674 Value: "99=YsDT5i3E-CXax-",
675 Path: "/",
676 Domain: ".google.ch",
677 HttpOnly: true,
678 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
679 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
680 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
681 },
682 {
683 Name: ".ASPXAUTH",
684 Value: "7E3AA",
685 Path: "/",
686 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
687 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
688 HttpOnly: true,
689 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
690 },
691 }
692 var c []*Cookie
693 b.ReportAllocs()
694 b.ResetTimer()
695 for i := 0; i < b.N; i++ {
696 c = readSetCookies(header)
697 }
698 if !reflect.DeepEqual(c, wantCookies) {
699 b.Fatalf("readSetCookies:\nhave: %s\nwant: %s\n", toJSON(c), toJSON(wantCookies))
700 }
701 }
702
703 func BenchmarkReadCookies(b *testing.B) {
704 header := Header{
705 "Cookie": {
706 `de=; client_region=0; rpld1=0:hispeed.ch|20:che|21:zh|22:zurich|23:47.36|24:8.53|; rpld0=1:08|; backplane-channel=newspaper.com:1471; devicetype=0; osfam=0; rplmct=2; s_pers=%20s_vmonthnum%3D1472680800496%2526vn%253D1%7C1472680800496%3B%20s_nr%3D1471686767664-New%7C1474278767664%3B%20s_lv%3D1471686767669%7C1566294767669%3B%20s_lv_s%3DFirst%2520Visit%7C1471688567669%3B%20s_monthinvisit%3Dtrue%7C1471688567677%3B%20gvp_p5%3Dsports%253Ablog%253Aearly-lead%2520-%2520184693%2520-%252020160820%2520-%2520u-s%7C1471688567681%3B%20gvp_p51%3Dwp%2520-%2520sports%7C1471688567684%3B; s_sess=%20s_wp_ep%3Dhomepage%3B%20s._ref%3Dhttps%253A%252F%252Fwww.google.ch%252F%3B%20s_cc%3Dtrue%3B%20s_ppvl%3Dsports%25253Ablog%25253Aearly-lead%252520-%252520184693%252520-%25252020160820%252520-%252520u-lawyer%252C12%252C12%252C502%252C1231%252C502%252C1680%252C1050%252C2%252CP%3B%20s_ppv%3Dsports%25253Ablog%25253Aearly-lead%252520-%252520184693%252520-%25252020160820%252520-%252520u-s-lawyer%252C12%252C12%252C502%252C1231%252C502%252C1680%252C1050%252C2%252CP%3B%20s_dslv%3DFirst%2520Visit%3B%20s_sq%3Dwpninewspapercom%253D%252526pid%25253Dsports%2525253Ablog%2525253Aearly-lead%25252520-%25252520184693%25252520-%2525252020160820%25252520-%25252520u-s%252526pidt%25253D1%252526oid%25253Dhttps%2525253A%2525252F%2525252Fwww.newspaper.com%2525252F%2525253Fnid%2525253Dmenu_nav_homepage%252526ot%25253DA%3B`,
707 },
708 }
709 wantCookies := []*Cookie{
710 {Name: "de", Value: ""},
711 {Name: "client_region", Value: "0"},
712 {Name: "rpld1", Value: "0:hispeed.ch|20:che|21:zh|22:zurich|23:47.36|24:8.53|"},
713 {Name: "rpld0", Value: "1:08|"},
714 {Name: "backplane-channel", Value: "newspaper.com:1471"},
715 {Name: "devicetype", Value: "0"},
716 {Name: "osfam", Value: "0"},
717 {Name: "rplmct", Value: "2"},
718 {Name: "s_pers", Value: "%20s_vmonthnum%3D1472680800496%2526vn%253D1%7C1472680800496%3B%20s_nr%3D1471686767664-New%7C1474278767664%3B%20s_lv%3D1471686767669%7C1566294767669%3B%20s_lv_s%3DFirst%2520Visit%7C1471688567669%3B%20s_monthinvisit%3Dtrue%7C1471688567677%3B%20gvp_p5%3Dsports%253Ablog%253Aearly-lead%2520-%2520184693%2520-%252020160820%2520-%2520u-s%7C1471688567681%3B%20gvp_p51%3Dwp%2520-%2520sports%7C1471688567684%3B"},
719 {Name: "s_sess", Value: "%20s_wp_ep%3Dhomepage%3B%20s._ref%3Dhttps%253A%252F%252Fwww.google.ch%252F%3B%20s_cc%3Dtrue%3B%20s_ppvl%3Dsports%25253Ablog%25253Aearly-lead%252520-%252520184693%252520-%25252020160820%252520-%252520u-lawyer%252C12%252C12%252C502%252C1231%252C502%252C1680%252C1050%252C2%252CP%3B%20s_ppv%3Dsports%25253Ablog%25253Aearly-lead%252520-%252520184693%252520-%25252020160820%252520-%252520u-s-lawyer%252C12%252C12%252C502%252C1231%252C502%252C1680%252C1050%252C2%252CP%3B%20s_dslv%3DFirst%2520Visit%3B%20s_sq%3Dwpninewspapercom%253D%252526pid%25253Dsports%2525253Ablog%2525253Aearly-lead%25252520-%25252520184693%25252520-%2525252020160820%25252520-%25252520u-s%252526pidt%25253D1%252526oid%25253Dhttps%2525253A%2525252F%2525252Fwww.newspaper.com%2525252F%2525253Fnid%2525253Dmenu_nav_homepage%252526ot%25253DA%3B"},
720 }
721 var c []*Cookie
722 b.ReportAllocs()
723 b.ResetTimer()
724 for i := 0; i < b.N; i++ {
725 c = readCookies(header, "")
726 }
727 if !reflect.DeepEqual(c, wantCookies) {
728 b.Fatalf("readCookies:\nhave: %s\nwant: %s\n", toJSON(c), toJSON(wantCookies))
729 }
730 }
731
732 func TestParseCookie(t *testing.T) {
733 tests := []struct {
734 line string
735 cookies []*Cookie
736 err error
737 godebug string
738 }{
739 {
740 line: "Cookie-1=v$1",
741 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}},
742 },
743 {
744 line: "Cookie-1=v$1;c2=v2",
745 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}},
746 },
747 {
748 line: `Cookie-1="v$1";c2="v2"`,
749 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2", Quoted: true}},
750 },
751 {
752 line: "k1=",
753 cookies: []*Cookie{{Name: "k1", Value: ""}},
754 },
755 {
756 line: "",
757 err: errBlankCookie,
758 },
759 {
760 line: "equal-not-found",
761 err: errEqualNotFoundInCookie,
762 },
763 {
764 line: "=v1",
765 err: errInvalidCookieName,
766 },
767 {
768 line: "k1=\\",
769 err: errInvalidCookieValue,
770 },
771 {
772 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
773 err: errCookieNumLimitExceeded,
774 },
775 {
776 line: strings.Repeat(";a=", 10)[1:],
777 err: errCookieNumLimitExceeded,
778 godebug: "httpcookiemaxnum=5",
779 },
780 {
781 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
782 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
783 godebug: "httpcookiemaxnum=0",
784 },
785 {
786 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
787 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
788 godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
789 },
790 }
791 for i, tt := range tests {
792 t.Setenv("GODEBUG", tt.godebug)
793 gotCookies, gotErr := ParseCookie(tt.line)
794 if !errors.Is(gotErr, tt.err) {
795 t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err)
796 }
797 if !reflect.DeepEqual(gotCookies, tt.cookies) {
798 t.Errorf("#%d ParseCookie:\ngot cookies: %s\nwant cookies: %s\n", i, toJSON(gotCookies), toJSON(tt.cookies))
799 }
800 }
801 }
802
803 func TestParseSetCookie(t *testing.T) {
804 tests := []struct {
805 line string
806 cookie *Cookie
807 err error
808 }{
809 {
810 line: "Cookie-1=v$1",
811 cookie: &Cookie{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"},
812 },
813 {
814 line: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
815 cookie: &Cookie{
816 Name: "NID",
817 Value: "99=YsDT5i3E-CXax-",
818 Path: "/",
819 Domain: ".google.ch",
820 HttpOnly: true,
821 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
822 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
823 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
824 },
825 },
826 {
827 line: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
828 cookie: &Cookie{
829 Name: ".ASPXAUTH",
830 Value: "7E3AA",
831 Path: "/",
832 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
833 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
834 HttpOnly: true,
835 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
836 },
837 },
838 {
839 line: "ASP.NET_SessionId=foo; path=/; HttpOnly",
840 cookie: &Cookie{
841 Name: "ASP.NET_SessionId",
842 Value: "foo",
843 Path: "/",
844 HttpOnly: true,
845 Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
846 },
847 },
848 {
849 line: "samesitedefault=foo; SameSite",
850 cookie: &Cookie{
851 Name: "samesitedefault",
852 Value: "foo",
853 SameSite: SameSiteDefaultMode,
854 Raw: "samesitedefault=foo; SameSite",
855 },
856 },
857 {
858 line: "samesiteinvalidisdefault=foo; SameSite=invalid",
859 cookie: &Cookie{
860 Name: "samesiteinvalidisdefault",
861 Value: "foo",
862 SameSite: SameSiteDefaultMode,
863 Raw: "samesiteinvalidisdefault=foo; SameSite=invalid",
864 },
865 },
866 {
867 line: "samesitelax=foo; SameSite=Lax",
868 cookie: &Cookie{
869 Name: "samesitelax",
870 Value: "foo",
871 SameSite: SameSiteLaxMode,
872 Raw: "samesitelax=foo; SameSite=Lax",
873 },
874 },
875 {
876 line: "samesitestrict=foo; SameSite=Strict",
877 cookie: &Cookie{
878 Name: "samesitestrict",
879 Value: "foo",
880 SameSite: SameSiteStrictMode,
881 Raw: "samesitestrict=foo; SameSite=Strict",
882 },
883 },
884 {
885 line: "samesitenone=foo; SameSite=None",
886 cookie: &Cookie{
887 Name: "samesitenone",
888 Value: "foo",
889 SameSite: SameSiteNoneMode,
890 Raw: "samesitenone=foo; SameSite=None",
891 },
892 },
893
894
895 {
896 line: `special-1=a z`,
897 cookie: &Cookie{Name: "special-1", Value: "a z", Raw: `special-1=a z`},
898 },
899 {
900 line: `special-2=" z"`,
901 cookie: &Cookie{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`},
902 },
903 {
904 line: `special-3="a "`,
905 cookie: &Cookie{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`},
906 },
907 {
908 line: `special-4=" "`,
909 cookie: &Cookie{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`},
910 },
911 {
912 line: `special-5=a,z`,
913 cookie: &Cookie{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`},
914 },
915 {
916 line: `special-6=",z"`,
917 cookie: &Cookie{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`},
918 },
919 {
920 line: `special-7=a,`,
921 cookie: &Cookie{Name: "special-7", Value: "a,", Raw: `special-7=a,`},
922 },
923 {
924 line: `special-8=","`,
925 cookie: &Cookie{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`},
926 },
927
928
929 {
930 line: `special-9 =","`,
931 cookie: &Cookie{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`},
932 },
933 {
934 line: "",
935 err: errBlankCookie,
936 },
937 {
938 line: "equal-not-found",
939 err: errEqualNotFoundInCookie,
940 },
941 {
942 line: "=v1",
943 err: errInvalidCookieName,
944 },
945 {
946 line: "k1=\\",
947 err: errInvalidCookieValue,
948 },
949 }
950 for i, tt := range tests {
951 gotCookie, gotErr := ParseSetCookie(tt.line)
952 if !errors.Is(gotErr, tt.err) {
953 t.Errorf("#%d ParseSetCookie got error %v, want error %v", i, gotErr, tt.err)
954 continue
955 }
956 if !reflect.DeepEqual(gotCookie, tt.cookie) {
957 t.Errorf("#%d ParseSetCookie:\ngot cookie: %s\nwant cookie: %s\n", i, toJSON(gotCookie), toJSON(tt.cookie))
958 }
959 }
960 }
961
View as plain text