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 }