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