Black Lives Matter. Support the Equal Justice Initiative.

Source file src/net/http/transfer_test.go

Documentation: net/http

     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  	"crypto/rand"
    11  	"fmt"
    12  	"io"
    13  	"os"
    14  	"reflect"
    15  	"strings"
    16  	"testing"
    17  )
    18  
    19  func TestBodyReadBadTrailer(t *testing.T) {
    20  	b := &body{
    21  		src: strings.NewReader("foobar"),
    22  		hdr: true, // force reading the trailer
    23  		r:   bufio.NewReader(strings.NewReader("")),
    24  	}
    25  	buf := make([]byte, 7)
    26  	n, err := b.Read(buf[:3])
    27  	got := string(buf[:n])
    28  	if got != "foo" || err != nil {
    29  		t.Fatalf(`first Read = %d (%q), %v; want 3 ("foo")`, n, got, err)
    30  	}
    31  
    32  	n, err = b.Read(buf[:])
    33  	got = string(buf[:n])
    34  	if got != "bar" || err != nil {
    35  		t.Fatalf(`second Read = %d (%q), %v; want 3 ("bar")`, n, got, err)
    36  	}
    37  
    38  	n, err = b.Read(buf[:])
    39  	got = string(buf[:n])
    40  	if err == nil {
    41  		t.Errorf("final Read was successful (%q), expected error from trailer read", got)
    42  	}
    43  }
    44  
    45  func TestFinalChunkedBodyReadEOF(t *testing.T) {
    46  	res, err := ReadResponse(bufio.NewReader(strings.NewReader(
    47  		"HTTP/1.1 200 OK\r\n"+
    48  			"Transfer-Encoding: chunked\r\n"+
    49  			"\r\n"+
    50  			"0a\r\n"+
    51  			"Body here\n\r\n"+
    52  			"09\r\n"+
    53  			"continued\r\n"+
    54  			"0\r\n"+
    55  			"\r\n")), nil)
    56  	if err != nil {
    57  		t.Fatal(err)
    58  	}
    59  	want := "Body here\ncontinued"
    60  	buf := make([]byte, len(want))
    61  	n, err := res.Body.Read(buf)
    62  	if n != len(want) || err != io.EOF {
    63  		t.Logf("body = %#v", res.Body)
    64  		t.Errorf("Read = %v, %v; want %d, EOF", n, err, len(want))
    65  	}
    66  	if string(buf) != want {
    67  		t.Errorf("buf = %q; want %q", buf, want)
    68  	}
    69  }
    70  
    71  func TestDetectInMemoryReaders(t *testing.T) {
    72  	pr, _ := io.Pipe()
    73  	tests := []struct {
    74  		r    io.Reader
    75  		want bool
    76  	}{
    77  		{pr, false},
    78  
    79  		{bytes.NewReader(nil), true},
    80  		{bytes.NewBuffer(nil), true},
    81  		{strings.NewReader(""), true},
    82  
    83  		{io.NopCloser(pr), false},
    84  
    85  		{io.NopCloser(bytes.NewReader(nil)), true},
    86  		{io.NopCloser(bytes.NewBuffer(nil)), true},
    87  		{io.NopCloser(strings.NewReader("")), true},
    88  	}
    89  	for i, tt := range tests {
    90  		got := isKnownInMemoryReader(tt.r)
    91  		if got != tt.want {
    92  			t.Errorf("%d: got = %v; want %v", i, got, tt.want)
    93  		}
    94  	}
    95  }
    96  
    97  type mockTransferWriter struct {
    98  	CalledReader io.Reader
    99  	WriteCalled  bool
   100  }
   101  
   102  var _ io.ReaderFrom = (*mockTransferWriter)(nil)
   103  
   104  func (w *mockTransferWriter) ReadFrom(r io.Reader) (int64, error) {
   105  	w.CalledReader = r
   106  	return io.Copy(io.Discard, r)
   107  }
   108  
   109  func (w *mockTransferWriter) Write(p []byte) (int, error) {
   110  	w.WriteCalled = true
   111  	return io.Discard.Write(p)
   112  }
   113  
   114  func TestTransferWriterWriteBodyReaderTypes(t *testing.T) {
   115  	fileType := reflect.TypeOf(&os.File{})
   116  	bufferType := reflect.TypeOf(&bytes.Buffer{})
   117  
   118  	nBytes := int64(1 << 10)
   119  	newFileFunc := func() (r io.Reader, done func(), err error) {
   120  		f, err := os.CreateTemp("", "net-http-newfilefunc")
   121  		if err != nil {
   122  			return nil, nil, err
   123  		}
   124  
   125  		// Write some bytes to the file to enable reading.
   126  		if _, err := io.CopyN(f, rand.Reader, nBytes); err != nil {
   127  			return nil, nil, fmt.Errorf("failed to write data to file: %v", err)
   128  		}
   129  		if _, err := f.Seek(0, 0); err != nil {
   130  			return nil, nil, fmt.Errorf("failed to seek to front: %v", err)
   131  		}
   132  
   133  		done = func() {
   134  			f.Close()
   135  			os.Remove(f.Name())
   136  		}
   137  
   138  		return f, done, nil
   139  	}
   140  
   141  	newBufferFunc := func() (io.Reader, func(), error) {
   142  		return bytes.NewBuffer(make([]byte, nBytes)), func() {}, nil
   143  	}
   144  
   145  	cases := []struct {
   146  		name             string
   147  		bodyFunc         func() (io.Reader, func(), error)
   148  		method           string
   149  		contentLength    int64
   150  		transferEncoding []string
   151  		limitedReader    bool
   152  		expectedReader   reflect.Type
   153  		expectedWrite    bool
   154  	}{
   155  		{
   156  			name:           "file, non-chunked, size set",
   157  			bodyFunc:       newFileFunc,
   158  			method:         "PUT",
   159  			contentLength:  nBytes,
   160  			limitedReader:  true,
   161  			expectedReader: fileType,
   162  		},
   163  		{
   164  			name:   "file, non-chunked, size set, nopCloser wrapped",
   165  			method: "PUT",
   166  			bodyFunc: func() (io.Reader, func(), error) {
   167  				r, cleanup, err := newFileFunc()
   168  				return io.NopCloser(r), cleanup, err
   169  			},
   170  			contentLength:  nBytes,
   171  			limitedReader:  true,
   172  			expectedReader: fileType,
   173  		},
   174  		{
   175  			name:           "file, non-chunked, negative size",
   176  			method:         "PUT",
   177  			bodyFunc:       newFileFunc,
   178  			contentLength:  -1,
   179  			expectedReader: fileType,
   180  		},
   181  		{
   182  			name:           "file, non-chunked, CONNECT, negative size",
   183  			method:         "CONNECT",
   184  			bodyFunc:       newFileFunc,
   185  			contentLength:  -1,
   186  			expectedReader: fileType,
   187  		},
   188  		{
   189  			name:             "file, chunked",
   190  			method:           "PUT",
   191  			bodyFunc:         newFileFunc,
   192  			transferEncoding: []string{"chunked"},
   193  			expectedWrite:    true,
   194  		},
   195  		{
   196  			name:           "buffer, non-chunked, size set",
   197  			bodyFunc:       newBufferFunc,
   198  			method:         "PUT",
   199  			contentLength:  nBytes,
   200  			limitedReader:  true,
   201  			expectedReader: bufferType,
   202  		},
   203  		{
   204  			name:   "buffer, non-chunked, size set, nopCloser wrapped",
   205  			method: "PUT",
   206  			bodyFunc: func() (io.Reader, func(), error) {
   207  				r, cleanup, err := newBufferFunc()
   208  				return io.NopCloser(r), cleanup, err
   209  			},
   210  			contentLength:  nBytes,
   211  			limitedReader:  true,
   212  			expectedReader: bufferType,
   213  		},
   214  		{
   215  			name:          "buffer, non-chunked, negative size",
   216  			method:        "PUT",
   217  			bodyFunc:      newBufferFunc,
   218  			contentLength: -1,
   219  			expectedWrite: true,
   220  		},
   221  		{
   222  			name:          "buffer, non-chunked, CONNECT, negative size",
   223  			method:        "CONNECT",
   224  			bodyFunc:      newBufferFunc,
   225  			contentLength: -1,
   226  			expectedWrite: true,
   227  		},
   228  		{
   229  			name:             "buffer, chunked",
   230  			method:           "PUT",
   231  			bodyFunc:         newBufferFunc,
   232  			transferEncoding: []string{"chunked"},
   233  			expectedWrite:    true,
   234  		},
   235  	}
   236  
   237  	for _, tc := range cases {
   238  		t.Run(tc.name, func(t *testing.T) {
   239  			body, cleanup, err := tc.bodyFunc()
   240  			if err != nil {
   241  				t.Fatal(err)
   242  			}
   243  			defer cleanup()
   244  
   245  			mw := &mockTransferWriter{}
   246  			tw := &transferWriter{
   247  				Body:             body,
   248  				ContentLength:    tc.contentLength,
   249  				TransferEncoding: tc.transferEncoding,
   250  			}
   251  
   252  			if err := tw.writeBody(mw); err != nil {
   253  				t.Fatal(err)
   254  			}
   255  
   256  			if tc.expectedReader != nil {
   257  				if mw.CalledReader == nil {
   258  					t.Fatal("did not call ReadFrom")
   259  				}
   260  
   261  				var actualReader reflect.Type
   262  				lr, ok := mw.CalledReader.(*io.LimitedReader)
   263  				if ok && tc.limitedReader {
   264  					actualReader = reflect.TypeOf(lr.R)
   265  				} else {
   266  					actualReader = reflect.TypeOf(mw.CalledReader)
   267  				}
   268  
   269  				if tc.expectedReader != actualReader {
   270  					t.Fatalf("got reader %T want %T", actualReader, tc.expectedReader)
   271  				}
   272  			}
   273  
   274  			if tc.expectedWrite && !mw.WriteCalled {
   275  				t.Fatal("did not invoke Write")
   276  			}
   277  		})
   278  	}
   279  }
   280  
   281  func TestParseTransferEncoding(t *testing.T) {
   282  	tests := []struct {
   283  		hdr     Header
   284  		wantErr error
   285  	}{
   286  		{
   287  			hdr:     Header{"Transfer-Encoding": {"fugazi"}},
   288  			wantErr: &unsupportedTEError{`unsupported transfer encoding: "fugazi"`},
   289  		},
   290  		{
   291  			hdr:     Header{"Transfer-Encoding": {"chunked, chunked", "identity", "chunked"}},
   292  			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked, chunked" "identity" "chunked"]`},
   293  		},
   294  		{
   295  			hdr:     Header{"Transfer-Encoding": {""}},
   296  			wantErr: &unsupportedTEError{`unsupported transfer encoding: ""`},
   297  		},
   298  		{
   299  			hdr:     Header{"Transfer-Encoding": {"chunked, identity"}},
   300  			wantErr: &unsupportedTEError{`unsupported transfer encoding: "chunked, identity"`},
   301  		},
   302  		{
   303  			hdr:     Header{"Transfer-Encoding": {"chunked", "identity"}},
   304  			wantErr: &unsupportedTEError{`too many transfer encodings: ["chunked" "identity"]`},
   305  		},
   306  		{
   307  			hdr:     Header{"Transfer-Encoding": {"\x0bchunked"}},
   308  			wantErr: &unsupportedTEError{`unsupported transfer encoding: "\vchunked"`},
   309  		},
   310  		{
   311  			hdr:     Header{"Transfer-Encoding": {"chunked"}},
   312  			wantErr: nil,
   313  		},
   314  	}
   315  
   316  	for i, tt := range tests {
   317  		tr := &transferReader{
   318  			Header:     tt.hdr,
   319  			ProtoMajor: 1,
   320  			ProtoMinor: 1,
   321  		}
   322  		gotErr := tr.parseTransferEncoding()
   323  		if !reflect.DeepEqual(gotErr, tt.wantErr) {
   324  			t.Errorf("%d.\ngot error:\n%v\nwant error:\n%v\n\n", i, gotErr, tt.wantErr)
   325  		}
   326  	}
   327  }
   328  
   329  // issue 39017 - disallow Content-Length values such as "+3"
   330  func TestParseContentLength(t *testing.T) {
   331  	tests := []struct {
   332  		cl      string
   333  		wantErr error
   334  	}{
   335  		{
   336  			cl:      "3",
   337  			wantErr: nil,
   338  		},
   339  		{
   340  			cl:      "+3",
   341  			wantErr: badStringError("bad Content-Length", "+3"),
   342  		},
   343  		{
   344  			cl:      "-3",
   345  			wantErr: badStringError("bad Content-Length", "-3"),
   346  		},
   347  		{
   348  			// max int64, for safe conversion before returning
   349  			cl:      "9223372036854775807",
   350  			wantErr: nil,
   351  		},
   352  		{
   353  			cl:      "9223372036854775808",
   354  			wantErr: badStringError("bad Content-Length", "9223372036854775808"),
   355  		},
   356  	}
   357  
   358  	for _, tt := range tests {
   359  		if _, gotErr := parseContentLength(tt.cl); !reflect.DeepEqual(gotErr, tt.wantErr) {
   360  			t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
   361  		}
   362  	}
   363  }
   364  

View as plain text