netramesh

Форк
0
/
response_test.go 
979 строк · 23.4 Кб
1
// Copyright 2010 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
	"bufio"
9
	"bytes"
10
	"compress/gzip"
11
	"crypto/rand"
12
	"fmt"
13
	"go/ast"
14
	"io"
15
	"io/ioutil"
16
	"net/http/internal"
17
	"net/url"
18
	"reflect"
19
	"regexp"
20
	"strings"
21
	"testing"
22
)
23

24
type respTest struct {
25
	Raw  string
26
	Resp Response
27
	Body string
28
}
29

30
func dummyReq(method string) *Request {
31
	return &Request{Method: method}
32
}
33

34
func dummyReq11(method string) *Request {
35
	return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
36
}
37

38
var respTests = []respTest{
39
	// Unchunked response without Content-Length.
40
	{
41
		"HTTP/1.0 200 OK\r\n" +
42
			"Connection: close\r\n" +
43
			"\r\n" +
44
			"Body here\n",
45

46
		Response{
47
			Status:     "200 OK",
48
			StatusCode: 200,
49
			Proto:      "HTTP/1.0",
50
			ProtoMajor: 1,
51
			ProtoMinor: 0,
52
			Request:    dummyReq("GET"),
53
			Header: Header{
54
				"Connection": {"close"}, // TODO(rsc): Delete?
55
			},
56
			Close:         true,
57
			ContentLength: -1,
58
		},
59

60
		"Body here\n",
61
	},
62

63
	// Unchunked HTTP/1.1 response without Content-Length or
64
	// Connection headers.
65
	{
66
		"HTTP/1.1 200 OK\r\n" +
67
			"\r\n" +
68
			"Body here\n",
69

70
		Response{
71
			Status:        "200 OK",
72
			StatusCode:    200,
73
			Proto:         "HTTP/1.1",
74
			ProtoMajor:    1,
75
			ProtoMinor:    1,
76
			Header:        Header{},
77
			Request:       dummyReq("GET"),
78
			Close:         true,
79
			ContentLength: -1,
80
		},
81

82
		"Body here\n",
83
	},
84

85
	// Unchunked HTTP/1.1 204 response without Content-Length.
86
	{
87
		"HTTP/1.1 204 No Content\r\n" +
88
			"\r\n" +
89
			"Body should not be read!\n",
90

91
		Response{
92
			Status:        "204 No Content",
93
			StatusCode:    204,
94
			Proto:         "HTTP/1.1",
95
			ProtoMajor:    1,
96
			ProtoMinor:    1,
97
			Header:        Header{},
98
			Request:       dummyReq("GET"),
99
			Close:         false,
100
			ContentLength: 0,
101
		},
102

103
		"",
104
	},
105

106
	// Unchunked response with Content-Length.
107
	{
108
		"HTTP/1.0 200 OK\r\n" +
109
			"Content-Length: 10\r\n" +
110
			"Connection: close\r\n" +
111
			"\r\n" +
112
			"Body here\n",
113

114
		Response{
115
			Status:     "200 OK",
116
			StatusCode: 200,
117
			Proto:      "HTTP/1.0",
118
			ProtoMajor: 1,
119
			ProtoMinor: 0,
120
			Request:    dummyReq("GET"),
121
			Header: Header{
122
				"Connection":     {"close"},
123
				"Content-Length": {"10"},
124
			},
125
			Close:         true,
126
			ContentLength: 10,
127
		},
128

129
		"Body here\n",
130
	},
131

132
	// Chunked response without Content-Length.
133
	{
134
		"HTTP/1.1 200 OK\r\n" +
135
			"Transfer-Encoding: chunked\r\n" +
136
			"\r\n" +
137
			"0a\r\n" +
138
			"Body here\n\r\n" +
139
			"09\r\n" +
140
			"continued\r\n" +
141
			"0\r\n" +
142
			"\r\n",
143

144
		Response{
145
			Status:           "200 OK",
146
			StatusCode:       200,
147
			Proto:            "HTTP/1.1",
148
			ProtoMajor:       1,
149
			ProtoMinor:       1,
150
			Request:          dummyReq("GET"),
151
			Header:           Header{},
152
			Close:            false,
153
			ContentLength:    -1,
154
			TransferEncoding: []string{"chunked"},
155
		},
156

157
		"Body here\ncontinued",
158
	},
159

160
	// Chunked response with Content-Length.
161
	{
162
		"HTTP/1.1 200 OK\r\n" +
163
			"Transfer-Encoding: chunked\r\n" +
164
			"Content-Length: 10\r\n" +
165
			"\r\n" +
166
			"0a\r\n" +
167
			"Body here\n\r\n" +
168
			"0\r\n" +
169
			"\r\n",
170

171
		Response{
172
			Status:           "200 OK",
173
			StatusCode:       200,
174
			Proto:            "HTTP/1.1",
175
			ProtoMajor:       1,
176
			ProtoMinor:       1,
177
			Request:          dummyReq("GET"),
178
			Header:           Header{},
179
			Close:            false,
180
			ContentLength:    -1,
181
			TransferEncoding: []string{"chunked"},
182
		},
183

184
		"Body here\n",
185
	},
186

187
	// Chunked response in response to a HEAD request
188
	{
189
		"HTTP/1.1 200 OK\r\n" +
190
			"Transfer-Encoding: chunked\r\n" +
191
			"\r\n",
192

193
		Response{
194
			Status:           "200 OK",
195
			StatusCode:       200,
196
			Proto:            "HTTP/1.1",
197
			ProtoMajor:       1,
198
			ProtoMinor:       1,
199
			Request:          dummyReq("HEAD"),
200
			Header:           Header{},
201
			TransferEncoding: []string{"chunked"},
202
			Close:            false,
203
			ContentLength:    -1,
204
		},
205

206
		"",
207
	},
208

209
	// Content-Length in response to a HEAD request
210
	{
211
		"HTTP/1.0 200 OK\r\n" +
212
			"Content-Length: 256\r\n" +
213
			"\r\n",
214

215
		Response{
216
			Status:           "200 OK",
217
			StatusCode:       200,
218
			Proto:            "HTTP/1.0",
219
			ProtoMajor:       1,
220
			ProtoMinor:       0,
221
			Request:          dummyReq("HEAD"),
222
			Header:           Header{"Content-Length": {"256"}},
223
			TransferEncoding: nil,
224
			Close:            true,
225
			ContentLength:    256,
226
		},
227

228
		"",
229
	},
230

231
	// Content-Length in response to a HEAD request with HTTP/1.1
232
	{
233
		"HTTP/1.1 200 OK\r\n" +
234
			"Content-Length: 256\r\n" +
235
			"\r\n",
236

237
		Response{
238
			Status:           "200 OK",
239
			StatusCode:       200,
240
			Proto:            "HTTP/1.1",
241
			ProtoMajor:       1,
242
			ProtoMinor:       1,
243
			Request:          dummyReq("HEAD"),
244
			Header:           Header{"Content-Length": {"256"}},
245
			TransferEncoding: nil,
246
			Close:            false,
247
			ContentLength:    256,
248
		},
249

250
		"",
251
	},
252

253
	// No Content-Length or Chunked in response to a HEAD request
254
	{
255
		"HTTP/1.0 200 OK\r\n" +
256
			"\r\n",
257

258
		Response{
259
			Status:           "200 OK",
260
			StatusCode:       200,
261
			Proto:            "HTTP/1.0",
262
			ProtoMajor:       1,
263
			ProtoMinor:       0,
264
			Request:          dummyReq("HEAD"),
265
			Header:           Header{},
266
			TransferEncoding: nil,
267
			Close:            true,
268
			ContentLength:    -1,
269
		},
270

271
		"",
272
	},
273

274
	// explicit Content-Length of 0.
275
	{
276
		"HTTP/1.1 200 OK\r\n" +
277
			"Content-Length: 0\r\n" +
278
			"\r\n",
279

280
		Response{
281
			Status:     "200 OK",
282
			StatusCode: 200,
283
			Proto:      "HTTP/1.1",
284
			ProtoMajor: 1,
285
			ProtoMinor: 1,
286
			Request:    dummyReq("GET"),
287
			Header: Header{
288
				"Content-Length": {"0"},
289
			},
290
			Close:         false,
291
			ContentLength: 0,
292
		},
293

294
		"",
295
	},
296

297
	// Status line without a Reason-Phrase, but trailing space.
298
	// (permitted by RFC 7230, section 3.1.2)
299
	{
300
		"HTTP/1.0 303 \r\n\r\n",
301
		Response{
302
			Status:        "303 ",
303
			StatusCode:    303,
304
			Proto:         "HTTP/1.0",
305
			ProtoMajor:    1,
306
			ProtoMinor:    0,
307
			Request:       dummyReq("GET"),
308
			Header:        Header{},
309
			Close:         true,
310
			ContentLength: -1,
311
		},
312

313
		"",
314
	},
315

316
	// Status line without a Reason-Phrase, and no trailing space.
317
	// (not permitted by RFC 7230, but we'll accept it anyway)
318
	{
319
		"HTTP/1.0 303\r\n\r\n",
320
		Response{
321
			Status:        "303",
322
			StatusCode:    303,
323
			Proto:         "HTTP/1.0",
324
			ProtoMajor:    1,
325
			ProtoMinor:    0,
326
			Request:       dummyReq("GET"),
327
			Header:        Header{},
328
			Close:         true,
329
			ContentLength: -1,
330
		},
331

332
		"",
333
	},
334

335
	// golang.org/issue/4767: don't special-case multipart/byteranges responses
336
	{
337
		`HTTP/1.1 206 Partial Content
338
Connection: close
339
Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
340

341
some body`,
342
		Response{
343
			Status:     "206 Partial Content",
344
			StatusCode: 206,
345
			Proto:      "HTTP/1.1",
346
			ProtoMajor: 1,
347
			ProtoMinor: 1,
348
			Request:    dummyReq("GET"),
349
			Header: Header{
350
				"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
351
			},
352
			Close:         true,
353
			ContentLength: -1,
354
		},
355

356
		"some body",
357
	},
358

359
	// Unchunked response without Content-Length, Request is nil
360
	{
361
		"HTTP/1.0 200 OK\r\n" +
362
			"Connection: close\r\n" +
363
			"\r\n" +
364
			"Body here\n",
365

366
		Response{
367
			Status:     "200 OK",
368
			StatusCode: 200,
369
			Proto:      "HTTP/1.0",
370
			ProtoMajor: 1,
371
			ProtoMinor: 0,
372
			Header: Header{
373
				"Connection": {"close"}, // TODO(rsc): Delete?
374
			},
375
			Close:         true,
376
			ContentLength: -1,
377
		},
378

379
		"Body here\n",
380
	},
381

382
	// 206 Partial Content. golang.org/issue/8923
383
	{
384
		"HTTP/1.1 206 Partial Content\r\n" +
385
			"Content-Type: text/plain; charset=utf-8\r\n" +
386
			"Accept-Ranges: bytes\r\n" +
387
			"Content-Range: bytes 0-5/1862\r\n" +
388
			"Content-Length: 6\r\n\r\n" +
389
			"foobar",
390

391
		Response{
392
			Status:     "206 Partial Content",
393
			StatusCode: 206,
394
			Proto:      "HTTP/1.1",
395
			ProtoMajor: 1,
396
			ProtoMinor: 1,
397
			Request:    dummyReq("GET"),
398
			Header: Header{
399
				"Accept-Ranges":  []string{"bytes"},
400
				"Content-Length": []string{"6"},
401
				"Content-Type":   []string{"text/plain; charset=utf-8"},
402
				"Content-Range":  []string{"bytes 0-5/1862"},
403
			},
404
			ContentLength: 6,
405
		},
406

407
		"foobar",
408
	},
409

410
	// Both keep-alive and close, on the same Connection line. (Issue 8840)
411
	{
412
		"HTTP/1.1 200 OK\r\n" +
413
			"Content-Length: 256\r\n" +
414
			"Connection: keep-alive, close\r\n" +
415
			"\r\n",
416

417
		Response{
418
			Status:     "200 OK",
419
			StatusCode: 200,
420
			Proto:      "HTTP/1.1",
421
			ProtoMajor: 1,
422
			ProtoMinor: 1,
423
			Request:    dummyReq("HEAD"),
424
			Header: Header{
425
				"Content-Length": {"256"},
426
			},
427
			TransferEncoding: nil,
428
			Close:            true,
429
			ContentLength:    256,
430
		},
431

432
		"",
433
	},
434

435
	// Both keep-alive and close, on different Connection lines. (Issue 8840)
436
	{
437
		"HTTP/1.1 200 OK\r\n" +
438
			"Content-Length: 256\r\n" +
439
			"Connection: keep-alive\r\n" +
440
			"Connection: close\r\n" +
441
			"\r\n",
442

443
		Response{
444
			Status:     "200 OK",
445
			StatusCode: 200,
446
			Proto:      "HTTP/1.1",
447
			ProtoMajor: 1,
448
			ProtoMinor: 1,
449
			Request:    dummyReq("HEAD"),
450
			Header: Header{
451
				"Content-Length": {"256"},
452
			},
453
			TransferEncoding: nil,
454
			Close:            true,
455
			ContentLength:    256,
456
		},
457

458
		"",
459
	},
460

461
	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
462
	// Without a Content-Length.
463
	{
464
		"HTTP/1.0 200 OK\r\n" +
465
			"Transfer-Encoding: bogus\r\n" +
466
			"\r\n" +
467
			"Body here\n",
468

469
		Response{
470
			Status:        "200 OK",
471
			StatusCode:    200,
472
			Proto:         "HTTP/1.0",
473
			ProtoMajor:    1,
474
			ProtoMinor:    0,
475
			Request:       dummyReq("GET"),
476
			Header:        Header{},
477
			Close:         true,
478
			ContentLength: -1,
479
		},
480

481
		"Body here\n",
482
	},
483

484
	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
485
	// With a Content-Length.
486
	{
487
		"HTTP/1.0 200 OK\r\n" +
488
			"Transfer-Encoding: bogus\r\n" +
489
			"Content-Length: 10\r\n" +
490
			"\r\n" +
491
			"Body here\n",
492

493
		Response{
494
			Status:     "200 OK",
495
			StatusCode: 200,
496
			Proto:      "HTTP/1.0",
497
			ProtoMajor: 1,
498
			ProtoMinor: 0,
499
			Request:    dummyReq("GET"),
500
			Header: Header{
501
				"Content-Length": {"10"},
502
			},
503
			Close:         true,
504
			ContentLength: 10,
505
		},
506

507
		"Body here\n",
508
	},
509

510
	{
511
		"HTTP/1.1 200 OK\r\n" +
512
			"Content-Encoding: gzip\r\n" +
513
			"Content-Length: 23\r\n" +
514
			"Connection: keep-alive\r\n" +
515
			"Keep-Alive: timeout=7200\r\n\r\n" +
516
			"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
517
		Response{
518
			Status:     "200 OK",
519
			StatusCode: 200,
520
			Proto:      "HTTP/1.1",
521
			ProtoMajor: 1,
522
			ProtoMinor: 1,
523
			Request:    dummyReq("GET"),
524
			Header: Header{
525
				"Content-Length":   {"23"},
526
				"Content-Encoding": {"gzip"},
527
				"Connection":       {"keep-alive"},
528
				"Keep-Alive":       {"timeout=7200"},
529
			},
530
			Close:         false,
531
			ContentLength: 23,
532
		},
533
		"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
534
	},
535

536
	// Issue 19989: two spaces between HTTP version and status.
537
	{
538
		"HTTP/1.0  401 Unauthorized\r\n" +
539
			"Content-type: text/html\r\n" +
540
			"WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
541
			"Your Authentication failed.\r\n",
542
		Response{
543
			Status:     "401 Unauthorized",
544
			StatusCode: 401,
545
			Proto:      "HTTP/1.0",
546
			ProtoMajor: 1,
547
			ProtoMinor: 0,
548
			Request:    dummyReq("GET"),
549
			Header: Header{
550
				"Content-Type":     {"text/html"},
551
				"Www-Authenticate": {`Basic realm=""`},
552
			},
553
			Close:         true,
554
			ContentLength: -1,
555
		},
556
		"Your Authentication failed.\r\n",
557
	},
558
}
559

560
// tests successful calls to ReadResponse, and inspects the returned Response.
561
// For error cases, see TestReadResponseErrors below.
562
func TestReadResponse(t *testing.T) {
563
	for i, tt := range respTests {
564
		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
565
		if err != nil {
566
			t.Errorf("#%d: %v", i, err)
567
			continue
568
		}
569
		rbody := resp.Body
570
		resp.Body = nil
571
		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
572
		var bout bytes.Buffer
573
		if rbody != nil {
574
			_, err = io.Copy(&bout, rbody)
575
			if err != nil {
576
				t.Errorf("#%d: %v", i, err)
577
				continue
578
			}
579
			rbody.Close()
580
		}
581
		body := bout.String()
582
		if body != tt.Body {
583
			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
584
		}
585
	}
586
}
587

588
func TestWriteResponse(t *testing.T) {
589
	for i, tt := range respTests {
590
		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
591
		if err != nil {
592
			t.Errorf("#%d: %v", i, err)
593
			continue
594
		}
595
		err = resp.Write(ioutil.Discard)
596
		if err != nil {
597
			t.Errorf("#%d: %v", i, err)
598
			continue
599
		}
600
	}
601
}
602

603
var readResponseCloseInMiddleTests = []struct {
604
	chunked, compressed bool
605
}{
606
	{false, false},
607
	{true, false},
608
	{true, true},
609
}
610

611
// TestReadResponseCloseInMiddle tests that closing a body after
612
// reading only part of its contents advances the read to the end of
613
// the request, right up until the next request.
614
func TestReadResponseCloseInMiddle(t *testing.T) {
615
	t.Parallel()
616
	for _, test := range readResponseCloseInMiddleTests {
617
		fatalf := func(format string, args ...interface{}) {
618
			args = append([]interface{}{test.chunked, test.compressed}, args...)
619
			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
620
		}
621
		checkErr := func(err error, msg string) {
622
			if err == nil {
623
				return
624
			}
625
			fatalf(msg+": %v", err)
626
		}
627
		var buf bytes.Buffer
628
		buf.WriteString("HTTP/1.1 200 OK\r\n")
629
		if test.chunked {
630
			buf.WriteString("Transfer-Encoding: chunked\r\n")
631
		} else {
632
			buf.WriteString("Content-Length: 1000000\r\n")
633
		}
634
		var wr io.Writer = &buf
635
		if test.chunked {
636
			wr = internal.NewChunkedWriter(wr)
637
		}
638
		if test.compressed {
639
			buf.WriteString("Content-Encoding: gzip\r\n")
640
			wr = gzip.NewWriter(wr)
641
		}
642
		buf.WriteString("\r\n")
643

644
		chunk := bytes.Repeat([]byte{'x'}, 1000)
645
		for i := 0; i < 1000; i++ {
646
			if test.compressed {
647
				// Otherwise this compresses too well.
648
				_, err := io.ReadFull(rand.Reader, chunk)
649
				checkErr(err, "rand.Reader ReadFull")
650
			}
651
			wr.Write(chunk)
652
		}
653
		if test.compressed {
654
			err := wr.(*gzip.Writer).Close()
655
			checkErr(err, "compressor close")
656
		}
657
		if test.chunked {
658
			buf.WriteString("0\r\n\r\n")
659
		}
660
		buf.WriteString("Next Request Here")
661

662
		bufr := bufio.NewReader(&buf)
663
		resp, err := ReadResponse(bufr, dummyReq("GET"))
664
		checkErr(err, "ReadResponse")
665
		expectedLength := int64(-1)
666
		if !test.chunked {
667
			expectedLength = 1000000
668
		}
669
		if resp.ContentLength != expectedLength {
670
			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
671
		}
672
		if resp.Body == nil {
673
			fatalf("nil body")
674
		}
675
		if test.compressed {
676
			gzReader, err := gzip.NewReader(resp.Body)
677
			checkErr(err, "gzip.NewReader")
678
			resp.Body = &readerAndCloser{gzReader, resp.Body}
679
		}
680

681
		rbuf := make([]byte, 2500)
682
		n, err := io.ReadFull(resp.Body, rbuf)
683
		checkErr(err, "2500 byte ReadFull")
684
		if n != 2500 {
685
			fatalf("ReadFull only read %d bytes", n)
686
		}
687
		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
688
			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
689
		}
690
		resp.Body.Close()
691

692
		rest, err := ioutil.ReadAll(bufr)
693
		checkErr(err, "ReadAll on remainder")
694
		if e, g := "Next Request Here", string(rest); e != g {
695
			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
696
				return fmt.Sprintf("x(repeated x%d)", len(match))
697
			})
698
			fatalf("remainder = %q, expected %q", g, e)
699
		}
700
	}
701
}
702

703
func diff(t *testing.T, prefix string, have, want interface{}) {
704
	hv := reflect.ValueOf(have).Elem()
705
	wv := reflect.ValueOf(want).Elem()
706
	if hv.Type() != wv.Type() {
707
		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
708
	}
709
	for i := 0; i < hv.NumField(); i++ {
710
		name := hv.Type().Field(i).Name
711
		if !ast.IsExported(name) {
712
			continue
713
		}
714
		hf := hv.Field(i).Interface()
715
		wf := wv.Field(i).Interface()
716
		if !reflect.DeepEqual(hf, wf) {
717
			t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
718
		}
719
	}
720
}
721

722
type responseLocationTest struct {
723
	location string // Response's Location header or ""
724
	requrl   string // Response.Request.URL or ""
725
	want     string
726
	wantErr  error
727
}
728

729
var responseLocationTests = []responseLocationTest{
730
	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
731
	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
732
	{"", "http://bar.com/baz", "", ErrNoLocation},
733
	{"/bar", "", "/bar", nil},
734
}
735

736
func TestLocationResponse(t *testing.T) {
737
	for i, tt := range responseLocationTests {
738
		res := new(Response)
739
		res.Header = make(Header)
740
		res.Header.Set("Location", tt.location)
741
		if tt.requrl != "" {
742
			res.Request = &Request{}
743
			var err error
744
			res.Request.URL, err = url.Parse(tt.requrl)
745
			if err != nil {
746
				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
747
			}
748
		}
749

750
		got, err := res.Location()
751
		if tt.wantErr != nil {
752
			if err == nil {
753
				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
754
				continue
755
			}
756
			if g, e := err.Error(), tt.wantErr.Error(); g != e {
757
				t.Errorf("%d. err=%q; want %q", i, g, e)
758
				continue
759
			}
760
			continue
761
		}
762
		if err != nil {
763
			t.Errorf("%d. err=%q", i, err)
764
			continue
765
		}
766
		if g, e := got.String(), tt.want; g != e {
767
			t.Errorf("%d. Location=%q; want %q", i, g, e)
768
		}
769
	}
770
}
771

772
func TestResponseStatusStutter(t *testing.T) {
773
	r := &Response{
774
		Status:     "123 some status",
775
		StatusCode: 123,
776
		ProtoMajor: 1,
777
		ProtoMinor: 3,
778
	}
779
	var buf bytes.Buffer
780
	r.Write(&buf)
781
	if strings.Contains(buf.String(), "123 123") {
782
		t.Errorf("stutter in status: %s", buf.String())
783
	}
784
}
785

786
func TestResponseContentLengthShortBody(t *testing.T) {
787
	const shortBody = "Short body, not 123 bytes."
788
	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
789
		"Content-Length: 123\r\n" +
790
		"\r\n" +
791
		shortBody))
792
	res, err := ReadResponse(br, &Request{Method: "GET"})
793
	if err != nil {
794
		t.Fatal(err)
795
	}
796
	if res.ContentLength != 123 {
797
		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
798
	}
799
	var buf bytes.Buffer
800
	n, err := io.Copy(&buf, res.Body)
801
	if n != int64(len(shortBody)) {
802
		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
803
	}
804
	if buf.String() != shortBody {
805
		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
806
	}
807
	if err != io.ErrUnexpectedEOF {
808
		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
809
	}
810
}
811

812
// Test various ReadResponse error cases. (also tests success cases, but mostly
813
// it's about errors).  This does not test anything involving the bodies. Only
814
// the return value from ReadResponse itself.
815
func TestReadResponseErrors(t *testing.T) {
816
	type testCase struct {
817
		name    string // optional, defaults to in
818
		in      string
819
		wantErr interface{} // nil, err value, or string substring
820
	}
821

822
	status := func(s string, wantErr interface{}) testCase {
823
		if wantErr == true {
824
			wantErr = "malformed HTTP status code"
825
		}
826
		return testCase{
827
			name:    fmt.Sprintf("status %q", s),
828
			in:      "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
829
			wantErr: wantErr,
830
		}
831
	}
832

833
	version := func(s string, wantErr interface{}) testCase {
834
		if wantErr == true {
835
			wantErr = "malformed HTTP version"
836
		}
837
		return testCase{
838
			name:    fmt.Sprintf("version %q", s),
839
			in:      s + " 200 OK\r\n\r\n",
840
			wantErr: wantErr,
841
		}
842
	}
843

844
	contentLength := func(status, body string, wantErr interface{}) testCase {
845
		return testCase{
846
			name:    fmt.Sprintf("status %q %q", status, body),
847
			in:      fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
848
			wantErr: wantErr,
849
		}
850
	}
851

852
	errMultiCL := "message cannot contain multiple Content-Length headers"
853

854
	tests := []testCase{
855
		{"", "", io.ErrUnexpectedEOF},
856
		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF},
857
		{"", "HTTP/1.1", "malformed HTTP response"},
858
		{"", "HTTP/2.0", "malformed HTTP response"},
859
		status("20X Unknown", true),
860
		status("abcd Unknown", true),
861
		status("二百/两百 OK", true),
862
		status(" Unknown", true),
863
		status("c8 OK", true),
864
		status("0x12d Moved Permanently", true),
865
		status("200 OK", nil),
866
		status("000 OK", nil),
867
		status("001 OK", nil),
868
		status("404 NOTFOUND", nil),
869
		status("20 OK", true),
870
		status("00 OK", true),
871
		status("-10 OK", true),
872
		status("1000 OK", true),
873
		status("999 Done", nil),
874
		status("-1 OK", true),
875
		status("-200 OK", true),
876
		version("HTTP/1.2", nil),
877
		version("HTTP/2.0", nil),
878
		version("HTTP/1.100000000002", true),
879
		version("HTTP/1.-1", true),
880
		version("HTTP/A.B", true),
881
		version("HTTP/1", true),
882
		version("http/1.1", true),
883

884
		contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL),
885
		contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
886
		contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
887
		contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
888
		contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
889
		contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
890

891
		// multiple content-length headers for 204 and 304 should still be checked
892
		contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL),
893
		contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil),
894
		contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL),
895
		contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil),
896

897
		// golang.org/issue/22464
898
		{"leading space in header", "HTTP/1.1 200 OK\r\n Content-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
899
		{"leading tab in header", "HTTP/1.1 200 OK\r\n\tContent-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
900
	}
901

902
	for i, tt := range tests {
903
		br := bufio.NewReader(strings.NewReader(tt.in))
904
		_, rerr := ReadResponse(br, nil)
905
		if err := matchErr(rerr, tt.wantErr); err != nil {
906
			name := tt.name
907
			if name == "" {
908
				name = fmt.Sprintf("%d. input %q", i, tt.in)
909
			}
910
			t.Errorf("%s: %v", name, err)
911
		}
912
	}
913
}
914

915
// wantErr can be nil, an error value to match exactly, or type string to
916
// match a substring.
917
func matchErr(err error, wantErr interface{}) error {
918
	if err == nil {
919
		if wantErr == nil {
920
			return nil
921
		}
922
		if sub, ok := wantErr.(string); ok {
923
			return fmt.Errorf("unexpected success; want error with substring %q", sub)
924
		}
925
		return fmt.Errorf("unexpected success; want error %v", wantErr)
926
	}
927
	if wantErr == nil {
928
		return fmt.Errorf("%v; want success", err)
929
	}
930
	if sub, ok := wantErr.(string); ok {
931
		if strings.Contains(err.Error(), sub) {
932
			return nil
933
		}
934
		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
935
	}
936
	if err == wantErr {
937
		return nil
938
	}
939
	return fmt.Errorf("%v; want %v", err, wantErr)
940
}
941

942
func TestNeedsSniff(t *testing.T) {
943
	// needsSniff returns true with an empty response.
944
	r := &response{}
945
	if got, want := r.needsSniff(), true; got != want {
946
		t.Errorf("needsSniff = %t; want %t", got, want)
947
	}
948
	// needsSniff returns false when Content-Type = nil.
949
	r.handlerHeader = Header{"Content-Type": nil}
950
	if got, want := r.needsSniff(), false; got != want {
951
		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
952
	}
953
}
954

955
// A response should only write out single Connection: close header. Tests #19499.
956
func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
957
	const connectionCloseHeader = "Connection: close"
958

959
	res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
960
	if err != nil {
961
		t.Fatalf("ReadResponse failed %v", err)
962
	}
963

964
	var buf1 bytes.Buffer
965
	if err = res.Write(&buf1); err != nil {
966
		t.Fatalf("Write failed %v", err)
967
	}
968
	if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
969
		t.Fatalf("ReadResponse failed %v", err)
970
	}
971

972
	var buf2 bytes.Buffer
973
	if err = res.Write(&buf2); err != nil {
974
		t.Fatalf("Write failed %v", err)
975
	}
976
	if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
977
		t.Errorf("Found %d %q header", count, connectionCloseHeader)
978
	}
979
}
980

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.