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