github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/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 "net/http/internal" 16 "net/url" 17 "reflect" 18 "regexp" 19 "strings" 20 "testing" 21 ) 22 23 type respTest struct { 24 Raw string 25 Resp Response 26 Body string 27 } 28 29 func dummyReq(method string) *Request { 30 return &Request{Method: method} 31 } 32 33 func dummyReq11(method string) *Request { 34 return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1} 35 } 36 37 var respTests = []respTest{ 38 // Unchunked response without Content-Length. 39 { 40 "HTTP/1.0 200 OK\r\n" + 41 "Connection: close\r\n" + 42 "\r\n" + 43 "Body here\n", 44 45 Response{ 46 Status: "200 OK", 47 StatusCode: 200, 48 Proto: "HTTP/1.0", 49 ProtoMajor: 1, 50 ProtoMinor: 0, 51 Request: dummyReq("GET"), 52 Header: Header{ 53 "Connection": {"close"}, // TODO(rsc): Delete? 54 }, 55 Close: true, 56 ContentLength: -1, 57 }, 58 59 "Body here\n", 60 }, 61 62 // Unchunked HTTP/1.1 response without Content-Length or 63 // Connection headers. 64 { 65 "HTTP/1.1 200 OK\r\n" + 66 "\r\n" + 67 "Body here\n", 68 69 Response{ 70 Status: "200 OK", 71 StatusCode: 200, 72 Proto: "HTTP/1.1", 73 ProtoMajor: 1, 74 ProtoMinor: 1, 75 Header: Header{}, 76 Request: dummyReq("GET"), 77 Close: true, 78 ContentLength: -1, 79 }, 80 81 "Body here\n", 82 }, 83 84 // Unchunked HTTP/1.1 204 response without Content-Length. 85 { 86 "HTTP/1.1 204 No Content\r\n" + 87 "\r\n" + 88 "Body should not be read!\n", 89 90 Response{ 91 Status: "204 No Content", 92 StatusCode: 204, 93 Proto: "HTTP/1.1", 94 ProtoMajor: 1, 95 ProtoMinor: 1, 96 Header: Header{}, 97 Request: dummyReq("GET"), 98 Close: false, 99 ContentLength: 0, 100 }, 101 102 "", 103 }, 104 105 // Unchunked response with Content-Length. 106 { 107 "HTTP/1.0 200 OK\r\n" + 108 "Content-Length: 10\r\n" + 109 "Connection: close\r\n" + 110 "\r\n" + 111 "Body here\n", 112 113 Response{ 114 Status: "200 OK", 115 StatusCode: 200, 116 Proto: "HTTP/1.0", 117 ProtoMajor: 1, 118 ProtoMinor: 0, 119 Request: dummyReq("GET"), 120 Header: Header{ 121 "Connection": {"close"}, 122 "Content-Length": {"10"}, 123 }, 124 Close: true, 125 ContentLength: 10, 126 }, 127 128 "Body here\n", 129 }, 130 131 // Chunked response without Content-Length. 132 { 133 "HTTP/1.1 200 OK\r\n" + 134 "Transfer-Encoding: chunked\r\n" + 135 "\r\n" + 136 "0a\r\n" + 137 "Body here\n\r\n" + 138 "09\r\n" + 139 "continued\r\n" + 140 "0\r\n" + 141 "\r\n", 142 143 Response{ 144 Status: "200 OK", 145 StatusCode: 200, 146 Proto: "HTTP/1.1", 147 ProtoMajor: 1, 148 ProtoMinor: 1, 149 Request: dummyReq("GET"), 150 Header: Header{}, 151 Close: false, 152 ContentLength: -1, 153 TransferEncoding: []string{"chunked"}, 154 }, 155 156 "Body here\ncontinued", 157 }, 158 159 // Trailer header but no TransferEncoding 160 { 161 "HTTP/1.0 200 OK\r\n" + 162 "Trailer: Content-MD5, Content-Sources\r\n" + 163 "Content-Length: 10\r\n" + 164 "Connection: close\r\n" + 165 "\r\n" + 166 "Body here\n", 167 168 Response{ 169 Status: "200 OK", 170 StatusCode: 200, 171 Proto: "HTTP/1.0", 172 ProtoMajor: 1, 173 ProtoMinor: 0, 174 Request: dummyReq("GET"), 175 Header: Header{ 176 "Connection": {"close"}, 177 "Content-Length": {"10"}, 178 "Trailer": []string{"Content-MD5, Content-Sources"}, 179 }, 180 Close: true, 181 ContentLength: 10, 182 }, 183 184 "Body here\n", 185 }, 186 187 // Chunked response with Content-Length. 188 { 189 "HTTP/1.1 200 OK\r\n" + 190 "Transfer-Encoding: chunked\r\n" + 191 "Content-Length: 10\r\n" + 192 "\r\n" + 193 "0a\r\n" + 194 "Body here\n\r\n" + 195 "0\r\n" + 196 "\r\n", 197 198 Response{ 199 Status: "200 OK", 200 StatusCode: 200, 201 Proto: "HTTP/1.1", 202 ProtoMajor: 1, 203 ProtoMinor: 1, 204 Request: dummyReq("GET"), 205 Header: Header{}, 206 Close: false, 207 ContentLength: -1, 208 TransferEncoding: []string{"chunked"}, 209 }, 210 211 "Body here\n", 212 }, 213 214 // Chunked response in response to a HEAD request 215 { 216 "HTTP/1.1 200 OK\r\n" + 217 "Transfer-Encoding: chunked\r\n" + 218 "\r\n", 219 220 Response{ 221 Status: "200 OK", 222 StatusCode: 200, 223 Proto: "HTTP/1.1", 224 ProtoMajor: 1, 225 ProtoMinor: 1, 226 Request: dummyReq("HEAD"), 227 Header: Header{}, 228 TransferEncoding: []string{"chunked"}, 229 Close: false, 230 ContentLength: -1, 231 }, 232 233 "", 234 }, 235 236 // Content-Length in response to a HEAD request 237 { 238 "HTTP/1.0 200 OK\r\n" + 239 "Content-Length: 256\r\n" + 240 "\r\n", 241 242 Response{ 243 Status: "200 OK", 244 StatusCode: 200, 245 Proto: "HTTP/1.0", 246 ProtoMajor: 1, 247 ProtoMinor: 0, 248 Request: dummyReq("HEAD"), 249 Header: Header{"Content-Length": {"256"}}, 250 TransferEncoding: nil, 251 Close: true, 252 ContentLength: 256, 253 }, 254 255 "", 256 }, 257 258 // Content-Length in response to a HEAD request with HTTP/1.1 259 { 260 "HTTP/1.1 200 OK\r\n" + 261 "Content-Length: 256\r\n" + 262 "\r\n", 263 264 Response{ 265 Status: "200 OK", 266 StatusCode: 200, 267 Proto: "HTTP/1.1", 268 ProtoMajor: 1, 269 ProtoMinor: 1, 270 Request: dummyReq("HEAD"), 271 Header: Header{"Content-Length": {"256"}}, 272 TransferEncoding: nil, 273 Close: false, 274 ContentLength: 256, 275 }, 276 277 "", 278 }, 279 280 // No Content-Length or Chunked in response to a HEAD request 281 { 282 "HTTP/1.0 200 OK\r\n" + 283 "\r\n", 284 285 Response{ 286 Status: "200 OK", 287 StatusCode: 200, 288 Proto: "HTTP/1.0", 289 ProtoMajor: 1, 290 ProtoMinor: 0, 291 Request: dummyReq("HEAD"), 292 Header: Header{}, 293 TransferEncoding: nil, 294 Close: true, 295 ContentLength: -1, 296 }, 297 298 "", 299 }, 300 301 // explicit Content-Length of 0. 302 { 303 "HTTP/1.1 200 OK\r\n" + 304 "Content-Length: 0\r\n" + 305 "\r\n", 306 307 Response{ 308 Status: "200 OK", 309 StatusCode: 200, 310 Proto: "HTTP/1.1", 311 ProtoMajor: 1, 312 ProtoMinor: 1, 313 Request: dummyReq("GET"), 314 Header: Header{ 315 "Content-Length": {"0"}, 316 }, 317 Close: false, 318 ContentLength: 0, 319 }, 320 321 "", 322 }, 323 324 // Status line without a Reason-Phrase, but trailing space. 325 // (permitted by RFC 7230, section 3.1.2) 326 { 327 "HTTP/1.0 303 \r\n\r\n", 328 Response{ 329 Status: "303 ", 330 StatusCode: 303, 331 Proto: "HTTP/1.0", 332 ProtoMajor: 1, 333 ProtoMinor: 0, 334 Request: dummyReq("GET"), 335 Header: Header{}, 336 Close: true, 337 ContentLength: -1, 338 }, 339 340 "", 341 }, 342 343 // Status line without a Reason-Phrase, and no trailing space. 344 // (not permitted by RFC 7230, but we'll accept it anyway) 345 { 346 "HTTP/1.0 303\r\n\r\n", 347 Response{ 348 Status: "303", 349 StatusCode: 303, 350 Proto: "HTTP/1.0", 351 ProtoMajor: 1, 352 ProtoMinor: 0, 353 Request: dummyReq("GET"), 354 Header: Header{}, 355 Close: true, 356 ContentLength: -1, 357 }, 358 359 "", 360 }, 361 362 // golang.org/issue/4767: don't special-case multipart/byteranges responses 363 { 364 `HTTP/1.1 206 Partial Content 365 Connection: close 366 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef 367 368 some body`, 369 Response{ 370 Status: "206 Partial Content", 371 StatusCode: 206, 372 Proto: "HTTP/1.1", 373 ProtoMajor: 1, 374 ProtoMinor: 1, 375 Request: dummyReq("GET"), 376 Header: Header{ 377 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"}, 378 }, 379 Close: true, 380 ContentLength: -1, 381 }, 382 383 "some body", 384 }, 385 386 // Unchunked response without Content-Length, Request is nil 387 { 388 "HTTP/1.0 200 OK\r\n" + 389 "Connection: close\r\n" + 390 "\r\n" + 391 "Body here\n", 392 393 Response{ 394 Status: "200 OK", 395 StatusCode: 200, 396 Proto: "HTTP/1.0", 397 ProtoMajor: 1, 398 ProtoMinor: 0, 399 Header: Header{ 400 "Connection": {"close"}, // TODO(rsc): Delete? 401 }, 402 Close: true, 403 ContentLength: -1, 404 }, 405 406 "Body here\n", 407 }, 408 409 // 206 Partial Content. golang.org/issue/8923 410 { 411 "HTTP/1.1 206 Partial Content\r\n" + 412 "Content-Type: text/plain; charset=utf-8\r\n" + 413 "Accept-Ranges: bytes\r\n" + 414 "Content-Range: bytes 0-5/1862\r\n" + 415 "Content-Length: 6\r\n\r\n" + 416 "foobar", 417 418 Response{ 419 Status: "206 Partial Content", 420 StatusCode: 206, 421 Proto: "HTTP/1.1", 422 ProtoMajor: 1, 423 ProtoMinor: 1, 424 Request: dummyReq("GET"), 425 Header: Header{ 426 "Accept-Ranges": []string{"bytes"}, 427 "Content-Length": []string{"6"}, 428 "Content-Type": []string{"text/plain; charset=utf-8"}, 429 "Content-Range": []string{"bytes 0-5/1862"}, 430 }, 431 ContentLength: 6, 432 }, 433 434 "foobar", 435 }, 436 437 // Both keep-alive and close, on the same Connection line. (Issue 8840) 438 { 439 "HTTP/1.1 200 OK\r\n" + 440 "Content-Length: 256\r\n" + 441 "Connection: keep-alive, close\r\n" + 442 "\r\n", 443 444 Response{ 445 Status: "200 OK", 446 StatusCode: 200, 447 Proto: "HTTP/1.1", 448 ProtoMajor: 1, 449 ProtoMinor: 1, 450 Request: dummyReq("HEAD"), 451 Header: Header{ 452 "Content-Length": {"256"}, 453 }, 454 TransferEncoding: nil, 455 Close: true, 456 ContentLength: 256, 457 }, 458 459 "", 460 }, 461 462 // Both keep-alive and close, on different Connection lines. (Issue 8840) 463 { 464 "HTTP/1.1 200 OK\r\n" + 465 "Content-Length: 256\r\n" + 466 "Connection: keep-alive\r\n" + 467 "Connection: close\r\n" + 468 "\r\n", 469 470 Response{ 471 Status: "200 OK", 472 StatusCode: 200, 473 Proto: "HTTP/1.1", 474 ProtoMajor: 1, 475 ProtoMinor: 1, 476 Request: dummyReq("HEAD"), 477 Header: Header{ 478 "Content-Length": {"256"}, 479 }, 480 TransferEncoding: nil, 481 Close: true, 482 ContentLength: 256, 483 }, 484 485 "", 486 }, 487 488 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 489 // Without a Content-Length. 490 { 491 "HTTP/1.0 200 OK\r\n" + 492 "Transfer-Encoding: bogus\r\n" + 493 "\r\n" + 494 "Body here\n", 495 496 Response{ 497 Status: "200 OK", 498 StatusCode: 200, 499 Proto: "HTTP/1.0", 500 ProtoMajor: 1, 501 ProtoMinor: 0, 502 Request: dummyReq("GET"), 503 Header: Header{}, 504 Close: true, 505 ContentLength: -1, 506 }, 507 508 "Body here\n", 509 }, 510 511 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding. 512 // With a Content-Length. 513 { 514 "HTTP/1.0 200 OK\r\n" + 515 "Transfer-Encoding: bogus\r\n" + 516 "Content-Length: 10\r\n" + 517 "\r\n" + 518 "Body here\n", 519 520 Response{ 521 Status: "200 OK", 522 StatusCode: 200, 523 Proto: "HTTP/1.0", 524 ProtoMajor: 1, 525 ProtoMinor: 0, 526 Request: dummyReq("GET"), 527 Header: Header{ 528 "Content-Length": {"10"}, 529 }, 530 Close: true, 531 ContentLength: 10, 532 }, 533 534 "Body here\n", 535 }, 536 537 { 538 "HTTP/1.1 200 OK\r\n" + 539 "Content-Encoding: gzip\r\n" + 540 "Content-Length: 23\r\n" + 541 "Connection: keep-alive\r\n" + 542 "Keep-Alive: timeout=7200\r\n\r\n" + 543 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", 544 Response{ 545 Status: "200 OK", 546 StatusCode: 200, 547 Proto: "HTTP/1.1", 548 ProtoMajor: 1, 549 ProtoMinor: 1, 550 Request: dummyReq("GET"), 551 Header: Header{ 552 "Content-Length": {"23"}, 553 "Content-Encoding": {"gzip"}, 554 "Connection": {"keep-alive"}, 555 "Keep-Alive": {"timeout=7200"}, 556 }, 557 Close: false, 558 ContentLength: 23, 559 }, 560 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00", 561 }, 562 563 // Issue 19989: two spaces between HTTP version and status. 564 { 565 "HTTP/1.0 401 Unauthorized\r\n" + 566 "Content-type: text/html\r\n" + 567 "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" + 568 "Your Authentication failed.\r\n", 569 Response{ 570 Status: "401 Unauthorized", 571 StatusCode: 401, 572 Proto: "HTTP/1.0", 573 ProtoMajor: 1, 574 ProtoMinor: 0, 575 Request: dummyReq("GET"), 576 Header: Header{ 577 "Content-Type": {"text/html"}, 578 "Www-Authenticate": {`Basic realm=""`}, 579 }, 580 Close: true, 581 ContentLength: -1, 582 }, 583 "Your Authentication failed.\r\n", 584 }, 585 } 586 587 // tests successful calls to ReadResponse, and inspects the returned Response. 588 // For error cases, see TestReadResponseErrors below. 589 func TestReadResponse(t *testing.T) { 590 for i, tt := range respTests { 591 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 592 if err != nil { 593 t.Errorf("#%d: %v", i, err) 594 continue 595 } 596 rbody := resp.Body 597 resp.Body = nil 598 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp) 599 var bout bytes.Buffer 600 if rbody != nil { 601 _, err = io.Copy(&bout, rbody) 602 if err != nil { 603 t.Errorf("#%d: %v", i, err) 604 continue 605 } 606 rbody.Close() 607 } 608 body := bout.String() 609 if body != tt.Body { 610 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body) 611 } 612 } 613 } 614 615 func TestWriteResponse(t *testing.T) { 616 for i, tt := range respTests { 617 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request) 618 if err != nil { 619 t.Errorf("#%d: %v", i, err) 620 continue 621 } 622 err = resp.Write(io.Discard) 623 if err != nil { 624 t.Errorf("#%d: %v", i, err) 625 continue 626 } 627 } 628 } 629 630 var readResponseCloseInMiddleTests = []struct { 631 chunked, compressed bool 632 }{ 633 {false, false}, 634 {true, false}, 635 {true, true}, 636 } 637 638 type readerAndCloser struct { 639 io.Reader 640 io.Closer 641 } 642 643 // TestReadResponseCloseInMiddle tests that closing a body after 644 // reading only part of its contents advances the read to the end of 645 // the request, right up until the next request. 646 func TestReadResponseCloseInMiddle(t *testing.T) { 647 t.Parallel() 648 for _, test := range readResponseCloseInMiddleTests { 649 fatalf := func(format string, args ...interface{}) { 650 args = append([]interface{}{test.chunked, test.compressed}, args...) 651 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...) 652 } 653 checkErr := func(err error, msg string) { 654 if err == nil { 655 return 656 } 657 fatalf(msg+": %v", err) 658 } 659 var buf bytes.Buffer 660 buf.WriteString("HTTP/1.1 200 OK\r\n") 661 if test.chunked { 662 buf.WriteString("Transfer-Encoding: chunked\r\n") 663 } else { 664 buf.WriteString("Content-Length: 1000000\r\n") 665 } 666 var wr io.Writer = &buf 667 if test.chunked { 668 wr = internal.NewChunkedWriter(wr) 669 } 670 if test.compressed { 671 buf.WriteString("Content-Encoding: gzip\r\n") 672 wr = gzip.NewWriter(wr) 673 } 674 buf.WriteString("\r\n") 675 676 chunk := bytes.Repeat([]byte{'x'}, 1000) 677 for i := 0; i < 1000; i++ { 678 if test.compressed { 679 // Otherwise this compresses too well. 680 _, err := io.ReadFull(rand.Reader, chunk) 681 checkErr(err, "rand.Reader ReadFull") 682 } 683 wr.Write(chunk) 684 } 685 if test.compressed { 686 err := wr.(*gzip.Writer).Close() 687 checkErr(err, "compressor close") 688 } 689 if test.chunked { 690 buf.WriteString("0\r\n\r\n") 691 } 692 buf.WriteString("Next Request Here") 693 694 bufr := bufio.NewReader(&buf) 695 resp, err := ReadResponse(bufr, dummyReq("GET")) 696 checkErr(err, "ReadResponse") 697 expectedLength := int64(-1) 698 if !test.chunked { 699 expectedLength = 1000000 700 } 701 if resp.ContentLength != expectedLength { 702 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength) 703 } 704 if resp.Body == nil { 705 fatalf("nil body") 706 } 707 if test.compressed { 708 gzReader, err := gzip.NewReader(resp.Body) 709 checkErr(err, "gzip.NewReader") 710 resp.Body = &readerAndCloser{gzReader, resp.Body} 711 } 712 713 rbuf := make([]byte, 2500) 714 n, err := io.ReadFull(resp.Body, rbuf) 715 checkErr(err, "2500 byte ReadFull") 716 if n != 2500 { 717 fatalf("ReadFull only read %d bytes", n) 718 } 719 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) { 720 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf)) 721 } 722 resp.Body.Close() 723 724 rest, err := io.ReadAll(bufr) 725 checkErr(err, "ReadAll on remainder") 726 if e, g := "Next Request Here", string(rest); e != g { 727 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string { 728 return fmt.Sprintf("x(repeated x%d)", len(match)) 729 }) 730 fatalf("remainder = %q, expected %q", g, e) 731 } 732 } 733 } 734 735 func diff(t *testing.T, prefix string, have, want interface{}) { 736 t.Helper() 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 }