github.com/aloncn/graphics-go@v0.0.1/src/net/http/response_test.go (about)

     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  	"io"
    14  	"io/ioutil"
    15  	"net/http/internal"
    16  	"net/url"
    17  	"reflect"
    18  	"regexp"
    19  	"strings"
    20  	"testing"
    21  )
    22  
    23  type respTest struct {
    24  	Raw  string
    25  	Resp Response
    26  	Body string
    27  }
    28  
    29  func dummyReq(method string) *Request {
    30  	return &Request{Method: method}
    31  }
    32  
    33  func dummyReq11(method string) *Request {
    34  	return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
    35  }
    36  
    37  var respTests = []respTest{
    38  	// Unchunked response without Content-Length.
    39  	{
    40  		"HTTP/1.0 200 OK\r\n" +
    41  			"Connection: close\r\n" +
    42  			"\r\n" +
    43  			"Body here\n",
    44  
    45  		Response{
    46  			Status:     "200 OK",
    47  			StatusCode: 200,
    48  			Proto:      "HTTP/1.0",
    49  			ProtoMajor: 1,
    50  			ProtoMinor: 0,
    51  			Request:    dummyReq("GET"),
    52  			Header: Header{
    53  				"Connection": {"close"}, // TODO(rsc): Delete?
    54  			},
    55  			Close:         true,
    56  			ContentLength: -1,
    57  		},
    58  
    59  		"Body here\n",
    60  	},
    61  
    62  	// Unchunked HTTP/1.1 response without Content-Length or
    63  	// Connection headers.
    64  	{
    65  		"HTTP/1.1 200 OK\r\n" +
    66  			"\r\n" +
    67  			"Body here\n",
    68  
    69  		Response{
    70  			Status:        "200 OK",
    71  			StatusCode:    200,
    72  			Proto:         "HTTP/1.1",
    73  			ProtoMajor:    1,
    74  			ProtoMinor:    1,
    75  			Header:        Header{},
    76  			Request:       dummyReq("GET"),
    77  			Close:         true,
    78  			ContentLength: -1,
    79  		},
    80  
    81  		"Body here\n",
    82  	},
    83  
    84  	// Unchunked HTTP/1.1 204 response without Content-Length.
    85  	{
    86  		"HTTP/1.1 204 No Content\r\n" +
    87  			"\r\n" +
    88  			"Body should not be read!\n",
    89  
    90  		Response{
    91  			Status:        "204 No Content",
    92  			StatusCode:    204,
    93  			Proto:         "HTTP/1.1",
    94  			ProtoMajor:    1,
    95  			ProtoMinor:    1,
    96  			Header:        Header{},
    97  			Request:       dummyReq("GET"),
    98  			Close:         false,
    99  			ContentLength: 0,
   100  		},
   101  
   102  		"",
   103  	},
   104  
   105  	// Unchunked response with Content-Length.
   106  	{
   107  		"HTTP/1.0 200 OK\r\n" +
   108  			"Content-Length: 10\r\n" +
   109  			"Connection: close\r\n" +
   110  			"\r\n" +
   111  			"Body here\n",
   112  
   113  		Response{
   114  			Status:     "200 OK",
   115  			StatusCode: 200,
   116  			Proto:      "HTTP/1.0",
   117  			ProtoMajor: 1,
   118  			ProtoMinor: 0,
   119  			Request:    dummyReq("GET"),
   120  			Header: Header{
   121  				"Connection":     {"close"},
   122  				"Content-Length": {"10"},
   123  			},
   124  			Close:         true,
   125  			ContentLength: 10,
   126  		},
   127  
   128  		"Body here\n",
   129  	},
   130  
   131  	// Chunked response without Content-Length.
   132  	{
   133  		"HTTP/1.1 200 OK\r\n" +
   134  			"Transfer-Encoding: chunked\r\n" +
   135  			"\r\n" +
   136  			"0a\r\n" +
   137  			"Body here\n\r\n" +
   138  			"09\r\n" +
   139  			"continued\r\n" +
   140  			"0\r\n" +
   141  			"\r\n",
   142  
   143  		Response{
   144  			Status:           "200 OK",
   145  			StatusCode:       200,
   146  			Proto:            "HTTP/1.1",
   147  			ProtoMajor:       1,
   148  			ProtoMinor:       1,
   149  			Request:          dummyReq("GET"),
   150  			Header:           Header{},
   151  			Close:            false,
   152  			ContentLength:    -1,
   153  			TransferEncoding: []string{"chunked"},
   154  		},
   155  
   156  		"Body here\ncontinued",
   157  	},
   158  
   159  	// Chunked response with Content-Length.
   160  	{
   161  		"HTTP/1.1 200 OK\r\n" +
   162  			"Transfer-Encoding: chunked\r\n" +
   163  			"Content-Length: 10\r\n" +
   164  			"\r\n" +
   165  			"0a\r\n" +
   166  			"Body here\n\r\n" +
   167  			"0\r\n" +
   168  			"\r\n",
   169  
   170  		Response{
   171  			Status:           "200 OK",
   172  			StatusCode:       200,
   173  			Proto:            "HTTP/1.1",
   174  			ProtoMajor:       1,
   175  			ProtoMinor:       1,
   176  			Request:          dummyReq("GET"),
   177  			Header:           Header{},
   178  			Close:            false,
   179  			ContentLength:    -1,
   180  			TransferEncoding: []string{"chunked"},
   181  		},
   182  
   183  		"Body here\n",
   184  	},
   185  
   186  	// Chunked response in response to a HEAD request
   187  	{
   188  		"HTTP/1.1 200 OK\r\n" +
   189  			"Transfer-Encoding: chunked\r\n" +
   190  			"\r\n",
   191  
   192  		Response{
   193  			Status:           "200 OK",
   194  			StatusCode:       200,
   195  			Proto:            "HTTP/1.1",
   196  			ProtoMajor:       1,
   197  			ProtoMinor:       1,
   198  			Request:          dummyReq("HEAD"),
   199  			Header:           Header{},
   200  			TransferEncoding: []string{"chunked"},
   201  			Close:            false,
   202  			ContentLength:    -1,
   203  		},
   204  
   205  		"",
   206  	},
   207  
   208  	// Content-Length in response to a HEAD request
   209  	{
   210  		"HTTP/1.0 200 OK\r\n" +
   211  			"Content-Length: 256\r\n" +
   212  			"\r\n",
   213  
   214  		Response{
   215  			Status:           "200 OK",
   216  			StatusCode:       200,
   217  			Proto:            "HTTP/1.0",
   218  			ProtoMajor:       1,
   219  			ProtoMinor:       0,
   220  			Request:          dummyReq("HEAD"),
   221  			Header:           Header{"Content-Length": {"256"}},
   222  			TransferEncoding: nil,
   223  			Close:            true,
   224  			ContentLength:    256,
   225  		},
   226  
   227  		"",
   228  	},
   229  
   230  	// Content-Length in response to a HEAD request with HTTP/1.1
   231  	{
   232  		"HTTP/1.1 200 OK\r\n" +
   233  			"Content-Length: 256\r\n" +
   234  			"\r\n",
   235  
   236  		Response{
   237  			Status:           "200 OK",
   238  			StatusCode:       200,
   239  			Proto:            "HTTP/1.1",
   240  			ProtoMajor:       1,
   241  			ProtoMinor:       1,
   242  			Request:          dummyReq("HEAD"),
   243  			Header:           Header{"Content-Length": {"256"}},
   244  			TransferEncoding: nil,
   245  			Close:            false,
   246  			ContentLength:    256,
   247  		},
   248  
   249  		"",
   250  	},
   251  
   252  	// No Content-Length or Chunked in response to a HEAD request
   253  	{
   254  		"HTTP/1.0 200 OK\r\n" +
   255  			"\r\n",
   256  
   257  		Response{
   258  			Status:           "200 OK",
   259  			StatusCode:       200,
   260  			Proto:            "HTTP/1.0",
   261  			ProtoMajor:       1,
   262  			ProtoMinor:       0,
   263  			Request:          dummyReq("HEAD"),
   264  			Header:           Header{},
   265  			TransferEncoding: nil,
   266  			Close:            true,
   267  			ContentLength:    -1,
   268  		},
   269  
   270  		"",
   271  	},
   272  
   273  	// explicit Content-Length of 0.
   274  	{
   275  		"HTTP/1.1 200 OK\r\n" +
   276  			"Content-Length: 0\r\n" +
   277  			"\r\n",
   278  
   279  		Response{
   280  			Status:     "200 OK",
   281  			StatusCode: 200,
   282  			Proto:      "HTTP/1.1",
   283  			ProtoMajor: 1,
   284  			ProtoMinor: 1,
   285  			Request:    dummyReq("GET"),
   286  			Header: Header{
   287  				"Content-Length": {"0"},
   288  			},
   289  			Close:         false,
   290  			ContentLength: 0,
   291  		},
   292  
   293  		"",
   294  	},
   295  
   296  	// Status line without a Reason-Phrase, but trailing space.
   297  	// (permitted by RFC 2616)
   298  	{
   299  		"HTTP/1.0 303 \r\n\r\n",
   300  		Response{
   301  			Status:        "303 ",
   302  			StatusCode:    303,
   303  			Proto:         "HTTP/1.0",
   304  			ProtoMajor:    1,
   305  			ProtoMinor:    0,
   306  			Request:       dummyReq("GET"),
   307  			Header:        Header{},
   308  			Close:         true,
   309  			ContentLength: -1,
   310  		},
   311  
   312  		"",
   313  	},
   314  
   315  	// Status line without a Reason-Phrase, and no trailing space.
   316  	// (not permitted by RFC 2616, but we'll accept it anyway)
   317  	{
   318  		"HTTP/1.0 303\r\n\r\n",
   319  		Response{
   320  			Status:        "303 ",
   321  			StatusCode:    303,
   322  			Proto:         "HTTP/1.0",
   323  			ProtoMajor:    1,
   324  			ProtoMinor:    0,
   325  			Request:       dummyReq("GET"),
   326  			Header:        Header{},
   327  			Close:         true,
   328  			ContentLength: -1,
   329  		},
   330  
   331  		"",
   332  	},
   333  
   334  	// golang.org/issue/4767: don't special-case multipart/byteranges responses
   335  	{
   336  		`HTTP/1.1 206 Partial Content
   337  Connection: close
   338  Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
   339  
   340  some body`,
   341  		Response{
   342  			Status:     "206 Partial Content",
   343  			StatusCode: 206,
   344  			Proto:      "HTTP/1.1",
   345  			ProtoMajor: 1,
   346  			ProtoMinor: 1,
   347  			Request:    dummyReq("GET"),
   348  			Header: Header{
   349  				"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
   350  			},
   351  			Close:         true,
   352  			ContentLength: -1,
   353  		},
   354  
   355  		"some body",
   356  	},
   357  
   358  	// Unchunked response without Content-Length, Request is nil
   359  	{
   360  		"HTTP/1.0 200 OK\r\n" +
   361  			"Connection: close\r\n" +
   362  			"\r\n" +
   363  			"Body here\n",
   364  
   365  		Response{
   366  			Status:     "200 OK",
   367  			StatusCode: 200,
   368  			Proto:      "HTTP/1.0",
   369  			ProtoMajor: 1,
   370  			ProtoMinor: 0,
   371  			Header: Header{
   372  				"Connection": {"close"}, // TODO(rsc): Delete?
   373  			},
   374  			Close:         true,
   375  			ContentLength: -1,
   376  		},
   377  
   378  		"Body here\n",
   379  	},
   380  
   381  	// 206 Partial Content. golang.org/issue/8923
   382  	{
   383  		"HTTP/1.1 206 Partial Content\r\n" +
   384  			"Content-Type: text/plain; charset=utf-8\r\n" +
   385  			"Accept-Ranges: bytes\r\n" +
   386  			"Content-Range: bytes 0-5/1862\r\n" +
   387  			"Content-Length: 6\r\n\r\n" +
   388  			"foobar",
   389  
   390  		Response{
   391  			Status:     "206 Partial Content",
   392  			StatusCode: 206,
   393  			Proto:      "HTTP/1.1",
   394  			ProtoMajor: 1,
   395  			ProtoMinor: 1,
   396  			Request:    dummyReq("GET"),
   397  			Header: Header{
   398  				"Accept-Ranges":  []string{"bytes"},
   399  				"Content-Length": []string{"6"},
   400  				"Content-Type":   []string{"text/plain; charset=utf-8"},
   401  				"Content-Range":  []string{"bytes 0-5/1862"},
   402  			},
   403  			ContentLength: 6,
   404  		},
   405  
   406  		"foobar",
   407  	},
   408  
   409  	// Both keep-alive and close, on the same Connection line. (Issue 8840)
   410  	{
   411  		"HTTP/1.1 200 OK\r\n" +
   412  			"Content-Length: 256\r\n" +
   413  			"Connection: keep-alive, close\r\n" +
   414  			"\r\n",
   415  
   416  		Response{
   417  			Status:     "200 OK",
   418  			StatusCode: 200,
   419  			Proto:      "HTTP/1.1",
   420  			ProtoMajor: 1,
   421  			ProtoMinor: 1,
   422  			Request:    dummyReq("HEAD"),
   423  			Header: Header{
   424  				"Content-Length": {"256"},
   425  			},
   426  			TransferEncoding: nil,
   427  			Close:            true,
   428  			ContentLength:    256,
   429  		},
   430  
   431  		"",
   432  	},
   433  
   434  	// Both keep-alive and close, on different Connection lines. (Issue 8840)
   435  	{
   436  		"HTTP/1.1 200 OK\r\n" +
   437  			"Content-Length: 256\r\n" +
   438  			"Connection: keep-alive\r\n" +
   439  			"Connection: close\r\n" +
   440  			"\r\n",
   441  
   442  		Response{
   443  			Status:     "200 OK",
   444  			StatusCode: 200,
   445  			Proto:      "HTTP/1.1",
   446  			ProtoMajor: 1,
   447  			ProtoMinor: 1,
   448  			Request:    dummyReq("HEAD"),
   449  			Header: Header{
   450  				"Content-Length": {"256"},
   451  			},
   452  			TransferEncoding: nil,
   453  			Close:            true,
   454  			ContentLength:    256,
   455  		},
   456  
   457  		"",
   458  	},
   459  
   460  	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
   461  	// Without a Content-Length.
   462  	{
   463  		"HTTP/1.0 200 OK\r\n" +
   464  			"Transfer-Encoding: bogus\r\n" +
   465  			"\r\n" +
   466  			"Body here\n",
   467  
   468  		Response{
   469  			Status:        "200 OK",
   470  			StatusCode:    200,
   471  			Proto:         "HTTP/1.0",
   472  			ProtoMajor:    1,
   473  			ProtoMinor:    0,
   474  			Request:       dummyReq("GET"),
   475  			Header:        Header{},
   476  			Close:         true,
   477  			ContentLength: -1,
   478  		},
   479  
   480  		"Body here\n",
   481  	},
   482  
   483  	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
   484  	// With a Content-Length.
   485  	{
   486  		"HTTP/1.0 200 OK\r\n" +
   487  			"Transfer-Encoding: bogus\r\n" +
   488  			"Content-Length: 10\r\n" +
   489  			"\r\n" +
   490  			"Body here\n",
   491  
   492  		Response{
   493  			Status:     "200 OK",
   494  			StatusCode: 200,
   495  			Proto:      "HTTP/1.0",
   496  			ProtoMajor: 1,
   497  			ProtoMinor: 0,
   498  			Request:    dummyReq("GET"),
   499  			Header: Header{
   500  				"Content-Length": {"10"},
   501  			},
   502  			Close:         true,
   503  			ContentLength: 10,
   504  		},
   505  
   506  		"Body here\n",
   507  	},
   508  }
   509  
   510  // tests successful calls to ReadResponse, and inspects the returned Response.
   511  // For error cases, see TestReadResponseErrors below.
   512  func TestReadResponse(t *testing.T) {
   513  	for i, tt := range respTests {
   514  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   515  		if err != nil {
   516  			t.Errorf("#%d: %v", i, err)
   517  			continue
   518  		}
   519  		rbody := resp.Body
   520  		resp.Body = nil
   521  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
   522  		var bout bytes.Buffer
   523  		if rbody != nil {
   524  			_, err = io.Copy(&bout, rbody)
   525  			if err != nil {
   526  				t.Errorf("#%d: %v", i, err)
   527  				continue
   528  			}
   529  			rbody.Close()
   530  		}
   531  		body := bout.String()
   532  		if body != tt.Body {
   533  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
   534  		}
   535  	}
   536  }
   537  
   538  func TestWriteResponse(t *testing.T) {
   539  	for i, tt := range respTests {
   540  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   541  		if err != nil {
   542  			t.Errorf("#%d: %v", i, err)
   543  			continue
   544  		}
   545  		err = resp.Write(ioutil.Discard)
   546  		if err != nil {
   547  			t.Errorf("#%d: %v", i, err)
   548  			continue
   549  		}
   550  	}
   551  }
   552  
   553  var readResponseCloseInMiddleTests = []struct {
   554  	chunked, compressed bool
   555  }{
   556  	{false, false},
   557  	{true, false},
   558  	{true, true},
   559  }
   560  
   561  // TestReadResponseCloseInMiddle tests that closing a body after
   562  // reading only part of its contents advances the read to the end of
   563  // the request, right up until the next request.
   564  func TestReadResponseCloseInMiddle(t *testing.T) {
   565  	for _, test := range readResponseCloseInMiddleTests {
   566  		fatalf := func(format string, args ...interface{}) {
   567  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   568  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   569  		}
   570  		checkErr := func(err error, msg string) {
   571  			if err == nil {
   572  				return
   573  			}
   574  			fatalf(msg+": %v", err)
   575  		}
   576  		var buf bytes.Buffer
   577  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   578  		if test.chunked {
   579  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   580  		} else {
   581  			buf.WriteString("Content-Length: 1000000\r\n")
   582  		}
   583  		var wr io.Writer = &buf
   584  		if test.chunked {
   585  			wr = internal.NewChunkedWriter(wr)
   586  		}
   587  		if test.compressed {
   588  			buf.WriteString("Content-Encoding: gzip\r\n")
   589  			wr = gzip.NewWriter(wr)
   590  		}
   591  		buf.WriteString("\r\n")
   592  
   593  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   594  		for i := 0; i < 1000; i++ {
   595  			if test.compressed {
   596  				// Otherwise this compresses too well.
   597  				_, err := io.ReadFull(rand.Reader, chunk)
   598  				checkErr(err, "rand.Reader ReadFull")
   599  			}
   600  			wr.Write(chunk)
   601  		}
   602  		if test.compressed {
   603  			err := wr.(*gzip.Writer).Close()
   604  			checkErr(err, "compressor close")
   605  		}
   606  		if test.chunked {
   607  			buf.WriteString("0\r\n\r\n")
   608  		}
   609  		buf.WriteString("Next Request Here")
   610  
   611  		bufr := bufio.NewReader(&buf)
   612  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   613  		checkErr(err, "ReadResponse")
   614  		expectedLength := int64(-1)
   615  		if !test.chunked {
   616  			expectedLength = 1000000
   617  		}
   618  		if resp.ContentLength != expectedLength {
   619  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   620  		}
   621  		if resp.Body == nil {
   622  			fatalf("nil body")
   623  		}
   624  		if test.compressed {
   625  			gzReader, err := gzip.NewReader(resp.Body)
   626  			checkErr(err, "gzip.NewReader")
   627  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   628  		}
   629  
   630  		rbuf := make([]byte, 2500)
   631  		n, err := io.ReadFull(resp.Body, rbuf)
   632  		checkErr(err, "2500 byte ReadFull")
   633  		if n != 2500 {
   634  			fatalf("ReadFull only read %d bytes", n)
   635  		}
   636  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   637  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   638  		}
   639  		resp.Body.Close()
   640  
   641  		rest, err := ioutil.ReadAll(bufr)
   642  		checkErr(err, "ReadAll on remainder")
   643  		if e, g := "Next Request Here", string(rest); e != g {
   644  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
   645  				return fmt.Sprintf("x(repeated x%d)", len(match))
   646  			})
   647  			fatalf("remainder = %q, expected %q", g, e)
   648  		}
   649  	}
   650  }
   651  
   652  func diff(t *testing.T, prefix string, have, want interface{}) {
   653  	hv := reflect.ValueOf(have).Elem()
   654  	wv := reflect.ValueOf(want).Elem()
   655  	if hv.Type() != wv.Type() {
   656  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   657  	}
   658  	for i := 0; i < hv.NumField(); i++ {
   659  		hf := hv.Field(i).Interface()
   660  		wf := wv.Field(i).Interface()
   661  		if !reflect.DeepEqual(hf, wf) {
   662  			t.Errorf("%s: %s = %v want %v", prefix, hv.Type().Field(i).Name, hf, wf)
   663  		}
   664  	}
   665  }
   666  
   667  type responseLocationTest struct {
   668  	location string // Response's Location header or ""
   669  	requrl   string // Response.Request.URL or ""
   670  	want     string
   671  	wantErr  error
   672  }
   673  
   674  var responseLocationTests = []responseLocationTest{
   675  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   676  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   677  	{"", "http://bar.com/baz", "", ErrNoLocation},
   678  	{"/bar", "", "/bar", nil},
   679  }
   680  
   681  func TestLocationResponse(t *testing.T) {
   682  	for i, tt := range responseLocationTests {
   683  		res := new(Response)
   684  		res.Header = make(Header)
   685  		res.Header.Set("Location", tt.location)
   686  		if tt.requrl != "" {
   687  			res.Request = &Request{}
   688  			var err error
   689  			res.Request.URL, err = url.Parse(tt.requrl)
   690  			if err != nil {
   691  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   692  			}
   693  		}
   694  
   695  		got, err := res.Location()
   696  		if tt.wantErr != nil {
   697  			if err == nil {
   698  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   699  				continue
   700  			}
   701  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   702  				t.Errorf("%d. err=%q; want %q", i, g, e)
   703  				continue
   704  			}
   705  			continue
   706  		}
   707  		if err != nil {
   708  			t.Errorf("%d. err=%q", i, err)
   709  			continue
   710  		}
   711  		if g, e := got.String(), tt.want; g != e {
   712  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   713  		}
   714  	}
   715  }
   716  
   717  func TestResponseStatusStutter(t *testing.T) {
   718  	r := &Response{
   719  		Status:     "123 some status",
   720  		StatusCode: 123,
   721  		ProtoMajor: 1,
   722  		ProtoMinor: 3,
   723  	}
   724  	var buf bytes.Buffer
   725  	r.Write(&buf)
   726  	if strings.Contains(buf.String(), "123 123") {
   727  		t.Errorf("stutter in status: %s", buf.String())
   728  	}
   729  }
   730  
   731  func TestResponseContentLengthShortBody(t *testing.T) {
   732  	const shortBody = "Short body, not 123 bytes."
   733  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   734  		"Content-Length: 123\r\n" +
   735  		"\r\n" +
   736  		shortBody))
   737  	res, err := ReadResponse(br, &Request{Method: "GET"})
   738  	if err != nil {
   739  		t.Fatal(err)
   740  	}
   741  	if res.ContentLength != 123 {
   742  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   743  	}
   744  	var buf bytes.Buffer
   745  	n, err := io.Copy(&buf, res.Body)
   746  	if n != int64(len(shortBody)) {
   747  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   748  	}
   749  	if buf.String() != shortBody {
   750  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   751  	}
   752  	if err != io.ErrUnexpectedEOF {
   753  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   754  	}
   755  }
   756  
   757  // Test various ReadResponse error cases. (also tests success cases, but mostly
   758  // it's about errors).  This does not test anything involving the bodies. Only
   759  // the return value from ReadResponse itself.
   760  func TestReadResponseErrors(t *testing.T) {
   761  	type testCase struct {
   762  		name    string // optional, defaults to in
   763  		in      string
   764  		wantErr interface{} // nil, err value, or string substring
   765  	}
   766  
   767  	status := func(s string, wantErr interface{}) testCase {
   768  		if wantErr == true {
   769  			wantErr = "malformed HTTP status code"
   770  		}
   771  		return testCase{
   772  			name:    fmt.Sprintf("status %q", s),
   773  			in:      "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
   774  			wantErr: wantErr,
   775  		}
   776  	}
   777  
   778  	version := func(s string, wantErr interface{}) testCase {
   779  		if wantErr == true {
   780  			wantErr = "malformed HTTP version"
   781  		}
   782  		return testCase{
   783  			name:    fmt.Sprintf("version %q", s),
   784  			in:      s + " 200 OK\r\n\r\n",
   785  			wantErr: wantErr,
   786  		}
   787  	}
   788  
   789  	tests := []testCase{
   790  		{"", "", io.ErrUnexpectedEOF},
   791  		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF},
   792  		{"", "HTTP/1.1", "malformed HTTP response"},
   793  		{"", "HTTP/2.0", "malformed HTTP response"},
   794  		status("20X Unknown", true),
   795  		status("abcd Unknown", true),
   796  		status("二百/两百 OK", true),
   797  		status(" Unknown", true),
   798  		status("c8 OK", true),
   799  		status("0x12d Moved Permanently", true),
   800  		status("200 OK", nil),
   801  		status("000 OK", nil),
   802  		status("001 OK", nil),
   803  		status("404 NOTFOUND", nil),
   804  		status("20 OK", true),
   805  		status("00 OK", true),
   806  		status("-10 OK", true),
   807  		status("1000 OK", true),
   808  		status("999 Done", nil),
   809  		status("-1 OK", true),
   810  		status("-200 OK", true),
   811  		version("HTTP/1.2", nil),
   812  		version("HTTP/2.0", nil),
   813  		version("HTTP/1.100000000002", true),
   814  		version("HTTP/1.-1", true),
   815  		version("HTTP/A.B", true),
   816  		version("HTTP/1", true),
   817  		version("http/1.1", true),
   818  	}
   819  	for i, tt := range tests {
   820  		br := bufio.NewReader(strings.NewReader(tt.in))
   821  		_, rerr := ReadResponse(br, nil)
   822  		if err := matchErr(rerr, tt.wantErr); err != nil {
   823  			name := tt.name
   824  			if name == "" {
   825  				name = fmt.Sprintf("%d. input %q", i, tt.in)
   826  			}
   827  			t.Errorf("%s: %v", name, err)
   828  		}
   829  	}
   830  }
   831  
   832  // wantErr can be nil, an error value to match exactly, or type string to
   833  // match a substring.
   834  func matchErr(err error, wantErr interface{}) error {
   835  	if err == nil {
   836  		if wantErr == nil {
   837  			return nil
   838  		}
   839  		if sub, ok := wantErr.(string); ok {
   840  			return fmt.Errorf("unexpected success; want error with substring %q", sub)
   841  		}
   842  		return fmt.Errorf("unexpected success; want error %v", wantErr)
   843  	}
   844  	if wantErr == nil {
   845  		return fmt.Errorf("%v; want success", err)
   846  	}
   847  	if sub, ok := wantErr.(string); ok {
   848  		if strings.Contains(err.Error(), sub) {
   849  			return nil
   850  		}
   851  		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
   852  	}
   853  	if err == wantErr {
   854  		return nil
   855  	}
   856  	return fmt.Errorf("%v; want %v", err, wantErr)
   857  }
   858  
   859  func TestNeedsSniff(t *testing.T) {
   860  	// needsSniff returns true with an empty response.
   861  	r := &response{}
   862  	if got, want := r.needsSniff(), true; got != want {
   863  		t.Errorf("needsSniff = %t; want %t", got, want)
   864  	}
   865  	// needsSniff returns false when Content-Type = nil.
   866  	r.handlerHeader = Header{"Content-Type": nil}
   867  	if got, want := r.needsSniff(), false; got != want {
   868  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
   869  	}
   870  }