gitee.com/ks-custle/core-gm@v0.0.0-20230922171213-b83bdd97b62c/gmhttp/cgi/host_test.go (about) 1 // Copyright 2011 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 // Tests for package cgi 6 7 package cgi 8 9 import ( 10 "bufio" 11 "bytes" 12 "fmt" 13 "io" 14 "net" 15 "os" 16 "os/exec" 17 "path/filepath" 18 "reflect" 19 "runtime" 20 "strconv" 21 "strings" 22 "testing" 23 "time" 24 25 http "gitee.com/ks-custle/core-gm/gmhttp" 26 "gitee.com/ks-custle/core-gm/gmhttp/httptest" 27 ) 28 29 func newRequest(httpreq string) *http.Request { 30 buf := bufio.NewReader(strings.NewReader(httpreq)) 31 req, err := http.ReadRequest(buf) 32 if err != nil { 33 panic("cgi: bogus http request in test: " + httpreq) 34 } 35 req.RemoteAddr = "1.2.3.4:1234" 36 return req 37 } 38 39 func runCgiTest(t *testing.T, h *Handler, 40 httpreq string, 41 expectedMap map[string]string, checks ...func(reqInfo map[string]string)) *httptest.ResponseRecorder { 42 rw := httptest.NewRecorder() 43 req := newRequest(httpreq) 44 h.ServeHTTP(rw, req) 45 runResponseChecks(t, rw, expectedMap, checks...) 46 return rw 47 } 48 49 func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder, 50 expectedMap map[string]string, checks ...func(reqInfo map[string]string)) { 51 // Make a map to hold the test map that the CGI returns. 52 m := make(map[string]string) 53 m["_body"] = rw.Body.String() 54 linesRead := 0 55 readlines: 56 for { 57 line, err := rw.Body.ReadString('\n') 58 switch { 59 case err == io.EOF: 60 break readlines 61 case err != nil: 62 t.Fatalf("unexpected error reading from CGI: %v", err) 63 } 64 linesRead++ 65 trimmedLine := strings.TrimRight(line, "\r\n") 66 split := strings.SplitN(trimmedLine, "=", 2) 67 if len(split) != 2 { 68 t.Fatalf("Unexpected %d parts from invalid line number %v: %q; existing map=%v", 69 len(split), linesRead, line, m) 70 } 71 m[split[0]] = split[1] 72 } 73 74 for key, expected := range expectedMap { 75 got := m[key] 76 if key == "cwd" { 77 // For Windows. golang.org/issue/4645. 78 fi1, _ := os.Stat(got) 79 fi2, _ := os.Stat(expected) 80 if os.SameFile(fi1, fi2) { 81 got = expected 82 } 83 } 84 if got != expected { 85 t.Errorf("for key %q got %q; expected %q", key, got, expected) 86 } 87 } 88 for _, check := range checks { 89 check(m) 90 } 91 } 92 93 var cgiTested, cgiWorks bool 94 95 func check(t *testing.T) { 96 if !cgiTested { 97 cgiTested = true 98 cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil 99 } 100 if !cgiWorks { 101 // No Perl on Windows, needed by test.cgi 102 // TODO: make the child process be Go, not Perl. 103 t.Skip("Skipping test: test.cgi failed.") 104 } 105 } 106 107 func TestCGIBasicGet(t *testing.T) { 108 check(t) 109 h := &Handler{ 110 Path: "testdata/test.cgi", 111 Root: "/test.cgi", 112 } 113 expectedMap := map[string]string{ 114 "test": "Hello CGI", 115 "param-a": "b", 116 "param-foo": "bar", 117 "env-GATEWAY_INTERFACE": "CGI/1.1", 118 "env-HTTP_HOST": "example.com", 119 "env-PATH_INFO": "", 120 "env-QUERY_STRING": "foo=bar&a=b", 121 "env-REMOTE_ADDR": "1.2.3.4", 122 "env-REMOTE_HOST": "1.2.3.4", 123 "env-REMOTE_PORT": "1234", 124 "env-REQUEST_METHOD": "GET", 125 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b", 126 "env-SCRIPT_FILENAME": "testdata/test.cgi", 127 "env-SCRIPT_NAME": "/test.cgi", 128 "env-SERVER_NAME": "example.com", 129 "env-SERVER_PORT": "80", 130 "env-SERVER_SOFTWARE": "go", 131 } 132 replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) 133 134 if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected { 135 t.Errorf("got a Content-Type of %q; expected %q", got, expected) 136 } 137 if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected { 138 t.Errorf("got a X-Test-Header of %q; expected %q", got, expected) 139 } 140 } 141 142 func TestCGIEnvIPv6(t *testing.T) { 143 check(t) 144 h := &Handler{ 145 Path: "testdata/test.cgi", 146 Root: "/test.cgi", 147 } 148 expectedMap := map[string]string{ 149 "test": "Hello CGI", 150 "param-a": "b", 151 "param-foo": "bar", 152 "env-GATEWAY_INTERFACE": "CGI/1.1", 153 "env-HTTP_HOST": "example.com", 154 "env-PATH_INFO": "", 155 "env-QUERY_STRING": "foo=bar&a=b", 156 "env-REMOTE_ADDR": "2000::3000", 157 "env-REMOTE_HOST": "2000::3000", 158 "env-REMOTE_PORT": "12345", 159 "env-REQUEST_METHOD": "GET", 160 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b", 161 "env-SCRIPT_FILENAME": "testdata/test.cgi", 162 "env-SCRIPT_NAME": "/test.cgi", 163 "env-SERVER_NAME": "example.com", 164 "env-SERVER_PORT": "80", 165 "env-SERVER_SOFTWARE": "go", 166 } 167 168 rw := httptest.NewRecorder() 169 req := newRequest("GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n") 170 req.RemoteAddr = "[2000::3000]:12345" 171 h.ServeHTTP(rw, req) 172 runResponseChecks(t, rw, expectedMap) 173 } 174 175 func TestCGIBasicGetAbsPath(t *testing.T) { 176 check(t) 177 pwd, err := os.Getwd() 178 if err != nil { 179 t.Fatalf("getwd error: %v", err) 180 } 181 h := &Handler{ 182 Path: pwd + "/testdata/test.cgi", 183 Root: "/test.cgi", 184 } 185 expectedMap := map[string]string{ 186 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b", 187 "env-SCRIPT_FILENAME": pwd + "/testdata/test.cgi", 188 "env-SCRIPT_NAME": "/test.cgi", 189 } 190 runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) 191 } 192 193 func TestPathInfo(t *testing.T) { 194 check(t) 195 h := &Handler{ 196 Path: "testdata/test.cgi", 197 Root: "/test.cgi", 198 } 199 expectedMap := map[string]string{ 200 "param-a": "b", 201 "env-PATH_INFO": "/extrapath", 202 "env-QUERY_STRING": "a=b", 203 "env-REQUEST_URI": "/test.cgi/extrapath?a=b", 204 "env-SCRIPT_FILENAME": "testdata/test.cgi", 205 "env-SCRIPT_NAME": "/test.cgi", 206 } 207 runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) 208 } 209 210 func TestPathInfoDirRoot(t *testing.T) { 211 check(t) 212 h := &Handler{ 213 Path: "testdata/test.cgi", 214 Root: "/myscript/", 215 } 216 expectedMap := map[string]string{ 217 "env-PATH_INFO": "bar", 218 "env-QUERY_STRING": "a=b", 219 "env-REQUEST_URI": "/myscript/bar?a=b", 220 "env-SCRIPT_FILENAME": "testdata/test.cgi", 221 "env-SCRIPT_NAME": "/myscript/", 222 } 223 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) 224 } 225 226 func TestDupHeaders(t *testing.T) { 227 check(t) 228 h := &Handler{ 229 Path: "testdata/test.cgi", 230 } 231 expectedMap := map[string]string{ 232 "env-REQUEST_URI": "/myscript/bar?a=b", 233 "env-SCRIPT_FILENAME": "testdata/test.cgi", 234 "env-HTTP_COOKIE": "nom=NOM; yum=YUM", 235 "env-HTTP_X_FOO": "val1, val2", 236 } 237 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+ 238 "Cookie: nom=NOM\n"+ 239 "Cookie: yum=YUM\n"+ 240 "X-Foo: val1\n"+ 241 "X-Foo: val2\n"+ 242 "Host: example.com\n\n", 243 expectedMap) 244 } 245 246 // Issue 16405: CGI+http.Transport differing uses of HTTP_PROXY. 247 // Verify we don't set the HTTP_PROXY environment variable. 248 // Hope nobody was depending on it. It's not a known header, though. 249 func TestDropProxyHeader(t *testing.T) { 250 check(t) 251 h := &Handler{ 252 Path: "testdata/test.cgi", 253 } 254 expectedMap := map[string]string{ 255 "env-REQUEST_URI": "/myscript/bar?a=b", 256 "env-SCRIPT_FILENAME": "testdata/test.cgi", 257 "env-HTTP_X_FOO": "a", 258 } 259 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+ 260 "X-Foo: a\n"+ 261 "Proxy: should_be_stripped\n"+ 262 "Host: example.com\n\n", 263 expectedMap, 264 func(reqInfo map[string]string) { 265 if v, ok := reqInfo["env-HTTP_PROXY"]; ok { 266 t.Errorf("HTTP_PROXY = %q; should be absent", v) 267 } 268 }) 269 } 270 271 func TestPathInfoNoRoot(t *testing.T) { 272 check(t) 273 h := &Handler{ 274 Path: "testdata/test.cgi", 275 Root: "", 276 } 277 expectedMap := map[string]string{ 278 "env-PATH_INFO": "/bar", 279 "env-QUERY_STRING": "a=b", 280 "env-REQUEST_URI": "/bar?a=b", 281 "env-SCRIPT_FILENAME": "testdata/test.cgi", 282 "env-SCRIPT_NAME": "/", 283 } 284 runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap) 285 } 286 287 func TestCGIBasicPost(t *testing.T) { 288 check(t) 289 postReq := `POST /test.cgi?a=b HTTP/1.0 290 Host: example.com 291 Content-Type: application/x-www-form-urlencoded 292 Content-Length: 15 293 294 postfoo=postbar` 295 h := &Handler{ 296 Path: "testdata/test.cgi", 297 Root: "/test.cgi", 298 } 299 expectedMap := map[string]string{ 300 "test": "Hello CGI", 301 "param-postfoo": "postbar", 302 "env-REQUEST_METHOD": "POST", 303 "env-CONTENT_LENGTH": "15", 304 "env-REQUEST_URI": "/test.cgi?a=b", 305 } 306 runCgiTest(t, h, postReq, expectedMap) 307 } 308 309 func chunk(s string) string { 310 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s) 311 } 312 313 // The CGI spec doesn't allow chunked requests. 314 func TestCGIPostChunked(t *testing.T) { 315 check(t) 316 postReq := `POST /test.cgi?a=b HTTP/1.1 317 Host: example.com 318 Content-Type: application/x-www-form-urlencoded 319 Transfer-Encoding: chunked 320 321 ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("") 322 323 h := &Handler{ 324 Path: "testdata/test.cgi", 325 Root: "/test.cgi", 326 } 327 expectedMap := map[string]string{} 328 resp := runCgiTest(t, h, postReq, expectedMap) 329 if got, expected := resp.Code, http.StatusBadRequest; got != expected { 330 t.Fatalf("Expected %v response code from chunked request body; got %d", 331 expected, got) 332 } 333 } 334 335 //goland:noinspection HttpUrlsUsage 336 func TestRedirect(t *testing.T) { 337 check(t) 338 h := &Handler{ 339 Path: "testdata/test.cgi", 340 Root: "/test.cgi", 341 } 342 rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil) 343 if e, g := 302, rec.Code; e != g { 344 t.Errorf("expected status code %d; got %d", e, g) 345 } 346 if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g { 347 t.Errorf("expected Location header of %q; got %q", e, g) 348 } 349 } 350 351 func TestInternalRedirect(t *testing.T) { 352 check(t) 353 baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) { 354 _, _ = fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path) 355 _, _ = fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr) 356 }) 357 h := &Handler{ 358 Path: "testdata/test.cgi", 359 Root: "/test.cgi", 360 PathLocationHandler: baseHandler, 361 } 362 expectedMap := map[string]string{ 363 "basepath": "/foo", 364 "remoteaddr": "1.2.3.4:1234", 365 } 366 runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap) 367 } 368 369 // TestCopyError tests that we kill the process if there's an error copying 370 // its output. (for example, from the client having gone away) 371 // 372 //goland:noinspection HttpUrlsUsage 373 func TestCopyError(t *testing.T) { 374 check(t) 375 if runtime.GOOS == "windows" { 376 t.Skipf("skipping test on %q", runtime.GOOS) 377 } 378 h := &Handler{ 379 Path: "testdata/test.cgi", 380 Root: "/test.cgi", 381 } 382 ts := httptest.NewServer(h) 383 defer ts.Close() 384 385 conn, err := net.Dial("tcp", ts.Listener.Addr().String()) 386 if err != nil { 387 t.Fatal(err) 388 } 389 req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil) 390 err = req.Write(conn) 391 if err != nil { 392 t.Fatalf("Write: %v", err) 393 } 394 395 res, err := http.ReadResponse(bufio.NewReader(conn), req) 396 if err != nil { 397 t.Fatalf("ReadResponse: %v", err) 398 } 399 400 pidstr := res.Header.Get("X-CGI-Pid") 401 if pidstr == "" { 402 t.Fatalf("expected an X-CGI-Pid header in response") 403 } 404 pid, err := strconv.Atoi(pidstr) 405 if err != nil { 406 t.Fatalf("invalid X-CGI-Pid value") 407 } 408 409 var buf [5000]byte 410 n, err := io.ReadFull(res.Body, buf[:]) 411 if err != nil { 412 t.Fatalf("ReadFull: %d bytes, %v", n, err) 413 } 414 415 childRunning := func() bool { 416 return isProcessRunning(pid) 417 } 418 419 if !childRunning() { 420 t.Fatalf("pre-conn.Close, expected child to be running") 421 } 422 _ = conn.Close() 423 424 tries := 0 425 for tries < 25 && childRunning() { 426 time.Sleep(50 * time.Millisecond * time.Duration(tries)) 427 tries++ 428 } 429 if childRunning() { 430 t.Fatalf("post-conn.Close, expected child to be gone") 431 } 432 } 433 434 func TestDirUnix(t *testing.T) { 435 check(t) 436 if runtime.GOOS == "windows" { 437 t.Skipf("skipping test on %q", runtime.GOOS) 438 } 439 cwd, _ := os.Getwd() 440 h := &Handler{ 441 Path: "testdata/test.cgi", 442 Root: "/test.cgi", 443 Dir: cwd, 444 } 445 expectedMap := map[string]string{ 446 "cwd": cwd, 447 } 448 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap) 449 450 cwd, _ = os.Getwd() 451 cwd = filepath.Join(cwd, "testdata") 452 h = &Handler{ 453 Path: "testdata/test.cgi", 454 Root: "/test.cgi", 455 } 456 expectedMap = map[string]string{ 457 "cwd": cwd, 458 } 459 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap) 460 } 461 462 func findPerl(t *testing.T) string { 463 t.Helper() 464 perl, err := exec.LookPath("perl") 465 if err != nil { 466 t.Skip("Skipping test: perl not found.") 467 } 468 perl, _ = filepath.Abs(perl) 469 470 cmd := exec.Command(perl, "-e", "print 123") 471 cmd.Env = []string{"PATH=/garbage"} 472 out, err := cmd.Output() 473 if err != nil || string(out) != "123" { 474 t.Skipf("Skipping test: %s is not functional", perl) 475 } 476 return perl 477 } 478 479 func TestDirWindows(t *testing.T) { 480 if runtime.GOOS != "windows" { 481 t.Skip("Skipping windows specific test.") 482 } 483 484 cgifile, _ := filepath.Abs("testdata/test.cgi") 485 486 perl := findPerl(t) 487 488 cwd, _ := os.Getwd() 489 h := &Handler{ 490 Path: perl, 491 Root: "/test.cgi", 492 Dir: cwd, 493 Args: []string{cgifile}, 494 Env: []string{"SCRIPT_FILENAME=" + cgifile}, 495 } 496 expectedMap := map[string]string{ 497 "cwd": cwd, 498 } 499 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap) 500 501 // If not specify Dir on windows, working directory should be 502 // base directory of perl. 503 cwd, _ = filepath.Split(perl) 504 if cwd != "" && cwd[len(cwd)-1] == filepath.Separator { 505 cwd = cwd[:len(cwd)-1] 506 } 507 h = &Handler{ 508 Path: perl, 509 Root: "/test.cgi", 510 Args: []string{cgifile}, 511 Env: []string{"SCRIPT_FILENAME=" + cgifile}, 512 } 513 expectedMap = map[string]string{ 514 "cwd": cwd, 515 } 516 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap) 517 } 518 519 func TestEnvOverride(t *testing.T) { 520 check(t) 521 cgifile, _ := filepath.Abs("testdata/test.cgi") 522 523 perl := findPerl(t) 524 525 cwd, _ := os.Getwd() 526 h := &Handler{ 527 Path: perl, 528 Root: "/test.cgi", 529 Dir: cwd, 530 Args: []string{cgifile}, 531 Env: []string{ 532 "SCRIPT_FILENAME=" + cgifile, 533 "REQUEST_URI=/foo/bar", 534 "PATH=/wibble"}, 535 } 536 expectedMap := map[string]string{ 537 "cwd": cwd, 538 "env-SCRIPT_FILENAME": cgifile, 539 "env-REQUEST_URI": "/foo/bar", 540 "env-PATH": "/wibble", 541 } 542 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap) 543 } 544 545 func TestHandlerStderr(t *testing.T) { 546 check(t) 547 var stderr bytes.Buffer 548 h := &Handler{ 549 Path: "testdata/test.cgi", 550 Root: "/test.cgi", 551 Stderr: &stderr, 552 } 553 554 rw := httptest.NewRecorder() 555 req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n") 556 h.ServeHTTP(rw, req) 557 if got, want := stderr.String(), "Hello, stderr!\n"; got != want { 558 t.Errorf("Stderr = %q; want %q", got, want) 559 } 560 } 561 562 func TestRemoveLeadingDuplicates(t *testing.T) { 563 tests := []struct { 564 env []string 565 want []string 566 }{ 567 { 568 env: []string{"a=b", "b=c", "a=b2"}, 569 want: []string{"b=c", "a=b2"}, 570 }, 571 { 572 env: []string{"a=b", "b=c", "d", "e=f"}, 573 want: []string{"a=b", "b=c", "d", "e=f"}, 574 }, 575 } 576 for _, tt := range tests { 577 got := removeLeadingDuplicates(tt.env) 578 if !reflect.DeepEqual(got, tt.want) { 579 t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want) 580 } 581 } 582 }