Source file
src/net/http/readrequest_test.go
1
2
3
4
5 package http
6
7 import (
8 "bufio"
9 "bytes"
10 "fmt"
11 "io"
12 "net/url"
13 "reflect"
14 "strings"
15 "testing"
16 )
17
18 type reqTest struct {
19 Raw string
20 Req *Request
21 Body string
22 Trailer Header
23 Error string
24 }
25
26 var noError = ""
27 var noBodyStr = ""
28 var noTrailer Header = nil
29
30 var reqTests = []reqTest{
31
32 {
33 "GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
34 "Host: www.techcrunch.com\r\n" +
35 "User-Agent: Fake\r\n" +
36 "Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
37 "Accept-Language: en-us,en;q=0.5\r\n" +
38 "Accept-Encoding: gzip,deflate\r\n" +
39 "Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
40 "Keep-Alive: 300\r\n" +
41 "Content-Length: 7\r\n" +
42 "Proxy-Connection: keep-alive\r\n\r\n" +
43 "abcdef\n???",
44
45 &Request{
46 Method: "GET",
47 URL: &url.URL{
48 Scheme: "http",
49 Host: "www.techcrunch.com",
50 Path: "/",
51 },
52 Proto: "HTTP/1.1",
53 ProtoMajor: 1,
54 ProtoMinor: 1,
55 Header: Header{
56 "Accept": {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
57 "Accept-Language": {"en-us,en;q=0.5"},
58 "Accept-Encoding": {"gzip,deflate"},
59 "Accept-Charset": {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
60 "Keep-Alive": {"300"},
61 "Proxy-Connection": {"keep-alive"},
62 "Content-Length": {"7"},
63 "User-Agent": {"Fake"},
64 },
65 Close: false,
66 ContentLength: 7,
67 Host: "www.techcrunch.com",
68 RequestURI: "http://www.techcrunch.com/",
69 },
70
71 "abcdef\n",
72
73 noTrailer,
74 noError,
75 },
76
77
78 {
79 "GET / HTTP/1.1\r\n" +
80 "Host: foo.com\r\n\r\n",
81
82 &Request{
83 Method: "GET",
84 URL: &url.URL{
85 Path: "/",
86 },
87 Proto: "HTTP/1.1",
88 ProtoMajor: 1,
89 ProtoMinor: 1,
90 Header: Header{},
91 Close: false,
92 ContentLength: 0,
93 Host: "foo.com",
94 RequestURI: "/",
95 },
96
97 noBodyStr,
98 noTrailer,
99 noError,
100 },
101
102
103
104 {
105 "GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" +
106 "Host: test\r\n\r\n",
107
108 &Request{
109 Method: "GET",
110 URL: &url.URL{
111 Path: "//user@host/is/actually/a/path/",
112 },
113 Proto: "HTTP/1.1",
114 ProtoMajor: 1,
115 ProtoMinor: 1,
116 Header: Header{},
117 Close: false,
118 ContentLength: 0,
119 Host: "test",
120 RequestURI: "//user@host/is/actually/a/path/",
121 },
122
123 noBodyStr,
124 noTrailer,
125 noError,
126 },
127
128
129 {
130 "GET ../../../../etc/passwd HTTP/1.1\r\n" +
131 "Host: test\r\n\r\n",
132 nil,
133 noBodyStr,
134 noTrailer,
135 `parse "../../../../etc/passwd": invalid URI for request`,
136 },
137
138
139 {
140 "GET HTTP/1.1\r\n" +
141 "Host: test\r\n\r\n",
142 nil,
143 noBodyStr,
144 noTrailer,
145 `parse "": empty url`,
146 },
147
148
149 {
150 "POST / HTTP/1.1\r\n" +
151 "Host: foo.com\r\n" +
152 "Transfer-Encoding: chunked\r\n\r\n" +
153 "3\r\nfoo\r\n" +
154 "3\r\nbar\r\n" +
155 "0\r\n" +
156 "Trailer-Key: Trailer-Value\r\n" +
157 "\r\n",
158 &Request{
159 Method: "POST",
160 URL: &url.URL{
161 Path: "/",
162 },
163 TransferEncoding: []string{"chunked"},
164 Proto: "HTTP/1.1",
165 ProtoMajor: 1,
166 ProtoMinor: 1,
167 Header: Header{},
168 ContentLength: -1,
169 Host: "foo.com",
170 RequestURI: "/",
171 },
172
173 "foobar",
174 Header{
175 "Trailer-Key": {"Trailer-Value"},
176 },
177 noError,
178 },
179
180
181 {
182 "POST / HTTP/1.1\r\n" +
183 "Host: foo.com\r\n" +
184 "Transfer-Encoding: chunked\r\n" +
185 "Content-Length: 9999\r\n\r\n" +
186 "3\r\nfoo\r\n" +
187 "3\r\nbar\r\n" +
188 "0\r\n" +
189 "\r\n",
190 &Request{
191 Method: "POST",
192 URL: &url.URL{
193 Path: "/",
194 },
195 TransferEncoding: []string{"chunked"},
196 Proto: "HTTP/1.1",
197 ProtoMajor: 1,
198 ProtoMinor: 1,
199 Header: Header{},
200 ContentLength: -1,
201 Host: "foo.com",
202 RequestURI: "/",
203 },
204
205 "foobar",
206 noTrailer,
207 noError,
208 },
209
210
211 {
212 "POST / HTTP/1.1\r\n" +
213 "Host: foo.com\r\n" +
214 "Transfer-Encoding: chunked\r\n" +
215 "Content-Length: notdigits\r\n\r\n" +
216 "3\r\nfoo\r\n" +
217 "3\r\nbar\r\n" +
218 "0\r\n" +
219 "\r\n",
220 nil,
221 noBodyStr,
222 noTrailer,
223 `bad Content-Length "notdigits"`,
224 },
225
226
227 {
228 "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n",
229
230 &Request{
231 Method: "CONNECT",
232 URL: &url.URL{
233 Host: "www.google.com:443",
234 },
235 Proto: "HTTP/1.1",
236 ProtoMajor: 1,
237 ProtoMinor: 1,
238 Header: Header{},
239 Close: false,
240 ContentLength: 0,
241 Host: "www.google.com:443",
242 RequestURI: "www.google.com:443",
243 },
244
245 noBodyStr,
246 noTrailer,
247 noError,
248 },
249
250
251 {
252 "CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n",
253
254 &Request{
255 Method: "CONNECT",
256 URL: &url.URL{
257 Host: "127.0.0.1:6060",
258 },
259 Proto: "HTTP/1.1",
260 ProtoMajor: 1,
261 ProtoMinor: 1,
262 Header: Header{},
263 Close: false,
264 ContentLength: 0,
265 Host: "127.0.0.1:6060",
266 RequestURI: "127.0.0.1:6060",
267 },
268
269 noBodyStr,
270 noTrailer,
271 noError,
272 },
273
274
275 {
276 "CONNECT /_goRPC_ HTTP/1.1\r\n\r\n",
277
278 &Request{
279 Method: "CONNECT",
280 URL: &url.URL{
281 Path: "/_goRPC_",
282 },
283 Proto: "HTTP/1.1",
284 ProtoMajor: 1,
285 ProtoMinor: 1,
286 Header: Header{},
287 Close: false,
288 ContentLength: 0,
289 Host: "",
290 RequestURI: "/_goRPC_",
291 },
292
293 noBodyStr,
294 noTrailer,
295 noError,
296 },
297
298
299 {
300 "NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",
301 &Request{
302 Method: "NOTIFY",
303 URL: &url.URL{
304 Path: "*",
305 },
306 Proto: "HTTP/1.1",
307 ProtoMajor: 1,
308 ProtoMinor: 1,
309 Header: Header{
310 "Server": []string{"foo"},
311 },
312 Close: false,
313 ContentLength: 0,
314 RequestURI: "*",
315 },
316
317 noBodyStr,
318 noTrailer,
319 noError,
320 },
321
322
323 {
324 "OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",
325 &Request{
326 Method: "OPTIONS",
327 URL: &url.URL{
328 Path: "*",
329 },
330 Proto: "HTTP/1.1",
331 ProtoMajor: 1,
332 ProtoMinor: 1,
333 Header: Header{
334 "Server": []string{"foo"},
335 },
336 Close: false,
337 ContentLength: 0,
338 RequestURI: "*",
339 },
340
341 noBodyStr,
342 noTrailer,
343 noError,
344 },
345
346
347 {
348 "GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n",
349 &Request{
350 Method: "GET",
351 URL: &url.URL{
352 Path: "/",
353 },
354 Header: Header{
355
356
357
358 "Connection": []string{"close"},
359 },
360 Host: "issue8261.com",
361 Proto: "HTTP/1.1",
362 ProtoMajor: 1,
363 ProtoMinor: 1,
364 Close: true,
365 RequestURI: "/",
366 },
367
368 noBodyStr,
369 noTrailer,
370 noError,
371 },
372
373
374
375 {
376 "HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
377 &Request{
378 Method: "HEAD",
379 URL: &url.URL{
380 Path: "/",
381 },
382 Header: Header{
383 "Connection": []string{"close"},
384 "Content-Length": []string{"0"},
385 },
386 Host: "issue8261.com",
387 Proto: "HTTP/1.1",
388 ProtoMajor: 1,
389 ProtoMinor: 1,
390 Close: true,
391 RequestURI: "/",
392 },
393
394 noBodyStr,
395 noTrailer,
396 noError,
397 },
398
399
400 {
401 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
402 &Request{
403 Method: "PRI",
404 URL: &url.URL{
405 Path: "*",
406 },
407 Header: Header{},
408 Proto: "HTTP/2.0",
409 ProtoMajor: 2,
410 ProtoMinor: 0,
411 RequestURI: "*",
412 ContentLength: -1,
413 Close: true,
414 },
415 noBodyStr,
416 noTrailer,
417 noError,
418 },
419 }
420
421 func TestReadRequest(t *testing.T) {
422 for i := range reqTests {
423 tt := &reqTests[i]
424 req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw)))
425 if err != nil {
426 if err.Error() != tt.Error {
427 t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error)
428 }
429 continue
430 }
431 rbody := req.Body
432 req.Body = nil
433 testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw)
434 diff(t, testName, req, tt.Req)
435 var bout strings.Builder
436 if rbody != nil {
437 _, err := io.Copy(&bout, rbody)
438 if err != nil {
439 t.Fatalf("%s: copying body: %v", testName, err)
440 }
441 rbody.Close()
442 }
443 body := bout.String()
444 if body != tt.Body {
445 t.Errorf("%s: Body = %q want %q", testName, body, tt.Body)
446 }
447 if !reflect.DeepEqual(tt.Trailer, req.Trailer) {
448 t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer)
449 }
450 }
451 }
452
453
454
455 func reqBytes(req string) []byte {
456 return []byte(strings.ReplaceAll(strings.TrimSpace(req), "\n", "\r\n") + "\r\n\r\n")
457 }
458
459 var badRequestTests = []struct {
460 name string
461 req []byte
462 }{
463 {"bad_connect_host", reqBytes("CONNECT []%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a HTTP/1.0")},
464 {"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1
465 Content-Length: 3
466 Content-Length: 4
467
468 abc`)},
469 {"smuggle_two_content_len_head", reqBytes(`HEAD / HTTP/1.1
470 Host: foo
471 Content-Length: 4
472 Content-Length: 5
473
474 1234`)},
475
476
477 {"leading_space_in_header", reqBytes(`GET / HTTP/1.1
478 Host: foo`)},
479 {"leading_tab_in_header", reqBytes(`GET / HTTP/1.1
480 ` + "\t" + `Host: foo`)},
481 }
482
483 func TestReadRequest_Bad(t *testing.T) {
484 for _, tt := range badRequestTests {
485 got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req)))
486 if err == nil {
487 all, err := io.ReadAll(got.Body)
488 t.Errorf("%s: got unexpected request = %#v\n Body = %q, %v", tt.name, got, all, err)
489 }
490 }
491 }
492
View as plain text