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