github.com/guyezi/gofrontend@v0.0.0-20200228202240-7a62a49e62c0/libgo/go/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/token" 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 // Trailer header but no TransferEncoding 161 { 162 "HTTP/1.0 200 OK\r\n" + 163 "Trailer: Content-MD5, Content-Sources\r\n" + 164 "Content-Length: 10\r\n" + 165 "Connection: close\r\n" + 166 "\r\n" + 167 "Body here\n", 168 169 Response{ 170 Status: "200 OK", 171 StatusCode: 200, 172 Proto: "HTTP/1.0", 173 ProtoMajor: 1, 174 ProtoMinor: 0, 175 Request: dummyReq("GET"), 176 Header: Header{ 177 "Connection": {"close"}, 178 "Content-Length": {"10"}, 179 "Trailer": []string{"Content-MD5, Content-Sources"}, 180 }, 181 Close: true, 182 ContentLength: 10, 183 }, 184 185 "Body here\n", 186 }, 187 188 // Chunked response with Content-Length. 189 { 190 "HTTP/1.1 200 OK\r\n" + 191 "Transfer-Encoding: chunked\r\n" + 192 "Content-Length: 10\r\n" + 193 "\r\n" + 194 "0a\r\n" + 195 "Body here\n\r\n" + 196 "0\r\n" + 197 "\r\n", 198 199 Response{ 200 Status: "200 OK", 201 StatusCode: 200, 202 Proto: "HTTP/1.1", 203 ProtoMajor: 1, 204 ProtoMinor: 1, 205 Request: dummyReq("GET"), 206 Header: Header{}, 207 Close: false, 208 ContentLength: -1, 209 TransferEncoding: []string{"chunked"}, 210 }, 211 212 "Body here\n", 213 }, 214 215 // Chunked response in response to a HEAD request 216 { 217 "HTTP/1.1 200 OK\r\n" + 218 "Transfer-Encoding: chunked\r\n" + 219 "\r\n", 220 221 Response{ 222 Status: "200 OK", 223 StatusCode: 200, 224 Proto: "HTTP/1.1", 225 ProtoMajor: 1, 226 ProtoMinor: 1, 227 Request: dummyReq("HEAD"), 228 Header: Header{}, 229 TransferEncoding: []string{"chunked"}, 230 Close: false, 231 ContentLength: -1, 232 }, 233 234 "", 235 }, 236 237 // Content-Length in response to a HEAD request 238 { 239 "HTTP/1.0 200 OK\r\n" + 240 "Content-Length: 256\r\n" + 241 "\r\n", 242 243 Response{ 244 Status: "200 OK", 245 StatusCode: 200, 246 Proto: "HTTP/1.0", 247 ProtoMajor: 1, 248 ProtoMinor: 0, 249 Request: dummyReq("HEAD"), 250 Header: Header{"Content-Length": {"256"}}, 251 TransferEncoding: nil, 252 Close: true, 253 ContentLength: 256, 254 }, 255 256 "", 257 }, 258 259 // Content-Length in response to a HEAD request with HTTP/1.1 260 { 261 "HTTP/1.1 200 OK\r\n" + 262 "Content-Length: 256\r\n" + 263 "\r\n", 264 265 Response{ 266 Status: "200 OK", 267 StatusCode: 200, 268 Proto: "HTTP/1.1", 269 ProtoMajor: 1, 270 ProtoMinor: 1, 271 Request: dummyReq("HEAD"), 272 Header: Header{"Content-Length": {"256"}}, 273 TransferEncoding: nil, 274 Close: false, 275 ContentLength: 256, 276 }, 277 278 "", 279 }, 280 281 // No Content-Length or Chunked in response to a HEAD request 282 { 283 "HTTP/1.0 200 OK\r\n" + 284 "\r\n", 285 286 Response{ 287 Status: "200 OK", 288 StatusCode: 200, 289 Proto: "HTTP/1.0", 290 ProtoMajor: 1, 291 ProtoMinor: 0, 292 Request: dummyReq("HEAD"), 293 Header: Header{}, 294 TransferEncoding: nil, 295 Close: true, 296 ContentLength: -1, 297 }, 298 299 "", 300 }, 301 302 // explicit Content-Length of 0. 303 { 304 "HTTP/1.1 200 OK\r\n" + 305 "Content-Length: 0\r\n" + 306 "\r\n", 307 308 Response{ 309 Status: "200 OK", 310 StatusCode: 200, 311 Proto: "HTTP/1.1", 312 ProtoMajor: 1, 313 ProtoMinor: 1, 314 Request: dummyReq("GET"), 315 Header: Header{ 316 "Content-Length": {"0"}, 317 }, 318 Close: false, 319 ContentLength: 0, 320 }, 321 322 "", 323 }, 324 325 // Status line without a Reason-Phrase, but trailing space. 326 // (permitted by RFC 7230, section 3.1.2) 327 { 328 "HTTP/1.0 303 \r\n\r\n", 329 Response{ 330 Status: "303 ", 331 StatusCode: 303, 332 Proto: "HTTP/1.0", 333 ProtoMajor: 1, 334 ProtoMinor: 0, 335 Request: dummyReq("GET"), 336 Header: Header{}, 337 Close: true, 338 ContentLength: -1, 339 }, 340 341 "", 342 }, 343 344 // Status line without a Reason-Phrase, and no trailing space. 345 // (not permitted by RFC 7230, but we'll accept it anyway) 346 { 347 "HTTP/1.0 303\r\n\r\n", 348 Response{ 349 Status: "303", 350 StatusCode: 303, 351 Proto: "HTTP/1.0", 352 ProtoMajor: 1, 353 ProtoMinor: 0, 354 Request: dummyReq("GET"), 355 Header: Header{}, 356 Close: true, 357 ContentLength: -1, 358 }, 359 360 "", 361 }, 362 363 // golang.org/issue/4767: don't special-case multipart/byteranges responses 364 { 365 `HTTP/1.1 206 Partial Content 366 Connection: close 367 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef 368 369 some body`, 370 Response{ 371 Status: "206 Partial Content", 372 StatusCode: 206, 373 Proto: "HTTP/1.1", 374 ProtoMajor: 1, 375 ProtoMinor: 1, 376 Request: dummyReq("GET"), 377 Header: Header{ 378 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, 379 }, 380 Close: true, 381 ContentLength: -1, 382 }, 383 384 "some body", 385 }, 386 387 // Unchunked response without Content-Length, Request is nil 388 { 389 "HTTP/1.0 200 OK\r\n" + 390 "Connection: close\r\n" + 391 "\r\n" + 392 "Body here\n", 393 394 Response{ 395 Status: "200 OK", 396 StatusCode: 200, 397 Proto: "HTTP/1.0", 398 ProtoMajor: 1, 399 ProtoMinor: 0, 400 Header: Header{ 401 "Connection": {"close"}, // TODO(rsc): Delete? 402 }, 403 Close: true, 404 ContentLength: -1, 405 }, 406 407 "Body here\n", 408 }, 409 410 // 206 Partial Content. golang.org/issue/8923 411 { 412 "HTTP/1.1 206 Partial Content\r\n" + 413 "Content-Type: text/plain; charset=utf-8\r\n" + 414 "Accept-Ranges: bytes\r\n" + 415 "Content-Range: bytes 0-5/1862\r\n" + 416 "Content-Length: 6\r\n\r\n" + 417 "foobar", 418 419 Response{ 420 Status: "206 Partial Content", 421 StatusCode: 206, 422 Proto: "HTTP/1.1", 423 ProtoMajor: 1, 424 ProtoMinor: 1, 425 Request: dummyReq("GET"), 426 Header: Header{ 427 "Accept-Ranges": []string{"bytes"}, 428 "Content-Length": []string{"6"}, 429 "Content-Type": []string{"text/plain; charset=utf-8"}, 430 "Content-Range": []string{"bytes 0-5/1862"}, 431 }, 432 ContentLength: 6, 433 }, 434 435 "foobar", 436 }, 437 438 // Both keep-alive and close, on the same Connection line. (Issue 8840) 439 { 440 "HTTP/1.1 200 OK\r\n" + 441 "Content-Length: 256\r\n" + 442 "Connection: keep-alive, close\r\n" + 443 "\r\n", 444 445 Response{ 446 Status: "200 OK", 447 StatusCode: 200, 448 Proto: "HTTP/1.1", 449 ProtoMajor: 1, 450 ProtoMinor: 1, 451 Request: dummyReq("HEAD"), 452 Header: Header{ 453 "Content-Length": {"256"}, 454 }, 455 TransferEncoding: nil, 456 Close: true, 457 ContentLength: 256, 458 }, 459 460 "", 461 }, 462 463 // Both keep-alive and close, on different Connection lines. (Issue 8840) 464 { 465 "HTTP/1.1 200 OK\r\n" + 466 "Content-Length: 256\r\n" + 467 "Connection: keep-alive\r\n" + 468 "Connection: close\r\n" + 469 "\r\n", 470 471 Response{ 472 Status: "200 OK", 473 StatusCode: 200, 474 Proto: "HTTP/1.1", 475 ProtoMajor: 1, 476 ProtoMinor: 1, 477 Request: dummyReq("HEAD"), 478 Header: Header{ 479 "Content-Length": {"256"}, 480 }, 481 TransferEncoding: nil, 482 Close: true, 483 ContentLength: 256, 484 }, 485 486 "", 487 }, 488 489 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 490 // Without a Content-Length. 491 { 492 "HTTP/1.0 200 OK\r\n" + 493 "Transfer-Encoding: bogus\r\n" + 494 "\r\n" + 495 "Body here\n", 496 497 Response{ 498 Status: "200 OK", 499 StatusCode: 200, 500 Proto: "HTTP/1.0", 501 ProtoMajor: 1, 502 ProtoMinor: 0, 503 Request: dummyReq("GET"), 504 Header: Header{}, 505 Close: true, 506 ContentLength: -1, 507 }, 508 509 "Body here\n", 510 }, 511 512 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 513 // With a Content-Length. 514 { 515 "HTTP/1.0 200 OK\r\n" + 516 "Transfer-Encoding: bogus\r\n" + 517 "Content-Length: 10\r\n" + 518 "\r\n" + 519 "Body here\n", 520 521 Response{ 522 Status: "200 OK", 523 StatusCode: 200, 524 Proto: "HTTP/1.0", 525 ProtoMajor: 1, 526 ProtoMinor: 0, 527 Request: dummyReq("GET"), 528 Header: Header{ 529 "Content-Length": {"10"}, 530 }, 531 Close: true, 532 ContentLength: 10, 533 }, 534 535 "Body here\n", 536 }, 537 538 { 539 "HTTP/1.1 200 OK\r\n" + 540 "Content-Encoding: gzip\r\n" + 541 "Content-Length: 23\r\n" + 542 "Connection: keep-alive\r\n" + 543 "Keep-Alive: timeout=7200\r\n\r\n" + 544 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", 545 Response{ 546 Status: "200 OK", 547 StatusCode: 200, 548 Proto: "HTTP/1.1", 549 ProtoMajor: 1, 550 ProtoMinor: 1, 551 Request: dummyReq("GET"), 552 Header: Header{ 553 "Content-Length": {"23"}, 554 "Content-Encoding": {"gzip"}, 555 "Connection": {"keep-alive"}, 556 "Keep-Alive": {"timeout=7200"}, 557 }, 558 Close: false, 559 ContentLength: 23, 560 }, 561 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", 562 }, 563 564 // Issue 19989: two spaces between HTTP version and status. 565 { 566 "HTTP/1.0 401 Unauthorized\r\n" + 567 "Content-type: text/html\r\n" + 568 "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" + 569 "Your Authentication failed.\r\n", 570 Response{ 571 Status: "401 Unauthorized", 572 StatusCode: 401, 573 Proto: "HTTP/1.0", 574 ProtoMajor: 1, 575 ProtoMinor: 0, 576 Request: dummyReq("GET"), 577 Header: Header{ 578 "Content-Type": {"text/html"}, 579 "Www-Authenticate": {`Basic realm=""`}, 580 }, 581 Close: true, 582 ContentLength: -1, 583 }, 584 "Your Authentication failed.\r\n", 585 }, 586 } 587 588 // tests successful calls to ReadResponse, and inspects the returned Response. 589 // For error cases, see TestReadResponseErrors below. 590 func TestReadResponse(t *testing.T) { 591 for i, tt := range respTests { 592 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 593 if err != nil { 594 t.Errorf("#%d: %v", i, err) 595 continue 596 } 597 rbody := resp.Body 598 resp.Body = nil 599 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 600 var bout bytes.Buffer 601 if rbody != nil { 602 _, err = io.Copy(&bout, rbody) 603 if err != nil { 604 t.Errorf("#%d: %v", i, err) 605 continue 606 } 607 rbody.Close() 608 } 609 body := bout.String() 610 if body != tt.Body { 611 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 612 } 613 } 614 } 615 616 func TestWriteResponse(t *testing.T) { 617 for i, tt := range respTests { 618 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 619 if err != nil { 620 t.Errorf("#%d: %v", i, err) 621 continue 622 } 623 err = resp.Write(ioutil.Discard) 624 if err != nil { 625 t.Errorf("#%d: %v", i, err) 626 continue 627 } 628 } 629 } 630 631 var readResponseCloseInMiddleTests = []struct { 632 chunked, compressed bool 633 }{ 634 {false, false}, 635 {true, false}, 636 {true, true}, 637 } 638 639 type readerAndCloser struct { 640 io.Reader 641 io.Closer 642 } 643 644 // TestReadResponseCloseInMiddle tests that closing a body after 645 // reading only part of its contents advances the read to the end of 646 // the request, right up until the next request. 647 func TestReadResponseCloseInMiddle(t *testing.T) { 648 t.Parallel() 649 for _, test := range readResponseCloseInMiddleTests { 650 fatalf := func(format string, args ...interface{}) { 651 args = append([]interface{}{test.chunked, test.compressed}, args...) 652 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 653 } 654 checkErr := func(err error, msg string) { 655 if err == nil { 656 return 657 } 658 fatalf(msg+": %v", err) 659 } 660 var buf bytes.Buffer 661 buf.WriteString("HTTP/1.1 200 OK\r\n") 662 if test.chunked { 663 buf.WriteString("Transfer-Encoding: chunked\r\n") 664 } else { 665 buf.WriteString("Content-Length: 1000000\r\n") 666 } 667 var wr io.Writer = &buf 668 if test.chunked { 669 wr = internal.NewChunkedWriter(wr) 670 } 671 if test.compressed { 672 buf.WriteString("Content-Encoding: gzip\r\n") 673 wr = gzip.NewWriter(wr) 674 } 675 buf.WriteString("\r\n") 676 677 chunk := bytes.Repeat([]byte{'x'}, 1000) 678 for i := 0; i < 1000; i++ { 679 if test.compressed { 680 // Otherwise this compresses too well. 681 _, err := io.ReadFull(rand.Reader, chunk) 682 checkErr(err, "rand.Reader ReadFull") 683 } 684 wr.Write(chunk) 685 } 686 if test.compressed { 687 err := wr.(*gzip.Writer).Close() 688 checkErr(err, "compressor close") 689 } 690 if test.chunked { 691 buf.WriteString("0\r\n\r\n") 692 } 693 buf.WriteString("Next Request Here") 694 695 bufr := bufio.NewReader(&buf) 696 resp, err := ReadResponse(bufr, dummyReq("GET")) 697 checkErr(err, "ReadResponse") 698 expectedLength := int64(-1) 699 if !test.chunked { 700 expectedLength = 1000000 701 } 702 if resp.ContentLength != expectedLength { 703 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 704 } 705 if resp.Body == nil { 706 fatalf("nil body") 707 } 708 if test.compressed { 709 gzReader, err := gzip.NewReader(resp.Body) 710 checkErr(err, "gzip.NewReader") 711 resp.Body = &readerAndCloser{gzReader, resp.Body} 712 } 713 714 rbuf := make([]byte, 2500) 715 n, err := io.ReadFull(resp.Body, rbuf) 716 checkErr(err, "2500 byte ReadFull") 717 if n != 2500 { 718 fatalf("ReadFull only read %d bytes", n) 719 } 720 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 721 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 722 } 723 resp.Body.Close() 724 725 rest, err := ioutil.ReadAll(bufr) 726 checkErr(err, "ReadAll on remainder") 727 if e, g := "Next Request Here", string(rest); e != g { 728 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { 729 return fmt.Sprintf("x(repeated x%d)", len(match)) 730 }) 731 fatalf("remainder = %q, expected %q", g, e) 732 } 733 } 734 } 735 736 func diff(t *testing.T, prefix string, have, want interface{}) { 737 hv := reflect.ValueOf(have).Elem() 738 wv := reflect.ValueOf(want).Elem() 739 if hv.Type() != wv.Type() { 740 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type()) 741 } 742 for i := 0; i < hv.NumField(); i++ { 743 name := hv.Type().Field(i).Name 744 if !token.IsExported(name) { 745 continue 746 } 747 hf := hv.Field(i).Interface() 748 wf := wv.Field(i).Interface() 749 if !reflect.DeepEqual(hf, wf) { 750 t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf) 751 } 752 } 753 } 754 755 type responseLocationTest struct { 756 location string // Response's Location header or "" 757 requrl string // Response.Request.URL or "" 758 want string 759 wantErr error 760 } 761 762 var responseLocationTests = []responseLocationTest{ 763 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil}, 764 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil}, 765 {"", "http://bar.com/baz", "", ErrNoLocation}, 766 {"/bar", "", "/bar", nil}, 767 } 768 769 func TestLocationResponse(t *testing.T) { 770 for i, tt := range responseLocationTests { 771 res := new(Response) 772 res.Header = make(Header) 773 res.Header.Set("Location", tt.location) 774 if tt.requrl != "" { 775 res.Request = &Request{} 776 var err error 777 res.Request.URL, err = url.Parse(tt.requrl) 778 if err != nil { 779 t.Fatalf("bad test URL %q: %v", tt.requrl, err) 780 } 781 } 782 783 got, err := res.Location() 784 if tt.wantErr != nil { 785 if err == nil { 786 t.Errorf("%d. err=nil; want %q", i, tt.wantErr) 787 continue 788 } 789 if g, e := err.Error(), tt.wantErr.Error(); g != e { 790 t.Errorf("%d. err=%q; want %q", i, g, e) 791 continue 792 } 793 continue 794 } 795 if err != nil { 796 t.Errorf("%d. err=%q", i, err) 797 continue 798 } 799 if g, e := got.String(), tt.want; g != e { 800 t.Errorf("%d. Location=%q; want %q", i, g, e) 801 } 802 } 803 } 804 805 func TestResponseStatusStutter(t *testing.T) { 806 r := &Response{ 807 Status: "123 some status", 808 StatusCode: 123, 809 ProtoMajor: 1, 810 ProtoMinor: 3, 811 } 812 var buf bytes.Buffer 813 r.Write(&buf) 814 if strings.Contains(buf.String(), "123 123") { 815 t.Errorf("stutter in status: %s", buf.String()) 816 } 817 } 818 819 func TestResponseContentLengthShortBody(t *testing.T) { 820 const shortBody = "Short body, not 123 bytes." 821 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" + 822 "Content-Length: 123\r\n" + 823 "\r\n" + 824 shortBody)) 825 res, err := ReadResponse(br, &Request{Method: "GET"}) 826 if err != nil { 827 t.Fatal(err) 828 } 829 if res.ContentLength != 123 { 830 t.Fatalf("Content-Length = %d; want 123", res.ContentLength) 831 } 832 var buf bytes.Buffer 833 n, err := io.Copy(&buf, res.Body) 834 if n != int64(len(shortBody)) { 835 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody) 836 } 837 if buf.String() != shortBody { 838 t.Errorf("Read body %q; want %q", buf.String(), shortBody) 839 } 840 if err != io.ErrUnexpectedEOF { 841 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err) 842 } 843 } 844 845 // Test various ReadResponse error cases. (also tests success cases, but mostly 846 // it's about errors). This does not test anything involving the bodies. Only 847 // the return value from ReadResponse itself. 848 func TestReadResponseErrors(t *testing.T) { 849 type testCase struct { 850 name string // optional, defaults to in 851 in string 852 wantErr interface{} // nil, err value, or string substring 853 } 854 855 status := func(s string, wantErr interface{}) testCase { 856 if wantErr == true { 857 wantErr = "malformed HTTP status code" 858 } 859 return testCase{ 860 name: fmt.Sprintf("status %q", s), 861 in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n", 862 wantErr: wantErr, 863 } 864 } 865 866 version := func(s string, wantErr interface{}) testCase { 867 if wantErr == true { 868 wantErr = "malformed HTTP version" 869 } 870 return testCase{ 871 name: fmt.Sprintf("version %q", s), 872 in: s + " 200 OK\r\n\r\n", 873 wantErr: wantErr, 874 } 875 } 876 877 contentLength := func(status, body string, wantErr interface{}) testCase { 878 return testCase{ 879 name: fmt.Sprintf("status %q %q", status, body), 880 in: fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body), 881 wantErr: wantErr, 882 } 883 } 884 885 errMultiCL := "message cannot contain multiple Content-Length headers" 886 887 tests := []testCase{ 888 {"", "", io.ErrUnexpectedEOF}, 889 {"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF}, 890 {"", "HTTP/1.1", "malformed HTTP response"}, 891 {"", "HTTP/2.0", "malformed HTTP response"}, 892 status("20X Unknown", true), 893 status("abcd Unknown", true), 894 status("二百/两百 OK", true), 895 status(" Unknown", true), 896 status("c8 OK", true), 897 status("0x12d Moved Permanently", true), 898 status("200 OK", nil), 899 status("000 OK", nil), 900 status("001 OK", nil), 901 status("404 NOTFOUND", nil), 902 status("20 OK", true), 903 status("00 OK", true), 904 status("-10 OK", true), 905 status("1000 OK", true), 906 status("999 Done", nil), 907 status("-1 OK", true), 908 status("-200 OK", true), 909 version("HTTP/1.2", nil), 910 version("HTTP/2.0", nil), 911 version("HTTP/1.100000000002", true), 912 version("HTTP/1.-1", true), 913 version("HTTP/A.B", true), 914 version("HTTP/1", true), 915 version("http/1.1", true), 916 917 contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL), 918 contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil), 919 contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL), 920 contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil), 921 contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil), 922 contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL), 923 924 // multiple content-length headers for 204 and 304 should still be checked 925 contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL), 926 contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil), 927 contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL), 928 contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil), 929 930 // golang.org/issue/22464 931 {"leading space in header", "HTTP/1.1 200 OK\r\n Content-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"}, 932 {"leading tab in header", "HTTP/1.1 200 OK\r\n\tContent-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"}, 933 } 934 935 for i, tt := range tests { 936 br := bufio.NewReader(strings.NewReader(tt.in)) 937 _, rerr := ReadResponse(br, nil) 938 if err := matchErr(rerr, tt.wantErr); err != nil { 939 name := tt.name 940 if name == "" { 941 name = fmt.Sprintf("%d. input %q", i, tt.in) 942 } 943 t.Errorf("%s: %v", name, err) 944 } 945 } 946 } 947 948 // wantErr can be nil, an error value to match exactly, or type string to 949 // match a substring. 950 func matchErr(err error, wantErr interface{}) error { 951 if err == nil { 952 if wantErr == nil { 953 return nil 954 } 955 if sub, ok := wantErr.(string); ok { 956 return fmt.Errorf("unexpected success; want error with substring %q", sub) 957 } 958 return fmt.Errorf("unexpected success; want error %v", wantErr) 959 } 960 if wantErr == nil { 961 return fmt.Errorf("%v; want success", err) 962 } 963 if sub, ok := wantErr.(string); ok { 964 if strings.Contains(err.Error(), sub) { 965 return nil 966 } 967 return fmt.Errorf("error = %v; want an error with substring %q", err, sub) 968 } 969 if err == wantErr { 970 return nil 971 } 972 return fmt.Errorf("%v; want %v", err, wantErr) 973 } 974 975 func TestNeedsSniff(t *testing.T) { 976 // needsSniff returns true with an empty response. 977 r := &response{} 978 if got, want := r.needsSniff(), true; got != want { 979 t.Errorf("needsSniff = %t; want %t", got, want) 980 } 981 // needsSniff returns false when Content-Type = nil. 982 r.handlerHeader = Header{"Content-Type": nil} 983 if got, want := r.needsSniff(), false; got != want { 984 t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want) 985 } 986 } 987 988 // A response should only write out single Connection: close header. Tests #19499. 989 func TestResponseWritesOnlySingleConnectionClose(t *testing.T) { 990 const connectionCloseHeader = "Connection: close" 991 992 res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil) 993 if err != nil { 994 t.Fatalf("ReadResponse failed %v", err) 995 } 996 997 var buf1 bytes.Buffer 998 if err = res.Write(&buf1); err != nil { 999 t.Fatalf("Write failed %v", err) 1000 } 1001 if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil { 1002 t.Fatalf("ReadResponse failed %v", err) 1003 } 1004 1005 var buf2 bytes.Buffer 1006 if err = res.Write(&buf2); err != nil { 1007 t.Fatalf("Write failed %v", err) 1008 } 1009 if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 { 1010 t.Errorf("Found %d %q header", count, connectionCloseHeader) 1011 } 1012 }