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