...

Source file src/net/http/response_test.go

Documentation: net/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/token"
		14  	"io"
		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  	// Trailer header but no TransferEncoding
	 160  	{
	 161  		"HTTP/1.0 200 OK\r\n" +
	 162  			"Trailer: Content-MD5, Content-Sources\r\n" +
	 163  			"Content-Length: 10\r\n" +
	 164  			"Connection: close\r\n" +
	 165  			"\r\n" +
	 166  			"Body here\n",
	 167  
	 168  		Response{
	 169  			Status:		 "200 OK",
	 170  			StatusCode: 200,
	 171  			Proto:			"HTTP/1.0",
	 172  			ProtoMajor: 1,
	 173  			ProtoMinor: 0,
	 174  			Request:		dummyReq("GET"),
	 175  			Header: Header{
	 176  				"Connection":		 {"close"},
	 177  				"Content-Length": {"10"},
	 178  				"Trailer":				[]string{"Content-MD5, Content-Sources"},
	 179  			},
	 180  			Close:				 true,
	 181  			ContentLength: 10,
	 182  		},
	 183  
	 184  		"Body here\n",
	 185  	},
	 186  
	 187  	// Chunked response with Content-Length.
	 188  	{
	 189  		"HTTP/1.1 200 OK\r\n" +
	 190  			"Transfer-Encoding: chunked\r\n" +
	 191  			"Content-Length: 10\r\n" +
	 192  			"\r\n" +
	 193  			"0a\r\n" +
	 194  			"Body here\n\r\n" +
	 195  			"0\r\n" +
	 196  			"\r\n",
	 197  
	 198  		Response{
	 199  			Status:					 "200 OK",
	 200  			StatusCode:			 200,
	 201  			Proto:						"HTTP/1.1",
	 202  			ProtoMajor:			 1,
	 203  			ProtoMinor:			 1,
	 204  			Request:					dummyReq("GET"),
	 205  			Header:					 Header{},
	 206  			Close:						false,
	 207  			ContentLength:		-1,
	 208  			TransferEncoding: []string{"chunked"},
	 209  		},
	 210  
	 211  		"Body here\n",
	 212  	},
	 213  
	 214  	// Chunked response in response to a HEAD request
	 215  	{
	 216  		"HTTP/1.1 200 OK\r\n" +
	 217  			"Transfer-Encoding: chunked\r\n" +
	 218  			"\r\n",
	 219  
	 220  		Response{
	 221  			Status:					 "200 OK",
	 222  			StatusCode:			 200,
	 223  			Proto:						"HTTP/1.1",
	 224  			ProtoMajor:			 1,
	 225  			ProtoMinor:			 1,
	 226  			Request:					dummyReq("HEAD"),
	 227  			Header:					 Header{},
	 228  			TransferEncoding: []string{"chunked"},
	 229  			Close:						false,
	 230  			ContentLength:		-1,
	 231  		},
	 232  
	 233  		"",
	 234  	},
	 235  
	 236  	// Content-Length in response to a HEAD request
	 237  	{
	 238  		"HTTP/1.0 200 OK\r\n" +
	 239  			"Content-Length: 256\r\n" +
	 240  			"\r\n",
	 241  
	 242  		Response{
	 243  			Status:					 "200 OK",
	 244  			StatusCode:			 200,
	 245  			Proto:						"HTTP/1.0",
	 246  			ProtoMajor:			 1,
	 247  			ProtoMinor:			 0,
	 248  			Request:					dummyReq("HEAD"),
	 249  			Header:					 Header{"Content-Length": {"256"}},
	 250  			TransferEncoding: nil,
	 251  			Close:						true,
	 252  			ContentLength:		256,
	 253  		},
	 254  
	 255  		"",
	 256  	},
	 257  
	 258  	// Content-Length in response to a HEAD request with HTTP/1.1
	 259  	{
	 260  		"HTTP/1.1 200 OK\r\n" +
	 261  			"Content-Length: 256\r\n" +
	 262  			"\r\n",
	 263  
	 264  		Response{
	 265  			Status:					 "200 OK",
	 266  			StatusCode:			 200,
	 267  			Proto:						"HTTP/1.1",
	 268  			ProtoMajor:			 1,
	 269  			ProtoMinor:			 1,
	 270  			Request:					dummyReq("HEAD"),
	 271  			Header:					 Header{"Content-Length": {"256"}},
	 272  			TransferEncoding: nil,
	 273  			Close:						false,
	 274  			ContentLength:		256,
	 275  		},
	 276  
	 277  		"",
	 278  	},
	 279  
	 280  	// No Content-Length or Chunked in response to a HEAD request
	 281  	{
	 282  		"HTTP/1.0 200 OK\r\n" +
	 283  			"\r\n",
	 284  
	 285  		Response{
	 286  			Status:					 "200 OK",
	 287  			StatusCode:			 200,
	 288  			Proto:						"HTTP/1.0",
	 289  			ProtoMajor:			 1,
	 290  			ProtoMinor:			 0,
	 291  			Request:					dummyReq("HEAD"),
	 292  			Header:					 Header{},
	 293  			TransferEncoding: nil,
	 294  			Close:						true,
	 295  			ContentLength:		-1,
	 296  		},
	 297  
	 298  		"",
	 299  	},
	 300  
	 301  	// explicit Content-Length of 0.
	 302  	{
	 303  		"HTTP/1.1 200 OK\r\n" +
	 304  			"Content-Length: 0\r\n" +
	 305  			"\r\n",
	 306  
	 307  		Response{
	 308  			Status:		 "200 OK",
	 309  			StatusCode: 200,
	 310  			Proto:			"HTTP/1.1",
	 311  			ProtoMajor: 1,
	 312  			ProtoMinor: 1,
	 313  			Request:		dummyReq("GET"),
	 314  			Header: Header{
	 315  				"Content-Length": {"0"},
	 316  			},
	 317  			Close:				 false,
	 318  			ContentLength: 0,
	 319  		},
	 320  
	 321  		"",
	 322  	},
	 323  
	 324  	// Status line without a Reason-Phrase, but trailing space.
	 325  	// (permitted by RFC 7230, section 3.1.2)
	 326  	{
	 327  		"HTTP/1.0 303 \r\n\r\n",
	 328  		Response{
	 329  			Status:				"303 ",
	 330  			StatusCode:		303,
	 331  			Proto:				 "HTTP/1.0",
	 332  			ProtoMajor:		1,
	 333  			ProtoMinor:		0,
	 334  			Request:			 dummyReq("GET"),
	 335  			Header:				Header{},
	 336  			Close:				 true,
	 337  			ContentLength: -1,
	 338  		},
	 339  
	 340  		"",
	 341  	},
	 342  
	 343  	// Status line without a Reason-Phrase, and no trailing space.
	 344  	// (not permitted by RFC 7230, but we'll accept it anyway)
	 345  	{
	 346  		"HTTP/1.0 303\r\n\r\n",
	 347  		Response{
	 348  			Status:				"303",
	 349  			StatusCode:		303,
	 350  			Proto:				 "HTTP/1.0",
	 351  			ProtoMajor:		1,
	 352  			ProtoMinor:		0,
	 353  			Request:			 dummyReq("GET"),
	 354  			Header:				Header{},
	 355  			Close:				 true,
	 356  			ContentLength: -1,
	 357  		},
	 358  
	 359  		"",
	 360  	},
	 361  
	 362  	// golang.org/issue/4767: don't special-case multipart/byteranges responses
	 363  	{
	 364  		`HTTP/1.1 206 Partial Content
	 365  Connection: close
	 366  Content-Type: multipart/byteranges; boundary=18a75608c8f47cef
	 367  
	 368  some body`,
	 369  		Response{
	 370  			Status:		 "206 Partial Content",
	 371  			StatusCode: 206,
	 372  			Proto:			"HTTP/1.1",
	 373  			ProtoMajor: 1,
	 374  			ProtoMinor: 1,
	 375  			Request:		dummyReq("GET"),
	 376  			Header: Header{
	 377  				"Content-Type": []string{"multipart/byteranges; boundary=18a75608c8f47cef"},
	 378  			},
	 379  			Close:				 true,
	 380  			ContentLength: -1,
	 381  		},
	 382  
	 383  		"some body",
	 384  	},
	 385  
	 386  	// Unchunked response without Content-Length, Request is nil
	 387  	{
	 388  		"HTTP/1.0 200 OK\r\n" +
	 389  			"Connection: close\r\n" +
	 390  			"\r\n" +
	 391  			"Body here\n",
	 392  
	 393  		Response{
	 394  			Status:		 "200 OK",
	 395  			StatusCode: 200,
	 396  			Proto:			"HTTP/1.0",
	 397  			ProtoMajor: 1,
	 398  			ProtoMinor: 0,
	 399  			Header: Header{
	 400  				"Connection": {"close"}, // TODO(rsc): Delete?
	 401  			},
	 402  			Close:				 true,
	 403  			ContentLength: -1,
	 404  		},
	 405  
	 406  		"Body here\n",
	 407  	},
	 408  
	 409  	// 206 Partial Content. golang.org/issue/8923
	 410  	{
	 411  		"HTTP/1.1 206 Partial Content\r\n" +
	 412  			"Content-Type: text/plain; charset=utf-8\r\n" +
	 413  			"Accept-Ranges: bytes\r\n" +
	 414  			"Content-Range: bytes 0-5/1862\r\n" +
	 415  			"Content-Length: 6\r\n\r\n" +
	 416  			"foobar",
	 417  
	 418  		Response{
	 419  			Status:		 "206 Partial Content",
	 420  			StatusCode: 206,
	 421  			Proto:			"HTTP/1.1",
	 422  			ProtoMajor: 1,
	 423  			ProtoMinor: 1,
	 424  			Request:		dummyReq("GET"),
	 425  			Header: Header{
	 426  				"Accept-Ranges":	[]string{"bytes"},
	 427  				"Content-Length": []string{"6"},
	 428  				"Content-Type":	 []string{"text/plain; charset=utf-8"},
	 429  				"Content-Range":	[]string{"bytes 0-5/1862"},
	 430  			},
	 431  			ContentLength: 6,
	 432  		},
	 433  
	 434  		"foobar",
	 435  	},
	 436  
	 437  	// Both keep-alive and close, on the same Connection line. (Issue 8840)
	 438  	{
	 439  		"HTTP/1.1 200 OK\r\n" +
	 440  			"Content-Length: 256\r\n" +
	 441  			"Connection: keep-alive, close\r\n" +
	 442  			"\r\n",
	 443  
	 444  		Response{
	 445  			Status:		 "200 OK",
	 446  			StatusCode: 200,
	 447  			Proto:			"HTTP/1.1",
	 448  			ProtoMajor: 1,
	 449  			ProtoMinor: 1,
	 450  			Request:		dummyReq("HEAD"),
	 451  			Header: Header{
	 452  				"Content-Length": {"256"},
	 453  			},
	 454  			TransferEncoding: nil,
	 455  			Close:						true,
	 456  			ContentLength:		256,
	 457  		},
	 458  
	 459  		"",
	 460  	},
	 461  
	 462  	// Both keep-alive and close, on different Connection lines. (Issue 8840)
	 463  	{
	 464  		"HTTP/1.1 200 OK\r\n" +
	 465  			"Content-Length: 256\r\n" +
	 466  			"Connection: keep-alive\r\n" +
	 467  			"Connection: close\r\n" +
	 468  			"\r\n",
	 469  
	 470  		Response{
	 471  			Status:		 "200 OK",
	 472  			StatusCode: 200,
	 473  			Proto:			"HTTP/1.1",
	 474  			ProtoMajor: 1,
	 475  			ProtoMinor: 1,
	 476  			Request:		dummyReq("HEAD"),
	 477  			Header: Header{
	 478  				"Content-Length": {"256"},
	 479  			},
	 480  			TransferEncoding: nil,
	 481  			Close:						true,
	 482  			ContentLength:		256,
	 483  		},
	 484  
	 485  		"",
	 486  	},
	 487  
	 488  	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
	 489  	// Without a Content-Length.
	 490  	{
	 491  		"HTTP/1.0 200 OK\r\n" +
	 492  			"Transfer-Encoding: bogus\r\n" +
	 493  			"\r\n" +
	 494  			"Body here\n",
	 495  
	 496  		Response{
	 497  			Status:				"200 OK",
	 498  			StatusCode:		200,
	 499  			Proto:				 "HTTP/1.0",
	 500  			ProtoMajor:		1,
	 501  			ProtoMinor:		0,
	 502  			Request:			 dummyReq("GET"),
	 503  			Header:				Header{},
	 504  			Close:				 true,
	 505  			ContentLength: -1,
	 506  		},
	 507  
	 508  		"Body here\n",
	 509  	},
	 510  
	 511  	// Issue 12785: HTTP/1.0 response with bogus (to be ignored) Transfer-Encoding.
	 512  	// With a Content-Length.
	 513  	{
	 514  		"HTTP/1.0 200 OK\r\n" +
	 515  			"Transfer-Encoding: bogus\r\n" +
	 516  			"Content-Length: 10\r\n" +
	 517  			"\r\n" +
	 518  			"Body here\n",
	 519  
	 520  		Response{
	 521  			Status:		 "200 OK",
	 522  			StatusCode: 200,
	 523  			Proto:			"HTTP/1.0",
	 524  			ProtoMajor: 1,
	 525  			ProtoMinor: 0,
	 526  			Request:		dummyReq("GET"),
	 527  			Header: Header{
	 528  				"Content-Length": {"10"},
	 529  			},
	 530  			Close:				 true,
	 531  			ContentLength: 10,
	 532  		},
	 533  
	 534  		"Body here\n",
	 535  	},
	 536  
	 537  	{
	 538  		"HTTP/1.1 200 OK\r\n" +
	 539  			"Content-Encoding: gzip\r\n" +
	 540  			"Content-Length: 23\r\n" +
	 541  			"Connection: keep-alive\r\n" +
	 542  			"Keep-Alive: timeout=7200\r\n\r\n" +
	 543  			"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
	 544  		Response{
	 545  			Status:		 "200 OK",
	 546  			StatusCode: 200,
	 547  			Proto:			"HTTP/1.1",
	 548  			ProtoMajor: 1,
	 549  			ProtoMinor: 1,
	 550  			Request:		dummyReq("GET"),
	 551  			Header: Header{
	 552  				"Content-Length":	 {"23"},
	 553  				"Content-Encoding": {"gzip"},
	 554  				"Connection":			 {"keep-alive"},
	 555  				"Keep-Alive":			 {"timeout=7200"},
	 556  			},
	 557  			Close:				 false,
	 558  			ContentLength: 23,
	 559  		},
	 560  		"\x1f\x8b\b\x00\x00\x00\x00\x00\x00\x00s\xf3\xf7\a\x00\xab'\xd4\x1a\x03\x00\x00\x00",
	 561  	},
	 562  
	 563  	// Issue 19989: two spaces between HTTP version and status.
	 564  	{
	 565  		"HTTP/1.0	401 Unauthorized\r\n" +
	 566  			"Content-type: text/html\r\n" +
	 567  			"WWW-Authenticate: Basic realm=\"\"\r\n\r\n" +
	 568  			"Your Authentication failed.\r\n",
	 569  		Response{
	 570  			Status:		 "401 Unauthorized",
	 571  			StatusCode: 401,
	 572  			Proto:			"HTTP/1.0",
	 573  			ProtoMajor: 1,
	 574  			ProtoMinor: 0,
	 575  			Request:		dummyReq("GET"),
	 576  			Header: Header{
	 577  				"Content-Type":		 {"text/html"},
	 578  				"Www-Authenticate": {`Basic realm=""`},
	 579  			},
	 580  			Close:				 true,
	 581  			ContentLength: -1,
	 582  		},
	 583  		"Your Authentication failed.\r\n",
	 584  	},
	 585  }
	 586  
	 587  // tests successful calls to ReadResponse, and inspects the returned Response.
	 588  // For error cases, see TestReadResponseErrors below.
	 589  func TestReadResponse(t *testing.T) {
	 590  	for i, tt := range respTests {
	 591  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
	 592  		if err != nil {
	 593  			t.Errorf("#%d: %v", i, err)
	 594  			continue
	 595  		}
	 596  		rbody := resp.Body
	 597  		resp.Body = nil
	 598  		diff(t, fmt.Sprintf("#%d Response", i), resp, &tt.Resp)
	 599  		var bout bytes.Buffer
	 600  		if rbody != nil {
	 601  			_, err = io.Copy(&bout, rbody)
	 602  			if err != nil {
	 603  				t.Errorf("#%d: %v", i, err)
	 604  				continue
	 605  			}
	 606  			rbody.Close()
	 607  		}
	 608  		body := bout.String()
	 609  		if body != tt.Body {
	 610  			t.Errorf("#%d: Body = %q want %q", i, body, tt.Body)
	 611  		}
	 612  	}
	 613  }
	 614  
	 615  func TestWriteResponse(t *testing.T) {
	 616  	for i, tt := range respTests {
	 617  		resp, err := ReadResponse(bufio.NewReader(strings.NewReader(tt.Raw)), tt.Resp.Request)
	 618  		if err != nil {
	 619  			t.Errorf("#%d: %v", i, err)
	 620  			continue
	 621  		}
	 622  		err = resp.Write(io.Discard)
	 623  		if err != nil {
	 624  			t.Errorf("#%d: %v", i, err)
	 625  			continue
	 626  		}
	 627  	}
	 628  }
	 629  
	 630  var readResponseCloseInMiddleTests = []struct {
	 631  	chunked, compressed bool
	 632  }{
	 633  	{false, false},
	 634  	{true, false},
	 635  	{true, true},
	 636  }
	 637  
	 638  type readerAndCloser struct {
	 639  	io.Reader
	 640  	io.Closer
	 641  }
	 642  
	 643  // TestReadResponseCloseInMiddle tests that closing a body after
	 644  // reading only part of its contents advances the read to the end of
	 645  // the request, right up until the next request.
	 646  func TestReadResponseCloseInMiddle(t *testing.T) {
	 647  	t.Parallel()
	 648  	for _, test := range readResponseCloseInMiddleTests {
	 649  		fatalf := func(format string, args ...interface{}) {
	 650  			args = append([]interface{}{test.chunked, test.compressed}, args...)
	 651  			t.Fatalf("on test chunked=%v, compressed=%v: "+format, args...)
	 652  		}
	 653  		checkErr := func(err error, msg string) {
	 654  			if err == nil {
	 655  				return
	 656  			}
	 657  			fatalf(msg+": %v", err)
	 658  		}
	 659  		var buf bytes.Buffer
	 660  		buf.WriteString("HTTP/1.1 200 OK\r\n")
	 661  		if test.chunked {
	 662  			buf.WriteString("Transfer-Encoding: chunked\r\n")
	 663  		} else {
	 664  			buf.WriteString("Content-Length: 1000000\r\n")
	 665  		}
	 666  		var wr io.Writer = &buf
	 667  		if test.chunked {
	 668  			wr = internal.NewChunkedWriter(wr)
	 669  		}
	 670  		if test.compressed {
	 671  			buf.WriteString("Content-Encoding: gzip\r\n")
	 672  			wr = gzip.NewWriter(wr)
	 673  		}
	 674  		buf.WriteString("\r\n")
	 675  
	 676  		chunk := bytes.Repeat([]byte{'x'}, 1000)
	 677  		for i := 0; i < 1000; i++ {
	 678  			if test.compressed {
	 679  				// Otherwise this compresses too well.
	 680  				_, err := io.ReadFull(rand.Reader, chunk)
	 681  				checkErr(err, "rand.Reader ReadFull")
	 682  			}
	 683  			wr.Write(chunk)
	 684  		}
	 685  		if test.compressed {
	 686  			err := wr.(*gzip.Writer).Close()
	 687  			checkErr(err, "compressor close")
	 688  		}
	 689  		if test.chunked {
	 690  			buf.WriteString("0\r\n\r\n")
	 691  		}
	 692  		buf.WriteString("Next Request Here")
	 693  
	 694  		bufr := bufio.NewReader(&buf)
	 695  		resp, err := ReadResponse(bufr, dummyReq("GET"))
	 696  		checkErr(err, "ReadResponse")
	 697  		expectedLength := int64(-1)
	 698  		if !test.chunked {
	 699  			expectedLength = 1000000
	 700  		}
	 701  		if resp.ContentLength != expectedLength {
	 702  			fatalf("expected response length %d, got %d", expectedLength, resp.ContentLength)
	 703  		}
	 704  		if resp.Body == nil {
	 705  			fatalf("nil body")
	 706  		}
	 707  		if test.compressed {
	 708  			gzReader, err := gzip.NewReader(resp.Body)
	 709  			checkErr(err, "gzip.NewReader")
	 710  			resp.Body = &readerAndCloser{gzReader, resp.Body}
	 711  		}
	 712  
	 713  		rbuf := make([]byte, 2500)
	 714  		n, err := io.ReadFull(resp.Body, rbuf)
	 715  		checkErr(err, "2500 byte ReadFull")
	 716  		if n != 2500 {
	 717  			fatalf("ReadFull only read %d bytes", n)
	 718  		}
	 719  		if test.compressed == false && !bytes.Equal(bytes.Repeat([]byte{'x'}, 2500), rbuf) {
	 720  			fatalf("ReadFull didn't read 2500 'x'; got %q", string(rbuf))
	 721  		}
	 722  		resp.Body.Close()
	 723  
	 724  		rest, err := io.ReadAll(bufr)
	 725  		checkErr(err, "ReadAll on remainder")
	 726  		if e, g := "Next Request Here", string(rest); e != g {
	 727  			g = regexp.MustCompile(`(xx+)`).ReplaceAllStringFunc(g, func(match string) string {
	 728  				return fmt.Sprintf("x(repeated x%d)", len(match))
	 729  			})
	 730  			fatalf("remainder = %q, expected %q", g, e)
	 731  		}
	 732  	}
	 733  }
	 734  
	 735  func diff(t *testing.T, prefix string, have, want interface{}) {
	 736  	t.Helper()
	 737  	hv := reflect.ValueOf(have).Elem()
	 738  	wv := reflect.ValueOf(want).Elem()
	 739  	if hv.Type() != wv.Type() {
	 740  		t.Errorf("%s: type mismatch %v want %v", prefix, hv.Type(), wv.Type())
	 741  	}
	 742  	for i := 0; i < hv.NumField(); i++ {
	 743  		name := hv.Type().Field(i).Name
	 744  		if !token.IsExported(name) {
	 745  			continue
	 746  		}
	 747  		hf := hv.Field(i).Interface()
	 748  		wf := wv.Field(i).Interface()
	 749  		if !reflect.DeepEqual(hf, wf) {
	 750  			t.Errorf("%s: %s = %v want %v", prefix, name, hf, wf)
	 751  		}
	 752  	}
	 753  }
	 754  
	 755  type responseLocationTest struct {
	 756  	location string // Response's Location header or ""
	 757  	requrl	 string // Response.Request.URL or ""
	 758  	want		 string
	 759  	wantErr	error
	 760  }
	 761  
	 762  var responseLocationTests = []responseLocationTest{
	 763  	{"/foo", "http://bar.com/baz", "http://bar.com/foo", nil},
	 764  	{"http://foo.com/", "http://bar.com/baz", "http://foo.com/", nil},
	 765  	{"", "http://bar.com/baz", "", ErrNoLocation},
	 766  	{"/bar", "", "/bar", nil},
	 767  }
	 768  
	 769  func TestLocationResponse(t *testing.T) {
	 770  	for i, tt := range responseLocationTests {
	 771  		res := new(Response)
	 772  		res.Header = make(Header)
	 773  		res.Header.Set("Location", tt.location)
	 774  		if tt.requrl != "" {
	 775  			res.Request = &Request{}
	 776  			var err error
	 777  			res.Request.URL, err = url.Parse(tt.requrl)
	 778  			if err != nil {
	 779  				t.Fatalf("bad test URL %q: %v", tt.requrl, err)
	 780  			}
	 781  		}
	 782  
	 783  		got, err := res.Location()
	 784  		if tt.wantErr != nil {
	 785  			if err == nil {
	 786  				t.Errorf("%d. err=nil; want %q", i, tt.wantErr)
	 787  				continue
	 788  			}
	 789  			if g, e := err.Error(), tt.wantErr.Error(); g != e {
	 790  				t.Errorf("%d. err=%q; want %q", i, g, e)
	 791  				continue
	 792  			}
	 793  			continue
	 794  		}
	 795  		if err != nil {
	 796  			t.Errorf("%d. err=%q", i, err)
	 797  			continue
	 798  		}
	 799  		if g, e := got.String(), tt.want; g != e {
	 800  			t.Errorf("%d. Location=%q; want %q", i, g, e)
	 801  		}
	 802  	}
	 803  }
	 804  
	 805  func TestResponseStatusStutter(t *testing.T) {
	 806  	r := &Response{
	 807  		Status:		 "123 some status",
	 808  		StatusCode: 123,
	 809  		ProtoMajor: 1,
	 810  		ProtoMinor: 3,
	 811  	}
	 812  	var buf bytes.Buffer
	 813  	r.Write(&buf)
	 814  	if strings.Contains(buf.String(), "123 123") {
	 815  		t.Errorf("stutter in status: %s", buf.String())
	 816  	}
	 817  }
	 818  
	 819  func TestResponseContentLengthShortBody(t *testing.T) {
	 820  	const shortBody = "Short body, not 123 bytes."
	 821  	br := bufio.NewReader(strings.NewReader("HTTP/1.1 200 OK\r\n" +
	 822  		"Content-Length: 123\r\n" +
	 823  		"\r\n" +
	 824  		shortBody))
	 825  	res, err := ReadResponse(br, &Request{Method: "GET"})
	 826  	if err != nil {
	 827  		t.Fatal(err)
	 828  	}
	 829  	if res.ContentLength != 123 {
	 830  		t.Fatalf("Content-Length = %d; want 123", res.ContentLength)
	 831  	}
	 832  	var buf bytes.Buffer
	 833  	n, err := io.Copy(&buf, res.Body)
	 834  	if n != int64(len(shortBody)) {
	 835  		t.Errorf("Copied %d bytes; want %d, len(%q)", n, len(shortBody), shortBody)
	 836  	}
	 837  	if buf.String() != shortBody {
	 838  		t.Errorf("Read body %q; want %q", buf.String(), shortBody)
	 839  	}
	 840  	if err != io.ErrUnexpectedEOF {
	 841  		t.Errorf("io.Copy error = %#v; want io.ErrUnexpectedEOF", err)
	 842  	}
	 843  }
	 844  
	 845  // Test various ReadResponse error cases. (also tests success cases, but mostly
	 846  // it's about errors).	This does not test anything involving the bodies. Only
	 847  // the return value from ReadResponse itself.
	 848  func TestReadResponseErrors(t *testing.T) {
	 849  	type testCase struct {
	 850  		name		string // optional, defaults to in
	 851  		in			string
	 852  		wantErr interface{} // nil, err value, or string substring
	 853  	}
	 854  
	 855  	status := func(s string, wantErr interface{}) testCase {
	 856  		if wantErr == true {
	 857  			wantErr = "malformed HTTP status code"
	 858  		}
	 859  		return testCase{
	 860  			name:		fmt.Sprintf("status %q", s),
	 861  			in:			"HTTP/1.1 " + s + "\r\nFoo: bar\r\n\r\n",
	 862  			wantErr: wantErr,
	 863  		}
	 864  	}
	 865  
	 866  	version := func(s string, wantErr interface{}) testCase {
	 867  		if wantErr == true {
	 868  			wantErr = "malformed HTTP version"
	 869  		}
	 870  		return testCase{
	 871  			name:		fmt.Sprintf("version %q", s),
	 872  			in:			s + " 200 OK\r\n\r\n",
	 873  			wantErr: wantErr,
	 874  		}
	 875  	}
	 876  
	 877  	contentLength := func(status, body string, wantErr interface{}) testCase {
	 878  		return testCase{
	 879  			name:		fmt.Sprintf("status %q %q", status, body),
	 880  			in:			fmt.Sprintf("HTTP/1.1 %s\r\n%s", status, body),
	 881  			wantErr: wantErr,
	 882  		}
	 883  	}
	 884  
	 885  	errMultiCL := "message cannot contain multiple Content-Length headers"
	 886  
	 887  	tests := []testCase{
	 888  		{"", "", io.ErrUnexpectedEOF},
	 889  		{"", "HTTP/1.1 301 Moved Permanently\r\nFoo: bar", io.ErrUnexpectedEOF},
	 890  		{"", "HTTP/1.1", "malformed HTTP response"},
	 891  		{"", "HTTP/2.0", "malformed HTTP response"},
	 892  		status("20X Unknown", true),
	 893  		status("abcd Unknown", true),
	 894  		status("二百/两百 OK", true),
	 895  		status(" Unknown", true),
	 896  		status("c8 OK", true),
	 897  		status("0x12d Moved Permanently", true),
	 898  		status("200 OK", nil),
	 899  		status("000 OK", nil),
	 900  		status("001 OK", nil),
	 901  		status("404 NOTFOUND", nil),
	 902  		status("20 OK", true),
	 903  		status("00 OK", true),
	 904  		status("-10 OK", true),
	 905  		status("1000 OK", true),
	 906  		status("999 Done", nil),
	 907  		status("-1 OK", true),
	 908  		status("-200 OK", true),
	 909  		version("HTTP/1.2", nil),
	 910  		version("HTTP/2.0", nil),
	 911  		version("HTTP/1.100000000002", true),
	 912  		version("HTTP/1.-1", true),
	 913  		version("HTTP/A.B", true),
	 914  		version("HTTP/1", true),
	 915  		version("http/1.1", true),
	 916  
	 917  		contentLength("200 OK", "Content-Length: 10\r\nContent-Length: 7\r\n\r\nGopher hey\r\n", errMultiCL),
	 918  		contentLength("200 OK", "Content-Length: 7\r\nContent-Length: 7\r\n\r\nGophers\r\n", nil),
	 919  		contentLength("201 OK", "Content-Length: 0\r\nContent-Length: 7\r\n\r\nGophers\r\n", errMultiCL),
	 920  		contentLength("300 OK", "Content-Length: 0\r\nContent-Length: 0 \r\n\r\nGophers\r\n", nil),
	 921  		contentLength("200 OK", "Content-Length:\r\nContent-Length:\r\n\r\nGophers\r\n", nil),
	 922  		contentLength("206 OK", "Content-Length:\r\nContent-Length: 0 \r\nConnection: close\r\n\r\nGophers\r\n", errMultiCL),
	 923  
	 924  		// multiple content-length headers for 204 and 304 should still be checked
	 925  		contentLength("204 OK", "Content-Length: 7\r\nContent-Length: 8\r\n\r\n", errMultiCL),
	 926  		contentLength("204 OK", "Content-Length: 3\r\nContent-Length: 3\r\n\r\n", nil),
	 927  		contentLength("304 OK", "Content-Length: 880\r\nContent-Length: 1\r\n\r\n", errMultiCL),
	 928  		contentLength("304 OK", "Content-Length: 961\r\nContent-Length: 961\r\n\r\n", nil),
	 929  
	 930  		// golang.org/issue/22464
	 931  		{"leading space in header", "HTTP/1.1 200 OK\r\n Content-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
	 932  		{"leading tab in header", "HTTP/1.1 200 OK\r\n\tContent-type: text/html\r\nFoo: bar\r\n\r\n", "malformed MIME"},
	 933  	}
	 934  
	 935  	for i, tt := range tests {
	 936  		br := bufio.NewReader(strings.NewReader(tt.in))
	 937  		_, rerr := ReadResponse(br, nil)
	 938  		if err := matchErr(rerr, tt.wantErr); err != nil {
	 939  			name := tt.name
	 940  			if name == "" {
	 941  				name = fmt.Sprintf("%d. input %q", i, tt.in)
	 942  			}
	 943  			t.Errorf("%s: %v", name, err)
	 944  		}
	 945  	}
	 946  }
	 947  
	 948  // wantErr can be nil, an error value to match exactly, or type string to
	 949  // match a substring.
	 950  func matchErr(err error, wantErr interface{}) error {
	 951  	if err == nil {
	 952  		if wantErr == nil {
	 953  			return nil
	 954  		}
	 955  		if sub, ok := wantErr.(string); ok {
	 956  			return fmt.Errorf("unexpected success; want error with substring %q", sub)
	 957  		}
	 958  		return fmt.Errorf("unexpected success; want error %v", wantErr)
	 959  	}
	 960  	if wantErr == nil {
	 961  		return fmt.Errorf("%v; want success", err)
	 962  	}
	 963  	if sub, ok := wantErr.(string); ok {
	 964  		if strings.Contains(err.Error(), sub) {
	 965  			return nil
	 966  		}
	 967  		return fmt.Errorf("error = %v; want an error with substring %q", err, sub)
	 968  	}
	 969  	if err == wantErr {
	 970  		return nil
	 971  	}
	 972  	return fmt.Errorf("%v; want %v", err, wantErr)
	 973  }
	 974  
	 975  func TestNeedsSniff(t *testing.T) {
	 976  	// needsSniff returns true with an empty response.
	 977  	r := &response{}
	 978  	if got, want := r.needsSniff(), true; got != want {
	 979  		t.Errorf("needsSniff = %t; want %t", got, want)
	 980  	}
	 981  	// needsSniff returns false when Content-Type = nil.
	 982  	r.handlerHeader = Header{"Content-Type": nil}
	 983  	if got, want := r.needsSniff(), false; got != want {
	 984  		t.Errorf("needsSniff empty Content-Type = %t; want %t", got, want)
	 985  	}
	 986  }
	 987  
	 988  // A response should only write out single Connection: close header. Tests #19499.
	 989  func TestResponseWritesOnlySingleConnectionClose(t *testing.T) {
	 990  	const connectionCloseHeader = "Connection: close"
	 991  
	 992  	res, err := ReadResponse(bufio.NewReader(strings.NewReader("HTTP/1.0 200 OK\r\n\r\nAAAA")), nil)
	 993  	if err != nil {
	 994  		t.Fatalf("ReadResponse failed %v", err)
	 995  	}
	 996  
	 997  	var buf1 bytes.Buffer
	 998  	if err = res.Write(&buf1); err != nil {
	 999  		t.Fatalf("Write failed %v", err)
	1000  	}
	1001  	if res, err = ReadResponse(bufio.NewReader(&buf1), nil); err != nil {
	1002  		t.Fatalf("ReadResponse failed %v", err)
	1003  	}
	1004  
	1005  	var buf2 bytes.Buffer
	1006  	if err = res.Write(&buf2); err != nil {
	1007  		t.Fatalf("Write failed %v", err)
	1008  	}
	1009  	if count := strings.Count(buf2.String(), connectionCloseHeader); count != 1 {
	1010  		t.Errorf("Found %d %q header", count, connectionCloseHeader)
	1011  	}
	1012  }
	1013  

View as plain text