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