...

Source file src/net/http/readrequest_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  	"fmt"
		11  	"io"
		12  	"net/url"
		13  	"reflect"
		14  	"strings"
		15  	"testing"
		16  )
		17  
		18  type reqTest struct {
		19  	Raw		 string
		20  	Req		 *Request
		21  	Body		string
		22  	Trailer Header
		23  	Error	 string
		24  }
		25  
		26  var noError = ""
		27  var noBodyStr = ""
		28  var noTrailer Header = nil
		29  
		30  var reqTests = []reqTest{
		31  	// Baseline test; All Request fields included for template use
		32  	{
		33  		"GET http://www.techcrunch.com/ HTTP/1.1\r\n" +
		34  			"Host: www.techcrunch.com\r\n" +
		35  			"User-Agent: Fake\r\n" +
		36  			"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n" +
		37  			"Accept-Language: en-us,en;q=0.5\r\n" +
		38  			"Accept-Encoding: gzip,deflate\r\n" +
		39  			"Accept-Charset: ISO-8859-1,utf-8;q=0.7,*;q=0.7\r\n" +
		40  			"Keep-Alive: 300\r\n" +
		41  			"Content-Length: 7\r\n" +
		42  			"Proxy-Connection: keep-alive\r\n\r\n" +
		43  			"abcdef\n???",
		44  
		45  		&Request{
		46  			Method: "GET",
		47  			URL: &url.URL{
		48  				Scheme: "http",
		49  				Host:	 "www.techcrunch.com",
		50  				Path:	 "/",
		51  			},
		52  			Proto:			"HTTP/1.1",
		53  			ProtoMajor: 1,
		54  			ProtoMinor: 1,
		55  			Header: Header{
		56  				"Accept":					 {"text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8"},
		57  				"Accept-Language":	{"en-us,en;q=0.5"},
		58  				"Accept-Encoding":	{"gzip,deflate"},
		59  				"Accept-Charset":	 {"ISO-8859-1,utf-8;q=0.7,*;q=0.7"},
		60  				"Keep-Alive":			 {"300"},
		61  				"Proxy-Connection": {"keep-alive"},
		62  				"Content-Length":	 {"7"},
		63  				"User-Agent":			 {"Fake"},
		64  			},
		65  			Close:				 false,
		66  			ContentLength: 7,
		67  			Host:					"www.techcrunch.com",
		68  			RequestURI:		"http://www.techcrunch.com/",
		69  		},
		70  
		71  		"abcdef\n",
		72  
		73  		noTrailer,
		74  		noError,
		75  	},
		76  
		77  	// GET request with no body (the normal case)
		78  	{
		79  		"GET / HTTP/1.1\r\n" +
		80  			"Host: foo.com\r\n\r\n",
		81  
		82  		&Request{
		83  			Method: "GET",
		84  			URL: &url.URL{
		85  				Path: "/",
		86  			},
		87  			Proto:				 "HTTP/1.1",
		88  			ProtoMajor:		1,
		89  			ProtoMinor:		1,
		90  			Header:				Header{},
		91  			Close:				 false,
		92  			ContentLength: 0,
		93  			Host:					"foo.com",
		94  			RequestURI:		"/",
		95  		},
		96  
		97  		noBodyStr,
		98  		noTrailer,
		99  		noError,
	 100  	},
	 101  
	 102  	// Tests that we don't parse a path that looks like a
	 103  	// scheme-relative URI as a scheme-relative URI.
	 104  	{
	 105  		"GET //user@host/is/actually/a/path/ HTTP/1.1\r\n" +
	 106  			"Host: test\r\n\r\n",
	 107  
	 108  		&Request{
	 109  			Method: "GET",
	 110  			URL: &url.URL{
	 111  				Path: "//user@host/is/actually/a/path/",
	 112  			},
	 113  			Proto:				 "HTTP/1.1",
	 114  			ProtoMajor:		1,
	 115  			ProtoMinor:		1,
	 116  			Header:				Header{},
	 117  			Close:				 false,
	 118  			ContentLength: 0,
	 119  			Host:					"test",
	 120  			RequestURI:		"//user@host/is/actually/a/path/",
	 121  		},
	 122  
	 123  		noBodyStr,
	 124  		noTrailer,
	 125  		noError,
	 126  	},
	 127  
	 128  	// Tests a bogus absolute-path on the Request-Line (RFC 7230 section 5.3.1)
	 129  	{
	 130  		"GET ../../../../etc/passwd HTTP/1.1\r\n" +
	 131  			"Host: test\r\n\r\n",
	 132  		nil,
	 133  		noBodyStr,
	 134  		noTrailer,
	 135  		`parse "../../../../etc/passwd": invalid URI for request`,
	 136  	},
	 137  
	 138  	// Tests missing URL:
	 139  	{
	 140  		"GET	HTTP/1.1\r\n" +
	 141  			"Host: test\r\n\r\n",
	 142  		nil,
	 143  		noBodyStr,
	 144  		noTrailer,
	 145  		`parse "": empty url`,
	 146  	},
	 147  
	 148  	// Tests chunked body with trailer:
	 149  	{
	 150  		"POST / HTTP/1.1\r\n" +
	 151  			"Host: foo.com\r\n" +
	 152  			"Transfer-Encoding: chunked\r\n\r\n" +
	 153  			"3\r\nfoo\r\n" +
	 154  			"3\r\nbar\r\n" +
	 155  			"0\r\n" +
	 156  			"Trailer-Key: Trailer-Value\r\n" +
	 157  			"\r\n",
	 158  		&Request{
	 159  			Method: "POST",
	 160  			URL: &url.URL{
	 161  				Path: "/",
	 162  			},
	 163  			TransferEncoding: []string{"chunked"},
	 164  			Proto:						"HTTP/1.1",
	 165  			ProtoMajor:			 1,
	 166  			ProtoMinor:			 1,
	 167  			Header:					 Header{},
	 168  			ContentLength:		-1,
	 169  			Host:						 "foo.com",
	 170  			RequestURI:			 "/",
	 171  		},
	 172  
	 173  		"foobar",
	 174  		Header{
	 175  			"Trailer-Key": {"Trailer-Value"},
	 176  		},
	 177  		noError,
	 178  	},
	 179  
	 180  	// Tests chunked body and a bogus Content-Length which should be deleted.
	 181  	{
	 182  		"POST / HTTP/1.1\r\n" +
	 183  			"Host: foo.com\r\n" +
	 184  			"Transfer-Encoding: chunked\r\n" +
	 185  			"Content-Length: 9999\r\n\r\n" + // to be removed.
	 186  			"3\r\nfoo\r\n" +
	 187  			"3\r\nbar\r\n" +
	 188  			"0\r\n" +
	 189  			"\r\n",
	 190  		&Request{
	 191  			Method: "POST",
	 192  			URL: &url.URL{
	 193  				Path: "/",
	 194  			},
	 195  			TransferEncoding: []string{"chunked"},
	 196  			Proto:						"HTTP/1.1",
	 197  			ProtoMajor:			 1,
	 198  			ProtoMinor:			 1,
	 199  			Header:					 Header{},
	 200  			ContentLength:		-1,
	 201  			Host:						 "foo.com",
	 202  			RequestURI:			 "/",
	 203  		},
	 204  
	 205  		"foobar",
	 206  		noTrailer,
	 207  		noError,
	 208  	},
	 209  
	 210  	// CONNECT request with domain name:
	 211  	{
	 212  		"CONNECT www.google.com:443 HTTP/1.1\r\n\r\n",
	 213  
	 214  		&Request{
	 215  			Method: "CONNECT",
	 216  			URL: &url.URL{
	 217  				Host: "www.google.com:443",
	 218  			},
	 219  			Proto:				 "HTTP/1.1",
	 220  			ProtoMajor:		1,
	 221  			ProtoMinor:		1,
	 222  			Header:				Header{},
	 223  			Close:				 false,
	 224  			ContentLength: 0,
	 225  			Host:					"www.google.com:443",
	 226  			RequestURI:		"www.google.com:443",
	 227  		},
	 228  
	 229  		noBodyStr,
	 230  		noTrailer,
	 231  		noError,
	 232  	},
	 233  
	 234  	// CONNECT request with IP address:
	 235  	{
	 236  		"CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n",
	 237  
	 238  		&Request{
	 239  			Method: "CONNECT",
	 240  			URL: &url.URL{
	 241  				Host: "127.0.0.1:6060",
	 242  			},
	 243  			Proto:				 "HTTP/1.1",
	 244  			ProtoMajor:		1,
	 245  			ProtoMinor:		1,
	 246  			Header:				Header{},
	 247  			Close:				 false,
	 248  			ContentLength: 0,
	 249  			Host:					"127.0.0.1:6060",
	 250  			RequestURI:		"127.0.0.1:6060",
	 251  		},
	 252  
	 253  		noBodyStr,
	 254  		noTrailer,
	 255  		noError,
	 256  	},
	 257  
	 258  	// CONNECT request for RPC:
	 259  	{
	 260  		"CONNECT /_goRPC_ HTTP/1.1\r\n\r\n",
	 261  
	 262  		&Request{
	 263  			Method: "CONNECT",
	 264  			URL: &url.URL{
	 265  				Path: "/_goRPC_",
	 266  			},
	 267  			Proto:				 "HTTP/1.1",
	 268  			ProtoMajor:		1,
	 269  			ProtoMinor:		1,
	 270  			Header:				Header{},
	 271  			Close:				 false,
	 272  			ContentLength: 0,
	 273  			Host:					"",
	 274  			RequestURI:		"/_goRPC_",
	 275  		},
	 276  
	 277  		noBodyStr,
	 278  		noTrailer,
	 279  		noError,
	 280  	},
	 281  
	 282  	// SSDP Notify request. golang.org/issue/3692
	 283  	{
	 284  		"NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n",
	 285  		&Request{
	 286  			Method: "NOTIFY",
	 287  			URL: &url.URL{
	 288  				Path: "*",
	 289  			},
	 290  			Proto:			"HTTP/1.1",
	 291  			ProtoMajor: 1,
	 292  			ProtoMinor: 1,
	 293  			Header: Header{
	 294  				"Server": []string{"foo"},
	 295  			},
	 296  			Close:				 false,
	 297  			ContentLength: 0,
	 298  			RequestURI:		"*",
	 299  		},
	 300  
	 301  		noBodyStr,
	 302  		noTrailer,
	 303  		noError,
	 304  	},
	 305  
	 306  	// OPTIONS request. Similar to golang.org/issue/3692
	 307  	{
	 308  		"OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n",
	 309  		&Request{
	 310  			Method: "OPTIONS",
	 311  			URL: &url.URL{
	 312  				Path: "*",
	 313  			},
	 314  			Proto:			"HTTP/1.1",
	 315  			ProtoMajor: 1,
	 316  			ProtoMinor: 1,
	 317  			Header: Header{
	 318  				"Server": []string{"foo"},
	 319  			},
	 320  			Close:				 false,
	 321  			ContentLength: 0,
	 322  			RequestURI:		"*",
	 323  		},
	 324  
	 325  		noBodyStr,
	 326  		noTrailer,
	 327  		noError,
	 328  	},
	 329  
	 330  	// Connection: close. golang.org/issue/8261
	 331  	{
	 332  		"GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n",
	 333  		&Request{
	 334  			Method: "GET",
	 335  			URL: &url.URL{
	 336  				Path: "/",
	 337  			},
	 338  			Header: Header{
	 339  				// This wasn't removed from Go 1.0 to
	 340  				// Go 1.3, so locking it in that we
	 341  				// keep this:
	 342  				"Connection": []string{"close"},
	 343  			},
	 344  			Host:			 "issue8261.com",
	 345  			Proto:			"HTTP/1.1",
	 346  			ProtoMajor: 1,
	 347  			ProtoMinor: 1,
	 348  			Close:			true,
	 349  			RequestURI: "/",
	 350  		},
	 351  
	 352  		noBodyStr,
	 353  		noTrailer,
	 354  		noError,
	 355  	},
	 356  
	 357  	// HEAD with Content-Length 0. Make sure this is permitted,
	 358  	// since I think we used to send it.
	 359  	{
	 360  		"HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n",
	 361  		&Request{
	 362  			Method: "HEAD",
	 363  			URL: &url.URL{
	 364  				Path: "/",
	 365  			},
	 366  			Header: Header{
	 367  				"Connection":		 []string{"close"},
	 368  				"Content-Length": []string{"0"},
	 369  			},
	 370  			Host:			 "issue8261.com",
	 371  			Proto:			"HTTP/1.1",
	 372  			ProtoMajor: 1,
	 373  			ProtoMinor: 1,
	 374  			Close:			true,
	 375  			RequestURI: "/",
	 376  		},
	 377  
	 378  		noBodyStr,
	 379  		noTrailer,
	 380  		noError,
	 381  	},
	 382  
	 383  	// http2 client preface:
	 384  	{
	 385  		"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n",
	 386  		&Request{
	 387  			Method: "PRI",
	 388  			URL: &url.URL{
	 389  				Path: "*",
	 390  			},
	 391  			Header:				Header{},
	 392  			Proto:				 "HTTP/2.0",
	 393  			ProtoMajor:		2,
	 394  			ProtoMinor:		0,
	 395  			RequestURI:		"*",
	 396  			ContentLength: -1,
	 397  			Close:				 true,
	 398  		},
	 399  		noBodyStr,
	 400  		noTrailer,
	 401  		noError,
	 402  	},
	 403  }
	 404  
	 405  func TestReadRequest(t *testing.T) {
	 406  	for i := range reqTests {
	 407  		tt := &reqTests[i]
	 408  		req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw)))
	 409  		if err != nil {
	 410  			if err.Error() != tt.Error {
	 411  				t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error)
	 412  			}
	 413  			continue
	 414  		}
	 415  		rbody := req.Body
	 416  		req.Body = nil
	 417  		testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw)
	 418  		diff(t, testName, req, tt.Req)
	 419  		var bout bytes.Buffer
	 420  		if rbody != nil {
	 421  			_, err := io.Copy(&bout, rbody)
	 422  			if err != nil {
	 423  				t.Fatalf("%s: copying body: %v", testName, err)
	 424  			}
	 425  			rbody.Close()
	 426  		}
	 427  		body := bout.String()
	 428  		if body != tt.Body {
	 429  			t.Errorf("%s: Body = %q want %q", testName, body, tt.Body)
	 430  		}
	 431  		if !reflect.DeepEqual(tt.Trailer, req.Trailer) {
	 432  			t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer)
	 433  		}
	 434  	}
	 435  }
	 436  
	 437  // reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters,
	 438  // ending in \r\n\r\n
	 439  func reqBytes(req string) []byte {
	 440  	return []byte(strings.ReplaceAll(strings.TrimSpace(req), "\n", "\r\n") + "\r\n\r\n")
	 441  }
	 442  
	 443  var badRequestTests = []struct {
	 444  	name string
	 445  	req	[]byte
	 446  }{
	 447  	{"bad_connect_host", reqBytes("CONNECT []%20%48%54%54%50%2f%31%2e%31%0a%4d%79%48%65%61%64%65%72%3a%20%31%32%33%0a%0a HTTP/1.0")},
	 448  	{"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1
	 449  Content-Length: 3
	 450  Content-Length: 4
	 451  
	 452  abc`)},
	 453  	{"smuggle_content_len_head", reqBytes(`HEAD / HTTP/1.1
	 454  Host: foo
	 455  Content-Length: 5`)},
	 456  
	 457  	// golang.org/issue/22464
	 458  	{"leading_space_in_header", reqBytes(`HEAD / HTTP/1.1
	 459   Host: foo
	 460  Content-Length: 5`)},
	 461  	{"leading_tab_in_header", reqBytes(`HEAD / HTTP/1.1
	 462  \tHost: foo
	 463  Content-Length: 5`)},
	 464  }
	 465  
	 466  func TestReadRequest_Bad(t *testing.T) {
	 467  	for _, tt := range badRequestTests {
	 468  		got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req)))
	 469  		if err == nil {
	 470  			all, err := io.ReadAll(got.Body)
	 471  			t.Errorf("%s: got unexpected request = %#v\n	Body = %q, %v", tt.name, got, all, err)
	 472  		}
	 473  	}
	 474  }
	 475  

View as plain text