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