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 	"go/ast"
     14 	"io"
     15 	"io/ioutil"
     16 	"net/http/internal"
     17 	"net/url"
     18 	"reflect"
     19 	"regexp"
     20 	"strings"
     21 	"testing"
     22 )
     23 
     24 type respTest struct {
     25 	Raw  string
     26 	Resp Response
     27 	Body string
     28 }
     29 
     30 func dummyReq(method string) *Request {
     31 	return &Request{Method: method}
     32 }
     33 
     34 func dummyReq11(method string) *Request {
     35 	return &Request{Method: method, Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1}
     36 }
     37 
     38 var 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 	// Chunked response with Content-Length.
    161 	{
    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",
    170 
    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"},
    182 		},
    183 
    184 		"Body here\n",
    185 	},
    186 
    187 	// Chunked response in response to a HEAD request
    188 	{
    189 		"HTTP/1.1 200 OK\r\n" +
    190 			"Transfer-Encoding: chunked\r\n" +
    191 			"\r\n",
    192 
    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,
    204 		},
    205 
    206 		"",
    207 	},
    208 
    209 	// Content-Length in response to a HEAD request
    210 	{
    211 		"HTTP/1.0 200 OK\r\n" +
    212 			"Content-Length: 256\r\n" +
    213 			"\r\n",
    214 
    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,
    226 		},
    227 
    228 		"",
    229 	},
    230 
    231 	// Content-Length in response to a HEAD request with HTTP/1.1
    232 	{
    233 		"HTTP/1.1 200 OK\r\n" +
    234 			"Content-Length: 256\r\n" +
    235 			"\r\n",
    236 
    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,
    248 		},
    249 
    250 		"",
    251 	},
    252 
    253 	// No Content-Length or Chunked in response to a HEAD request
    254 	{
    255 		"HTTP/1.0 200 OK\r\n" +
    256 			"\r\n",
    257 
    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,
    269 		},
    270 
    271 		"",
    272 	},
    273 
    274 	// explicit Content-Length of 0.
    275 	{
    276 		"HTTP/1.1 200 OK\r\n" +
    277 			"Content-Length: 0\r\n" +
    278 			"\r\n",
    279 
    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"},
    289 			},
    290 			Close:         false,
    291 			ContentLength: 0,
    292 		},
    293 
    294 		"",
    295 	},
    296 
    297 	// Status line without a Reason-Phrase, but trailing space.
    298 	// (permitted by RFC 2616)
    299 	{
    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,
    311 		},
    312 
    313 		"",
    314 	},
    315 
    316 	// Status line without a Reason-Phrase, and no trailing space.
    317 	// (not permitted by RFC 2616, but we'll accept it anyway)
    318 	{
    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,
    330 		},
    331 
    332 		"",
    333 	},
    334 
    335 	// golang.org/issue/4767: don't special-case multipart/byteranges responses
    336 	{
    337 		`HTTP/1.1 206 Partial Content
    338 Connection: close
    339 Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
    340 
    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"},
    351 			},
    352 			Close:         true,
    353 			ContentLength: -1,
    354 		},
    355 
    356 		"some body",
    357 	},
    358 
    359 	// Unchunked response without Content-Length, Request is nil
    360 	{
    361 		"HTTP/1.0 200 OK\r\n" +
    362 			"Connection: close\r\n" +
    363 			"\r\n" +
    364 			"Body here\n",
    365 
    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?
    374 			},
    375 			Close:         true,
    376 			ContentLength: -1,
    377 		},
    378 
    379 		"Body here\n",
    380 	},
    381 
    382 	// 206 Partial Content. golang.org/issue/8923
    383 	{
    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",
    390 
    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"},
    403 			},
    404 			ContentLength: 6,
    405 		},
    406 
    407 		"foobar",
    408 	},
    409 
    410 	// Both keep-alive and close, on the same Connection line. (Issue 8840)
    411 	{
    412 		"HTTP/1.1 200 OK\r\n" +
    413 			"Content-Length: 256\r\n" +
    414 			"Connection: keep-alive, close\r\n" +
    415 			"\r\n",
    416 
    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"},
    426 			},
    427 			TransferEncoding: nil,
    428 			Close:            true,
    429 			ContentLength:    256,
    430 		},
    431 
    432 		"",
    433 	},
    434 
    435 	// Both keep-alive and close, on different Connection lines. (Issue 8840)
    436 	{
    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",
    442 
    443 		Response{
    444 			Status:     "200 OK",
    445 			StatusCode: 200,
    446 			Proto:      "HTTP/1.1",
    447 			ProtoMajor: 1,
    448 			ProtoMinor: 1,
    449 			Request:    dummyReq("HEAD"),
    450 			Header: Header{
    451 				"Content-Length": {"256"},
    452 			},
    453 			TransferEncoding: nil,
    454 			Close:            true,
    455 			ContentLength:    256,
    456 		},
    457 
    458 		"",
    459 	},
    460 
    461 	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
    462 	// Without a Content-Length.
    463 	{
    464 		"HTTP/1.0 200 OK\r\n" +
    465 			"Transfer-Encoding: bogus\r\n" +
    466 			"\r\n" +
    467 			"Body here\n",
    468 
    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,
    479 		},
    480 
    481 		"Body here\n",
    482 	},
    483 
    484 	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
    485 	// With a Content-Length.
    486 	{
    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",
    492 
    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"},
    502 			},
    503 			Close:         true,
    504 			ContentLength: 10,
    505 		},
    506 
    507 		"Body here\n",
    508 	},
    509 
    510 	{
    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"},
    529 			},
    530 			Close:         false,
    531 			ContentLength: 23,
    532 		},
    533 		"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
    534 	},
    535 }
    536 
    537 // tests successful calls to ReadResponse, and inspects the returned Response.
    538 // For error cases, see TestReadResponseErrors below.
    539 func TestReadResponse(t *testing.T) {
    540 	for i, tt := range respTests {
    541 		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
    542 		if err != nil {
    543 			t.Errorf("#%d: %v", i, err)
    544 			continue
    545 		}
    546 		rbody := resp.Body
    547 		resp.Body = nil
    548 		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
    549 		var bout bytes.Buffer
    550 		if rbody != nil {
    551 			_, err = io.Copy(&bout, rbody)
    552 			if err != nil {
    553 				t.Errorf("#%d: %v", i, err)
    554 				continue
    555 			}
    556 			rbody.Close()
    557 		}
    558 		body := bout.String()
    559 		if body != tt.Body {
    560 			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
    561 		}
    562 	}
    563 }
    564 
    565 func TestWriteResponse(t *testing.T) {
    566 	for i, tt := range respTests {
    567 		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
    568 		if err != nil {
    569 			t.Errorf("#%d: %v", i, err)
    570 			continue
    571 		}
    572 		err = resp.Write(ioutil.Discard)
    573 		if err != nil {
    574 			t.Errorf("#%d: %v", i, err)
    575 			continue
    576 		}
    577 	}
    578 }
    579 
    580 var readResponseCloseInMiddleTests = []struct {
    581 	chunked, compressed bool
    582 }{
    583 	{false, false},
    584 	{true, false},
    585 	{true, true},
    586 }
    587 
    588 // TestReadResponseCloseInMiddle tests that closing a body after
    589 // reading only part of its contents advances the read to the end of
    590 // the request, right up until the next request.
    591 func TestReadResponseCloseInMiddle(t *testing.T) {
    592 	t.Parallel()
    593 	for _, test := range readResponseCloseInMiddleTests {
    594 		fatalf := func(format string, args ...interface{}) {
    595 			args = append([]interface{}{test.chunked, test.compressed}, args...)
    596 			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
    597 		}
    598 		checkErr := func(err error, msg string) {
    599 			if err == nil {
    600 				return
    601 			}
    602 			fatalf(msg+": %v", err)
    603 		}
    604 		var buf bytes.Buffer
    605 		buf.WriteString("HTTP/1.1 200 OK\r\n")
    606 		if test.chunked {
    607 			buf.WriteString("Transfer-Encoding: chunked\r\n")
    608 		} else {
    609 			buf.WriteString("Content-Length: 1000000\r\n")
    610 		}
    611 		var wr io.Writer = &buf
    612 		if test.chunked {
    613 			wr = internal.NewChunkedWriter(wr)
    614 		}
    615 		if test.compressed {
    616 			buf.WriteString("Content-Encoding: gzip\r\n")
    617 			wr = gzip.NewWriter(wr)
    618 		}
    619 		buf.WriteString("\r\n")
    620 
    621 		chunk := bytes.Repeat([]byte{'x'}, 1000)
    622 		for i := 0; i < 1000; i++ {
    623 			if test.compressed {
    624 				// Otherwise this compresses too well.
    625 				_, err := io.ReadFull(rand.Reader, chunk)
    626 				checkErr(err, "rand.Reader ReadFull")
    627 			}
    628 			wr.Write(chunk)
    629 		}
    630 		if test.compressed {
    631 			err := wr.(*gzip.Writer).Close()
    632 			checkErr(err, "compressor close")
    633 		}
    634 		if test.chunked {
    635 			buf.WriteString("0\r\n\r\n")
    636 		}
    637 		buf.WriteString("Next Request Here")
    638 
    639 		bufr := bufio.NewReader(&buf)
    640 		resp, err := ReadResponse(bufr, dummyReq("GET"))
    641 		checkErr(err, "ReadResponse")
    642 		expectedLength := int64(-1)
    643 		if !test.chunked {
    644 			expectedLength = 1000000
    645 		}
    646 		if resp.ContentLength != expectedLength {
    647 			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
    648 		}
    649 		if resp.Body == nil {
    650 			fatalf("nil body")
    651 		}
    652 		if test.compressed {
    653 			gzReader, err := gzip.NewReader(resp.Body)
    654 			checkErr(err, "gzip.NewReader")
    655 			resp.Body = &readerAndCloser{gzReader, resp.Body}
    656 		}
    657 
    658 		rbuf := make([]byte, 2500)
    659 		n, err := io.ReadFull(resp.Body, rbuf)
    660 		checkErr(err, "2500 byte ReadFull")
    661 		if n != 2500 {
    662 			fatalf("ReadFull only read %d bytes", n)
    663 		}
    664 		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
    665 			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
    666 		}
    667 		resp.Body.Close()
    668 
    669 		rest, err := ioutil.ReadAll(bufr)
    670 		checkErr(err, "ReadAll on remainder")
    671 		if e, g := "Next Request Here", string(rest); e != g {
    672 			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
    673 				return fmt.Sprintf("x(repeated x%d)", len(match))
    674 			})
    675 			fatalf("remainder = %q, expected %q", g, e)
    676 		}
    677 	}
    678 }
    679 
    680 func diff(t *testing.T, prefix string, have, want interface{}) {
    681 	hv := reflect.ValueOf(have).Elem()
    682 	wv := reflect.ValueOf(want).Elem()
    683 	if hv.Type() != wv.Type() {
    684 		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
    685 	}
    686 	for i := 0; i < hv.NumField(); i++ {
    687 		name := hv.Type().Field(i).Name
    688 		if !ast.IsExported(name) {
    689 			continue
    690 		}
    691 		hf := hv.Field(i).Interface()
    692 		wf := wv.Field(i).Interface()
    693 		if !reflect.DeepEqual(hf, wf) {
    694 			t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
    695 		}
    696 	}
    697 }
    698 
    699 type responseLocationTest struct {
    700 	location string // Response's Location header or ""
    701 	requrl   string // Response.Request.URL or ""
    702 	want     string
    703 	wantErr  error
    704 }
    705 
    706 var responseLocationTests = []responseLocationTest{
    707 	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
    708 	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
    709 	{"", "http://bar.com/baz", "", ErrNoLocation},
    710 	{"/bar", "", "/bar", nil},
    711 }
    712 
    713 func TestLocationResponse(t *testing.T) {
    714 	for i, tt := range responseLocationTests {
    715 		res := new(Response)
    716 		res.Header = make(Header)
    717 		res.Header.Set("Location", tt.location)
    718 		if tt.requrl != "" {
    719 			res.Request = &Request{}
    720 			var err error
    721 			res.Request.URL, err = url.Parse(tt.requrl)
    722 			if err != nil {
    723 				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
    724 			}
    725 		}
    726 
    727 		got, err := res.Location()
    728 		if tt.wantErr != nil {
    729 			if err == nil {
    730 				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
    731 				continue
    732 			}
    733 			if g, e := err.Error(), tt.wantErr.Error(); g != e {
    734 				t.Errorf("%d. err=%q; want %q", i, g, e)
    735 				continue
    736 			}
    737 			continue
    738 		}
    739 		if err != nil {
    740 			t.Errorf("%d. err=%q", i, err)
    741 			continue
    742 		}
    743 		if g, e := got.String(), tt.want; g != e {
    744 			t.Errorf("%d. Location=%q; want %q", i, g, e)
    745 		}
    746 	}
    747 }
    748 
    749 func TestResponseStatusStutter(t *testing.T) {
    750 	r := &Response{
    751 		Status:     "123 some status",
    752 		StatusCode: 123,
    753 		ProtoMajor: 1,
    754 		ProtoMinor: 3,
    755 	}
    756 	var buf bytes.Buffer
    757 	r.Write(&buf)
    758 	if strings.Contains(buf.String(), "123 123") {
    759 		t.Errorf("stutter in status: %s", buf.String())
    760 	}
    761 }
    762 
    763 func TestResponseContentLengthShortBody(t *testing.T) {
    764 	const shortBody = "Short body, not 123 bytes."
    765 	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
    766 		"Content-Length: 123\r\n" +
    767 		"\r\n" +
    768 		shortBody))
    769 	res, err := ReadResponse(br, &Request{Method: "GET"})
    770 	if err != nil {
    771 		t.Fatal(err)
    772 	}
    773 	if res.ContentLength != 123 {
    774 		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
    775 	}
    776 	var buf bytes.Buffer
    777 	n, err := io.Copy(&buf, res.Body)
    778 	if n != int64(len(shortBody)) {
    779 		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
    780 	}
    781 	if buf.String() != shortBody {
    782 		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
    783 	}
    784 	if err != io.ErrUnexpectedEOF {
    785 		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
    786 	}
    787 }
    788 
    789 // Test various ReadResponse error cases. (also tests success cases, but mostly
    790 // it's about errors).  This does not test anything involving the bodies. Only
    791 // the return value from ReadResponse itself.
    792 func TestReadResponseErrors(t *testing.T) {
    793 	type testCase struct {
    794 		name    string // optional, defaults to in
    795 		in      string
    796 		header  Header
    797 		wantErr interface{} // nil, err value, or string substring
    798 	}
    799 
    800 	status := func(s string, wantErr interface{}) testCase {
    801 		if wantErr == true {
    802 			wantErr = "malformed HTTP status code"
    803 		}
    804 		return testCase{
    805 			name:    fmt.Sprintf("status %q", s),
    806 			in:      "HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
    807 			wantErr: wantErr,
    808 		}
    809 	}
    810 
    811 	version := func(s string, wantErr interface{}) testCase {
    812 		if wantErr == true {
    813 			wantErr = "malformed HTTP version"
    814 		}
    815 		return testCase{
    816 			name:    fmt.Sprintf("version %q", s),
    817 			in:      s + " 200 OK\r\n\r\n",
    818 			wantErr: wantErr,
    819 		}
    820 	}
    821 
    822 	contentLength := func(status, body string, wantErr interface{}, header Header) testCase {
    823 		return testCase{
    824 			name:    fmt.Sprintf("status %q %q", status, body),
    825 			in:      fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
    826 			wantErr: wantErr,
    827 			header:  header,
    828 		}
    829 	}
    830 
    831 	errMultiCL := "message cannot contain multiple Content-Length headers"
    832 
    833 	tests := []testCase{
    834 		{"", "", nil, io.ErrUnexpectedEOF},
    835 		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", nil, io.ErrUnexpectedEOF},
    836 		{"", "HTTP/1.1", nil, "malformed HTTP response"},
    837 		{"", "HTTP/2.0", nil, "malformed HTTP response"},
    838 		status("20X Unknown", true),
    839 		status("abcd Unknown", true),
    840 		status("/ OK", true),
    841 		status(" Unknown", true),
    842 		status("c8 OK", true),
    843 		status("0x12d Moved Permanently", true),
    844 		status("200 OK", nil),
    845 		status("000 OK", nil),
    846 		status("001 OK", nil),
    847 		status("404 NOTFOUND", nil),
    848 		status("20 OK", true),
    849 		status("00 OK", true),
    850 		status("-10 OK", true),
    851 		status("1000 OK", true),
    852 		status("999 Done", nil),
    853 		status("-1 OK", true),
    854 		status("-200 OK", true),
    855 		version("HTTP/1.2", nil),
    856 		version("HTTP/2.0", nil),
    857 		version("HTTP/1.100000000002", true),
    858 		version("HTTP/1.-1", true),
    859 		version("HTTP/A.B", true),
    860 		version("HTTP/1", true),
    861 		version("http/1.1", true),
    862 
    863 		contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL, nil),
    864 		contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"7"}}),
    865 		contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL, nil),
    866 		contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil, Header{"Content-Length": {"0"}}),
    867 		contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil, nil),
    868 		contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL, nil),
    869 
    870 		// multiple content-length headers for 204 and 304 should still be checked
    871 		contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL, nil),
    872 		contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil, nil),
    873 		contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL, nil),
    874 		contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil, nil),
    875 	}
    876 
    877 	for i, tt := range tests {
    878 		br := bufio.NewReader(strings.NewReader(tt.in))
    879 		_, rerr := ReadResponse(br, nil)
    880 		if err := matchErr(rerr, tt.wantErr); err != nil {
    881 			name := tt.name
    882 			if name == "" {
    883 				name = fmt.Sprintf("%d. input %q", i, tt.in)
    884 			}
    885 			t.Errorf("%s: %v", name, err)
    886 		}
    887 	}
    888 }
    889 
    890 // wantErr can be nil, an error value to match exactly, or type string to
    891 // match a substring.
    892 func matchErr(err error, wantErr interface{}) error {
    893 	if err == nil {
    894 		if wantErr == nil {
    895 			return nil
    896 		}
    897 		if sub, ok := wantErr.(string); ok {
    898 			return fmt.Errorf("unexpected success; want error with substring %q", sub)
    899 		}
    900 		return fmt.Errorf("unexpected success; want error %v", wantErr)
    901 	}
    902 	if wantErr == nil {
    903 		return fmt.Errorf("%v; want success", err)
    904 	}
    905 	if sub, ok := wantErr.(string); ok {
    906 		if strings.Contains(err.Error(), sub) {
    907 			return nil
    908 		}
    909 		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
    910 	}
    911 	if err == wantErr {
    912 		return nil
    913 	}
    914 	return fmt.Errorf("%v; want %v", err, wantErr)
    915 }
    916 
    917 func TestNeedsSniff(t *testing.T) {
    918 	// needsSniff returns true with an empty response.
    919 	r := &response{}
    920 	if got, want := r.needsSniff(), true; got != want {
    921 		t.Errorf("needsSniff = %t; want %t", got, want)
    922 	}
    923 	// needsSniff returns false when Content-Type = nil.
    924 	r.handlerHeader = Header{"Content-Type": nil}
    925 	if got, want := r.needsSniff(), false; got != want {
    926 		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
    927 	}
    928 }
    929