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