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