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.
30
func dummyReq(method string) *Request {
31
return &Request{Method: method}
34
func dummyReq11(method string) *Request {
35
return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
38
var respTests = []respTest{
39
// Unchunked response without Content-Length.
41
"HTTP/1.0 200 OK\r\n" +
42
"Connection: close\r\n" +
52
Request: dummyReq("GET"),
54
"Connection": {"close"}, // TODO(rsc): Delete?
63
// Unchunked HTTP/1.1 response without Content-Length or
64
// Connection headers.
66
"HTTP/1.1 200 OK\r\n" +
77
Request: dummyReq("GET"),
85
// Unchunked HTTP/1.1 204 response without Content-Length.
87
"HTTP/1.1 204 No Content\r\n" +
89
"Body should not be read!\n",
92
Status: "204 No Content",
98
Request: dummyReq("GET"),
106
// Unchunked response with Content-Length.
108
"HTTP/1.0 200 OK\r\n" +
109
"Content-Length: 10\r\n" +
110
"Connection: close\r\n" +
120
Request: dummyReq("GET"),
122
"Connection": {"close"},
123
"Content-Length": {"10"},
132
// Chunked response without Content-Length.
134
"HTTP/1.1 200 OK\r\n" +
135
"Transfer-Encoding: chunked\r\n" +
150
Request: dummyReq("GET"),
154
TransferEncoding: []string{"chunked"},
157
"Body here\ncontinued",
160
// Chunked response with Content-Length.
162
"HTTP/1.1 200 OK\r\n" +
163
"Transfer-Encoding: chunked\r\n" +
164
"Content-Length: 10\r\n" +
177
Request: dummyReq("GET"),
181
TransferEncoding: []string{"chunked"},
187
// Chunked response in response to a HEAD request
189
"HTTP/1.1 200 OK\r\n" +
190
"Transfer-Encoding: chunked\r\n" +
199
Request: dummyReq("HEAD"),
201
TransferEncoding: []string{"chunked"},
209
// Content-Length in response to a HEAD request
211
"HTTP/1.0 200 OK\r\n" +
212
"Content-Length: 256\r\n" +
221
Request: dummyReq("HEAD"),
222
Header: Header{"Content-Length": {"256"}},
223
TransferEncoding: nil,
231
// Content-Length in response to a HEAD request with HTTP/1.1
233
"HTTP/1.1 200 OK\r\n" +
234
"Content-Length: 256\r\n" +
243
Request: dummyReq("HEAD"),
244
Header: Header{"Content-Length": {"256"}},
245
TransferEncoding: nil,
253
// No Content-Length or Chunked in response to a HEAD request
255
"HTTP/1.0 200 OK\r\n" +
264
Request: dummyReq("HEAD"),
266
TransferEncoding: nil,
274
// explicit Content-Length of 0.
276
"HTTP/1.1 200 OK\r\n" +
277
"Content-Length: 0\r\n" +
286
Request: dummyReq("GET"),
288
"Content-Length": {"0"},
297
// Status line without a Reason-Phrase, but trailing space.
298
// (permitted by RFC 7230, section 3.1.2)
300
"HTTP/1.0 303 \r\n\r\n",
307
Request: dummyReq("GET"),
316
// Status line without a Reason-Phrase, and no trailing space.
317
// (not permitted by RFC 7230, but we'll accept it anyway)
319
"HTTP/1.0 303\r\n\r\n",
326
Request: dummyReq("GET"),
335
// golang.org/issue/4767: don't special-case multipart/byteranges responses
337
`HTTP/1.1 206 Partial Content
339
Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
343
Status: "206 Partial Content",
348
Request: dummyReq("GET"),
350
"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
359
// Unchunked response without Content-Length, Request is nil
361
"HTTP/1.0 200 OK\r\n" +
362
"Connection: close\r\n" +
373
"Connection": {"close"}, // TODO(rsc): Delete?
382
// 206 Partial Content. golang.org/issue/8923
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" +
392
Status: "206 Partial Content",
397
Request: dummyReq("GET"),
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"},
410
// Both keep-alive and close, on the same Connection line. (Issue 8840)
412
"HTTP/1.1 200 OK\r\n" +
413
"Content-Length: 256\r\n" +
414
"Connection: keep-alive, close\r\n" +
423
Request: dummyReq("HEAD"),
425
"Content-Length": {"256"},
427
TransferEncoding: nil,
435
// Both keep-alive and close, on different Connection lines. (Issue 8840)
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" +
449
Request: dummyReq("HEAD"),
451
"Content-Length": {"256"},
453
TransferEncoding: nil,
461
// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
462
// Without a Content-Length.
464
"HTTP/1.0 200 OK\r\n" +
465
"Transfer-Encoding: bogus\r\n" +
475
Request: dummyReq("GET"),
484
// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
485
// With a Content-Length.
487
"HTTP/1.0 200 OK\r\n" +
488
"Transfer-Encoding: bogus\r\n" +
489
"Content-Length: 10\r\n" +
499
Request: dummyReq("GET"),
501
"Content-Length": {"10"},
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",
523
Request: dummyReq("GET"),
525
"Content-Length": {"23"},
526
"Content-Encoding": {"gzip"},
527
"Connection": {"keep-alive"},
528
"Keep-Alive": {"timeout=7200"},
533
"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
536
// Issue 19989: two spaces between HTTP version and status.
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",
543
Status: "401 Unauthorized",
548
Request: dummyReq("GET"),
550
"Content-Type": {"text/html"},
551
"Www-Authenticate": {`Basic realm=""`},
556
"Your Authentication failed.\r\n",
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)
566
t.Errorf("#%d: %v", i, err)
571
diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
572
var bout bytes.Buffer
574
_, err = io.Copy(&bout, rbody)
576
t.Errorf("#%d: %v", i, err)
581
body := bout.String()
583
t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
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)
592
t.Errorf("#%d: %v", i, err)
595
err = resp.Write(ioutil.Discard)
597
t.Errorf("#%d: %v", i, err)
603
var readResponseCloseInMiddleTests = []struct {
604
chunked, compressed bool
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) {
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...)
621
checkErr := func(err error, msg string) {
625
fatalf(msg+": %v", err)
628
buf.WriteString("HTTP/1.1 200 OK\r\n")
630
buf.WriteString("Transfer-Encoding: chunked\r\n")
632
buf.WriteString("Content-Length: 1000000\r\n")
634
var wr io.Writer = &buf
636
wr = internal.NewChunkedWriter(wr)
639
buf.WriteString("Content-Encoding: gzip\r\n")
640
wr = gzip.NewWriter(wr)
642
buf.WriteString("\r\n")
644
chunk := bytes.Repeat([]byte{'x'}, 1000)
645
for i := 0; i < 1000; i++ {
647
// Otherwise this compresses too well.
648
_, err := io.ReadFull(rand.Reader, chunk)
649
checkErr(err, "rand.Reader ReadFull")
654
err := wr.(*gzip.Writer).Close()
655
checkErr(err, "compressor close")
658
buf.WriteString("0\r\n\r\n")
660
buf.WriteString("Next Request Here")
662
bufr := bufio.NewReader(&buf)
663
resp, err := ReadResponse(bufr, dummyReq("GET"))
664
checkErr(err, "ReadResponse")
665
expectedLength := int64(-1)
667
expectedLength = 1000000
669
if resp.ContentLength != expectedLength {
670
fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
672
if resp.Body == nil {
676
gzReader, err := gzip.NewReader(resp.Body)
677
checkErr(err, "gzip.NewReader")
678
resp.Body = &readerAndCloser{gzReader, resp.Body}
681
rbuf := make([]byte, 2500)
682
n, err := io.ReadFull(resp.Body, rbuf)
683
checkErr(err, "2500 byte ReadFull")
685
fatalf("ReadFull only read %d bytes", n)
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))
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))
698
fatalf("remainder = %q, expected %q", g, e)
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())
709
for i := 0; i < hv.NumField(); i++ {
710
name := hv.Type().Field(i).Name
711
if !ast.IsExported(name) {
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)
722
type responseLocationTest struct {
723
location string // Response's Location header or ""
724
requrl string // Response.Request.URL or ""
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},
736
func TestLocationResponse(t *testing.T) {
737
for i, tt := range responseLocationTests {
739
res.Header = make(Header)
740
res.Header.Set("Location", tt.location)
742
res.Request = &Request{}
744
res.Request.URL, err = url.Parse(tt.requrl)
746
t.Fatalf("bad test URL %q: %v", tt.requrl, err)
750
got, err := res.Location()
751
if tt.wantErr != nil {
753
t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
756
if g, e := err.Error(), tt.wantErr.Error(); g != e {
757
t.Errorf("%d. err=%q; want %q", i, g, e)
763
t.Errorf("%d. err=%q", i, err)
766
if g, e := got.String(), tt.want; g != e {
767
t.Errorf("%d. Location=%q; want %q", i, g, e)
772
func TestResponseStatusStutter(t *testing.T) {
774
Status: "123 some status",
781
if strings.Contains(buf.String(), "123 123") {
782
t.Errorf("stutter in status: %s", buf.String())
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" +
792
res, err := ReadResponse(br, &Request{Method: "GET"})
796
if res.ContentLength != 123 {
797
t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
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)
804
if buf.String() != shortBody {
805
t.Errorf("Read body %q; want %q", buf.String(), shortBody)
807
if err != io.ErrUnexpectedEOF {
808
t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
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
819
wantErr interface{} // nil, err value, or string substring
822
status := func(s string, wantErr interface{}) testCase {
824
wantErr = "malformed HTTP status code"
827
name: fmt.Sprintf("status %q", s),
828
in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
833
version := func(s string, wantErr interface{}) testCase {
835
wantErr = "malformed HTTP version"
838
name: fmt.Sprintf("version %q", s),
839
in: s + " 200 OK\r\n\r\n",
844
contentLength := func(status, body string, wantErr interface{}) testCase {
846
name: fmt.Sprintf("status %q %q", status, body),
847
in: fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
852
errMultiCL := "message cannot contain multiple Content-Length headers"
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),
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),
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),
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"},
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 {
908
name = fmt.Sprintf("%d. input %q", i, tt.in)
910
t.Errorf("%s: %v", name, err)
915
// wantErr can be nil, an error value to match exactly, or type string to
917
func matchErr(err error, wantErr interface{}) error {
922
if sub, ok := wantErr.(string); ok {
923
return fmt.Errorf("unexpected success; want error with substring %q", sub)
925
return fmt.Errorf("unexpected success; want error %v", wantErr)
928
return fmt.Errorf("%v; want success", err)
930
if sub, ok := wantErr.(string); ok {
931
if strings.Contains(err.Error(), sub) {
934
return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
939
return fmt.Errorf("%v; want %v", err, wantErr)
942
func TestNeedsSniff(t *testing.T) {
943
// needsSniff returns true with an empty response.
945
if got, want := r.needsSniff(), true; got != want {
946
t.Errorf("needsSniff = %t; want %t", got, want)
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)
955
// A response should only write out single Connection: close header. Tests #19499.
956
func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
957
const connectionCloseHeader = "Connection: close"
959
res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
961
t.Fatalf("ReadResponse failed %v", err)
964
var buf1 bytes.Buffer
965
if err = res.Write(&buf1); err != nil {
966
t.Fatalf("Write failed %v", err)
968
if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
969
t.Fatalf("ReadResponse failed %v", err)
972
var buf2 bytes.Buffer
973
if err = res.Write(&buf2); err != nil {
974
t.Fatalf("Write failed %v", err)
976
if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
977
t.Errorf("Found %d %q header", count, connectionCloseHeader)