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