]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/http/transfer_test.go
net/http: disallow empty Content-Length header
[gostls13.git] / src / net / http / transfer_test.go
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 %s want %s", 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:      "",
337                         wantErr: badStringError("invalid empty Content-Length", ""),
338                 },
339                 {
340                         cl:      "3",
341                         wantErr: nil,
342                 },
343                 {
344                         cl:      "+3",
345                         wantErr: badStringError("bad Content-Length", "+3"),
346                 },
347                 {
348                         cl:      "-3",
349                         wantErr: badStringError("bad Content-Length", "-3"),
350                 },
351                 {
352                         // max int64, for safe conversion before returning
353                         cl:      "9223372036854775807",
354                         wantErr: nil,
355                 },
356                 {
357                         cl:      "9223372036854775808",
358                         wantErr: badStringError("bad Content-Length", "9223372036854775808"),
359                 },
360         }
361
362         for _, tt := range tests {
363                 if _, gotErr := parseContentLength([]string{tt.cl}); !reflect.DeepEqual(gotErr, tt.wantErr) {
364                         t.Errorf("%q:\n\tgot=%v\n\twant=%v", tt.cl, gotErr, tt.wantErr)
365                 }
366         }
367 }