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