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 "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n",
213
214 &Request{
215 Method: "CONNECT",
216 URL: &url.URL{
217 Host: "www.google.com:443",
218 },
219 Proto: "HTTP/1.1",
220 ProtoMajor: 1,
221 ProtoMinor: 1,
222 Header: Header{},
223 Close: false,
224 ContentLength: 0,
225 Host: "www.google.com:443",
226 RequestURI: "www.google.com:443",
227 },
228
229 noBodyStr,
230 noTrailer,
231 noError,
232 },
233
234
235 {
236 "CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n",
237
238 &Request{
239 Method: "CONNECT",
240 URL: &url.URL{
241 Host: "127.0.0.1:6060",
242 },
243 Proto: "HTTP/1.1",
244 ProtoMajor: 1,
245 ProtoMinor: 1,
246 Header: Header{},
247 Close: false,
248 ContentLength: 0,
249 Host: "127.0.0.1:6060",
250 RequestURI: "127.0.0.1:6060",
251 },
252
253 noBodyStr,
254 noTrailer,
255 noError,
256 },
257
258
259 {
260 "CONNECT /_goRPC_ HTTP/1.1\r\n\r\n",
261
262 &Request{
263 Method: "CONNECT",
264 URL: &url.URL{
265 Path: "/_goRPC_",
266 },
267 Proto: "HTTP/1.1",
268 ProtoMajor: 1,
269 ProtoMinor: 1,
270 Header: Header{},
271 Close: false,
272 ContentLength: 0,
273 Host: "",
274 RequestURI: "/_goRPC_",
275 },
276
277 noBodyStr,
278 noTrailer,
279 noError,
280 },
281
282
283 {
284 "NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",
285 &Request{
286 Method: "NOTIFY",
287 URL: &url.URL{
288 Path: "*",
289 },
290 Proto: "HTTP/1.1",
291 ProtoMajor: 1,
292 ProtoMinor: 1,
293 Header: Header{
294 "Server": []string{"foo"},
295 },
296 Close: false,
297 ContentLength: 0,
298 RequestURI: "*",
299 },
300
301 noBodyStr,
302 noTrailer,
303 noError,
304 },
305
306
307 {
308 "OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",
309 &Request{
310 Method: "OPTIONS",
311 URL: &url.URL{
312 Path: "*",
313 },
314 Proto: "HTTP/1.1",
315 ProtoMajor: 1,
316 ProtoMinor: 1,
317 Header: Header{
318 "Server": []string{"foo"},
319 },
320 Close: false,
321 ContentLength: 0,
322 RequestURI: "*",
323 },
324
325 noBodyStr,
326 noTrailer,
327 noError,
328 },
329
330
331 {
332 "GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n",
333 &Request{
334 Method: "GET",
335 URL: &url.URL{
336 Path: "/",
337 },
338 Header: Header{
339
340
341
342 "Connection": []string{"close"},
343 },
344 Host: "issue8261.com",
345 Proto: "HTTP/1.1",
346 ProtoMajor: 1,
347 ProtoMinor: 1,
348 Close: true,
349 RequestURI: "/",
350 },
351
352 noBodyStr,
353 noTrailer,
354 noError,
355 },
356
357
358
359 {
360 "HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
361 &Request{
362 Method: "HEAD",
363 URL: &url.URL{
364 Path: "/",
365 },
366 Header: Header{
367 "Connection": []string{"close"},
368 "Content-Length": []string{"0"},
369 },
370 Host: "issue8261.com",
371 Proto: "HTTP/1.1",
372 ProtoMajor: 1,
373 ProtoMinor: 1,
374 Close: true,
375 RequestURI: "/",
376 },
377
378 noBodyStr,
379 noTrailer,
380 noError,
381 },
382
383
384 {
385 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
386 &Request{
387 Method: "PRI",
388 URL: &url.URL{
389 Path: "*",
390 },
391 Header: Header{},
392 Proto: "HTTP/2.0",
393 ProtoMajor: 2,
394 ProtoMinor: 0,
395 RequestURI: "*",
396 ContentLength: -1,
397 Close: true,
398 },
399 noBodyStr,
400 noTrailer,
401 noError,
402 },
403 }
404
405 func TestReadRequest(t *testing.T) {
406 for i := range reqTests {
407 tt := &reqTests[i]
408 req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw)))
409 if err != nil {
410 if err.Error() != tt.Error {
411 t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error)
412 }
413 continue
414 }
415 rbody := req.Body
416 req.Body = nil
417 testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw)
418 diff(t, testName, req, tt.Req)
419 var bout bytes.Buffer
420 if rbody != nil {
421 _, err := io.Copy(&bout, rbody)
422 if err != nil {
423 t.Fatalf("%s: copying body: %v", testName, err)
424 }
425 rbody.Close()
426 }
427 body := bout.String()
428 if body != tt.Body {
429 t.Errorf("%s: Body = %q want %q", testName, body, tt.Body)
430 }
431 if !reflect.DeepEqual(tt.Trailer, req.Trailer) {
432 t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer)
433 }
434 }
435 }
436
437
438
439 func reqBytes(req string) []byte {
440 return []byte(strings.ReplaceAll(strings.TrimSpace(req), "\n", "\r\n") + "\r\n\r\n")
441 }
442
443 var badRequestTests = []struct {
444 name string
445 req []byte
446 }{
447 {"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")},
448 {"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1
449 Content-Length: 3
450 Content-Length: 4
451
452 abc`)},
453 {"smuggle_content_len_head", reqBytes(`HEAD / HTTP/1.1
454 Host: foo
455 Content-Length: 5`)},
456
457
458 {"leading_space_in_header", reqBytes(`HEAD / HTTP/1.1
459 Host: foo
460 Content-Length: 5`)},
461 {"leading_tab_in_header", reqBytes(`HEAD / HTTP/1.1
462 \tHost: foo
463 Content-Length: 5`)},
464 }
465
466 func TestReadRequest_Bad(t *testing.T) {
467 for _, tt := range badRequestTests {
468 got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req)))
469 if err == nil {
470 all, err := io.ReadAll(got.Body)
471 t.Errorf("%s: got unexpected request = %#v\n Body = %q, %v", tt.name, got, all, err)
472 }
473 }
474 }
475
View as plain text