github.com/goproxy0/go@v0.0.0-20171111080102-49cc0c489d2c/src/net/http/response_test.go (about) 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 2616) 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 2616, 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 // leading whitespace in the first header. golang.org/issue/22464 560 { 561 "HTTP/1.1 200 OK\r\n" + 562 " Content-type: text/html\r\n" + 563 "\tIgnore: foobar\r\n" + 564 "Foo: bar\r\n\r\n", 565 Response{ 566 Status: "200 OK", 567 StatusCode: 200, 568 Proto: "HTTP/1.1", 569 ProtoMajor: 1, 570 ProtoMinor: 1, 571 Request: dummyReq("GET"), 572 Header: Header{ 573 "Foo": {"bar"}, 574 }, 575 Close: true, 576 ContentLength: -1, 577 }, 578 "", 579 }, 580 } 581 582 // tests successful calls to ReadResponse, and inspects the returned Response. 583 // For error cases, see TestReadResponseErrors below. 584 func TestReadResponse(t *testing.T) { 585 for i, tt := range respTests { 586 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 587 if err != nil { 588 t.Errorf("#%d: %v", i, err) 589 continue 590 } 591 rbody := resp.Body 592 resp.Body = nil 593 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 594 var bout bytes.Buffer 595 if rbody != nil { 596 _, err = io.Copy(&bout, rbody) 597 if err != nil { 598 t.Errorf("#%d: %v", i, err) 599 continue 600 } 601 rbody.Close() 602 } 603 body := bout.String() 604 if body != tt.Body { 605 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 606 } 607 } 608 } 609 610 func TestWriteResponse(t *testing.T) { 611 for i, tt := range respTests { 612 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 613 if err != nil { 614 t.Errorf("#%d: %v", i, err) 615 continue 616 } 617 err = resp.Write(ioutil.Discard) 618 if err != nil { 619 t.Errorf("#%d: %v", i, err) 620 continue 621 } 622 } 623 } 624 625 var readResponseCloseInMiddleTests = []struct { 626 chunked, compressed bool 627 }{ 628 {false, false}, 629 {true, false}, 630 {true, true}, 631 } 632 633 // TestReadResponseCloseInMiddle tests that closing a body after 634 // reading only part of its contents advances the read to the end of 635 // the request, right up until the next request. 636 func TestReadResponseCloseInMiddle(t *testing.T) { 637 t.Parallel() 638 for _, test := range readResponseCloseInMiddleTests { 639 fatalf := func(format string, args ...interface{}) { 640 args = append([]interface{}{test.chunked, test.compressed}, args...) 641 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 642 } 643 checkErr := func(err error, msg string) { 644 if err == nil { 645 return 646 } 647 fatalf(msg+": %v", err) 648 } 649 var buf bytes.Buffer 650 buf.WriteString("HTTP/1.1 200 OK\r\n") 651 if test.chunked { 652 buf.WriteString("Transfer-Encoding: chunked\r\n") 653 } else { 654 buf.WriteString("Content-Length: 1000000\r\n") 655 } 656 var wr io.Writer = &buf 657 if test.chunked { 658 wr = internal.NewChunkedWriter(wr) 659 } 660 if test.compressed { 661 buf.WriteString("Content-Encoding: gzip\r\n") 662 wr = gzip.NewWriter(wr) 663 } 664 buf.WriteString("\r\n") 665 666 chunk := bytes.Repeat([]byte{'x'}, 1000) 667 for i := 0; i < 1000; i++ { 668 if test.compressed { 669 // Otherwise this compresses too well. 670 _, err := io.ReadFull(rand.Reader, chunk) 671 checkErr(err, "rand.Reader ReadFull") 672 } 673 wr.Write(chunk) 674 } 675 if test.compressed { 676 err := wr.(*gzip.Writer).Close() 677 checkErr(err, "compressor close") 678 } 679 if test.chunked { 680 buf.WriteString("0\r\n\r\n") 681 } 682 buf.WriteString("Next Request Here") 683 684 bufr := bufio.NewReader(&buf) 685 resp, err := ReadResponse(bufr, dummyReq("GET")) 686 checkErr(err, "ReadResponse") 687 expectedLength := int64(-1) 688 if !test.chunked { 689 expectedLength = 1000000 690 } 691 if resp.ContentLength != expectedLength { 692 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 693 } 694 if resp.Body == nil { 695 fatalf("nil body") 696 } 697 if test.compressed { 698 gzReader, err := gzip.NewReader(resp.Body) 699 checkErr(err, "gzip.NewReader") 700 resp.Body = &readerAndCloser{gzReader, resp.Body} 701 } 702 703 rbuf := make([]byte, 2500) 704 n, err := io.ReadFull(resp.Body, rbuf) 705 checkErr(err, "2500 byte ReadFull") 706 if n != 2500 { 707 fatalf("ReadFull only read %d bytes", n) 708 } 709 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 710 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 711 } 712 resp.Body.Close() 713 714 rest, err := ioutil.ReadAll(bufr) 715 checkErr(err, "ReadAll on remainder") 716 if e, g := "Next Request Here", string(rest); e != g { 717 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { 718 return fmt.Sprintf("x(repeated x%d)", len(match)) 719 }) 720 fatalf("remainder = %q, expected %q", g, e) 721 } 722 } 723 } 724 725 func diff(t *testing.T, prefix string, have, want interface{}) { 726 hv := reflect.ValueOf(have).Elem() 727 wv := reflect.ValueOf(want).Elem() 728 if hv.Type() != wv.Type() { 729 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) 730 } 731 for i := 0; i < hv.NumField(); i++ { 732 name := hv.Type().Field(i).Name 733 if !ast.IsExported(name) { 734 continue 735 } 736 hf := hv.Field(i).Interface() 737 wf := wv.Field(i).Interface() 738 if !reflect.DeepEqual(hf, wf) { 739 t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf) 740 } 741 } 742 } 743 744 type responseLocationTest struct { 745 location string // Response's Location header or "" 746 requrl string // Response.Request.URL or "" 747 want string 748 wantErr error 749 } 750 751 var responseLocationTests = []responseLocationTest{ 752 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, 753 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, 754 {"", "http://bar.com/baz", "", ErrNoLocation}, 755 {"/bar", "", "/bar", nil}, 756 } 757 758 func TestLocationResponse(t *testing.T) { 759 for i, tt := range responseLocationTests { 760 res := new(Response) 761 res.Header = make(Header) 762 res.Header.Set("Location", tt.location) 763 if tt.requrl != "" { 764 res.Request = &Request{} 765 var err error 766 res.Request.URL, err = url.Parse(tt.requrl) 767 if err != nil { 768 t.Fatalf("bad test URL %q: %v", tt.requrl, err) 769 } 770 } 771 772 got, err := res.Location() 773 if tt.wantErr != nil { 774 if err == nil { 775 t.Errorf("%d. err=nil; want %q", i, tt.wantErr) 776 continue 777 } 778 if g, e := err.Error(), tt.wantErr.Error(); g != e { 779 t.Errorf("%d. err=%q; want %q", i, g, e) 780 continue 781 } 782 continue 783 } 784 if err != nil { 785 t.Errorf("%d. err=%q", i, err) 786 continue 787 } 788 if g, e := got.String(), tt.want; g != e { 789 t.Errorf("%d. Location=%q; want %q", i, g, e) 790 } 791 } 792 } 793 794 func TestResponseStatusStutter(t *testing.T) { 795 r := &Response{ 796 Status: "123 some status", 797 StatusCode: 123, 798 ProtoMajor: 1, 799 ProtoMinor: 3, 800 } 801 var buf bytes.Buffer 802 r.Write(&buf) 803 if strings.Contains(buf.String(), "123 123") { 804 t.Errorf("stutter in status: %s", buf.String()) 805 } 806 } 807 808 func TestResponseContentLengthShortBody(t *testing.T) { 809 const shortBody = "Short body, not 123 bytes." 810 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + 811 "Content-Length: 123\r\n" + 812 "\r\n" + 813 shortBody)) 814 res, err := ReadResponse(br, &Request{Method: "GET"}) 815 if err != nil { 816 t.Fatal(err) 817 } 818 if res.ContentLength != 123 { 819 t.Fatalf("Content-Length = %d; want 123", res.ContentLength) 820 } 821 var buf bytes.Buffer 822 n, err := io.Copy(&buf, res.Body) 823 if n != int64(len(shortBody)) { 824 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) 825 } 826 if buf.String() != shortBody { 827 t.Errorf("Read body %q; want %q", buf.String(), shortBody) 828 } 829 if err != io.ErrUnexpectedEOF { 830 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) 831 } 832 } 833 834 // Test various ReadResponse error cases. (also tests success cases, but mostly 835 // it's about errors). This does not test anything involving the bodies. Only 836 // the return value from ReadResponse itself. 837 func TestReadResponseErrors(t *testing.T) { 838 type testCase struct { 839 name string // optional, defaults to in 840 in string 841 header Header 842 wantErr interface{} // nil, err value, or string substring 843 } 844 845 status := func(s string, wantErr interface{}) testCase { 846 if wantErr == true { 847 wantErr = "malformed HTTP status code" 848 } 849 return testCase{ 850 name: fmt.Sprintf("status %q", s), 851 in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n", 852 wantErr: wantErr, 853 } 854 } 855 856 version := func(s string, wantErr interface{}) testCase { 857 if wantErr == true { 858 wantErr = "malformed HTTP version" 859 } 860 return testCase{ 861 name: fmt.Sprintf("version %q", s), 862 in: s + " 200 OK\r\n\r\n", 863 wantErr: wantErr, 864 } 865 } 866 867 contentLength := func(status, body string, wantErr interface{}, header Header) testCase { 868 return testCase{ 869 name: fmt.Sprintf("status %q %q", status, body), 870 in: fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body), 871 wantErr: wantErr, 872 header: header, 873 } 874 } 875 876 errMultiCL := "message cannot contain multiple Content-Length headers" 877 878 tests := []testCase{ 879 {"", "", nil, io.ErrUnexpectedEOF}, 880 {"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", nil, io.ErrUnexpectedEOF}, 881 {"", "HTTP/1.1", nil, "malformed HTTP response"}, 882 {"", "HTTP/2.0", nil, "malformed HTTP response"}, 883 status("20X Unknown", true), 884 status("abcd Unknown", true), 885 status("二百/两百 OK", true), 886 status(" Unknown", true), 887 status("c8 OK", true), 888 status("0x12d Moved Permanently", true), 889 status("200 OK", nil), 890 status("000 OK", nil), 891 status("001 OK", nil), 892 status("404 NOTFOUND", nil), 893 status("20 OK", true), 894 status("00 OK", true), 895 status("-10 OK", true), 896 status("1000 OK", true), 897 status("999 Done", nil), 898 status("-1 OK", true), 899 status("-200 OK", true), 900 version("HTTP/1.2", nil), 901 version("HTTP/2.0", nil), 902 version("HTTP/1.100000000002", true), 903 version("HTTP/1.-1", true), 904 version("HTTP/A.B", true), 905 version("HTTP/1", true), 906 version("http/1.1", true), 907 908 contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL, nil), 909 contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"7"}}), 910 contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL, nil), 911 contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"0"}}), 912 contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil, nil), 913 contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL, nil), 914 915 // multiple content-length headers for 204 and 304 should still be checked 916 contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL, nil), 917 contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil, nil), 918 contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL, nil), 919 contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil, nil), 920 } 921 922 for i, tt := range tests { 923 br := bufio.NewReader(strings.NewReader(tt.in)) 924 _, rerr := ReadResponse(br, nil) 925 if err := matchErr(rerr, tt.wantErr); err != nil { 926 name := tt.name 927 if name == "" { 928 name = fmt.Sprintf("%d. input %q", i, tt.in) 929 } 930 t.Errorf("%s: %v", name, err) 931 } 932 } 933 } 934 935 // wantErr can be nil, an error value to match exactly, or type string to 936 // match a substring. 937 func matchErr(err error, wantErr interface{}) error { 938 if err == nil { 939 if wantErr == nil { 940 return nil 941 } 942 if sub, ok := wantErr.(string); ok { 943 return fmt.Errorf("unexpected success; want error with substring %q", sub) 944 } 945 return fmt.Errorf("unexpected success; want error %v", wantErr) 946 } 947 if wantErr == nil { 948 return fmt.Errorf("%v; want success", err) 949 } 950 if sub, ok := wantErr.(string); ok { 951 if strings.Contains(err.Error(), sub) { 952 return nil 953 } 954 return fmt.Errorf("error = %v; want an error with substring %q", err, sub) 955 } 956 if err == wantErr { 957 return nil 958 } 959 return fmt.Errorf("%v; want %v", err, wantErr) 960 } 961 962 func TestNeedsSniff(t *testing.T) { 963 // needsSniff returns true with an empty response. 964 r := &response{} 965 if got, want := r.needsSniff(), true; got != want { 966 t.Errorf("needsSniff = %t; want %t", got, want) 967 } 968 // needsSniff returns false when Content-Type = nil. 969 r.handlerHeader = Header{"Content-Type": nil} 970 if got, want := r.needsSniff(), false; got != want { 971 t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) 972 } 973 } 974 975 // A response should only write out single Connection: close header. Tests #19499. 976 func TestResponseWritesOnlySingleConnectionClose(t *testing.T) { 977 const connectionCloseHeader = "Connection: close" 978 979 res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil) 980 if err != nil { 981 t.Fatalf("ReadResponse failed %v", err) 982 } 983 984 var buf1 bytes.Buffer 985 if err = res.Write(&buf1); err != nil { 986 t.Fatalf("Write failed %v", err) 987 } 988 if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil { 989 t.Fatalf("ReadResponse failed %v", err) 990 } 991 992 var buf2 bytes.Buffer 993 if err = res.Write(&buf2); err != nil { 994 t.Fatalf("Write failed %v", err) 995 } 996 if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 { 997 t.Errorf("Found %d %q header", count, connectionCloseHeader) 998 } 999 }