...
Run Format

Source file src/net/http/response_test.go

Documentation: net/http

     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  	// 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(ioutil.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  // TestReadResponseCloseInMiddle tests that closing a body after
   640  // reading only part of its contents advances the read to the end of
   641  // the request, right up until the next request.
   642  func TestReadResponseCloseInMiddle(t *testing.T) {
   643  	t.Parallel()
   644  	for _, test := range readResponseCloseInMiddleTests {
   645  		fatalf := func(format string, args ...interface{}) {
   646  			args = append([]interface{}{test.chunked, test.compressed}, args...)
   647  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
   648  		}
   649  		checkErr := func(err error, msg string) {
   650  			if err == nil {
   651  				return
   652  			}
   653  			fatalf(msg+": %v", err)
   654  		}
   655  		var buf bytes.Buffer
   656  		buf.WriteString("HTTP/1.1 200 OK\r\n")
   657  		if test.chunked {
   658  			buf.WriteString("Transfer-Encoding: chunked\r\n")
   659  		} else {
   660  			buf.WriteString("Content-Length: 1000000\r\n")
   661  		}
   662  		var wr io.Writer = &buf
   663  		if test.chunked {
   664  			wr = internal.NewChunkedWriter(wr)
   665  		}
   666  		if test.compressed {
   667  			buf.WriteString("Content-Encoding: gzip\r\n")
   668  			wr = gzip.NewWriter(wr)
   669  		}
   670  		buf.WriteString("\r\n")
   671  
   672  		chunk := bytes.Repeat([]byte{'x'}, 1000)
   673  		for i := 0; i < 1000; i++ {
   674  			if test.compressed {
   675  				// Otherwise this compresses too well.
   676  				_, err := io.ReadFull(rand.Reader, chunk)
   677  				checkErr(err, "rand.Reader ReadFull")
   678  			}
   679  			wr.Write(chunk)
   680  		}
   681  		if test.compressed {
   682  			err := wr.(*gzip.Writer).Close()
   683  			checkErr(err, "compressor close")
   684  		}
   685  		if test.chunked {
   686  			buf.WriteString("0\r\n\r\n")
   687  		}
   688  		buf.WriteString("Next Request Here")
   689  
   690  		bufr := bufio.NewReader(&buf)
   691  		resp, err := ReadResponse(bufr, dummyReq("GET"))
   692  		checkErr(err, "ReadResponse")
   693  		expectedLength := int64(-1)
   694  		if !test.chunked {
   695  			expectedLength = 1000000
   696  		}
   697  		if resp.ContentLength != expectedLength {
   698  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
   699  		}
   700  		if resp.Body == nil {
   701  			fatalf("nil body")
   702  		}
   703  		if test.compressed {
   704  			gzReader, err := gzip.NewReader(resp.Body)
   705  			checkErr(err, "gzip.NewReader")
   706  			resp.Body = &readerAndCloser{gzReader, resp.Body}
   707  		}
   708  
   709  		rbuf := make([]byte, 2500)
   710  		n, err := io.ReadFull(resp.Body, rbuf)
   711  		checkErr(err, "2500 byte ReadFull")
   712  		if n != 2500 {
   713  			fatalf("ReadFull only read %d bytes", n)
   714  		}
   715  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
   716  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
   717  		}
   718  		resp.Body.Close()
   719  
   720  		rest, err := ioutil.ReadAll(bufr)
   721  		checkErr(err, "ReadAll on remainder")
   722  		if e, g := "Next Request Here", string(rest); e != g {
   723  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
   724  				return fmt.Sprintf("x(repeated x%d)", len(match))
   725  			})
   726  			fatalf("remainder = %q, expected %q", g, e)
   727  		}
   728  	}
   729  }
   730  
   731  func diff(t *testing.T, prefix string, have, want interface{}) {
   732  	hv := reflect.ValueOf(have).Elem()
   733  	wv := reflect.ValueOf(want).Elem()
   734  	if hv.Type() != wv.Type() {
   735  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
   736  	}
   737  	for i := 0; i < hv.NumField(); i++ {
   738  		name := hv.Type().Field(i).Name
   739  		if !ast.IsExported(name) {
   740  			continue
   741  		}
   742  		hf := hv.Field(i).Interface()
   743  		wf := wv.Field(i).Interface()
   744  		if !reflect.DeepEqual(hf, wf) {
   745  			t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
   746  		}
   747  	}
   748  }
   749  
   750  type responseLocationTest struct {
   751  	location string // Response's Location header or ""
   752  	requrl   string // Response.Request.URL or ""
   753  	want     string
   754  	wantErr  error
   755  }
   756  
   757  var responseLocationTests = []responseLocationTest{
   758  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
   759  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
   760  	{"", "http://bar.com/baz", "", ErrNoLocation},
   761  	{"/bar", "", "/bar", nil},
   762  }
   763  
   764  func TestLocationResponse(t *testing.T) {
   765  	for i, tt := range responseLocationTests {
   766  		res := new(Response)
   767  		res.Header = make(Header)
   768  		res.Header.Set("Location", tt.location)
   769  		if tt.requrl != "" {
   770  			res.Request = &Request{}
   771  			var err error
   772  			res.Request.URL, err = url.Parse(tt.requrl)
   773  			if err != nil {
   774  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
   775  			}
   776  		}
   777  
   778  		got, err := res.Location()
   779  		if tt.wantErr != nil {
   780  			if err == nil {
   781  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
   782  				continue
   783  			}
   784  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
   785  				t.Errorf("%d. err=%q; want %q", i, g, e)
   786  				continue
   787  			}
   788  			continue
   789  		}
   790  		if err != nil {
   791  			t.Errorf("%d. err=%q", i, err)
   792  			continue
   793  		}
   794  		if g, e := got.String(), tt.want; g != e {
   795  			t.Errorf("%d. Location=%q; want %q", i, g, e)
   796  		}
   797  	}
   798  }
   799  
   800  func TestResponseStatusStutter(t *testing.T) {
   801  	r := &Response{
   802  		Status:     "123 some status",
   803  		StatusCode: 123,
   804  		ProtoMajor: 1,
   805  		ProtoMinor: 3,
   806  	}
   807  	var buf bytes.Buffer
   808  	r.Write(&buf)
   809  	if strings.Contains(buf.String(), "123 123") {
   810  		t.Errorf("stutter in status: %s", buf.String())
   811  	}
   812  }
   813  
   814  func TestResponseContentLengthShortBody(t *testing.T) {
   815  	const shortBody = "Short body, not 123 bytes."
   816  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
   817  		"Content-Length: 123\r\n" +
   818  		"\r\n" +
   819  		shortBody))
   820  	res, err := ReadResponse(br, &Request{Method: "GET"})
   821  	if err != nil {
   822  		t.Fatal(err)
   823  	}
   824  	if res.ContentLength != 123 {
   825  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
   826  	}
   827  	var buf bytes.Buffer
   828  	n, err := io.Copy(&buf, res.Body)
   829  	if n != int64(len(shortBody)) {
   830  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
   831  	}
   832  	if buf.String() != shortBody {
   833  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
   834  	}
   835  	if err != io.ErrUnexpectedEOF {
   836  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
   837  	}
   838  }
   839  
   840  // Test various ReadResponse error cases. (also tests success cases, but mostly
   841  // it's about errors).  This does not test anything involving the bodies. Only
   842  // the return value from ReadResponse itself.
   843  func TestReadResponseErrors(t *testing.T) {
   844  	type testCase struct {
   845  		name    string // optional, defaults to in
   846  		in      string
   847  		wantErr interface{} // nil, err value, or string substring
   848  	}
   849  
   850  	status := func(s string, wantErr interface{}) testCase {
   851  		if wantErr == true {
   852  			wantErr = "malformed HTTP status code"
   853  		}
   854  		return testCase{
   855  			name:    fmt.Sprintf("status %q", s),
   856  			in:      "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
   857  			wantErr: wantErr,
   858  		}
   859  	}
   860  
   861  	version := func(s string, wantErr interface{}) testCase {
   862  		if wantErr == true {
   863  			wantErr = "malformed HTTP version"
   864  		}
   865  		return testCase{
   866  			name:    fmt.Sprintf("version %q", s),
   867  			in:      s + " 200 OK\r\n\r\n",
   868  			wantErr: wantErr,
   869  		}
   870  	}
   871  
   872  	contentLength := func(status, body string, wantErr interface{}) testCase {
   873  		return testCase{
   874  			name:    fmt.Sprintf("status %q %q", status, body),
   875  			in:      fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
   876  			wantErr: wantErr,
   877  		}
   878  	}
   879  
   880  	errMultiCL := "message cannot contain multiple Content-Length headers"
   881  
   882  	tests := []testCase{
   883  		{"", "", io.ErrUnexpectedEOF},
   884  		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF},
   885  		{"", "HTTP/1.1", "malformed HTTP response"},
   886  		{"", "HTTP/2.0", "malformed HTTP response"},
   887  		status("20X Unknown", true),
   888  		status("abcd Unknown", true),
   889  		status("二百/两百 OK", true),
   890  		status(" Unknown", true),
   891  		status("c8 OK", true),
   892  		status("0x12d Moved Permanently", true),
   893  		status("200 OK", nil),
   894  		status("000 OK", nil),
   895  		status("001 OK", nil),
   896  		status("404 NOTFOUND", nil),
   897  		status("20 OK", true),
   898  		status("00 OK", true),
   899  		status("-10 OK", true),
   900  		status("1000 OK", true),
   901  		status("999 Done", nil),
   902  		status("-1 OK", true),
   903  		status("-200 OK", true),
   904  		version("HTTP/1.2", nil),
   905  		version("HTTP/2.0", nil),
   906  		version("HTTP/1.100000000002", true),
   907  		version("HTTP/1.-1", true),
   908  		version("HTTP/A.B", true),
   909  		version("HTTP/1", true),
   910  		version("http/1.1", true),
   911  
   912  		contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL),
   913  		contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
   914  		contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
   915  		contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
   916  		contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
   917  		contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
   918  
   919  		// multiple content-length headers for 204 and 304 should still be checked
   920  		contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL),
   921  		contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil),
   922  		contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL),
   923  		contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil),
   924  
   925  		// golang.org/issue/22464
   926  		{"leading space in header", "HTTP/1.1 200 OK\r\n Content-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
   927  		{"leading tab in header", "HTTP/1.1 200 OK\r\n\tContent-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
   928  	}
   929  
   930  	for i, tt := range tests {
   931  		br := bufio.NewReader(strings.NewReader(tt.in))
   932  		_, rerr := ReadResponse(br, nil)
   933  		if err := matchErr(rerr, tt.wantErr); err != nil {
   934  			name := tt.name
   935  			if name == "" {
   936  				name = fmt.Sprintf("%d. input %q", i, tt.in)
   937  			}
   938  			t.Errorf("%s: %v", name, err)
   939  		}
   940  	}
   941  }
   942  
   943  // wantErr can be nil, an error value to match exactly, or type string to
   944  // match a substring.
   945  func matchErr(err error, wantErr interface{}) error {
   946  	if err == nil {
   947  		if wantErr == nil {
   948  			return nil
   949  		}
   950  		if sub, ok := wantErr.(string); ok {
   951  			return fmt.Errorf("unexpected success; want error with substring %q", sub)
   952  		}
   953  		return fmt.Errorf("unexpected success; want error %v", wantErr)
   954  	}
   955  	if wantErr == nil {
   956  		return fmt.Errorf("%v; want success", err)
   957  	}
   958  	if sub, ok := wantErr.(string); ok {
   959  		if strings.Contains(err.Error(), sub) {
   960  			return nil
   961  		}
   962  		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
   963  	}
   964  	if err == wantErr {
   965  		return nil
   966  	}
   967  	return fmt.Errorf("%v; want %v", err, wantErr)
   968  }
   969  
   970  func TestNeedsSniff(t *testing.T) {
   971  	// needsSniff returns true with an empty response.
   972  	r := &response{}
   973  	if got, want := r.needsSniff(), true; got != want {
   974  		t.Errorf("needsSniff = %t; want %t", got, want)
   975  	}
   976  	// needsSniff returns false when Content-Type = nil.
   977  	r.handlerHeader = Header{"Content-Type": nil}
   978  	if got, want := r.needsSniff(), false; got != want {
   979  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
   980  	}
   981  }
   982  
   983  // A response should only write out single Connection: close header. Tests #19499.
   984  func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
   985  	const connectionCloseHeader = "Connection: close"
   986  
   987  	res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
   988  	if err != nil {
   989  		t.Fatalf("ReadResponse failed %v", err)
   990  	}
   991  
   992  	var buf1 bytes.Buffer
   993  	if err = res.Write(&buf1); err != nil {
   994  		t.Fatalf("Write failed %v", err)
   995  	}
   996  	if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
   997  		t.Fatalf("ReadResponse failed %v", err)
   998  	}
   999  
  1000  	var buf2 bytes.Buffer
  1001  	if err = res.Write(&buf2); err != nil {
  1002  		t.Fatalf("Write failed %v", err)
  1003  	}
  1004  	if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
  1005  		t.Errorf("Found %d %q header", count, connectionCloseHeader)
  1006  	}
  1007  }
  1008  

View as plain text