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.
5 // Tests for package cgi
28 func newRequest(httpreq string) *http.Request {
29 buf := bufio.NewReader(strings.NewReader(httpreq))
30 req, err := http.ReadRequest(buf)
32 panic("cgi: bogus http request in test: " + httpreq)
34 req.RemoteAddr = "1.2.3.4:1234"
38 func runCgiTest(t *testing.T, h *Handler, httpreq string, expectedMap map[string]string) *httptest.ResponseRecorder {
39 rw := httptest.NewRecorder()
40 req := newRequest(httpreq)
42 runResponseChecks(t, rw, expectedMap)
46 func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder, expectedMap map[string]string) {
47 // Make a map to hold the test map that the CGI returns.
48 m := make(map[string]string)
49 m["_body"] = rw.Body.String()
53 line, err := rw.Body.ReadString('\n')
58 t.Fatalf("unexpected error reading from CGI: %v", err)
61 trimmedLine := strings.TrimRight(line, "\r\n")
62 split := strings.SplitN(trimmedLine, "=", 2)
64 t.Fatalf("Unexpected %d parts from invalid line number %v: %q; existing map=%v",
65 len(split), linesRead, line, m)
67 m[split[0]] = split[1]
70 for key, expected := range expectedMap {
73 // For Windows. golang.org/issue/4645.
74 fi1, _ := os.Stat(got)
75 fi2, _ := os.Stat(expected)
76 if os.SameFile(fi1, fi2) {
81 t.Errorf("for key %q got %q; expected %q", key, got, expected)
86 var cgiTested, cgiWorks bool
88 func check(t *testing.T) {
91 cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil
94 // No Perl on Windows, needed by test.cgi
95 // TODO: make the child process be Go, not Perl.
96 t.Skip("Skipping test: test.cgi failed.")
100 func TestCGIBasicGet(t *testing.T) {
103 Path: "testdata/test.cgi",
106 expectedMap := map[string]string{
110 "env-GATEWAY_INTERFACE": "CGI/1.1",
111 "env-HTTP_HOST": "example.com",
113 "env-QUERY_STRING": "foo=bar&a=b",
114 "env-REMOTE_ADDR": "1.2.3.4",
115 "env-REMOTE_HOST": "1.2.3.4",
116 "env-REMOTE_PORT": "1234",
117 "env-REQUEST_METHOD": "GET",
118 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
119 "env-SCRIPT_FILENAME": "testdata/test.cgi",
120 "env-SCRIPT_NAME": "/test.cgi",
121 "env-SERVER_NAME": "example.com",
122 "env-SERVER_PORT": "80",
123 "env-SERVER_SOFTWARE": "go",
125 replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
127 if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected {
128 t.Errorf("got a Content-Type of %q; expected %q", got, expected)
130 if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
131 t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
135 func TestCGIEnvIPv6(t *testing.T) {
138 Path: "testdata/test.cgi",
141 expectedMap := map[string]string{
145 "env-GATEWAY_INTERFACE": "CGI/1.1",
146 "env-HTTP_HOST": "example.com",
148 "env-QUERY_STRING": "foo=bar&a=b",
149 "env-REMOTE_ADDR": "2000::3000",
150 "env-REMOTE_HOST": "2000::3000",
151 "env-REMOTE_PORT": "12345",
152 "env-REQUEST_METHOD": "GET",
153 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
154 "env-SCRIPT_FILENAME": "testdata/test.cgi",
155 "env-SCRIPT_NAME": "/test.cgi",
156 "env-SERVER_NAME": "example.com",
157 "env-SERVER_PORT": "80",
158 "env-SERVER_SOFTWARE": "go",
161 rw := httptest.NewRecorder()
162 req := newRequest("GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n")
163 req.RemoteAddr = "[2000::3000]:12345"
165 runResponseChecks(t, rw, expectedMap)
168 func TestCGIBasicGetAbsPath(t *testing.T) {
170 pwd, err := os.Getwd()
172 t.Fatalf("getwd error: %v", err)
175 Path: pwd + "/testdata/test.cgi",
178 expectedMap := map[string]string{
179 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
180 "env-SCRIPT_FILENAME": pwd + "/testdata/test.cgi",
181 "env-SCRIPT_NAME": "/test.cgi",
183 runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
186 func TestPathInfo(t *testing.T) {
189 Path: "testdata/test.cgi",
192 expectedMap := map[string]string{
194 "env-PATH_INFO": "/extrapath",
195 "env-QUERY_STRING": "a=b",
196 "env-REQUEST_URI": "/test.cgi/extrapath?a=b",
197 "env-SCRIPT_FILENAME": "testdata/test.cgi",
198 "env-SCRIPT_NAME": "/test.cgi",
200 runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
203 func TestPathInfoDirRoot(t *testing.T) {
206 Path: "testdata/test.cgi",
209 expectedMap := map[string]string{
210 "env-PATH_INFO": "bar",
211 "env-QUERY_STRING": "a=b",
212 "env-REQUEST_URI": "/myscript/bar?a=b",
213 "env-SCRIPT_FILENAME": "testdata/test.cgi",
214 "env-SCRIPT_NAME": "/myscript/",
216 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
219 func TestDupHeaders(t *testing.T) {
222 Path: "testdata/test.cgi",
224 expectedMap := map[string]string{
225 "env-REQUEST_URI": "/myscript/bar?a=b",
226 "env-SCRIPT_FILENAME": "testdata/test.cgi",
227 "env-HTTP_COOKIE": "nom=NOM; yum=YUM",
228 "env-HTTP_X_FOO": "val1, val2",
230 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
235 "Host: example.com\n\n",
239 func TestPathInfoNoRoot(t *testing.T) {
242 Path: "testdata/test.cgi",
245 expectedMap := map[string]string{
246 "env-PATH_INFO": "/bar",
247 "env-QUERY_STRING": "a=b",
248 "env-REQUEST_URI": "/bar?a=b",
249 "env-SCRIPT_FILENAME": "testdata/test.cgi",
250 "env-SCRIPT_NAME": "/",
252 runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
255 func TestCGIBasicPost(t *testing.T) {
257 postReq := `POST /test.cgi?a=b HTTP/1.0
259 Content-Type: application/x-www-form-urlencoded
264 Path: "testdata/test.cgi",
267 expectedMap := map[string]string{
269 "param-postfoo": "postbar",
270 "env-REQUEST_METHOD": "POST",
271 "env-CONTENT_LENGTH": "15",
272 "env-REQUEST_URI": "/test.cgi?a=b",
274 runCgiTest(t, h, postReq, expectedMap)
277 func chunk(s string) string {
278 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
281 // The CGI spec doesn't allow chunked requests.
282 func TestCGIPostChunked(t *testing.T) {
284 postReq := `POST /test.cgi?a=b HTTP/1.1
286 Content-Type: application/x-www-form-urlencoded
287 Transfer-Encoding: chunked
289 ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
292 Path: "testdata/test.cgi",
295 expectedMap := map[string]string{}
296 resp := runCgiTest(t, h, postReq, expectedMap)
297 if got, expected := resp.Code, http.StatusBadRequest; got != expected {
298 t.Fatalf("Expected %v response code from chunked request body; got %d",
303 func TestRedirect(t *testing.T) {
306 Path: "testdata/test.cgi",
309 rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
310 if e, g := 302, rec.Code; e != g {
311 t.Errorf("expected status code %d; got %d", e, g)
313 if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g {
314 t.Errorf("expected Location header of %q; got %q", e, g)
318 func TestInternalRedirect(t *testing.T) {
320 baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
321 fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
322 fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
325 Path: "testdata/test.cgi",
327 PathLocationHandler: baseHandler,
329 expectedMap := map[string]string{
331 "remoteaddr": "1.2.3.4:1234",
333 runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap)
336 // TestCopyError tests that we kill the process if there's an error copying
337 // its output. (for example, from the client having gone away)
338 func TestCopyError(t *testing.T) {
340 if runtime.GOOS == "windows" {
341 t.Skipf("skipping test on %q", runtime.GOOS)
344 Path: "testdata/test.cgi",
347 ts := httptest.NewServer(h)
350 conn, err := net.Dial("tcp", ts.Listener.Addr().String())
354 req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
355 err = req.Write(conn)
357 t.Fatalf("Write: %v", err)
360 res, err := http.ReadResponse(bufio.NewReader(conn), req)
362 t.Fatalf("ReadResponse: %v", err)
365 pidstr := res.Header.Get("X-CGI-Pid")
367 t.Fatalf("expected an X-CGI-Pid header in response")
369 pid, err := strconv.Atoi(pidstr)
371 t.Fatalf("invalid X-CGI-Pid value")
375 n, err := io.ReadFull(res.Body, buf[:])
377 t.Fatalf("ReadFull: %d bytes, %v", n, err)
380 childRunning := func() bool {
381 return isProcessRunning(t, pid)
385 t.Fatalf("pre-conn.Close, expected child to be running")
390 for tries < 25 && childRunning() {
391 time.Sleep(50 * time.Millisecond * time.Duration(tries))
395 t.Fatalf("post-conn.Close, expected child to be gone")
399 func TestDirUnix(t *testing.T) {
401 if runtime.GOOS == "windows" {
402 t.Skipf("skipping test on %q", runtime.GOOS)
406 Path: "testdata/test.cgi",
410 expectedMap := map[string]string{
413 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
416 cwd = filepath.Join(cwd, "testdata")
418 Path: "testdata/test.cgi",
421 expectedMap = map[string]string{
424 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
427 func TestDirWindows(t *testing.T) {
428 if runtime.GOOS != "windows" {
429 t.Skip("Skipping windows specific test.")
432 cgifile, _ := filepath.Abs("testdata/test.cgi")
436 perl, err = exec.LookPath("perl")
438 t.Skip("Skipping test: perl not found.")
440 perl, _ = filepath.Abs(perl)
447 Args: []string{cgifile},
448 Env: []string{"SCRIPT_FILENAME=" + cgifile},
450 expectedMap := map[string]string{
453 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
455 // If not specify Dir on windows, working directory should be
456 // base directory of perl.
457 cwd, _ = filepath.Split(perl)
458 if cwd != "" && cwd[len(cwd)-1] == filepath.Separator {
459 cwd = cwd[:len(cwd)-1]
464 Args: []string{cgifile},
465 Env: []string{"SCRIPT_FILENAME=" + cgifile},
467 expectedMap = map[string]string{
470 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
473 func TestEnvOverride(t *testing.T) {
474 cgifile, _ := filepath.Abs("testdata/test.cgi")
478 perl, err = exec.LookPath("perl")
480 t.Skipf("Skipping test: perl not found.")
482 perl, _ = filepath.Abs(perl)
489 Args: []string{cgifile},
491 "SCRIPT_FILENAME=" + cgifile,
492 "REQUEST_URI=/foo/bar",
495 expectedMap := map[string]string{
497 "env-SCRIPT_FILENAME": cgifile,
498 "env-REQUEST_URI": "/foo/bar",
499 "env-PATH": "/wibble",
501 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
504 func TestHandlerStderr(t *testing.T) {
506 var stderr bytes.Buffer
508 Path: "testdata/test.cgi",
513 rw := httptest.NewRecorder()
514 req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
516 if got, want := stderr.String(), "Hello, stderr!\n"; got != want {
517 t.Errorf("Stderr = %q; want %q", got, want)
521 func TestRemoveLeadingDuplicates(t *testing.T) {
527 env: []string{"a=b", "b=c", "a=b2"},
528 want: []string{"b=c", "a=b2"},
531 env: []string{"a=b", "b=c", "d", "e=f"},
532 want: []string{"a=b", "b=c", "d", "e=f"},
535 for _, tt := range tests {
536 got := removeLeadingDuplicates(tt.env)
537 if !reflect.DeepEqual(got, tt.want) {
538 t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want)