github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/net/http/readrequest_test.go (about) 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 // Tests chunked body and an invalid Content-Length. 211 { 212 "POST / HTTP/1.1\r\n" + 213 "Host: foo.com\r\n" + 214 "Transfer-Encoding: chunked\r\n" + 215 "Content-Length: notdigits\r\n\r\n" + // raise an error 216 "3\r\nfoo\r\n" + 217 "3\r\nbar\r\n" + 218 "0\r\n" + 219 "\r\n", 220 nil, 221 noBodyStr, 222 noTrailer, 223 `bad Content-Length "notdigits"`, 224 }, 225 226 // CONNECT request with domain name: 227 { 228 "CONNECT www.google.com:443 HTTP/1.1\r\n\r\n", 229 230 &Request{ 231 Method: "CONNECT", 232 URL: &url.URL{ 233 Host: "www.google.com:443", 234 }, 235 Proto: "HTTP/1.1", 236 ProtoMajor: 1, 237 ProtoMinor: 1, 238 Header: Header{}, 239 Close: false, 240 ContentLength: 0, 241 Host: "www.google.com:443", 242 RequestURI: "www.google.com:443", 243 }, 244 245 noBodyStr, 246 noTrailer, 247 noError, 248 }, 249 250 // CONNECT request with IP address: 251 { 252 "CONNECT 127.0.0.1:6060 HTTP/1.1\r\n\r\n", 253 254 &Request{ 255 Method: "CONNECT", 256 URL: &url.URL{ 257 Host: "127.0.0.1:6060", 258 }, 259 Proto: "HTTP/1.1", 260 ProtoMajor: 1, 261 ProtoMinor: 1, 262 Header: Header{}, 263 Close: false, 264 ContentLength: 0, 265 Host: "127.0.0.1:6060", 266 RequestURI: "127.0.0.1:6060", 267 }, 268 269 noBodyStr, 270 noTrailer, 271 noError, 272 }, 273 274 // CONNECT request for RPC: 275 { 276 "CONNECT /_goRPC_ HTTP/1.1\r\n\r\n", 277 278 &Request{ 279 Method: "CONNECT", 280 URL: &url.URL{ 281 Path: "/_goRPC_", 282 }, 283 Proto: "HTTP/1.1", 284 ProtoMajor: 1, 285 ProtoMinor: 1, 286 Header: Header{}, 287 Close: false, 288 ContentLength: 0, 289 Host: "", 290 RequestURI: "/_goRPC_", 291 }, 292 293 noBodyStr, 294 noTrailer, 295 noError, 296 }, 297 298 // SSDP Notify request. golang.org/issue/3692 299 { 300 "NOTIFY * HTTP/1.1\r\nServer: foo\r\n\r\n", 301 &Request{ 302 Method: "NOTIFY", 303 URL: &url.URL{ 304 Path: "*", 305 }, 306 Proto: "HTTP/1.1", 307 ProtoMajor: 1, 308 ProtoMinor: 1, 309 Header: Header{ 310 "Server": []string{"foo"}, 311 }, 312 Close: false, 313 ContentLength: 0, 314 RequestURI: "*", 315 }, 316 317 noBodyStr, 318 noTrailer, 319 noError, 320 }, 321 322 // OPTIONS request. Similar to golang.org/issue/3692 323 { 324 "OPTIONS * HTTP/1.1\r\nServer: foo\r\n\r\n", 325 &Request{ 326 Method: "OPTIONS", 327 URL: &url.URL{ 328 Path: "*", 329 }, 330 Proto: "HTTP/1.1", 331 ProtoMajor: 1, 332 ProtoMinor: 1, 333 Header: Header{ 334 "Server": []string{"foo"}, 335 }, 336 Close: false, 337 ContentLength: 0, 338 RequestURI: "*", 339 }, 340 341 noBodyStr, 342 noTrailer, 343 noError, 344 }, 345 346 // Connection: close. golang.org/issue/8261 347 { 348 "GET / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\n\r\n", 349 &Request{ 350 Method: "GET", 351 URL: &url.URL{ 352 Path: "/", 353 }, 354 Header: Header{ 355 // This wasn't removed from Go 1.0 to 356 // Go 1.3, so locking it in that we 357 // keep this: 358 "Connection": []string{"close"}, 359 }, 360 Host: "issue8261.com", 361 Proto: "HTTP/1.1", 362 ProtoMajor: 1, 363 ProtoMinor: 1, 364 Close: true, 365 RequestURI: "/", 366 }, 367 368 noBodyStr, 369 noTrailer, 370 noError, 371 }, 372 373 // HEAD with Content-Length 0. Make sure this is permitted, 374 // since I think we used to send it. 375 { 376 "HEAD / HTTP/1.1\r\nHost: issue8261.com\r\nConnection: close\r\nContent-Length: 0\r\n\r\n", 377 &Request{ 378 Method: "HEAD", 379 URL: &url.URL{ 380 Path: "/", 381 }, 382 Header: Header{ 383 "Connection": []string{"close"}, 384 "Content-Length": []string{"0"}, 385 }, 386 Host: "issue8261.com", 387 Proto: "HTTP/1.1", 388 ProtoMajor: 1, 389 ProtoMinor: 1, 390 Close: true, 391 RequestURI: "/", 392 }, 393 394 noBodyStr, 395 noTrailer, 396 noError, 397 }, 398 399 // http2 client preface: 400 { 401 "PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n", 402 &Request{ 403 Method: "PRI", 404 URL: &url.URL{ 405 Path: "*", 406 }, 407 Header: Header{}, 408 Proto: "HTTP/2.0", 409 ProtoMajor: 2, 410 ProtoMinor: 0, 411 RequestURI: "*", 412 ContentLength: -1, 413 Close: true, 414 }, 415 noBodyStr, 416 noTrailer, 417 noError, 418 }, 419 } 420 421 func TestReadRequest(t *testing.T) { 422 for i := range reqTests { 423 tt := &reqTests[i] 424 req, err := ReadRequest(bufio.NewReader(strings.NewReader(tt.Raw))) 425 if err != nil { 426 if err.Error() != tt.Error { 427 t.Errorf("#%d: error %q, want error %q", i, err.Error(), tt.Error) 428 } 429 continue 430 } 431 rbody := req.Body 432 req.Body = nil 433 testName := fmt.Sprintf("Test %d (%q)", i, tt.Raw) 434 diff(t, testName, req, tt.Req) 435 var bout strings.Builder 436 if rbody != nil { 437 _, err := io.Copy(&bout, rbody) 438 if err != nil { 439 t.Fatalf("%s: copying body: %v", testName, err) 440 } 441 rbody.Close() 442 } 443 body := bout.String() 444 if body != tt.Body { 445 t.Errorf("%s: Body = %q want %q", testName, body, tt.Body) 446 } 447 if !reflect.DeepEqual(tt.Trailer, req.Trailer) { 448 t.Errorf("%s: Trailers differ.\n got: %v\nwant: %v", testName, req.Trailer, tt.Trailer) 449 } 450 } 451 } 452 453 // reqBytes treats req as a request (with \n delimiters) and returns it with \r\n delimiters, 454 // ending in \r\n\r\n 455 func reqBytes(req string) []byte { 456 return []byte(strings.ReplaceAll(strings.TrimSpace(req), "\n", "\r\n") + "\r\n\r\n") 457 } 458 459 var badRequestTests = []struct { 460 name string 461 req []byte 462 }{ 463 {"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")}, 464 {"smuggle_two_contentlen", reqBytes(`POST / HTTP/1.1 465 Content-Length: 3 466 Content-Length: 4 467 468 abc`)}, 469 {"smuggle_two_content_len_head", reqBytes(`HEAD / HTTP/1.1 470 Host: foo 471 Content-Length: 4 472 Content-Length: 5 473 474 1234`)}, 475 476 // golang.org/issue/22464 477 {"leading_space_in_header", reqBytes(`GET / HTTP/1.1 478 Host: foo`)}, 479 {"leading_tab_in_header", reqBytes(`GET / HTTP/1.1 480 ` + "\t" + `Host: foo`)}, 481 } 482 483 func TestReadRequest_Bad(t *testing.T) { 484 for _, tt := range badRequestTests { 485 got, err := ReadRequest(bufio.NewReader(bytes.NewReader(tt.req))) 486 if err == nil { 487 all, err := io.ReadAll(got.Body) 488 t.Errorf("%s: got unexpected request = %#v\n Body = %q, %v", tt.name, got, all, err) 489 } 490 } 491 }