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