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