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