1// Copyright 2011 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 http
6
7import (
8	"bytes"
9	"internal/race"
10	"reflect"
11	"runtime"
12	"strings"
13	"testing"
14	"time"
15)
16
17var headerWriteTests = []struct {
18	h        Header
19	exclude  map[string]bool
20	expected string
21}{
22	{Header{}, nil, ""},
23	{
24		Header{
25			"Content-Type":   {"text/html; charset=UTF-8"},
26			"Content-Length": {"0"},
27		},
28		nil,
29		"Content-Length: 0\r\nContent-Type: text/html; charset=UTF-8\r\n",
30	},
31	{
32		Header{
33			"Content-Length": {"0", "1", "2"},
34		},
35		nil,
36		"Content-Length: 0\r\nContent-Length: 1\r\nContent-Length: 2\r\n",
37	},
38	{
39		Header{
40			"Expires":          {"-1"},
41			"Content-Length":   {"0"},
42			"Content-Encoding": {"gzip"},
43		},
44		map[string]bool{"Content-Length": true},
45		"Content-Encoding: gzip\r\nExpires: -1\r\n",
46	},
47	{
48		Header{
49			"Expires":          {"-1"},
50			"Content-Length":   {"0", "1", "2"},
51			"Content-Encoding": {"gzip"},
52		},
53		map[string]bool{"Content-Length": true},
54		"Content-Encoding: gzip\r\nExpires: -1\r\n",
55	},
56	{
57		Header{
58			"Expires":          {"-1"},
59			"Content-Length":   {"0"},
60			"Content-Encoding": {"gzip"},
61		},
62		map[string]bool{"Content-Length": true, "Expires": true, "Content-Encoding": true},
63		"",
64	},
65	{
66		Header{
67			"Nil":          nil,
68			"Empty":        {},
69			"Blank":        {""},
70			"Double-Blank": {"", ""},
71		},
72		nil,
73		"Blank: \r\nDouble-Blank: \r\nDouble-Blank: \r\n",
74	},
75	// Tests header sorting when over the insertion sort threshold side:
76	{
77		Header{
78			"k1": {"1a", "1b"},
79			"k2": {"2a", "2b"},
80			"k3": {"3a", "3b"},
81			"k4": {"4a", "4b"},
82			"k5": {"5a", "5b"},
83			"k6": {"6a", "6b"},
84			"k7": {"7a", "7b"},
85			"k8": {"8a", "8b"},
86			"k9": {"9a", "9b"},
87		},
88		map[string]bool{"k5": true},
89		"k1: 1a\r\nk1: 1b\r\nk2: 2a\r\nk2: 2b\r\nk3: 3a\r\nk3: 3b\r\n" +
90			"k4: 4a\r\nk4: 4b\r\nk6: 6a\r\nk6: 6b\r\n" +
91			"k7: 7a\r\nk7: 7b\r\nk8: 8a\r\nk8: 8b\r\nk9: 9a\r\nk9: 9b\r\n",
92	},
93	// Tests invalid characters in headers.
94	{
95		Header{
96			"Content-Type":             {"text/html; charset=UTF-8"},
97			"NewlineInValue":           {"1\r\nBar: 2"},
98			"NewlineInKey\r\n":         {"1"},
99			"Colon:InKey":              {"1"},
100			"Evil: 1\r\nSmuggledValue": {"1"},
101		},
102		nil,
103		"Content-Type: text/html; charset=UTF-8\r\n" +
104			"NewlineInValue: 1  Bar: 2\r\n",
105	},
106}
107
108func TestHeaderWrite(t *testing.T) {
109	var buf strings.Builder
110	for i, test := range headerWriteTests {
111		test.h.WriteSubset(&buf, test.exclude)
112		if buf.String() != test.expected {
113			t.Errorf("#%d:\n got: %q\nwant: %q", i, buf.String(), test.expected)
114		}
115		buf.Reset()
116	}
117}
118
119var parseTimeTests = []struct {
120	h   Header
121	err bool
122}{
123	{Header{"Date": {""}}, true},
124	{Header{"Date": {"invalid"}}, true},
125	{Header{"Date": {"1994-11-06T08:49:37Z00:00"}}, true},
126	{Header{"Date": {"Sun, 06 Nov 1994 08:49:37 GMT"}}, false},
127	{Header{"Date": {"Sunday, 06-Nov-94 08:49:37 GMT"}}, false},
128	{Header{"Date": {"Sun Nov  6 08:49:37 1994"}}, false},
129}
130
131func TestParseTime(t *testing.T) {
132	expect := time.Date(1994, 11, 6, 8, 49, 37, 0, time.UTC)
133	for i, test := range parseTimeTests {
134		d, err := ParseTime(test.h.Get("Date"))
135		if err != nil {
136			if !test.err {
137				t.Errorf("#%d:\n got err: %v", i, err)
138			}
139			continue
140		}
141		if test.err {
142			t.Errorf("#%d:\n  should err", i)
143			continue
144		}
145		if !expect.Equal(d) {
146			t.Errorf("#%d:\n got: %v\nwant: %v", i, d, expect)
147		}
148	}
149}
150
151type hasTokenTest struct {
152	header string
153	token  string
154	want   bool
155}
156
157var hasTokenTests = []hasTokenTest{
158	{"", "", false},
159	{"", "foo", false},
160	{"foo", "foo", true},
161	{"foo ", "foo", true},
162	{" foo", "foo", true},
163	{" foo ", "foo", true},
164	{"foo,bar", "foo", true},
165	{"bar,foo", "foo", true},
166	{"bar, foo", "foo", true},
167	{"bar,foo, baz", "foo", true},
168	{"bar, foo,baz", "foo", true},
169	{"bar,foo, baz", "foo", true},
170	{"bar, foo, baz", "foo", true},
171	{"FOO", "foo", true},
172	{"FOO ", "foo", true},
173	{" FOO", "foo", true},
174	{" FOO ", "foo", true},
175	{"FOO,BAR", "foo", true},
176	{"BAR,FOO", "foo", true},
177	{"BAR, FOO", "foo", true},
178	{"BAR,FOO, baz", "foo", true},
179	{"BAR, FOO,BAZ", "foo", true},
180	{"BAR,FOO, BAZ", "foo", true},
181	{"BAR, FOO, BAZ", "foo", true},
182	{"foobar", "foo", false},
183	{"barfoo ", "foo", false},
184}
185
186func TestHasToken(t *testing.T) {
187	for _, tt := range hasTokenTests {
188		if hasToken(tt.header, tt.token) != tt.want {
189			t.Errorf("hasToken(%q, %q) = %v; want %v", tt.header, tt.token, !tt.want, tt.want)
190		}
191	}
192}
193
194func TestNilHeaderClone(t *testing.T) {
195	t1 := Header(nil)
196	t2 := t1.Clone()
197	if t2 != nil {
198		t.Errorf("cloned header does not match original: got: %+v; want: %+v", t2, nil)
199	}
200}
201
202var testHeader = Header{
203	"Content-Length": {"123"},
204	"Content-Type":   {"text/plain"},
205	"Date":           {"some date at some time Z"},
206	"Server":         {DefaultUserAgent},
207}
208
209var buf bytes.Buffer
210
211func BenchmarkHeaderWriteSubset(b *testing.B) {
212	b.ReportAllocs()
213	for i := 0; i < b.N; i++ {
214		buf.Reset()
215		testHeader.WriteSubset(&buf, nil)
216	}
217}
218
219func TestHeaderWriteSubsetAllocs(t *testing.T) {
220	if testing.Short() {
221		t.Skip("skipping alloc test in short mode")
222	}
223	if race.Enabled {
224		t.Skip("skipping test under race detector")
225	}
226	if runtime.GOMAXPROCS(0) > 1 {
227		t.Skip("skipping; GOMAXPROCS>1")
228	}
229	n := testing.AllocsPerRun(100, func() {
230		buf.Reset()
231		testHeader.WriteSubset(&buf, nil)
232	})
233	if n > 0 {
234		t.Errorf("allocs = %g; want 0", n)
235	}
236}
237
238// Issue 34878: test that every call to
239// cloneOrMakeHeader never returns a nil Header.
240func TestCloneOrMakeHeader(t *testing.T) {
241	tests := []struct {
242		name     string
243		in, want Header
244	}{
245		{"nil", nil, Header{}},
246		{"empty", Header{}, Header{}},
247		{
248			name: "non-empty",
249			in:   Header{"foo": {"bar"}},
250			want: Header{"foo": {"bar"}},
251		},
252		{
253			name: "nil value",
254			in:   Header{"foo": nil},
255			want: Header{"foo": nil},
256		},
257	}
258
259	for _, tt := range tests {
260		t.Run(tt.name, func(t *testing.T) {
261			got := cloneOrMakeHeader(tt.in)
262			if got == nil {
263				t.Fatal("unexpected nil Header")
264			}
265			if !reflect.DeepEqual(got, tt.want) {
266				t.Fatalf("Got:  %#v\nWant: %#v", got, tt.want)
267			}
268			got.Add("A", "B")
269			got.Get("A")
270		})
271	}
272}
273