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