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