github.com/c12o16h1/go/src@v0.0.0-20200114212001-5a151c0f00ed/net/http/transfer_test.go (about)

     1  // Copyright 2012 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  	"io"
    14  	"io/ioutil"
    15  	"os"
    16  	"reflect"
    17  	"strings"
    18  	"testing"
    19  )
    20  
    21  func TestBodyReadBadTrailer(t *testing.T) {
    22  	b := &body{
    23  		src: strings.NewReader("foobar"),
    24  		hdr: true, // force reading the trailer
    25  		r:   bufio.NewReader(strings.NewReader("")),
    26  	}
    27  	buf := make([]byte, 7)
    28  	n, err := b.Read(buf[:3])
    29  	got := string(buf[:n])
    30  	if got != "foo" || err != nil {
    31  		t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err)
    32  	}
    33  
    34  	n, err = b.Read(buf[:])
    35  	got = string(buf[:n])
    36  	if got != "bar" || err != nil {
    37  		t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err)
    38  	}
    39  
    40  	n, err = b.Read(buf[:])
    41  	got = string(buf[:n])
    42  	if err == nil {
    43  		t.Errorf("final Read was successful (%q), expected error from trailer read", got)
    44  	}
    45  }
    46  
    47  func TestFinalChunkedBodyReadEOF(t *testing.T) {
    48  	res, err := ReadResponse(bufio.NewReader(strings.NewReader(
    49  		"HTTP/1.1 200 OK\r\n"+
    50  			"Transfer-Encoding: chunked\r\n"+
    51  			"\r\n"+
    52  			"0a\r\n"+
    53  			"Body here\n\r\n"+
    54  			"09\r\n"+
    55  			"continued\r\n"+
    56  			"0\r\n"+
    57  			"\r\n")), nil)
    58  	if err != nil {
    59  		t.Fatal(err)
    60  	}
    61  	want := "Body here\ncontinued"
    62  	buf := make([]byte, len(want))
    63  	n, err := res.Body.Read(buf)
    64  	if n != len(want) || err != io.EOF {
    65  		t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want))
    66  	}
    67  	if string(buf) != want {
    68  		t.Errorf("buf = %q; want %q", buf, want)
    69  	}
    70  }
    71  
    72  func TestDetectInMemoryReaders(t *testing.T) {
    73  	pr, _ := io.Pipe()
    74  	tests := []struct {
    75  		r    io.Reader
    76  		want bool
    77  	}{
    78  		{pr, false},
    79  
    80  		{bytes.NewReader(nil), true},
    81  		{bytes.NewBuffer(nil), true},
    82  		{strings.NewReader(""), true},
    83  
    84  		{ioutil.NopCloser(pr), false},
    85  
    86  		{ioutil.NopCloser(bytes.NewReader(nil)), true},
    87  		{ioutil.NopCloser(bytes.NewBuffer(nil)), true},
    88  		{ioutil.NopCloser(strings.NewReader("")), true},
    89  	}
    90  	for i, tt := range tests {
    91  		got := isKnownInMemoryReader(tt.r)
    92  		if got != tt.want {
    93  			t.Errorf("%d: got = %v; want %v", i, got, tt.want)
    94  		}
    95  	}
    96  }
    97  
    98  type mockTransferWriter struct {
    99  	CalledReader io.Reader
   100  	WriteCalled  bool
   101  }
   102  
   103  var _ io.ReaderFrom = (*mockTransferWriter)(nil)
   104  
   105  func (w *mockTransferWriter) ReadFrom(r io.Reader) (int64, error) {
   106  	w.CalledReader = r
   107  	return io.Copy(ioutil.Discard, r)
   108  }
   109  
   110  func (w *mockTransferWriter) Write(p []byte) (int, error) {
   111  	w.WriteCalled = true
   112  	return ioutil.Discard.Write(p)
   113  }
   114  
   115  func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
   116  	fileType := reflect.TypeOf(&os.File{})
   117  	bufferType := reflect.TypeOf(&bytes.Buffer{})
   118  
   119  	nBytes := int64(1 << 10)
   120  	newFileFunc := func() (r io.Reader, done func(), err error) {
   121  		f, err := ioutil.TempFile("", "net-http-newfilefunc")
   122  		if err != nil {
   123  			return nil, nil, err
   124  		}
   125  
   126  		// Write some bytes to the file to enable reading.
   127  		if _, err := io.CopyN(f, rand.Reader, nBytes); err != nil {
   128  			return nil, nil, fmt.Errorf("failed to write data to file: %v", err)
   129  		}
   130  		if _, err := f.Seek(0, 0); err != nil {
   131  			return nil, nil, fmt.Errorf("failed to seek to front: %v", err)
   132  		}
   133  
   134  		done = func() {
   135  			f.Close()
   136  			os.Remove(f.Name())
   137  		}
   138  
   139  		return f, done, nil
   140  	}
   141  
   142  	newBufferFunc := func() (io.Reader, func(), error) {
   143  		return bytes.NewBuffer(make([]byte, nBytes)), func() {}, nil
   144  	}
   145  
   146  	cases := []struct {
   147  		name             string
   148  		bodyFunc         func() (io.Reader, func(), error)
   149  		method           string
   150  		contentLength    int64
   151  		transferEncoding []string
   152  		limitedReader    bool
   153  		expectedReader   reflect.Type
   154  		expectedWrite    bool
   155  	}{
   156  		{
   157  			name:           "file, non-chunked, size set",
   158  			bodyFunc:       newFileFunc,
   159  			method:         "PUT",
   160  			contentLength:  nBytes,
   161  			limitedReader:  true,
   162  			expectedReader: fileType,
   163  		},
   164  		{
   165  			name:   "file, non-chunked, size set, nopCloser wrapped",
   166  			method: "PUT",
   167  			bodyFunc: func() (io.Reader, func(), error) {
   168  				r, cleanup, err := newFileFunc()
   169  				return ioutil.NopCloser(r), cleanup, err
   170  			},
   171  			contentLength:  nBytes,
   172  			limitedReader:  true,
   173  			expectedReader: fileType,
   174  		},
   175  		{
   176  			name:           "file, non-chunked, negative size",
   177  			method:         "PUT",
   178  			bodyFunc:       newFileFunc,
   179  			contentLength:  -1,
   180  			expectedReader: fileType,
   181  		},
   182  		{
   183  			name:           "file, non-chunked, CONNECT, negative size",
   184  			method:         "CONNECT",
   185  			bodyFunc:       newFileFunc,
   186  			contentLength:  -1,
   187  			expectedReader: fileType,
   188  		},
   189  		{
   190  			name:             "file, chunked",
   191  			method:           "PUT",
   192  			bodyFunc:         newFileFunc,
   193  			transferEncoding: []string{"chunked"},
   194  			expectedWrite:    true,
   195  		},
   196  		{
   197  			name:           "buffer, non-chunked, size set",
   198  			bodyFunc:       newBufferFunc,
   199  			method:         "PUT",
   200  			contentLength:  nBytes,
   201  			limitedReader:  true,
   202  			expectedReader: bufferType,
   203  		},
   204  		{
   205  			name:   "buffer, non-chunked, size set, nopCloser wrapped",
   206  			method: "PUT",
   207  			bodyFunc: func() (io.Reader, func(), error) {
   208  				r, cleanup, err := newBufferFunc()
   209  				return ioutil.NopCloser(r), cleanup, err
   210  			},
   211  			contentLength:  nBytes,
   212  			limitedReader:  true,
   213  			expectedReader: bufferType,
   214  		},
   215  		{
   216  			name:          "buffer, non-chunked, negative size",
   217  			method:        "PUT",
   218  			bodyFunc:      newBufferFunc,
   219  			contentLength: -1,
   220  			expectedWrite: true,
   221  		},
   222  		{
   223  			name:          "buffer, non-chunked, CONNECT, negative size",
   224  			method:        "CONNECT",
   225  			bodyFunc:      newBufferFunc,
   226  			contentLength: -1,
   227  			expectedWrite: true,
   228  		},
   229  		{
   230  			name:             "buffer, chunked",
   231  			method:           "PUT",
   232  			bodyFunc:         newBufferFunc,
   233  			transferEncoding: []string{"chunked"},
   234  			expectedWrite:    true,
   235  		},
   236  	}
   237  
   238  	for _, tc := range cases {
   239  		t.Run(tc.name, func(t *testing.T) {
   240  			body, cleanup, err := tc.bodyFunc()
   241  			if err != nil {
   242  				t.Fatal(err)
   243  			}
   244  			defer cleanup()
   245  
   246  			mw := &mockTransferWriter{}
   247  			tw := &transferWriter{
   248  				Body:             body,
   249  				ContentLength:    tc.contentLength,
   250  				TransferEncoding: tc.transferEncoding,
   251  			}
   252  
   253  			if err := tw.writeBody(mw); err != nil {
   254  				t.Fatal(err)
   255  			}
   256  
   257  			if tc.expectedReader != nil {
   258  				if mw.CalledReader == nil {
   259  					t.Fatal("did not call ReadFrom")
   260  				}
   261  
   262  				var actualReader reflect.Type
   263  				lr, ok := mw.CalledReader.(*io.LimitedReader)
   264  				if ok && tc.limitedReader {
   265  					actualReader = reflect.TypeOf(lr.R)
   266  				} else {
   267  					actualReader = reflect.TypeOf(mw.CalledReader)
   268  				}
   269  
   270  				if tc.expectedReader != actualReader {
   271  					t.Fatalf("got reader %T want %T", actualReader, tc.expectedReader)
   272  				}
   273  			}
   274  
   275  			if tc.expectedWrite && !mw.WriteCalled {
   276  				t.Fatal("did not invoke Write")
   277  			}
   278  		})
   279  	}
   280  }
   281  
   282  func TestFixTransferEncoding(t *testing.T) {
   283  	tests := []struct {
   284  		hdr     Header
   285  		wantErr error
   286  	}{
   287  		{
   288  			hdr:     Header{"Transfer-Encoding": {"fugazi"}},
   289  			wantErr: &unsupportedTEError{`unsupported transfer encoding: "fugazi"`},
   290  		},
   291  		{
   292  			hdr:     Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}},
   293  			wantErr: &badStringError{"chunked must be applied only once, as the last encoding", "chunked, chunked"},
   294  		},
   295  		{
   296  			hdr:     Header{"Transfer-Encoding": {"chunked"}},
   297  			wantErr: nil,
   298  		},
   299  	}
   300  
   301  	for i, tt := range tests {
   302  		tr := &transferReader{
   303  			Header:     tt.hdr,
   304  			ProtoMajor: 1,
   305  			ProtoMinor: 1,
   306  		}
   307  		gotErr := tr.fixTransferEncoding()
   308  		if !reflect.DeepEqual(gotErr, tt.wantErr) {
   309  			t.Errorf("%d.\ngot error:\n%v\nwant error:\n%v\n\n", i, gotErr, tt.wantErr)
   310  		}
   311  	}
   312  }
   313  
   314  func gzipIt(s string) string {
   315  	buf := new(bytes.Buffer)
   316  	gw := gzip.NewWriter(buf)
   317  	gw.Write([]byte(s))
   318  	gw.Close()
   319  	return buf.String()
   320  }
   321  
   322  func TestUnitTestProxyingReadCloserClosesBody(t *testing.T) {
   323  	var checker closeChecker
   324  	buf := new(bytes.Buffer)
   325  	buf.WriteString("Hello, Gophers!")
   326  	prc := &proxyingReadCloser{
   327  		Reader: buf,
   328  		Closer: &checker,
   329  	}
   330  	prc.Close()
   331  
   332  	read, err := ioutil.ReadAll(prc)
   333  	if err != nil {
   334  		t.Fatalf("Read error: %v", err)
   335  	}
   336  	if g, w := string(read), "Hello, Gophers!"; g != w {
   337  		t.Errorf("Read mismatch: got %q want %q", g, w)
   338  	}
   339  
   340  	if checker.closed != true {
   341  		t.Fatal("closeChecker.Close was never invoked")
   342  	}
   343  }
   344  
   345  func TestGzipTransferEncoding_request(t *testing.T) {
   346  	helloWorldGzipped := gzipIt("Hello, World!")
   347  
   348  	tests := []struct {
   349  		payload  string
   350  		wantErr  string
   351  		wantBody string
   352  	}{
   353  
   354  		{
   355  			// The case of "chunked" properly applied as the last encoding
   356  			// and a gzipped request payload that is streamed in 3 parts.
   357  			payload: `POST / HTTP/1.1
   358  Host: golang.org
   359  Transfer-Encoding: gzip, chunked
   360  Content-Type: text/html; charset=UTF-8
   361  
   362  ` + fmt.Sprintf("%02x\r\n%s\r\n%02x\r\n%s\r\n%02x\r\n%s\r\n0\r\n\r\n",
   363  				3, helloWorldGzipped[:3],
   364  				5, helloWorldGzipped[3:8],
   365  				len(helloWorldGzipped)-8, helloWorldGzipped[8:]),
   366  			wantBody: `Hello, World!`,
   367  		},
   368  
   369  		{
   370  			// The request specifies "Transfer-Encoding: chunked" so its body must be left untouched.
   371  			payload: `PUT / HTTP/1.1
   372  Host: golang.org
   373  Transfer-Encoding: chunked
   374  Connection: close
   375  Content-Type: text/html; charset=UTF-8
   376  
   377  ` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped),
   378  			// We want that payload as it was sent.
   379  			wantBody: helloWorldGzipped,
   380  		},
   381  
   382  		{
   383  			// Valid request, the body doesn't have "Transfer-Encoding: chunked" but implicitly encoded
   384  			// for chunking as per the advisory from RFC 7230 3.3.1 which advises for cases where.
   385  			payload: `POST / HTTP/1.1
   386  Host: localhost
   387  Transfer-Encoding: gzip
   388  Content-Type: text/html; charset=UTF-8
   389  
   390  ` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped),
   391  			wantBody: `Hello, World!`,
   392  		},
   393  
   394  		{
   395  			// Invalid request, the body isn't chunked nor is the connection terminated immediately
   396  			// hence invalid as per the advisory from RFC 7230 3.3.1 which advises for cases where
   397  			// a Transfer-Encoding that isn't finally chunked is provided.
   398  			payload: `PUT / HTTP/1.1
   399  Host: golang.org
   400  Transfer-Encoding: gzip
   401  Content-Length: 0
   402  Connection: close
   403  Content-Type: text/html; charset=UTF-8
   404  
   405  `,
   406  			wantErr: `EOF`,
   407  		},
   408  
   409  		{
   410  			// The case of chunked applied before another encoding.
   411  			payload: `PUT / HTTP/1.1
   412  Location: golang.org
   413  Transfer-Encoding: chunked, gzip
   414  Content-Length: 0
   415  Connection: close
   416  Content-Type: text/html; charset=UTF-8
   417  
   418  `,
   419  			wantErr: `chunked must be applied only once, as the last encoding "chunked, gzip"`,
   420  		},
   421  
   422  		{
   423  			// The case of chunked properly applied as the
   424  			// last encoding BUT with a bad "Content-Length".
   425  			payload: `POST / HTTP/1.1
   426  Host: golang.org
   427  Transfer-Encoding: gzip, chunked
   428  Content-Length: 10
   429  Connection: close
   430  Content-Type: text/html; charset=UTF-8
   431  
   432  ` + "0\r\n\r\n",
   433  			wantErr: "EOF",
   434  		},
   435  	}
   436  
   437  	for i, tt := range tests {
   438  		req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.payload)))
   439  		if tt.wantErr != "" {
   440  			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
   441  				t.Errorf("test %d. Error mismatch\nGot:  %v\nWant: %s", i, err, tt.wantErr)
   442  			}
   443  			continue
   444  		}
   445  
   446  		if err != nil {
   447  			t.Errorf("test %d. Unexpected ReadRequest error: %v\nPayload:\n%s", i, err, tt.payload)
   448  			continue
   449  		}
   450  
   451  		got, err := ioutil.ReadAll(req.Body)
   452  		req.Body.Close()
   453  		if err != nil {
   454  			t.Errorf("test %d. Failed to read response body: %v", i, err)
   455  		}
   456  		if g, w := string(got), tt.wantBody; g != w {
   457  			t.Errorf("test %d. Request body mimsatch\nGot:\n%s\n\nWant:\n%s", i, g, w)
   458  		}
   459  	}
   460  }
   461  
   462  func TestGzipTransferEncoding_response(t *testing.T) {
   463  	helloWorldGzipped := gzipIt("Hello, World!")
   464  
   465  	tests := []struct {
   466  		payload  string
   467  		wantErr  string
   468  		wantBody string
   469  	}{
   470  
   471  		{
   472  			// The case of "chunked" properly applied as the last encoding
   473  			// and a gzipped payload that is streamed in 3 parts.
   474  			payload: `HTTP/1.1 302 Found
   475  Location: https://golang.org/
   476  Transfer-Encoding: gzip, chunked
   477  Connection: close
   478  Content-Type: text/html; charset=UTF-8
   479  
   480  ` + fmt.Sprintf("%02x\r\n%s\r\n%02x\r\n%s\r\n%02x\r\n%s\r\n0\r\n\r\n",
   481  				3, helloWorldGzipped[:3],
   482  				5, helloWorldGzipped[3:8],
   483  				len(helloWorldGzipped)-8, helloWorldGzipped[8:]),
   484  			wantBody: `Hello, World!`,
   485  		},
   486  
   487  		{
   488  			// The response specifies "Transfer-Encoding: chunked" so response body must be left untouched.
   489  			payload: `HTTP/1.1 302 Found
   490  Location: https://golang.org/
   491  Transfer-Encoding: chunked
   492  Connection: close
   493  Content-Type: text/html; charset=UTF-8
   494  
   495  ` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped),
   496  			// We want that payload as it was sent.
   497  			wantBody: helloWorldGzipped,
   498  		},
   499  
   500  		{
   501  			// Valid response, the body doesn't have "Transfer-Encoding: chunked" but implicitly encoded
   502  			// for chunking as per the advisory from RFC 7230 3.3.1 which advises for cases where.
   503  			payload: `HTTP/1.1 302 Found
   504  Location: https://golang.org/
   505  Transfer-Encoding: gzip
   506  Connection: close
   507  Content-Type: text/html; charset=UTF-8
   508  
   509  ` + fmt.Sprintf("%0x\r\n%s\r\n0\r\n\r\n", len(helloWorldGzipped), helloWorldGzipped),
   510  			wantBody: `Hello, World!`,
   511  		},
   512  
   513  		{
   514  			// Invalid response, the body isn't chunked nor is the connection terminated immediately
   515  			// hence invalid as per the advisory from RFC 7230 3.3.1 which advises for cases where
   516  			// a Transfer-Encoding that isn't finally chunked is provided.
   517  			payload: `HTTP/1.1 302 Found
   518  Location: https://golang.org/
   519  Transfer-Encoding: gzip
   520  Content-Length: 0
   521  Connection: close
   522  Content-Type: text/html; charset=UTF-8
   523  
   524  `,
   525  			wantErr: `EOF`,
   526  		},
   527  
   528  		{
   529  			// The case of chunked applied before another encoding.
   530  			payload: `HTTP/1.1 302 Found
   531  Location: https://golang.org/
   532  Transfer-Encoding: chunked, gzip
   533  Content-Length: 0
   534  Connection: close
   535  Content-Type: text/html; charset=UTF-8
   536  
   537  `,
   538  			wantErr: `chunked must be applied only once, as the last encoding "chunked, gzip"`,
   539  		},
   540  
   541  		{
   542  			// The case of chunked properly applied as the
   543  			// last encoding BUT with a bad "Content-Length".
   544  			payload: `HTTP/1.1 302 Found
   545  Location: https://golang.org/
   546  Transfer-Encoding: gzip, chunked
   547  Content-Length: 10
   548  Connection: close
   549  Content-Type: text/html; charset=UTF-8
   550  
   551  ` + "0\r\n\r\n",
   552  			wantErr: "EOF",
   553  		},
   554  
   555  		{
   556  			// Including "identity" more than once.
   557  			payload: `HTTP/1.1 200 OK
   558  Location: https://golang.org/
   559  Transfer-Encoding: identity, identity
   560  Content-Length: 0
   561  Connection: close
   562  Content-Type: text/html; charset=UTF-8
   563  
   564  ` + "0\r\n\r\n",
   565  			wantErr: `"identity" when present must be the only transfer encoding "identity, identity"`,
   566  		},
   567  	}
   568  
   569  	for i, tt := range tests {
   570  		res, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.payload)), nil)
   571  		if tt.wantErr != "" {
   572  			if err == nil || !strings.Contains(err.Error(), tt.wantErr) {
   573  				t.Errorf("test %d. Error mismatch\nGot:  %v\nWant: %s", i, err, tt.wantErr)
   574  			}
   575  			continue
   576  		}
   577  
   578  		if err != nil {
   579  			t.Errorf("test %d. Unexpected ReadResponse error: %v\nPayload:\n%s", i, err, tt.payload)
   580  			continue
   581  		}
   582  
   583  		got, err := ioutil.ReadAll(res.Body)
   584  		res.Body.Close()
   585  		if err != nil {
   586  			t.Errorf("test %d. Failed to read response body: %v", i, err)
   587  		}
   588  		if g, w := string(got), tt.wantBody; g != w {
   589  			t.Errorf("test %d. Response body mimsatch\nGot:\n%s\n\nWant:\n%s", i, g, w)
   590  		}
   591  	}
   592  }