1// Copyright 2013 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 5package cookiejar 6 7import ( 8 "fmt" 9 "net/http" 10 "net/url" 11 "slices" 12 "strings" 13 "testing" 14 "time" 15) 16 17// tNow is the synthetic current time used as now during testing. 18var tNow = time.Date(2013, 1, 1, 12, 0, 0, 0, time.UTC) 19 20// testPSL implements PublicSuffixList with just two rules: "co.uk" 21// and the default rule "*". 22// The implementation has two intentional bugs: 23// 24// PublicSuffix("www.buggy.psl") == "xy" 25// PublicSuffix("www2.buggy.psl") == "com" 26type testPSL struct{} 27 28func (testPSL) String() string { 29 return "testPSL" 30} 31func (testPSL) PublicSuffix(d string) string { 32 if d == "co.uk" || strings.HasSuffix(d, ".co.uk") { 33 return "co.uk" 34 } 35 if d == "www.buggy.psl" { 36 return "xy" 37 } 38 if d == "www2.buggy.psl" { 39 return "com" 40 } 41 return d[strings.LastIndex(d, ".")+1:] 42} 43 44// newTestJar creates an empty Jar with testPSL as the public suffix list. 45func newTestJar() *Jar { 46 jar, err := New(&Options{PublicSuffixList: testPSL{}}) 47 if err != nil { 48 panic(err) 49 } 50 return jar 51} 52 53var hasDotSuffixTests = [...]struct { 54 s, suffix string 55}{ 56 {"", ""}, 57 {"", "."}, 58 {"", "x"}, 59 {".", ""}, 60 {".", "."}, 61 {".", ".."}, 62 {".", "x"}, 63 {".", "x."}, 64 {".", ".x"}, 65 {".", ".x."}, 66 {"x", ""}, 67 {"x", "."}, 68 {"x", ".."}, 69 {"x", "x"}, 70 {"x", "x."}, 71 {"x", ".x"}, 72 {"x", ".x."}, 73 {".x", ""}, 74 {".x", "."}, 75 {".x", ".."}, 76 {".x", "x"}, 77 {".x", "x."}, 78 {".x", ".x"}, 79 {".x", ".x."}, 80 {"x.", ""}, 81 {"x.", "."}, 82 {"x.", ".."}, 83 {"x.", "x"}, 84 {"x.", "x."}, 85 {"x.", ".x"}, 86 {"x.", ".x."}, 87 {"com", ""}, 88 {"com", "m"}, 89 {"com", "om"}, 90 {"com", "com"}, 91 {"com", ".com"}, 92 {"com", "x.com"}, 93 {"com", "xcom"}, 94 {"com", "xorg"}, 95 {"com", "org"}, 96 {"com", "rg"}, 97 {"foo.com", ""}, 98 {"foo.com", "m"}, 99 {"foo.com", "om"}, 100 {"foo.com", "com"}, 101 {"foo.com", ".com"}, 102 {"foo.com", "o.com"}, 103 {"foo.com", "oo.com"}, 104 {"foo.com", "foo.com"}, 105 {"foo.com", ".foo.com"}, 106 {"foo.com", "x.foo.com"}, 107 {"foo.com", "xfoo.com"}, 108 {"foo.com", "xfoo.org"}, 109 {"foo.com", "foo.org"}, 110 {"foo.com", "oo.org"}, 111 {"foo.com", "o.org"}, 112 {"foo.com", ".org"}, 113 {"foo.com", "org"}, 114 {"foo.com", "rg"}, 115} 116 117func TestHasDotSuffix(t *testing.T) { 118 for _, tc := range hasDotSuffixTests { 119 got := hasDotSuffix(tc.s, tc.suffix) 120 want := strings.HasSuffix(tc.s, "."+tc.suffix) 121 if got != want { 122 t.Errorf("s=%q, suffix=%q: got %v, want %v", tc.s, tc.suffix, got, want) 123 } 124 } 125} 126 127var canonicalHostTests = map[string]string{ 128 "www.example.com": "www.example.com", 129 "WWW.EXAMPLE.COM": "www.example.com", 130 "wWw.eXAmple.CoM": "www.example.com", 131 "www.example.com:80": "www.example.com", 132 "192.168.0.10": "192.168.0.10", 133 "192.168.0.5:8080": "192.168.0.5", 134 "2001:4860:0:2001::68": "2001:4860:0:2001::68", 135 "[2001:4860:0:::68]:8080": "2001:4860:0:::68", 136 "www.bücher.de": "www.xn--bcher-kva.de", 137 "www.example.com.": "www.example.com", 138 // TODO: Fix canonicalHost so that all of the following malformed 139 // domain names trigger an error. (This list is not exhaustive, e.g. 140 // malformed internationalized domain names are missing.) 141 ".": "", 142 "..": ".", 143 "...": "..", 144 ".net": ".net", 145 ".net.": ".net", 146 "a..": "a.", 147 "b.a..": "b.a.", 148 "weird.stuff...": "weird.stuff..", 149 "[bad.unmatched.bracket:": "error", 150} 151 152func TestCanonicalHost(t *testing.T) { 153 for h, want := range canonicalHostTests { 154 got, err := canonicalHost(h) 155 if want == "error" { 156 if err == nil { 157 t.Errorf("%q: got %q and nil error, want non-nil", h, got) 158 } 159 continue 160 } 161 if err != nil { 162 t.Errorf("%q: %v", h, err) 163 continue 164 } 165 if got != want { 166 t.Errorf("%q: got %q, want %q", h, got, want) 167 continue 168 } 169 } 170} 171 172var hasPortTests = map[string]bool{ 173 "www.example.com": false, 174 "www.example.com:80": true, 175 "127.0.0.1": false, 176 "127.0.0.1:8080": true, 177 "2001:4860:0:2001::68": false, 178 "[2001::0:::68]:80": true, 179} 180 181func TestHasPort(t *testing.T) { 182 for host, want := range hasPortTests { 183 if got := hasPort(host); got != want { 184 t.Errorf("%q: got %t, want %t", host, got, want) 185 } 186 } 187} 188 189var jarKeyTests = map[string]string{ 190 "foo.www.example.com": "example.com", 191 "www.example.com": "example.com", 192 "example.com": "example.com", 193 "com": "com", 194 "foo.www.bbc.co.uk": "bbc.co.uk", 195 "www.bbc.co.uk": "bbc.co.uk", 196 "bbc.co.uk": "bbc.co.uk", 197 "co.uk": "co.uk", 198 "uk": "uk", 199 "192.168.0.5": "192.168.0.5", 200 "www.buggy.psl": "www.buggy.psl", 201 "www2.buggy.psl": "buggy.psl", 202 // The following are actual outputs of canonicalHost for 203 // malformed inputs to canonicalHost (see above). 204 "": "", 205 ".": ".", 206 "..": ".", 207 ".net": ".net", 208 "a.": "a.", 209 "b.a.": "a.", 210 "weird.stuff..": ".", 211} 212 213func TestJarKey(t *testing.T) { 214 for host, want := range jarKeyTests { 215 if got := jarKey(host, testPSL{}); got != want { 216 t.Errorf("%q: got %q, want %q", host, got, want) 217 } 218 } 219} 220 221var jarKeyNilPSLTests = map[string]string{ 222 "foo.www.example.com": "example.com", 223 "www.example.com": "example.com", 224 "example.com": "example.com", 225 "com": "com", 226 "foo.www.bbc.co.uk": "co.uk", 227 "www.bbc.co.uk": "co.uk", 228 "bbc.co.uk": "co.uk", 229 "co.uk": "co.uk", 230 "uk": "uk", 231 "192.168.0.5": "192.168.0.5", 232 // The following are actual outputs of canonicalHost for 233 // malformed inputs to canonicalHost. 234 "": "", 235 ".": ".", 236 "..": "..", 237 ".net": ".net", 238 "a.": "a.", 239 "b.a.": "a.", 240 "weird.stuff..": "stuff..", 241} 242 243func TestJarKeyNilPSL(t *testing.T) { 244 for host, want := range jarKeyNilPSLTests { 245 if got := jarKey(host, nil); got != want { 246 t.Errorf("%q: got %q, want %q", host, got, want) 247 } 248 } 249} 250 251var isIPTests = map[string]bool{ 252 "127.0.0.1": true, 253 "1.2.3.4": true, 254 "2001:4860:0:2001::68": true, 255 "::1%zone": true, 256 "example.com": false, 257 "1.1.1.300": false, 258 "www.foo.bar.net": false, 259 "123.foo.bar.net": false, 260} 261 262func TestIsIP(t *testing.T) { 263 for host, want := range isIPTests { 264 if got := isIP(host); got != want { 265 t.Errorf("%q: got %t, want %t", host, got, want) 266 } 267 } 268} 269 270var defaultPathTests = map[string]string{ 271 "/": "/", 272 "/abc": "/", 273 "/abc/": "/abc", 274 "/abc/xyz": "/abc", 275 "/abc/xyz/": "/abc/xyz", 276 "/a/b/c.html": "/a/b", 277 "": "/", 278 "strange": "/", 279 "//": "/", 280 "/a//b": "/a/", 281 "/a/./b": "/a/.", 282 "/a/../b": "/a/..", 283} 284 285func TestDefaultPath(t *testing.T) { 286 for path, want := range defaultPathTests { 287 if got := defaultPath(path); got != want { 288 t.Errorf("%q: got %q, want %q", path, got, want) 289 } 290 } 291} 292 293var domainAndTypeTests = [...]struct { 294 host string // host Set-Cookie header was received from 295 domain string // domain attribute in Set-Cookie header 296 wantDomain string // expected domain of cookie 297 wantHostOnly bool // expected host-cookie flag 298 wantErr error // expected error 299}{ 300 {"www.example.com", "", "www.example.com", true, nil}, 301 {"127.0.0.1", "", "127.0.0.1", true, nil}, 302 {"2001:4860:0:2001::68", "", "2001:4860:0:2001::68", true, nil}, 303 {"www.example.com", "example.com", "example.com", false, nil}, 304 {"www.example.com", ".example.com", "example.com", false, nil}, 305 {"www.example.com", "www.example.com", "www.example.com", false, nil}, 306 {"www.example.com", ".www.example.com", "www.example.com", false, nil}, 307 {"foo.sso.example.com", "sso.example.com", "sso.example.com", false, nil}, 308 {"bar.co.uk", "bar.co.uk", "bar.co.uk", false, nil}, 309 {"foo.bar.co.uk", ".bar.co.uk", "bar.co.uk", false, nil}, 310 {"127.0.0.1", "127.0.0.1", "127.0.0.1", true, nil}, 311 {"2001:4860:0:2001::68", "2001:4860:0:2001::68", "2001:4860:0:2001::68", true, nil}, 312 {"www.example.com", ".", "", false, errMalformedDomain}, 313 {"www.example.com", "..", "", false, errMalformedDomain}, 314 {"www.example.com", "other.com", "", false, errIllegalDomain}, 315 {"www.example.com", "com", "", false, errIllegalDomain}, 316 {"www.example.com", ".com", "", false, errIllegalDomain}, 317 {"foo.bar.co.uk", ".co.uk", "", false, errIllegalDomain}, 318 {"127.www.0.0.1", "127.0.0.1", "", false, errIllegalDomain}, 319 {"com", "", "com", true, nil}, 320 {"com", "com", "com", true, nil}, 321 {"com", ".com", "com", true, nil}, 322 {"co.uk", "", "co.uk", true, nil}, 323 {"co.uk", "co.uk", "co.uk", true, nil}, 324 {"co.uk", ".co.uk", "co.uk", true, nil}, 325} 326 327func TestDomainAndType(t *testing.T) { 328 jar := newTestJar() 329 for _, tc := range domainAndTypeTests { 330 domain, hostOnly, err := jar.domainAndType(tc.host, tc.domain) 331 if err != tc.wantErr { 332 t.Errorf("%q/%q: got %q error, want %v", 333 tc.host, tc.domain, err, tc.wantErr) 334 continue 335 } 336 if err != nil { 337 continue 338 } 339 if domain != tc.wantDomain || hostOnly != tc.wantHostOnly { 340 t.Errorf("%q/%q: got %q/%t want %q/%t", 341 tc.host, tc.domain, domain, hostOnly, 342 tc.wantDomain, tc.wantHostOnly) 343 } 344 } 345} 346 347// expiresIn creates an expires attribute delta seconds from tNow. 348func expiresIn(delta int) string { 349 t := tNow.Add(time.Duration(delta) * time.Second) 350 return "expires=" + t.Format(time.RFC1123) 351} 352 353// mustParseURL parses s to a URL and panics on error. 354func mustParseURL(s string) *url.URL { 355 u, err := url.Parse(s) 356 if err != nil || u.Scheme == "" || u.Host == "" { 357 panic(fmt.Sprintf("Unable to parse URL %s.", s)) 358 } 359 return u 360} 361 362// jarTest encapsulates the following actions on a jar: 363// 1. Perform SetCookies with fromURL and the cookies from setCookies. 364// (Done at time tNow + 0 ms.) 365// 2. Check that the entries in the jar matches content. 366// (Done at time tNow + 1001 ms.) 367// 3. For each query in tests: Check that Cookies with toURL yields the 368// cookies in want. 369// (Query n done at tNow + (n+2)*1001 ms.) 370type jarTest struct { 371 description string // The description of what this test is supposed to test 372 fromURL string // The full URL of the request from which Set-Cookie headers where received 373 setCookies []string // All the cookies received from fromURL 374 content string // The whole (non-expired) content of the jar 375 queries []query // Queries to test the Jar.Cookies method 376} 377 378// query contains one test of the cookies returned from Jar.Cookies. 379type query struct { 380 toURL string // the URL in the Cookies call 381 want string // the expected list of cookies (order matters) 382} 383 384// run runs the jarTest. 385func (test jarTest) run(t *testing.T, jar *Jar) { 386 now := tNow 387 388 // Populate jar with cookies. 389 setCookies := make([]*http.Cookie, len(test.setCookies)) 390 for i, cs := range test.setCookies { 391 cookies := (&http.Response{Header: http.Header{"Set-Cookie": {cs}}}).Cookies() 392 if len(cookies) != 1 { 393 panic(fmt.Sprintf("Wrong cookie line %q: %#v", cs, cookies)) 394 } 395 setCookies[i] = cookies[0] 396 } 397 jar.setCookies(mustParseURL(test.fromURL), setCookies, now) 398 now = now.Add(1001 * time.Millisecond) 399 400 // Serialize non-expired entries in the form "name1=val1 name2=val2". 401 var cs []string 402 for _, submap := range jar.entries { 403 for _, cookie := range submap { 404 if !cookie.Expires.After(now) { 405 continue 406 } 407 408 v := cookie.Value 409 if strings.ContainsAny(v, " ,") || cookie.Quoted { 410 v = `"` + v + `"` 411 } 412 cs = append(cs, cookie.Name+"="+v) 413 } 414 } 415 slices.Sort(cs) 416 got := strings.Join(cs, " ") 417 418 // Make sure jar content matches our expectations. 419 if got != test.content { 420 t.Errorf("Test %q Content\ngot %q\nwant %q", 421 test.description, got, test.content) 422 } 423 424 // Test different calls to Cookies. 425 for i, query := range test.queries { 426 now = now.Add(1001 * time.Millisecond) 427 var s []string 428 for _, c := range jar.cookies(mustParseURL(query.toURL), now) { 429 s = append(s, c.String()) 430 } 431 if got := strings.Join(s, " "); got != query.want { 432 t.Errorf("Test %q #%d\ngot %q\nwant %q", test.description, i, got, query.want) 433 } 434 } 435} 436 437// basicsTests contains fundamental tests. Each jarTest has to be performed on 438// a fresh, empty Jar. 439var basicsTests = [...]jarTest{ 440 { 441 "Retrieval of a plain host cookie.", 442 "http://www.host.test/", 443 []string{"A=a"}, 444 "A=a", 445 []query{ 446 {"http://www.host.test", "A=a"}, 447 {"http://www.host.test/", "A=a"}, 448 {"http://www.host.test/some/path", "A=a"}, 449 {"https://www.host.test", "A=a"}, 450 {"https://www.host.test/", "A=a"}, 451 {"https://www.host.test/some/path", "A=a"}, 452 {"ftp://www.host.test", ""}, 453 {"ftp://www.host.test/", ""}, 454 {"ftp://www.host.test/some/path", ""}, 455 {"http://www.other.org", ""}, 456 {"http://sibling.host.test", ""}, 457 {"http://deep.www.host.test", ""}, 458 }, 459 }, 460 { 461 "Secure cookies are not returned to http.", 462 "http://www.host.test/", 463 []string{"A=a; secure"}, 464 "A=a", 465 []query{ 466 {"http://www.host.test", ""}, 467 {"http://www.host.test/", ""}, 468 {"http://www.host.test/some/path", ""}, 469 {"https://www.host.test", "A=a"}, 470 {"https://www.host.test/", "A=a"}, 471 {"https://www.host.test/some/path", "A=a"}, 472 }, 473 }, 474 { 475 "Explicit path.", 476 "http://www.host.test/", 477 []string{"A=a; path=/some/path"}, 478 "A=a", 479 []query{ 480 {"http://www.host.test", ""}, 481 {"http://www.host.test/", ""}, 482 {"http://www.host.test/some", ""}, 483 {"http://www.host.test/some/", ""}, 484 {"http://www.host.test/some/path", "A=a"}, 485 {"http://www.host.test/some/paths", ""}, 486 {"http://www.host.test/some/path/foo", "A=a"}, 487 {"http://www.host.test/some/path/foo/", "A=a"}, 488 }, 489 }, 490 { 491 "Implicit path #1: path is a directory.", 492 "http://www.host.test/some/path/", 493 []string{"A=a"}, 494 "A=a", 495 []query{ 496 {"http://www.host.test", ""}, 497 {"http://www.host.test/", ""}, 498 {"http://www.host.test/some", ""}, 499 {"http://www.host.test/some/", ""}, 500 {"http://www.host.test/some/path", "A=a"}, 501 {"http://www.host.test/some/paths", ""}, 502 {"http://www.host.test/some/path/foo", "A=a"}, 503 {"http://www.host.test/some/path/foo/", "A=a"}, 504 }, 505 }, 506 { 507 "Implicit path #2: path is not a directory.", 508 "http://www.host.test/some/path/index.html", 509 []string{"A=a"}, 510 "A=a", 511 []query{ 512 {"http://www.host.test", ""}, 513 {"http://www.host.test/", ""}, 514 {"http://www.host.test/some", ""}, 515 {"http://www.host.test/some/", ""}, 516 {"http://www.host.test/some/path", "A=a"}, 517 {"http://www.host.test/some/paths", ""}, 518 {"http://www.host.test/some/path/foo", "A=a"}, 519 {"http://www.host.test/some/path/foo/", "A=a"}, 520 }, 521 }, 522 { 523 "Implicit path #3: no path in URL at all.", 524 "http://www.host.test", 525 []string{"A=a"}, 526 "A=a", 527 []query{ 528 {"http://www.host.test", "A=a"}, 529 {"http://www.host.test/", "A=a"}, 530 {"http://www.host.test/some/path", "A=a"}, 531 }, 532 }, 533 { 534 "Cookies are sorted by path length.", 535 "http://www.host.test/", 536 []string{ 537 "A=a; path=/foo/bar", 538 "B=b; path=/foo/bar/baz/qux", 539 "C=c; path=/foo/bar/baz", 540 "D=d; path=/foo"}, 541 "A=a B=b C=c D=d", 542 []query{ 543 {"http://www.host.test/foo/bar/baz/qux", "B=b C=c A=a D=d"}, 544 {"http://www.host.test/foo/bar/baz/", "C=c A=a D=d"}, 545 {"http://www.host.test/foo/bar", "A=a D=d"}, 546 }, 547 }, 548 { 549 "Creation time determines sorting on same length paths.", 550 "http://www.host.test/", 551 []string{ 552 "A=a; path=/foo/bar", 553 "X=x; path=/foo/bar", 554 "Y=y; path=/foo/bar/baz/qux", 555 "B=b; path=/foo/bar/baz/qux", 556 "C=c; path=/foo/bar/baz", 557 "W=w; path=/foo/bar/baz", 558 "Z=z; path=/foo", 559 "D=d; path=/foo"}, 560 "A=a B=b C=c D=d W=w X=x Y=y Z=z", 561 []query{ 562 {"http://www.host.test/foo/bar/baz/qux", "Y=y B=b C=c W=w A=a X=x Z=z D=d"}, 563 {"http://www.host.test/foo/bar/baz/", "C=c W=w A=a X=x Z=z D=d"}, 564 {"http://www.host.test/foo/bar", "A=a X=x Z=z D=d"}, 565 }, 566 }, 567 { 568 "Sorting of same-name cookies.", 569 "http://www.host.test/", 570 []string{ 571 "A=1; path=/", 572 "A=2; path=/path", 573 "A=3; path=/quux", 574 "A=4; path=/path/foo", 575 "A=5; domain=.host.test; path=/path", 576 "A=6; domain=.host.test; path=/quux", 577 "A=7; domain=.host.test; path=/path/foo", 578 }, 579 "A=1 A=2 A=3 A=4 A=5 A=6 A=7", 580 []query{ 581 {"http://www.host.test/path", "A=2 A=5 A=1"}, 582 {"http://www.host.test/path/foo", "A=4 A=7 A=2 A=5 A=1"}, 583 }, 584 }, 585 { 586 "Disallow domain cookie on public suffix.", 587 "http://www.bbc.co.uk", 588 []string{ 589 "a=1", 590 "b=2; domain=co.uk", 591 }, 592 "a=1", 593 []query{{"http://www.bbc.co.uk", "a=1"}}, 594 }, 595 { 596 "Host cookie on IP.", 597 "http://192.168.0.10", 598 []string{"a=1"}, 599 "a=1", 600 []query{{"http://192.168.0.10", "a=1"}}, 601 }, 602 { 603 "Domain cookies on IP.", 604 "http://192.168.0.10", 605 []string{ 606 "a=1; domain=192.168.0.10", // allowed 607 "b=2; domain=172.31.9.9", // rejected, can't set cookie for other IP 608 "c=3; domain=.192.168.0.10", // rejected like in most browsers 609 }, 610 "a=1", 611 []query{ 612 {"http://192.168.0.10", "a=1"}, 613 {"http://172.31.9.9", ""}, 614 {"http://www.fancy.192.168.0.10", ""}, 615 }, 616 }, 617 { 618 "Port is ignored #1.", 619 "http://www.host.test/", 620 []string{"a=1"}, 621 "a=1", 622 []query{ 623 {"http://www.host.test", "a=1"}, 624 {"http://www.host.test:8080/", "a=1"}, 625 }, 626 }, 627 { 628 "Port is ignored #2.", 629 "http://www.host.test:8080/", 630 []string{"a=1"}, 631 "a=1", 632 []query{ 633 {"http://www.host.test", "a=1"}, 634 {"http://www.host.test:8080/", "a=1"}, 635 {"http://www.host.test:1234/", "a=1"}, 636 }, 637 }, 638 { 639 "IPv6 zone is not treated as a host.", 640 "https://example.com/", 641 []string{"a=1"}, 642 "a=1", 643 []query{ 644 {"https://[::1%25.example.com]:80/", ""}, 645 }, 646 }, 647 { 648 "Retrieval of cookies with quoted values", // issue #46443 649 "http://www.host.test/", 650 []string{ 651 `cookie-1="quoted"`, 652 `cookie-2="quoted with spaces"`, 653 `cookie-3="quoted,with,commas"`, 654 `cookie-4= ,`, 655 }, 656 `cookie-1="quoted" cookie-2="quoted with spaces" cookie-3="quoted,with,commas" cookie-4=" ,"`, 657 []query{ 658 { 659 "http://www.host.test", 660 `cookie-1="quoted" cookie-2="quoted with spaces" cookie-3="quoted,with,commas" cookie-4=" ,"`, 661 }, 662 }, 663 }, 664} 665 666func TestBasics(t *testing.T) { 667 for _, test := range basicsTests { 668 jar := newTestJar() 669 test.run(t, jar) 670 } 671} 672 673// updateAndDeleteTests contains jarTests which must be performed on the same 674// Jar. 675var updateAndDeleteTests = [...]jarTest{ 676 { 677 "Set initial cookies.", 678 "http://www.host.test", 679 []string{ 680 "a=1", 681 "b=2; secure", 682 "c=3; httponly", 683 "d=4; secure; httponly"}, 684 "a=1 b=2 c=3 d=4", 685 []query{ 686 {"http://www.host.test", "a=1 c=3"}, 687 {"https://www.host.test", "a=1 b=2 c=3 d=4"}, 688 }, 689 }, 690 { 691 "Update value via http.", 692 "http://www.host.test", 693 []string{ 694 "a=w", 695 "b=x; secure", 696 "c=y; httponly", 697 "d=z; secure; httponly"}, 698 "a=w b=x c=y d=z", 699 []query{ 700 {"http://www.host.test", "a=w c=y"}, 701 {"https://www.host.test", "a=w b=x c=y d=z"}, 702 }, 703 }, 704 { 705 "Clear Secure flag from an http.", 706 "http://www.host.test/", 707 []string{ 708 "b=xx", 709 "d=zz; httponly"}, 710 "a=w b=xx c=y d=zz", 711 []query{{"http://www.host.test", "a=w b=xx c=y d=zz"}}, 712 }, 713 { 714 "Delete all.", 715 "http://www.host.test/", 716 []string{ 717 "a=1; max-Age=-1", // delete via MaxAge 718 "b=2; " + expiresIn(-10), // delete via Expires 719 "c=2; max-age=-1; " + expiresIn(-10), // delete via both 720 "d=4; max-age=-1; " + expiresIn(10)}, // MaxAge takes precedence 721 "", 722 []query{{"http://www.host.test", ""}}, 723 }, 724 { 725 "Refill #1.", 726 "http://www.host.test", 727 []string{ 728 "A=1", 729 "A=2; path=/foo", 730 "A=3; domain=.host.test", 731 "A=4; path=/foo; domain=.host.test"}, 732 "A=1 A=2 A=3 A=4", 733 []query{{"http://www.host.test/foo", "A=2 A=4 A=1 A=3"}}, 734 }, 735 { 736 "Refill #2.", 737 "http://www.google.com", 738 []string{ 739 "A=6", 740 "A=7; path=/foo", 741 "A=8; domain=.google.com", 742 "A=9; path=/foo; domain=.google.com"}, 743 "A=1 A=2 A=3 A=4 A=6 A=7 A=8 A=9", 744 []query{ 745 {"http://www.host.test/foo", "A=2 A=4 A=1 A=3"}, 746 {"http://www.google.com/foo", "A=7 A=9 A=6 A=8"}, 747 }, 748 }, 749 { 750 "Delete A7.", 751 "http://www.google.com", 752 []string{"A=; path=/foo; max-age=-1"}, 753 "A=1 A=2 A=3 A=4 A=6 A=8 A=9", 754 []query{ 755 {"http://www.host.test/foo", "A=2 A=4 A=1 A=3"}, 756 {"http://www.google.com/foo", "A=9 A=6 A=8"}, 757 }, 758 }, 759 { 760 "Delete A4.", 761 "http://www.host.test", 762 []string{"A=; path=/foo; domain=host.test; max-age=-1"}, 763 "A=1 A=2 A=3 A=6 A=8 A=9", 764 []query{ 765 {"http://www.host.test/foo", "A=2 A=1 A=3"}, 766 {"http://www.google.com/foo", "A=9 A=6 A=8"}, 767 }, 768 }, 769 { 770 "Delete A6.", 771 "http://www.google.com", 772 []string{"A=; max-age=-1"}, 773 "A=1 A=2 A=3 A=8 A=9", 774 []query{ 775 {"http://www.host.test/foo", "A=2 A=1 A=3"}, 776 {"http://www.google.com/foo", "A=9 A=8"}, 777 }, 778 }, 779 { 780 "Delete A3.", 781 "http://www.host.test", 782 []string{"A=; domain=host.test; max-age=-1"}, 783 "A=1 A=2 A=8 A=9", 784 []query{ 785 {"http://www.host.test/foo", "A=2 A=1"}, 786 {"http://www.google.com/foo", "A=9 A=8"}, 787 }, 788 }, 789 { 790 "No cross-domain delete.", 791 "http://www.host.test", 792 []string{ 793 "A=; domain=google.com; max-age=-1", 794 "A=; path=/foo; domain=google.com; max-age=-1"}, 795 "A=1 A=2 A=8 A=9", 796 []query{ 797 {"http://www.host.test/foo", "A=2 A=1"}, 798 {"http://www.google.com/foo", "A=9 A=8"}, 799 }, 800 }, 801 { 802 "Delete A8 and A9.", 803 "http://www.google.com", 804 []string{ 805 "A=; domain=google.com; max-age=-1", 806 "A=; path=/foo; domain=google.com; max-age=-1"}, 807 "A=1 A=2", 808 []query{ 809 {"http://www.host.test/foo", "A=2 A=1"}, 810 {"http://www.google.com/foo", ""}, 811 }, 812 }, 813} 814 815func TestUpdateAndDelete(t *testing.T) { 816 jar := newTestJar() 817 for _, test := range updateAndDeleteTests { 818 test.run(t, jar) 819 } 820} 821 822func TestExpiration(t *testing.T) { 823 jar := newTestJar() 824 jarTest{ 825 "Expiration.", 826 "http://www.host.test", 827 []string{ 828 "a=1", 829 "b=2; max-age=3", 830 "c=3; " + expiresIn(3), 831 "d=4; max-age=5", 832 "e=5; " + expiresIn(5), 833 "f=6; max-age=100", 834 }, 835 "a=1 b=2 c=3 d=4 e=5 f=6", // executed at t0 + 1001 ms 836 []query{ 837 {"http://www.host.test", "a=1 b=2 c=3 d=4 e=5 f=6"}, // t0 + 2002 ms 838 {"http://www.host.test", "a=1 d=4 e=5 f=6"}, // t0 + 3003 ms 839 {"http://www.host.test", "a=1 d=4 e=5 f=6"}, // t0 + 4004 ms 840 {"http://www.host.test", "a=1 f=6"}, // t0 + 5005 ms 841 {"http://www.host.test", "a=1 f=6"}, // t0 + 6006 ms 842 }, 843 }.run(t, jar) 844} 845 846// 847// Tests derived from Chromium's cookie_store_unittest.h. 848// 849 850// See http://src.chromium.org/viewvc/chrome/trunk/src/net/cookies/cookie_store_unittest.h?revision=159685&content-type=text/plain 851// Some of the original tests are in a bad condition (e.g. 852// DomainWithTrailingDotTest) or are not RFC 6265 conforming (e.g. 853// TestNonDottedAndTLD #1 and #6) and have not been ported. 854 855// chromiumBasicsTests contains fundamental tests. Each jarTest has to be 856// performed on a fresh, empty Jar. 857var chromiumBasicsTests = [...]jarTest{ 858 { 859 "DomainWithTrailingDotTest.", 860 "http://www.google.com/", 861 []string{ 862 "a=1; domain=.www.google.com.", 863 "b=2; domain=.www.google.com.."}, 864 "", 865 []query{ 866 {"http://www.google.com", ""}, 867 }, 868 }, 869 { 870 "ValidSubdomainTest #1.", 871 "http://a.b.c.d.com", 872 []string{ 873 "a=1; domain=.a.b.c.d.com", 874 "b=2; domain=.b.c.d.com", 875 "c=3; domain=.c.d.com", 876 "d=4; domain=.d.com"}, 877 "a=1 b=2 c=3 d=4", 878 []query{ 879 {"http://a.b.c.d.com", "a=1 b=2 c=3 d=4"}, 880 {"http://b.c.d.com", "b=2 c=3 d=4"}, 881 {"http://c.d.com", "c=3 d=4"}, 882 {"http://d.com", "d=4"}, 883 }, 884 }, 885 { 886 "ValidSubdomainTest #2.", 887 "http://a.b.c.d.com", 888 []string{ 889 "a=1; domain=.a.b.c.d.com", 890 "b=2; domain=.b.c.d.com", 891 "c=3; domain=.c.d.com", 892 "d=4; domain=.d.com", 893 "X=bcd; domain=.b.c.d.com", 894 "X=cd; domain=.c.d.com"}, 895 "X=bcd X=cd a=1 b=2 c=3 d=4", 896 []query{ 897 {"http://b.c.d.com", "b=2 c=3 d=4 X=bcd X=cd"}, 898 {"http://c.d.com", "c=3 d=4 X=cd"}, 899 }, 900 }, 901 { 902 "InvalidDomainTest #1.", 903 "http://foo.bar.com", 904 []string{ 905 "a=1; domain=.yo.foo.bar.com", 906 "b=2; domain=.foo.com", 907 "c=3; domain=.bar.foo.com", 908 "d=4; domain=.foo.bar.com.net", 909 "e=5; domain=ar.com", 910 "f=6; domain=.", 911 "g=7; domain=/", 912 "h=8; domain=http://foo.bar.com", 913 "i=9; domain=..foo.bar.com", 914 "j=10; domain=..bar.com", 915 "k=11; domain=.foo.bar.com?blah", 916 "l=12; domain=.foo.bar.com/blah", 917 "m=12; domain=.foo.bar.com:80", 918 "n=14; domain=.foo.bar.com:", 919 "o=15; domain=.foo.bar.com#sup", 920 }, 921 "", // Jar is empty. 922 []query{{"http://foo.bar.com", ""}}, 923 }, 924 { 925 "InvalidDomainTest #2.", 926 "http://foo.com.com", 927 []string{"a=1; domain=.foo.com.com.com"}, 928 "", 929 []query{{"http://foo.bar.com", ""}}, 930 }, 931 { 932 "DomainWithoutLeadingDotTest #1.", 933 "http://manage.hosted.filefront.com", 934 []string{"a=1; domain=filefront.com"}, 935 "a=1", 936 []query{{"http://www.filefront.com", "a=1"}}, 937 }, 938 { 939 "DomainWithoutLeadingDotTest #2.", 940 "http://www.google.com", 941 []string{"a=1; domain=www.google.com"}, 942 "a=1", 943 []query{ 944 {"http://www.google.com", "a=1"}, 945 {"http://sub.www.google.com", "a=1"}, 946 {"http://something-else.com", ""}, 947 }, 948 }, 949 { 950 "CaseInsensitiveDomainTest.", 951 "http://www.google.com", 952 []string{ 953 "a=1; domain=.GOOGLE.COM", 954 "b=2; domain=.www.gOOgLE.coM"}, 955 "a=1 b=2", 956 []query{{"http://www.google.com", "a=1 b=2"}}, 957 }, 958 { 959 "TestIpAddress #1.", 960 "http://1.2.3.4/foo", 961 []string{"a=1; path=/"}, 962 "a=1", 963 []query{{"http://1.2.3.4/foo", "a=1"}}, 964 }, 965 { 966 "TestIpAddress #2.", 967 "http://1.2.3.4/foo", 968 []string{ 969 "a=1; domain=.1.2.3.4", 970 "b=2; domain=.3.4"}, 971 "", 972 []query{{"http://1.2.3.4/foo", ""}}, 973 }, 974 { 975 "TestIpAddress #3.", 976 "http://1.2.3.4/foo", 977 []string{"a=1; domain=1.2.3.3"}, 978 "", 979 []query{{"http://1.2.3.4/foo", ""}}, 980 }, 981 { 982 "TestIpAddress #4.", 983 "http://1.2.3.4/foo", 984 []string{"a=1; domain=1.2.3.4"}, 985 "a=1", 986 []query{{"http://1.2.3.4/foo", "a=1"}}, 987 }, 988 { 989 "TestNonDottedAndTLD #2.", 990 "http://com./index.html", 991 []string{"a=1"}, 992 "a=1", 993 []query{ 994 {"http://com./index.html", "a=1"}, 995 {"http://no-cookies.com./index.html", ""}, 996 }, 997 }, 998 { 999 "TestNonDottedAndTLD #3.", 1000 "http://a.b", 1001 []string{ 1002 "a=1; domain=.b", 1003 "b=2; domain=b"}, 1004 "", 1005 []query{{"http://bar.foo", ""}}, 1006 }, 1007 { 1008 "TestNonDottedAndTLD #4.", 1009 "http://google.com", 1010 []string{ 1011 "a=1; domain=.com", 1012 "b=2; domain=com"}, 1013 "", 1014 []query{{"http://google.com", ""}}, 1015 }, 1016 { 1017 "TestNonDottedAndTLD #5.", 1018 "http://google.co.uk", 1019 []string{ 1020 "a=1; domain=.co.uk", 1021 "b=2; domain=.uk"}, 1022 "", 1023 []query{ 1024 {"http://google.co.uk", ""}, 1025 {"http://else.co.com", ""}, 1026 {"http://else.uk", ""}, 1027 }, 1028 }, 1029 { 1030 "TestHostEndsWithDot.", 1031 "http://www.google.com", 1032 []string{ 1033 "a=1", 1034 "b=2; domain=.www.google.com."}, 1035 "a=1", 1036 []query{{"http://www.google.com", "a=1"}}, 1037 }, 1038 { 1039 "PathTest", 1040 "http://www.google.izzle", 1041 []string{"a=1; path=/wee"}, 1042 "a=1", 1043 []query{ 1044 {"http://www.google.izzle/wee", "a=1"}, 1045 {"http://www.google.izzle/wee/", "a=1"}, 1046 {"http://www.google.izzle/wee/war", "a=1"}, 1047 {"http://www.google.izzle/wee/war/more/more", "a=1"}, 1048 {"http://www.google.izzle/weehee", ""}, 1049 {"http://www.google.izzle/", ""}, 1050 }, 1051 }, 1052} 1053 1054func TestChromiumBasics(t *testing.T) { 1055 for _, test := range chromiumBasicsTests { 1056 jar := newTestJar() 1057 test.run(t, jar) 1058 } 1059} 1060 1061// chromiumDomainTests contains jarTests which must be executed all on the 1062// same Jar. 1063var chromiumDomainTests = [...]jarTest{ 1064 { 1065 "Fill #1.", 1066 "http://www.google.izzle", 1067 []string{"A=B"}, 1068 "A=B", 1069 []query{{"http://www.google.izzle", "A=B"}}, 1070 }, 1071 { 1072 "Fill #2.", 1073 "http://www.google.izzle", 1074 []string{"C=D; domain=.google.izzle"}, 1075 "A=B C=D", 1076 []query{{"http://www.google.izzle", "A=B C=D"}}, 1077 }, 1078 { 1079 "Verify A is a host cookie and not accessible from subdomain.", 1080 "http://unused.nil", 1081 []string{}, 1082 "A=B C=D", 1083 []query{{"http://foo.www.google.izzle", "C=D"}}, 1084 }, 1085 { 1086 "Verify domain cookies are found on proper domain.", 1087 "http://www.google.izzle", 1088 []string{"E=F; domain=.www.google.izzle"}, 1089 "A=B C=D E=F", 1090 []query{{"http://www.google.izzle", "A=B C=D E=F"}}, 1091 }, 1092 { 1093 "Leading dots in domain attributes are optional.", 1094 "http://www.google.izzle", 1095 []string{"G=H; domain=www.google.izzle"}, 1096 "A=B C=D E=F G=H", 1097 []query{{"http://www.google.izzle", "A=B C=D E=F G=H"}}, 1098 }, 1099 { 1100 "Verify domain enforcement works #1.", 1101 "http://www.google.izzle", 1102 []string{"K=L; domain=.bar.www.google.izzle"}, 1103 "A=B C=D E=F G=H", 1104 []query{{"http://bar.www.google.izzle", "C=D E=F G=H"}}, 1105 }, 1106 { 1107 "Verify domain enforcement works #2.", 1108 "http://unused.nil", 1109 []string{}, 1110 "A=B C=D E=F G=H", 1111 []query{{"http://www.google.izzle", "A=B C=D E=F G=H"}}, 1112 }, 1113} 1114 1115func TestChromiumDomain(t *testing.T) { 1116 jar := newTestJar() 1117 for _, test := range chromiumDomainTests { 1118 test.run(t, jar) 1119 } 1120 1121} 1122 1123// chromiumDeletionTests must be performed all on the same Jar. 1124var chromiumDeletionTests = [...]jarTest{ 1125 { 1126 "Create session cookie a1.", 1127 "http://www.google.com", 1128 []string{"a=1"}, 1129 "a=1", 1130 []query{{"http://www.google.com", "a=1"}}, 1131 }, 1132 { 1133 "Delete sc a1 via MaxAge.", 1134 "http://www.google.com", 1135 []string{"a=1; max-age=-1"}, 1136 "", 1137 []query{{"http://www.google.com", ""}}, 1138 }, 1139 { 1140 "Create session cookie b2.", 1141 "http://www.google.com", 1142 []string{"b=2"}, 1143 "b=2", 1144 []query{{"http://www.google.com", "b=2"}}, 1145 }, 1146 { 1147 "Delete sc b2 via Expires.", 1148 "http://www.google.com", 1149 []string{"b=2; " + expiresIn(-10)}, 1150 "", 1151 []query{{"http://www.google.com", ""}}, 1152 }, 1153 { 1154 "Create persistent cookie c3.", 1155 "http://www.google.com", 1156 []string{"c=3; max-age=3600"}, 1157 "c=3", 1158 []query{{"http://www.google.com", "c=3"}}, 1159 }, 1160 { 1161 "Delete pc c3 via MaxAge.", 1162 "http://www.google.com", 1163 []string{"c=3; max-age=-1"}, 1164 "", 1165 []query{{"http://www.google.com", ""}}, 1166 }, 1167 { 1168 "Create persistent cookie d4.", 1169 "http://www.google.com", 1170 []string{"d=4; max-age=3600"}, 1171 "d=4", 1172 []query{{"http://www.google.com", "d=4"}}, 1173 }, 1174 { 1175 "Delete pc d4 via Expires.", 1176 "http://www.google.com", 1177 []string{"d=4; " + expiresIn(-10)}, 1178 "", 1179 []query{{"http://www.google.com", ""}}, 1180 }, 1181} 1182 1183func TestChromiumDeletion(t *testing.T) { 1184 jar := newTestJar() 1185 for _, test := range chromiumDeletionTests { 1186 test.run(t, jar) 1187 } 1188} 1189 1190// domainHandlingTests tests and documents the rules for domain handling. 1191// Each test must be performed on an empty new Jar. 1192var domainHandlingTests = [...]jarTest{ 1193 { 1194 "Host cookie", 1195 "http://www.host.test", 1196 []string{"a=1"}, 1197 "a=1", 1198 []query{ 1199 {"http://www.host.test", "a=1"}, 1200 {"http://host.test", ""}, 1201 {"http://bar.host.test", ""}, 1202 {"http://foo.www.host.test", ""}, 1203 {"http://other.test", ""}, 1204 {"http://test", ""}, 1205 }, 1206 }, 1207 { 1208 "Domain cookie #1", 1209 "http://www.host.test", 1210 []string{"a=1; domain=host.test"}, 1211 "a=1", 1212 []query{ 1213 {"http://www.host.test", "a=1"}, 1214 {"http://host.test", "a=1"}, 1215 {"http://bar.host.test", "a=1"}, 1216 {"http://foo.www.host.test", "a=1"}, 1217 {"http://other.test", ""}, 1218 {"http://test", ""}, 1219 }, 1220 }, 1221 { 1222 "Domain cookie #2", 1223 "http://www.host.test", 1224 []string{"a=1; domain=.host.test"}, 1225 "a=1", 1226 []query{ 1227 {"http://www.host.test", "a=1"}, 1228 {"http://host.test", "a=1"}, 1229 {"http://bar.host.test", "a=1"}, 1230 {"http://foo.www.host.test", "a=1"}, 1231 {"http://other.test", ""}, 1232 {"http://test", ""}, 1233 }, 1234 }, 1235 { 1236 "Host cookie on IDNA domain #1", 1237 "http://www.bücher.test", 1238 []string{"a=1"}, 1239 "a=1", 1240 []query{ 1241 {"http://www.bücher.test", "a=1"}, 1242 {"http://www.xn--bcher-kva.test", "a=1"}, 1243 {"http://bücher.test", ""}, 1244 {"http://xn--bcher-kva.test", ""}, 1245 {"http://bar.bücher.test", ""}, 1246 {"http://bar.xn--bcher-kva.test", ""}, 1247 {"http://foo.www.bücher.test", ""}, 1248 {"http://foo.www.xn--bcher-kva.test", ""}, 1249 {"http://other.test", ""}, 1250 {"http://test", ""}, 1251 }, 1252 }, 1253 { 1254 "Host cookie on IDNA domain #2", 1255 "http://www.xn--bcher-kva.test", 1256 []string{"a=1"}, 1257 "a=1", 1258 []query{ 1259 {"http://www.bücher.test", "a=1"}, 1260 {"http://www.xn--bcher-kva.test", "a=1"}, 1261 {"http://bücher.test", ""}, 1262 {"http://xn--bcher-kva.test", ""}, 1263 {"http://bar.bücher.test", ""}, 1264 {"http://bar.xn--bcher-kva.test", ""}, 1265 {"http://foo.www.bücher.test", ""}, 1266 {"http://foo.www.xn--bcher-kva.test", ""}, 1267 {"http://other.test", ""}, 1268 {"http://test", ""}, 1269 }, 1270 }, 1271 { 1272 "Domain cookie on IDNA domain #1", 1273 "http://www.bücher.test", 1274 []string{"a=1; domain=xn--bcher-kva.test"}, 1275 "a=1", 1276 []query{ 1277 {"http://www.bücher.test", "a=1"}, 1278 {"http://www.xn--bcher-kva.test", "a=1"}, 1279 {"http://bücher.test", "a=1"}, 1280 {"http://xn--bcher-kva.test", "a=1"}, 1281 {"http://bar.bücher.test", "a=1"}, 1282 {"http://bar.xn--bcher-kva.test", "a=1"}, 1283 {"http://foo.www.bücher.test", "a=1"}, 1284 {"http://foo.www.xn--bcher-kva.test", "a=1"}, 1285 {"http://other.test", ""}, 1286 {"http://test", ""}, 1287 }, 1288 }, 1289 { 1290 "Domain cookie on IDNA domain #2", 1291 "http://www.xn--bcher-kva.test", 1292 []string{"a=1; domain=xn--bcher-kva.test"}, 1293 "a=1", 1294 []query{ 1295 {"http://www.bücher.test", "a=1"}, 1296 {"http://www.xn--bcher-kva.test", "a=1"}, 1297 {"http://bücher.test", "a=1"}, 1298 {"http://xn--bcher-kva.test", "a=1"}, 1299 {"http://bar.bücher.test", "a=1"}, 1300 {"http://bar.xn--bcher-kva.test", "a=1"}, 1301 {"http://foo.www.bücher.test", "a=1"}, 1302 {"http://foo.www.xn--bcher-kva.test", "a=1"}, 1303 {"http://other.test", ""}, 1304 {"http://test", ""}, 1305 }, 1306 }, 1307 { 1308 "Host cookie on TLD.", 1309 "http://com", 1310 []string{"a=1"}, 1311 "a=1", 1312 []query{ 1313 {"http://com", "a=1"}, 1314 {"http://any.com", ""}, 1315 {"http://any.test", ""}, 1316 }, 1317 }, 1318 { 1319 "Domain cookie on TLD becomes a host cookie.", 1320 "http://com", 1321 []string{"a=1; domain=com"}, 1322 "a=1", 1323 []query{ 1324 {"http://com", "a=1"}, 1325 {"http://any.com", ""}, 1326 {"http://any.test", ""}, 1327 }, 1328 }, 1329 { 1330 "Host cookie on public suffix.", 1331 "http://co.uk", 1332 []string{"a=1"}, 1333 "a=1", 1334 []query{ 1335 {"http://co.uk", "a=1"}, 1336 {"http://uk", ""}, 1337 {"http://some.co.uk", ""}, 1338 {"http://foo.some.co.uk", ""}, 1339 {"http://any.uk", ""}, 1340 }, 1341 }, 1342 { 1343 "Domain cookie on public suffix is ignored.", 1344 "http://some.co.uk", 1345 []string{"a=1; domain=co.uk"}, 1346 "", 1347 []query{ 1348 {"http://co.uk", ""}, 1349 {"http://uk", ""}, 1350 {"http://some.co.uk", ""}, 1351 {"http://foo.some.co.uk", ""}, 1352 {"http://any.uk", ""}, 1353 }, 1354 }, 1355} 1356 1357func TestDomainHandling(t *testing.T) { 1358 for _, test := range domainHandlingTests { 1359 jar := newTestJar() 1360 test.run(t, jar) 1361 } 1362} 1363 1364func TestIssue19384(t *testing.T) { 1365 cookies := []*http.Cookie{{Name: "name", Value: "value"}} 1366 for _, host := range []string{"", ".", "..", "..."} { 1367 jar, _ := New(nil) 1368 u := &url.URL{Scheme: "http", Host: host, Path: "/"} 1369 if got := jar.Cookies(u); len(got) != 0 { 1370 t.Errorf("host %q, got %v", host, got) 1371 } 1372 jar.SetCookies(u, cookies) 1373 if got := jar.Cookies(u); len(got) != 1 || got[0].Value != "value" { 1374 t.Errorf("host %q, got %v", host, got) 1375 } 1376 } 1377} 1378