github.com/tidwall/go@v0.0.0-20170415222209-6694a6888b7d/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  	"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 2616)
   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 2616, 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  
   537  // tests successful calls to ReadResponse, and inspects the returned Response.
   538  // For error cases, see TestReadResponseErrors below.
   539  func TestReadResponse(t *testing.T) {
   540  	for i, tt := range respTests {
   541  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   542  		if err != nil {
   543  			t.Errorf("#%d: %v", i, err)
   544  			continue
   545  		}
   546  		rbody := resp.Body
   547  		resp.Body = nil
   548  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
   549  		var bout bytes.Buffer
   550  		if rbody != nil {
   551  			_, err = io.Copy(&bout, rbody)
   552  			if err != nil {
   553  				t.Errorf("#%d: %v", i, err)
   554  				continue
   555  			}
   556  			rbody.Close()
   557  		}
   558  		body := bout.String()
   559  		if body != tt.Body {
   560  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
   561  		}
   562  	}
   563  }
   564  
   565  func TestWriteResponse(t *testing.T) {
   566  	for i, tt := range respTests {
   567  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
   568  		if err != nil {
   569  			t.Errorf("#%d: %v", i, err)
   570  			continue
   571  		}
   572  		err = resp.Write(ioutil.Discard)
   573  		if err != nil {
   574  			t.Errorf("#%d: %v", i, err)
   575  			continue
   576  		}
   577  	}
   578  }
   579  
   580  var readResponseCloseInMiddleTests = []struct {
   581  	chunked, compressed bool
   582  }{
   583  	{false, false},
   584  	{true, false},
   585  	{true, true},
   586  }
   587  
   588  // TestReadResponseCloseInMiddle tests that closing a body after
   589  // reading only part of its contents advances the read to the end of
   590  // the request, right up until the next request.
   591  func TestReadResponseCloseInMiddle(t *testing.T) {
   592  	t.Parallel()
   593  	for _, test := range readResponseCloseInMiddleTests {
   594  		fatalf := func(format string, args ...interface{}) {
   595  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   596  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   597  		}
   598  		checkErr := func(err error, msg string) {
   599  			if err == nil {
   600  				return
   601  			}
   602  			fatalf(msg+": %v", err)
   603  		}
   604  		var buf bytes.Buffer
   605  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   606  		if test.chunked {
   607  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   608  		} else {
   609  			buf.WriteString("Content-Length: 1000000\r\n")
   610  		}
   611  		var wr io.Writer = &buf
   612  		if test.chunked {
   613  			wr = internal.NewChunkedWriter(wr)
   614  		}
   615  		if test.compressed {
   616  			buf.WriteString("Content-Encoding: gzip\r\n")
   617  			wr = gzip.NewWriter(wr)
   618  		}
   619  		buf.WriteString("\r\n")
   620  
   621  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   622  		for i := 0; i < 1000; i++ {
   623  			if test.compressed {
   624  				// Otherwise this compresses too well.
   625  				_, err := io.ReadFull(rand.Reader, chunk)
   626  				checkErr(err, "rand.Reader ReadFull")
   627  			}
   628  			wr.Write(chunk)
   629  		}
   630  		if test.compressed {
   631  			err := wr.(*gzip.Writer).Close()
   632  			checkErr(err, "compressor close")
   633  		}
   634  		if test.chunked {
   635  			buf.WriteString("0\r\n\r\n")
   636  		}
   637  		buf.WriteString("Next Request Here")
   638  
   639  		bufr := bufio.NewReader(&buf)
   640  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   641  		checkErr(err, "ReadResponse")
   642  		expectedLength := int64(-1)
   643  		if !test.chunked {
   644  			expectedLength = 1000000
   645  		}
   646  		if resp.ContentLength != expectedLength {
   647  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   648  		}
   649  		if resp.Body == nil {
   650  			fatalf("nil body")
   651  		}
   652  		if test.compressed {
   653  			gzReader, err := gzip.NewReader(resp.Body)
   654  			checkErr(err, "gzip.NewReader")
   655  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   656  		}
   657  
   658  		rbuf := make([]byte, 2500)
   659  		n, err := io.ReadFull(resp.Body, rbuf)
   660  		checkErr(err, "2500 byte ReadFull")
   661  		if n != 2500 {
   662  			fatalf("ReadFull only read %d bytes", n)
   663  		}
   664  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   665  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   666  		}
   667  		resp.Body.Close()
   668  
   669  		rest, err := ioutil.ReadAll(bufr)
   670  		checkErr(err, "ReadAll on remainder")
   671  		if e, g := "Next Request Here", string(rest); e != g {
   672  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
   673  				return fmt.Sprintf("x(repeated x%d)", len(match))
   674  			})
   675  			fatalf("remainder = %q, expected %q", g, e)
   676  		}
   677  	}
   678  }
   679  
   680  func diff(t *testing.T, prefix string, have, want interface{}) {
   681  	hv := reflect.ValueOf(have).Elem()
   682  	wv := reflect.ValueOf(want).Elem()
   683  	if hv.Type() != wv.Type() {
   684  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   685  	}
   686  	for i := 0; i < hv.NumField(); i++ {
   687  		name := hv.Type().Field(i).Name
   688  		if !ast.IsExported(name) {
   689  			continue
   690  		}
   691  		hf := hv.Field(i).Interface()
   692  		wf := wv.Field(i).Interface()
   693  		if !reflect.DeepEqual(hf, wf) {
   694  			t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
   695  		}
   696  	}
   697  }
   698  
   699  type responseLocationTest struct {
   700  	location string // Response's Location header or ""
   701  	requrl   string // Response.Request.URL or ""
   702  	want     string
   703  	wantErr  error
   704  }
   705  
   706  var responseLocationTests = []responseLocationTest{
   707  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   708  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   709  	{"", "http://bar.com/baz", "", ErrNoLocation},
   710  	{"/bar", "", "/bar", nil},
   711  }
   712  
   713  func TestLocationResponse(t *testing.T) {
   714  	for i, tt := range responseLocationTests {
   715  		res := new(Response)
   716  		res.Header = make(Header)
   717  		res.Header.Set("Location", tt.location)
   718  		if tt.requrl != "" {
   719  			res.Request = &Request{}
   720  			var err error
   721  			res.Request.URL, err = url.Parse(tt.requrl)
   722  			if err != nil {
   723  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   724  			}
   725  		}
   726  
   727  		got, err := res.Location()
   728  		if tt.wantErr != nil {
   729  			if err == nil {
   730  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   731  				continue
   732  			}
   733  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   734  				t.Errorf("%d. err=%q; want %q", i, g, e)
   735  				continue
   736  			}
   737  			continue
   738  		}
   739  		if err != nil {
   740  			t.Errorf("%d. err=%q", i, err)
   741  			continue
   742  		}
   743  		if g, e := got.String(), tt.want; g != e {
   744  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   745  		}
   746  	}
   747  }
   748  
   749  func TestResponseStatusStutter(t *testing.T) {
   750  	r := &Response{
   751  		Status:     "123 some status",
   752  		StatusCode: 123,
   753  		ProtoMajor: 1,
   754  		ProtoMinor: 3,
   755  	}
   756  	var buf bytes.Buffer
   757  	r.Write(&buf)
   758  	if strings.Contains(buf.String(), "123 123") {
   759  		t.Errorf("stutter in status: %s", buf.String())
   760  	}
   761  }
   762  
   763  func TestResponseContentLengthShortBody(t *testing.T) {
   764  	const shortBody = "Short body, not 123 bytes."
   765  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   766  		"Content-Length: 123\r\n" +
   767  		"\r\n" +
   768  		shortBody))
   769  	res, err := ReadResponse(br, &Request{Method: "GET"})
   770  	if err != nil {
   771  		t.Fatal(err)
   772  	}
   773  	if res.ContentLength != 123 {
   774  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   775  	}
   776  	var buf bytes.Buffer
   777  	n, err := io.Copy(&buf, res.Body)
   778  	if n != int64(len(shortBody)) {
   779  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   780  	}
   781  	if buf.String() != shortBody {
   782  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   783  	}
   784  	if err != io.ErrUnexpectedEOF {
   785  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   786  	}
   787  }
   788  
   789  // Test various ReadResponse error cases. (also tests success cases, but mostly
   790  // it's about errors).  This does not test anything involving the bodies. Only
   791  // the return value from ReadResponse itself.
   792  func TestReadResponseErrors(t *testing.T) {
   793  	type testCase struct {
   794  		name    string // optional, defaults to in
   795  		in      string
   796  		header  Header
   797  		wantErr interface{} // nil, err value, or string substring
   798  	}
   799  
   800  	status := func(s string, wantErr interface{}) testCase {
   801  		if wantErr == true {
   802  			wantErr = "malformed HTTP status code"
   803  		}
   804  		return testCase{
   805  			name:    fmt.Sprintf("status %q", s),
   806  			in:      "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
   807  			wantErr: wantErr,
   808  		}
   809  	}
   810  
   811  	version := func(s string, wantErr interface{}) testCase {
   812  		if wantErr == true {
   813  			wantErr = "malformed HTTP version"
   814  		}
   815  		return testCase{
   816  			name:    fmt.Sprintf("version %q", s),
   817  			in:      s + " 200 OK\r\n\r\n",
   818  			wantErr: wantErr,
   819  		}
   820  	}
   821  
   822  	contentLength := func(status, body string, wantErr interface{}, header Header) testCase {
   823  		return testCase{
   824  			name:    fmt.Sprintf("status %q %q", status, body),
   825  			in:      fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
   826  			wantErr: wantErr,
   827  			header:  header,
   828  		}
   829  	}
   830  
   831  	errMultiCL := "message cannot contain multiple Content-Length headers"
   832  
   833  	tests := []testCase{
   834  		{"", "", nil, io.ErrUnexpectedEOF},
   835  		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", nil, io.ErrUnexpectedEOF},
   836  		{"", "HTTP/1.1", nil, "malformed HTTP response"},
   837  		{"", "HTTP/2.0", nil, "malformed HTTP response"},
   838  		status("20X Unknown", true),
   839  		status("abcd Unknown", true),
   840  		status("二百/两百 OK", true),
   841  		status(" Unknown", true),
   842  		status("c8 OK", true),
   843  		status("0x12d Moved Permanently", true),
   844  		status("200 OK", nil),
   845  		status("000 OK", nil),
   846  		status("001 OK", nil),
   847  		status("404 NOTFOUND", nil),
   848  		status("20 OK", true),
   849  		status("00 OK", true),
   850  		status("-10 OK", true),
   851  		status("1000 OK", true),
   852  		status("999 Done", nil),
   853  		status("-1 OK", true),
   854  		status("-200 OK", true),
   855  		version("HTTP/1.2", nil),
   856  		version("HTTP/2.0", nil),
   857  		version("HTTP/1.100000000002", true),
   858  		version("HTTP/1.-1", true),
   859  		version("HTTP/A.B", true),
   860  		version("HTTP/1", true),
   861  		version("http/1.1", true),
   862  
   863  		contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL, nil),
   864  		contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"7"}}),
   865  		contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL, nil),
   866  		contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"0"}}),
   867  		contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil, nil),
   868  		contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL, nil),
   869  
   870  		// multiple content-length headers for 204 and 304 should still be checked
   871  		contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL, nil),
   872  		contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil, nil),
   873  		contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL, nil),
   874  		contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil, nil),
   875  	}
   876  
   877  	for i, tt := range tests {
   878  		br := bufio.NewReader(strings.NewReader(tt.in))
   879  		_, rerr := ReadResponse(br, nil)
   880  		if err := matchErr(rerr, tt.wantErr); err != nil {
   881  			name := tt.name
   882  			if name == "" {
   883  				name = fmt.Sprintf("%d. input %q", i, tt.in)
   884  			}
   885  			t.Errorf("%s: %v", name, err)
   886  		}
   887  	}
   888  }
   889  
   890  // wantErr can be nil, an error value to match exactly, or type string to
   891  // match a substring.
   892  func matchErr(err error, wantErr interface{}) error {
   893  	if err == nil {
   894  		if wantErr == nil {
   895  			return nil
   896  		}
   897  		if sub, ok := wantErr.(string); ok {
   898  			return fmt.Errorf("unexpected success; want error with substring %q", sub)
   899  		}
   900  		return fmt.Errorf("unexpected success; want error %v", wantErr)
   901  	}
   902  	if wantErr == nil {
   903  		return fmt.Errorf("%v; want success", err)
   904  	}
   905  	if sub, ok := wantErr.(string); ok {
   906  		if strings.Contains(err.Error(), sub) {
   907  			return nil
   908  		}
   909  		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
   910  	}
   911  	if err == wantErr {
   912  		return nil
   913  	}
   914  	return fmt.Errorf("%v; want %v", err, wantErr)
   915  }
   916  
   917  func TestNeedsSniff(t *testing.T) {
   918  	// needsSniff returns true with an empty response.
   919  	r := &response{}
   920  	if got, want := r.needsSniff(), true; got != want {
   921  		t.Errorf("needsSniff = %t; want %t", got, want)
   922  	}
   923  	// needsSniff returns false when Content-Type = nil.
   924  	r.handlerHeader = Header{"Content-Type": nil}
   925  	if got, want := r.needsSniff(), false; got != want {
   926  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
   927  	}
   928  }