libgo: update to go1.9
[official-gcc.git] / libgo / go / net / http / response_test.go
blobf1a50bd59891ae62dcd890daeced95b6129fb112
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.
5 package http
7 import (
8 "bufio"
9 "bytes"
10 "compress/gzip"
11 "crypto/rand"
12 "fmt"
13 "go/ast"
14 "io"
15 "io/ioutil"
16 "net/http/internal"
17 "net/url"
18 "reflect"
19 "regexp"
20 "strings"
21 "testing"
24 type respTest struct {
25 Raw string
26 Resp Response
27 Body string
30 func dummyReq(method string) *Request {
31 return &Request{Method: method}
34 func dummyReq11(method string) *Request {
35 return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
38 var respTests = []respTest{
39 // Unchunked response without Content-Length.
41 "HTTP/1.0 200 OK\r\n" +
42 "Connection: close\r\n" +
43 "\r\n" +
44 "Body here\n",
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?
56 Close: true,
57 ContentLength: -1,
60 "Body here\n",
63 // Unchunked HTTP/1.1 response without Content-Length or
64 // Connection headers.
66 "HTTP/1.1 200 OK\r\n" +
67 "\r\n" +
68 "Body here\n",
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,
82 "Body here\n",
85 // Unchunked HTTP/1.1 204 response without Content-Length.
87 "HTTP/1.1 204 No Content\r\n" +
88 "\r\n" +
89 "Body should not be read!\n",
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,
106 // Unchunked response with Content-Length.
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",
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"},
125 Close: true,
126 ContentLength: 10,
129 "Body here\n",
132 // Chunked response without Content-Length.
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",
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"},
157 "Body here\ncontinued",
160 // Chunked response with Content-Length.
162 "HTTP/1.1 200 OK\r\n" +
163 "Transfer-Encoding: chunked\r\n" +
164 "Content-Length: 10\r\n" +
165 "\r\n" +
166 "0a\r\n" +
167 "Body here\n\r\n" +
168 "0\r\n" +
169 "\r\n",
171 Response{
172 Status: "200 OK",
173 StatusCode: 200,
174 Proto: "HTTP/1.1",
175 ProtoMajor: 1,
176 ProtoMinor: 1,
177 Request: dummyReq("GET"),
178 Header: Header{},
179 Close: false,
180 ContentLength: -1,
181 TransferEncoding: []string{"chunked"},
184 "Body here\n",
187 // Chunked response in response to a HEAD request
189 "HTTP/1.1 200 OK\r\n" +
190 "Transfer-Encoding: chunked\r\n" +
191 "\r\n",
193 Response{
194 Status: "200 OK",
195 StatusCode: 200,
196 Proto: "HTTP/1.1",
197 ProtoMajor: 1,
198 ProtoMinor: 1,
199 Request: dummyReq("HEAD"),
200 Header: Header{},
201 TransferEncoding: []string{"chunked"},
202 Close: false,
203 ContentLength: -1,
209 // Content-Length in response to a HEAD request
211 "HTTP/1.0 200 OK\r\n" +
212 "Content-Length: 256\r\n" +
213 "\r\n",
215 Response{
216 Status: "200 OK",
217 StatusCode: 200,
218 Proto: "HTTP/1.0",
219 ProtoMajor: 1,
220 ProtoMinor: 0,
221 Request: dummyReq("HEAD"),
222 Header: Header{"Content-Length": {"256"}},
223 TransferEncoding: nil,
224 Close: true,
225 ContentLength: 256,
231 // Content-Length in response to a HEAD request with HTTP/1.1
233 "HTTP/1.1 200 OK\r\n" +
234 "Content-Length: 256\r\n" +
235 "\r\n",
237 Response{
238 Status: "200 OK",
239 StatusCode: 200,
240 Proto: "HTTP/1.1",
241 ProtoMajor: 1,
242 ProtoMinor: 1,
243 Request: dummyReq("HEAD"),
244 Header: Header{"Content-Length": {"256"}},
245 TransferEncoding: nil,
246 Close: false,
247 ContentLength: 256,
253 // No Content-Length or Chunked in response to a HEAD request
255 "HTTP/1.0 200 OK\r\n" +
256 "\r\n",
258 Response{
259 Status: "200 OK",
260 StatusCode: 200,
261 Proto: "HTTP/1.0",
262 ProtoMajor: 1,
263 ProtoMinor: 0,
264 Request: dummyReq("HEAD"),
265 Header: Header{},
266 TransferEncoding: nil,
267 Close: true,
268 ContentLength: -1,
274 // explicit Content-Length of 0.
276 "HTTP/1.1 200 OK\r\n" +
277 "Content-Length: 0\r\n" +
278 "\r\n",
280 Response{
281 Status: "200 OK",
282 StatusCode: 200,
283 Proto: "HTTP/1.1",
284 ProtoMajor: 1,
285 ProtoMinor: 1,
286 Request: dummyReq("GET"),
287 Header: Header{
288 "Content-Length": {"0"},
290 Close: false,
291 ContentLength: 0,
297 // Status line without a Reason-Phrase, but trailing space.
298 // (permitted by RFC 2616)
300 "HTTP/1.0 303 \r\n\r\n",
301 Response{
302 Status: "303 ",
303 StatusCode: 303,
304 Proto: "HTTP/1.0",
305 ProtoMajor: 1,
306 ProtoMinor: 0,
307 Request: dummyReq("GET"),
308 Header: Header{},
309 Close: true,
310 ContentLength: -1,
316 // Status line without a Reason-Phrase, and no trailing space.
317 // (not permitted by RFC 2616, but we'll accept it anyway)
319 "HTTP/1.0 303\r\n\r\n",
320 Response{
321 Status: "303",
322 StatusCode: 303,
323 Proto: "HTTP/1.0",
324 ProtoMajor: 1,
325 ProtoMinor: 0,
326 Request: dummyReq("GET"),
327 Header: Header{},
328 Close: true,
329 ContentLength: -1,
335 // golang.org/issue/4767: don't special-case multipart/byteranges responses
337 `HTTP/1.1 206 Partial Content
338 Connection: close
339 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
341 some body`,
342 Response{
343 Status: "206 Partial Content",
344 StatusCode: 206,
345 Proto: "HTTP/1.1",
346 ProtoMajor: 1,
347 ProtoMinor: 1,
348 Request: dummyReq("GET"),
349 Header: Header{
350 "Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
352 Close: true,
353 ContentLength: -1,
356 "some body",
359 // Unchunked response without Content-Length, Request is nil
361 "HTTP/1.0 200 OK\r\n" +
362 "Connection: close\r\n" +
363 "\r\n" +
364 "Body here\n",
366 Response{
367 Status: "200 OK",
368 StatusCode: 200,
369 Proto: "HTTP/1.0",
370 ProtoMajor: 1,
371 ProtoMinor: 0,
372 Header: Header{
373 "Connection": {"close"}, // TODO(rsc): Delete?
375 Close: true,
376 ContentLength: -1,
379 "Body here\n",
382 // 206 Partial Content. golang.org/issue/8923
384 "HTTP/1.1 206 Partial Content\r\n" +
385 "Content-Type: text/plain; charset=utf-8\r\n" +
386 "Accept-Ranges: bytes\r\n" +
387 "Content-Range: bytes 0-5/1862\r\n" +
388 "Content-Length: 6\r\n\r\n" +
389 "foobar",
391 Response{
392 Status: "206 Partial Content",
393 StatusCode: 206,
394 Proto: "HTTP/1.1",
395 ProtoMajor: 1,
396 ProtoMinor: 1,
397 Request: dummyReq("GET"),
398 Header: Header{
399 "Accept-Ranges": []string{"bytes"},
400 "Content-Length": []string{"6"},
401 "Content-Type": []string{"text/plain; charset=utf-8"},
402 "Content-Range": []string{"bytes 0-5/1862"},
404 ContentLength: 6,
407 "foobar",
410 // Both keep-alive and close, on the same Connection line. (Issue 8840)
412 "HTTP/1.1 200 OK\r\n" +
413 "Content-Length: 256\r\n" +
414 "Connection: keep-alive, close\r\n" +
415 "\r\n",
417 Response{
418 Status: "200 OK",
419 StatusCode: 200,
420 Proto: "HTTP/1.1",
421 ProtoMajor: 1,
422 ProtoMinor: 1,
423 Request: dummyReq("HEAD"),
424 Header: Header{
425 "Content-Length": {"256"},
427 TransferEncoding: nil,
428 Close: true,
429 ContentLength: 256,
435 // Both keep-alive and close, on different Connection lines. (Issue 8840)
437 "HTTP/1.1 200 OK\r\n" +
438 "Content-Length: 256\r\n" +
439 "Connection: keep-alive\r\n" +
440 "Connection: close\r\n" +
441 "\r\n",
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"},
453 TransferEncoding: nil,
454 Close: true,
455 ContentLength: 256,
461 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
462 // Without a Content-Length.
464 "HTTP/1.0 200 OK\r\n" +
465 "Transfer-Encoding: bogus\r\n" +
466 "\r\n" +
467 "Body here\n",
469 Response{
470 Status: "200 OK",
471 StatusCode: 200,
472 Proto: "HTTP/1.0",
473 ProtoMajor: 1,
474 ProtoMinor: 0,
475 Request: dummyReq("GET"),
476 Header: Header{},
477 Close: true,
478 ContentLength: -1,
481 "Body here\n",
484 // Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
485 // With a Content-Length.
487 "HTTP/1.0 200 OK\r\n" +
488 "Transfer-Encoding: bogus\r\n" +
489 "Content-Length: 10\r\n" +
490 "\r\n" +
491 "Body here\n",
493 Response{
494 Status: "200 OK",
495 StatusCode: 200,
496 Proto: "HTTP/1.0",
497 ProtoMajor: 1,
498 ProtoMinor: 0,
499 Request: dummyReq("GET"),
500 Header: Header{
501 "Content-Length": {"10"},
503 Close: true,
504 ContentLength: 10,
507 "Body here\n",
511 "HTTP/1.1 200 OK\r\n" +
512 "Content-Encoding: gzip\r\n" +
513 "Content-Length: 23\r\n" +
514 "Connection: keep-alive\r\n" +
515 "Keep-Alive: timeout=7200\r\n\r\n" +
516 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
517 Response{
518 Status: "200 OK",
519 StatusCode: 200,
520 Proto: "HTTP/1.1",
521 ProtoMajor: 1,
522 ProtoMinor: 1,
523 Request: dummyReq("GET"),
524 Header: Header{
525 "Content-Length": {"23"},
526 "Content-Encoding": {"gzip"},
527 "Connection": {"keep-alive"},
528 "Keep-Alive": {"timeout=7200"},
530 Close: false,
531 ContentLength: 23,
533 "\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
536 // Issue 19989: two spaces between HTTP version and status.
538 "HTTP/1.0 401 Unauthorized\r\n" +
539 "Content-type: text/html\r\n" +
540 "WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
541 "Your Authentication failed.\r\n",
542 Response{
543 Status: "401 Unauthorized",
544 StatusCode: 401,
545 Proto: "HTTP/1.0",
546 ProtoMajor: 1,
547 ProtoMinor: 0,
548 Request: dummyReq("GET"),
549 Header: Header{
550 "Content-Type": {"text/html"},
551 "Www-Authenticate": {`Basic realm=""`},
553 Close: true,
554 ContentLength: -1,
556 "Your Authentication failed.\r\n",
560 // tests successful calls to ReadResponse, and inspects the returned Response.
561 // For error cases, see TestReadResponseErrors below.
562 func TestReadResponse(t *testing.T) {
563 for i, tt := range respTests {
564 resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
565 if err != nil {
566 t.Errorf("#%d: %v", i, err)
567 continue
569 rbody := resp.Body
570 resp.Body = nil
571 diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
572 var bout bytes.Buffer
573 if rbody != nil {
574 _, err = io.Copy(&bout, rbody)
575 if err != nil {
576 t.Errorf("#%d: %v", i, err)
577 continue
579 rbody.Close()
581 body := bout.String()
582 if body != tt.Body {
583 t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
588 func TestWriteResponse(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
595 err = resp.Write(ioutil.Discard)
596 if err != nil {
597 t.Errorf("#%d: %v", i, err)
598 continue
603 var readResponseCloseInMiddleTests = []struct {
604 chunked, compressed bool
606 {false, false},
607 {true, false},
608 {true, true},
611 // TestReadResponseCloseInMiddle tests that closing a body after
612 // reading only part of its contents advances the read to the end of
613 // the request, right up until the next request.
614 func TestReadResponseCloseInMiddle(t *testing.T) {
615 t.Parallel()
616 for _, test := range readResponseCloseInMiddleTests {
617 fatalf := func(format string, args ...interface{}) {
618 args = append([]interface{}{test.chunked, test.compressed}, args...)
619 t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
621 checkErr := func(err error, msg string) {
622 if err == nil {
623 return
625 fatalf(msg+": %v", err)
627 var buf bytes.Buffer
628 buf.WriteString("HTTP/1.1 200 OK\r\n")
629 if test.chunked {
630 buf.WriteString("Transfer-Encoding: chunked\r\n")
631 } else {
632 buf.WriteString("Content-Length: 1000000\r\n")
634 var wr io.Writer = &buf
635 if test.chunked {
636 wr = internal.NewChunkedWriter(wr)
638 if test.compressed {
639 buf.WriteString("Content-Encoding: gzip\r\n")
640 wr = gzip.NewWriter(wr)
642 buf.WriteString("\r\n")
644 chunk := bytes.Repeat([]byte{'x'}, 1000)
645 for i := 0; i < 1000; i++ {
646 if test.compressed {
647 // Otherwise this compresses too well.
648 _, err := io.ReadFull(rand.Reader, chunk)
649 checkErr(err, "rand.Reader ReadFull")
651 wr.Write(chunk)
653 if test.compressed {
654 err := wr.(*gzip.Writer).Close()
655 checkErr(err, "compressor close")
657 if test.chunked {
658 buf.WriteString("0\r\n\r\n")
660 buf.WriteString("Next Request Here")
662 bufr := bufio.NewReader(&buf)
663 resp, err := ReadResponse(bufr, dummyReq("GET"))
664 checkErr(err, "ReadResponse")
665 expectedLength := int64(-1)
666 if !test.chunked {
667 expectedLength = 1000000
669 if resp.ContentLength != expectedLength {
670 fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
672 if resp.Body == nil {
673 fatalf("nil body")
675 if test.compressed {
676 gzReader, err := gzip.NewReader(resp.Body)
677 checkErr(err, "gzip.NewReader")
678 resp.Body = &readerAndCloser{gzReader, resp.Body}
681 rbuf := make([]byte, 2500)
682 n, err := io.ReadFull(resp.Body, rbuf)
683 checkErr(err, "2500 byte ReadFull")
684 if n != 2500 {
685 fatalf("ReadFull only read %d bytes", n)
687 if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
688 fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
690 resp.Body.Close()
692 rest, err := ioutil.ReadAll(bufr)
693 checkErr(err, "ReadAll on remainder")
694 if e, g := "Next Request Here", string(rest); e != g {
695 g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
696 return fmt.Sprintf("x(repeated x%d)", len(match))
698 fatalf("remainder = %q, expected %q", g, e)
703 func diff(t *testing.T, prefix string, have, want interface{}) {
704 hv := reflect.ValueOf(have).Elem()
705 wv := reflect.ValueOf(want).Elem()
706 if hv.Type() != wv.Type() {
707 t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
709 for i := 0; i < hv.NumField(); i++ {
710 name := hv.Type().Field(i).Name
711 if !ast.IsExported(name) {
712 continue
714 hf := hv.Field(i).Interface()
715 wf := wv.Field(i).Interface()
716 if !reflect.DeepEqual(hf, wf) {
717 t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
722 type responseLocationTest struct {
723 location string // Response's Location header or ""
724 requrl string // Response.Request.URL or ""
725 want string
726 wantErr error
729 var responseLocationTests = []responseLocationTest{
730 {"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
731 {"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
732 {"", "http://bar.com/baz", "", ErrNoLocation},
733 {"/bar", "", "/bar", nil},
736 func TestLocationResponse(t *testing.T) {
737 for i, tt := range responseLocationTests {
738 res := new(Response)
739 res.Header = make(Header)
740 res.Header.Set("Location", tt.location)
741 if tt.requrl != "" {
742 res.Request = &Request{}
743 var err error
744 res.Request.URL, err = url.Parse(tt.requrl)
745 if err != nil {
746 t.Fatalf("bad test URL %q: %v", tt.requrl, err)
750 got, err := res.Location()
751 if tt.wantErr != nil {
752 if err == nil {
753 t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
754 continue
756 if g, e := err.Error(), tt.wantErr.Error(); g != e {
757 t.Errorf("%d. err=%q; want %q", i, g, e)
758 continue
760 continue
762 if err != nil {
763 t.Errorf("%d. err=%q", i, err)
764 continue
766 if g, e := got.String(), tt.want; g != e {
767 t.Errorf("%d. Location=%q; want %q", i, g, e)
772 func TestResponseStatusStutter(t *testing.T) {
773 r := &Response{
774 Status: "123 some status",
775 StatusCode: 123,
776 ProtoMajor: 1,
777 ProtoMinor: 3,
779 var buf bytes.Buffer
780 r.Write(&buf)
781 if strings.Contains(buf.String(), "123 123") {
782 t.Errorf("stutter in status: %s", buf.String())
786 func TestResponseContentLengthShortBody(t *testing.T) {
787 const shortBody = "Short body, not 123 bytes."
788 br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
789 "Content-Length: 123\r\n" +
790 "\r\n" +
791 shortBody))
792 res, err := ReadResponse(br, &Request{Method: "GET"})
793 if err != nil {
794 t.Fatal(err)
796 if res.ContentLength != 123 {
797 t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
799 var buf bytes.Buffer
800 n, err := io.Copy(&buf, res.Body)
801 if n != int64(len(shortBody)) {
802 t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
804 if buf.String() != shortBody {
805 t.Errorf("Read body %q; want %q", buf.String(), shortBody)
807 if err != io.ErrUnexpectedEOF {
808 t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
812 // Test various ReadResponse error cases. (also tests success cases, but mostly
813 // it's about errors). This does not test anything involving the bodies. Only
814 // the return value from ReadResponse itself.
815 func TestReadResponseErrors(t *testing.T) {
816 type testCase struct {
817 name string // optional, defaults to in
818 in string
819 header Header
820 wantErr interface{} // nil, err value, or string substring
823 status := func(s string, wantErr interface{}) testCase {
824 if wantErr == true {
825 wantErr = "malformed HTTP status code"
827 return testCase{
828 name: fmt.Sprintf("status %q", s),
829 in: "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
830 wantErr: wantErr,
834 version := func(s string, wantErr interface{}) testCase {
835 if wantErr == true {
836 wantErr = "malformed HTTP version"
838 return testCase{
839 name: fmt.Sprintf("version %q", s),
840 in: s + " 200 OK\r\n\r\n",
841 wantErr: wantErr,
845 contentLength := func(status, body string, wantErr interface{}, header Header) testCase {
846 return testCase{
847 name: fmt.Sprintf("status %q %q", status, body),
848 in: fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
849 wantErr: wantErr,
850 header: header,
854 errMultiCL := "message cannot contain multiple Content-Length headers"
856 tests := []testCase{
857 {"", "", nil, io.ErrUnexpectedEOF},
858 {"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", nil, io.ErrUnexpectedEOF},
859 {"", "HTTP/1.1", nil, "malformed HTTP response"},
860 {"", "HTTP/2.0", nil, "malformed HTTP response"},
861 status("20X Unknown", true),
862 status("abcd Unknown", true),
863 status("二百/两百 OK", true),
864 status(" Unknown", true),
865 status("c8 OK", true),
866 status("0x12d Moved Permanently", true),
867 status("200 OK", nil),
868 status("000 OK", nil),
869 status("001 OK", nil),
870 status("404 NOTFOUND", nil),
871 status("20 OK", true),
872 status("00 OK", true),
873 status("-10 OK", true),
874 status("1000 OK", true),
875 status("999 Done", nil),
876 status("-1 OK", true),
877 status("-200 OK", true),
878 version("HTTP/1.2", nil),
879 version("HTTP/2.0", nil),
880 version("HTTP/1.100000000002", true),
881 version("HTTP/1.-1", true),
882 version("HTTP/A.B", true),
883 version("HTTP/1", true),
884 version("http/1.1", true),
886 contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL, nil),
887 contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"7"}}),
888 contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL, nil),
889 contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"0"}}),
890 contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil, nil),
891 contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL, nil),
893 // multiple content-length headers for 204 and 304 should still be checked
894 contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL, nil),
895 contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil, nil),
896 contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL, nil),
897 contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil, nil),
900 for i, tt := range tests {
901 br := bufio.NewReader(strings.NewReader(tt.in))
902 _, rerr := ReadResponse(br, nil)
903 if err := matchErr(rerr, tt.wantErr); err != nil {
904 name := tt.name
905 if name == "" {
906 name = fmt.Sprintf("%d. input %q", i, tt.in)
908 t.Errorf("%s: %v", name, err)
913 // wantErr can be nil, an error value to match exactly, or type string to
914 // match a substring.
915 func matchErr(err error, wantErr interface{}) error {
916 if err == nil {
917 if wantErr == nil {
918 return nil
920 if sub, ok := wantErr.(string); ok {
921 return fmt.Errorf("unexpected success; want error with substring %q", sub)
923 return fmt.Errorf("unexpected success; want error %v", wantErr)
925 if wantErr == nil {
926 return fmt.Errorf("%v; want success", err)
928 if sub, ok := wantErr.(string); ok {
929 if strings.Contains(err.Error(), sub) {
930 return nil
932 return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
934 if err == wantErr {
935 return nil
937 return fmt.Errorf("%v; want %v", err, wantErr)
940 func TestNeedsSniff(t *testing.T) {
941 // needsSniff returns true with an empty response.
942 r := &response{}
943 if got, want := r.needsSniff(), true; got != want {
944 t.Errorf("needsSniff = %t; want %t", got, want)
946 // needsSniff returns false when Content-Type = nil.
947 r.handlerHeader = Header{"Content-Type": nil}
948 if got, want := r.needsSniff(), false; got != want {
949 t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
953 // A response should only write out single Connection: close header. Tests #19499.
954 func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
955 const connectionCloseHeader = "Connection: close"
957 res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
958 if err != nil {
959 t.Fatalf("ReadResponse failed %v", err)
962 var buf1 bytes.Buffer
963 if err = res.Write(&buf1); err != nil {
964 t.Fatalf("Write failed %v", err)
966 if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
967 t.Fatalf("ReadResponse failed %v", err)
970 var buf2 bytes.Buffer
971 if err = res.Write(&buf2); err != nil {
972 t.Fatalf("Write failed %v", err)
974 if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
975 t.Errorf("Found %d %q header", count, connectionCloseHeader)