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

View as plain text