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