github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/gmhttp/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 gmhttp 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 }