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 {"", true, `""`},
579 }
580 for _, tt := range tests {
581 if got := sanitizeCookieValue(tt.in, tt.quoted); got != tt.want {
582 t.Errorf("sanitizeCookieValue(%q) = %q; want %q", tt.in, got, tt.want)
583 }
584 }
585
586 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
587 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
588 }
589 }
590
591 func TestCookieSanitizePath(t *testing.T) {
592 defer log.SetOutput(os.Stderr)
593 var logbuf strings.Builder
594 log.SetOutput(&logbuf)
595
596 tests := []struct {
597 in, want string
598 }{
599 {"/path", "/path"},
600 {"/path with space/", "/path with space/"},
601 {"/just;no;semicolon\x00orstuff/", "/justnosemicolonorstuff/"},
602 }
603 for _, tt := range tests {
604 if got := sanitizeCookiePath(tt.in); got != tt.want {
605 t.Errorf("sanitizeCookiePath(%q) = %q; want %q", tt.in, got, tt.want)
606 }
607 }
608
609 if got, sub := logbuf.String(), "dropping invalid bytes"; !strings.Contains(got, sub) {
610 t.Errorf("Expected substring %q in log output. Got:\n%s", sub, got)
611 }
612 }
613
614 func TestCookieValid(t *testing.T) {
615 tests := []struct {
616 cookie *Cookie
617 valid bool
618 }{
619 {nil, false},
620 {&Cookie{Name: ""}, false},
621 {&Cookie{Name: "invalid-value", Value: "foo\"bar"}, false},
622 {&Cookie{Name: "invalid-path", Path: "/foo;bar/"}, false},
623 {&Cookie{Name: "invalid-secure-for-partitioned", Value: "foo", Path: "/", Secure: false, Partitioned: true}, false},
624 {&Cookie{Name: "invalid-domain", Domain: "example.com:80"}, false},
625 {&Cookie{Name: "invalid-expiry", Value: "", Expires: time.Date(1600, 1, 1, 1, 1, 1, 1, time.UTC)}, false},
626 {&Cookie{Name: "valid-empty"}, true},
627 {&Cookie{Name: "valid-expires", Value: "foo", Path: "/bar", Domain: "example.com", Expires: time.Unix(0, 0)}, true},
628 {&Cookie{Name: "valid-max-age", Value: "foo", Path: "/bar", Domain: "example.com", MaxAge: 60}, true},
629 {&Cookie{Name: "valid-all-fields", Value: "foo", Path: "/bar", Domain: "example.com", Expires: time.Unix(0, 0), MaxAge: 0}, true},
630 {&Cookie{Name: "valid-partitioned", Value: "foo", Path: "/", Secure: true, Partitioned: true}, true},
631 }
632
633 for _, tt := range tests {
634 err := tt.cookie.Valid()
635 if err != nil && tt.valid {
636 t.Errorf("%#v.Valid() returned error %v; want nil", tt.cookie, err)
637 }
638 if err == nil && !tt.valid {
639 t.Errorf("%#v.Valid() returned nil; want error", tt.cookie)
640 }
641 }
642 }
643
644 func BenchmarkCookieString(b *testing.B) {
645 const wantCookieString = `cookie-9=i3e01nf61b6t23bvfmplnanol3; Path=/restricted/; Domain=example.com; Expires=Tue, 10 Nov 2009 23:00:00 GMT; Max-Age=3600`
646 c := &Cookie{
647 Name: "cookie-9",
648 Value: "i3e01nf61b6t23bvfmplnanol3",
649 Expires: time.Unix(1257894000, 0),
650 Path: "/restricted/",
651 Domain: ".example.com",
652 MaxAge: 3600,
653 }
654 var benchmarkCookieString string
655 b.ReportAllocs()
656 b.ResetTimer()
657 for i := 0; i < b.N; i++ {
658 benchmarkCookieString = c.String()
659 }
660 if have, want := benchmarkCookieString, wantCookieString; have != want {
661 b.Fatalf("Have: %v Want: %v", have, want)
662 }
663 }
664
665 func BenchmarkReadSetCookies(b *testing.B) {
666 header := Header{
667 "Set-Cookie": {
668 "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
669 ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
670 },
671 }
672 wantCookies := []*Cookie{
673 {
674 Name: "NID",
675 Value: "99=YsDT5i3E-CXax-",
676 Path: "/",
677 Domain: ".google.ch",
678 HttpOnly: true,
679 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
680 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
681 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
682 },
683 {
684 Name: ".ASPXAUTH",
685 Value: "7E3AA",
686 Path: "/",
687 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
688 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
689 HttpOnly: true,
690 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
691 },
692 }
693 var c []*Cookie
694 b.ReportAllocs()
695 b.ResetTimer()
696 for i := 0; i < b.N; i++ {
697 c = readSetCookies(header)
698 }
699 if !reflect.DeepEqual(c, wantCookies) {
700 b.Fatalf("readSetCookies:\nhave: %s\nwant: %s\n", toJSON(c), toJSON(wantCookies))
701 }
702 }
703
704 func BenchmarkReadCookies(b *testing.B) {
705 header := Header{
706 "Cookie": {
707 `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`,
708 },
709 }
710 wantCookies := []*Cookie{
711 {Name: "de", Value: ""},
712 {Name: "client_region", Value: "0"},
713 {Name: "rpld1", Value: "0:hispeed.ch|20:che|21:zh|22:zurich|23:47.36|24:8.53|"},
714 {Name: "rpld0", Value: "1:08|"},
715 {Name: "backplane-channel", Value: "newspaper.com:1471"},
716 {Name: "devicetype", Value: "0"},
717 {Name: "osfam", Value: "0"},
718 {Name: "rplmct", Value: "2"},
719 {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"},
720 {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"},
721 }
722 var c []*Cookie
723 b.ReportAllocs()
724 b.ResetTimer()
725 for i := 0; i < b.N; i++ {
726 c = readCookies(header, "")
727 }
728 if !reflect.DeepEqual(c, wantCookies) {
729 b.Fatalf("readCookies:\nhave: %s\nwant: %s\n", toJSON(c), toJSON(wantCookies))
730 }
731 }
732
733 func TestParseCookie(t *testing.T) {
734 tests := []struct {
735 line string
736 cookies []*Cookie
737 err error
738 godebug string
739 }{
740 {
741 line: "Cookie-1=v$1",
742 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}},
743 },
744 {
745 line: "Cookie-1=v$1;c2=v2",
746 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1"}, {Name: "c2", Value: "v2"}},
747 },
748 {
749 line: `Cookie-1="v$1";c2="v2"`,
750 cookies: []*Cookie{{Name: "Cookie-1", Value: "v$1", Quoted: true}, {Name: "c2", Value: "v2", Quoted: true}},
751 },
752 {
753 line: "k1=",
754 cookies: []*Cookie{{Name: "k1", Value: ""}},
755 },
756 {
757 line: "",
758 err: errBlankCookie,
759 },
760 {
761 line: "equal-not-found",
762 err: errEqualNotFoundInCookie,
763 },
764 {
765 line: "=v1",
766 err: errInvalidCookieName,
767 },
768 {
769 line: "k1=\\",
770 err: errInvalidCookieValue,
771 },
772 {
773 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
774 err: errCookieNumLimitExceeded,
775 },
776 {
777 line: strings.Repeat(";a=", 10)[1:],
778 err: errCookieNumLimitExceeded,
779 godebug: "httpcookiemaxnum=5",
780 },
781 {
782 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
783 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
784 godebug: "httpcookiemaxnum=0",
785 },
786 {
787 line: strings.Repeat(";a=", defaultCookieMaxNum+1)[1:],
788 cookies: slices.Repeat([]*Cookie{{Name: "a", Value: "", Quoted: false}}, defaultCookieMaxNum+1),
789 godebug: fmt.Sprintf("httpcookiemaxnum=%v", defaultCookieMaxNum+1),
790 },
791 }
792 for i, tt := range tests {
793 t.Setenv("GODEBUG", tt.godebug)
794 gotCookies, gotErr := ParseCookie(tt.line)
795 if !errors.Is(gotErr, tt.err) {
796 t.Errorf("#%d ParseCookie got error %v, want error %v", i, gotErr, tt.err)
797 }
798 if !reflect.DeepEqual(gotCookies, tt.cookies) {
799 t.Errorf("#%d ParseCookie:\ngot cookies: %s\nwant cookies: %s\n", i, toJSON(gotCookies), toJSON(tt.cookies))
800 }
801 }
802 }
803
804 func TestParseSetCookie(t *testing.T) {
805 tests := []struct {
806 line string
807 cookie *Cookie
808 err error
809 }{
810 {
811 line: "Cookie-1=v$1",
812 cookie: &Cookie{Name: "Cookie-1", Value: "v$1", Raw: "Cookie-1=v$1"},
813 },
814 {
815 line: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
816 cookie: &Cookie{
817 Name: "NID",
818 Value: "99=YsDT5i3E-CXax-",
819 Path: "/",
820 Domain: ".google.ch",
821 HttpOnly: true,
822 Expires: time.Date(2011, 11, 23, 1, 5, 3, 0, time.UTC),
823 RawExpires: "Wed, 23-Nov-2011 01:05:03 GMT",
824 Raw: "NID=99=YsDT5i3E-CXax-; expires=Wed, 23-Nov-2011 01:05:03 GMT; path=/; domain=.google.ch; HttpOnly",
825 },
826 },
827 {
828 line: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
829 cookie: &Cookie{
830 Name: ".ASPXAUTH",
831 Value: "7E3AA",
832 Path: "/",
833 Expires: time.Date(2012, 3, 7, 14, 25, 6, 0, time.UTC),
834 RawExpires: "Wed, 07-Mar-2012 14:25:06 GMT",
835 HttpOnly: true,
836 Raw: ".ASPXAUTH=7E3AA; expires=Wed, 07-Mar-2012 14:25:06 GMT; path=/; HttpOnly",
837 },
838 },
839 {
840 line: "ASP.NET_SessionId=foo; path=/; HttpOnly",
841 cookie: &Cookie{
842 Name: "ASP.NET_SessionId",
843 Value: "foo",
844 Path: "/",
845 HttpOnly: true,
846 Raw: "ASP.NET_SessionId=foo; path=/; HttpOnly",
847 },
848 },
849 {
850 line: "samesitedefault=foo; SameSite",
851 cookie: &Cookie{
852 Name: "samesitedefault",
853 Value: "foo",
854 SameSite: SameSiteDefaultMode,
855 Raw: "samesitedefault=foo; SameSite",
856 },
857 },
858 {
859 line: "samesiteinvalidisdefault=foo; SameSite=invalid",
860 cookie: &Cookie{
861 Name: "samesiteinvalidisdefault",
862 Value: "foo",
863 SameSite: SameSiteDefaultMode,
864 Raw: "samesiteinvalidisdefault=foo; SameSite=invalid",
865 },
866 },
867 {
868 line: "samesitelax=foo; SameSite=Lax",
869 cookie: &Cookie{
870 Name: "samesitelax",
871 Value: "foo",
872 SameSite: SameSiteLaxMode,
873 Raw: "samesitelax=foo; SameSite=Lax",
874 },
875 },
876 {
877 line: "samesitestrict=foo; SameSite=Strict",
878 cookie: &Cookie{
879 Name: "samesitestrict",
880 Value: "foo",
881 SameSite: SameSiteStrictMode,
882 Raw: "samesitestrict=foo; SameSite=Strict",
883 },
884 },
885 {
886 line: "samesitenone=foo; SameSite=None",
887 cookie: &Cookie{
888 Name: "samesitenone",
889 Value: "foo",
890 SameSite: SameSiteNoneMode,
891 Raw: "samesitenone=foo; SameSite=None",
892 },
893 },
894
895
896 {
897 line: `special-1=a z`,
898 cookie: &Cookie{Name: "special-1", Value: "a z", Raw: `special-1=a z`},
899 },
900 {
901 line: `special-2=" z"`,
902 cookie: &Cookie{Name: "special-2", Value: " z", Quoted: true, Raw: `special-2=" z"`},
903 },
904 {
905 line: `special-3="a "`,
906 cookie: &Cookie{Name: "special-3", Value: "a ", Quoted: true, Raw: `special-3="a "`},
907 },
908 {
909 line: `special-4=" "`,
910 cookie: &Cookie{Name: "special-4", Value: " ", Quoted: true, Raw: `special-4=" "`},
911 },
912 {
913 line: `special-5=a,z`,
914 cookie: &Cookie{Name: "special-5", Value: "a,z", Raw: `special-5=a,z`},
915 },
916 {
917 line: `special-6=",z"`,
918 cookie: &Cookie{Name: "special-6", Value: ",z", Quoted: true, Raw: `special-6=",z"`},
919 },
920 {
921 line: `special-7=a,`,
922 cookie: &Cookie{Name: "special-7", Value: "a,", Raw: `special-7=a,`},
923 },
924 {
925 line: `special-8=","`,
926 cookie: &Cookie{Name: "special-8", Value: ",", Quoted: true, Raw: `special-8=","`},
927 },
928
929
930 {
931 line: `special-9 =","`,
932 cookie: &Cookie{Name: "special-9", Value: ",", Quoted: true, Raw: `special-9 =","`},
933 },
934 {
935 line: "",
936 err: errBlankCookie,
937 },
938 {
939 line: "equal-not-found",
940 err: errEqualNotFoundInCookie,
941 },
942 {
943 line: "=v1",
944 err: errInvalidCookieName,
945 },
946 {
947 line: "k1=\\",
948 err: errInvalidCookieValue,
949 },
950 }
951 for i, tt := range tests {
952 gotCookie, gotErr := ParseSetCookie(tt.line)
953 if !errors.Is(gotErr, tt.err) {
954 t.Errorf("#%d ParseSetCookie got error %v, want error %v", i, gotErr, tt.err)
955 continue
956 }
957 if !reflect.DeepEqual(gotCookie, tt.cookie) {
958 t.Errorf("#%d ParseSetCookie:\ngot cookie: %s\nwant cookie: %s\n", i, toJSON(gotCookie), toJSON(tt.cookie))
959 }
960 }
961 }
962
View as plain text