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
27 // TestMain executes the test binary as the cgi server if
28 // SERVER_SOFTWARE is set, and runs the tests otherwise.
29 func TestMain(m *testing.M) {
30 // SERVER_SOFTWARE swap variable is set when starting the cgi server.
31 if os.Getenv("SERVER_SOFTWARE") != "" {
39 func newRequest(httpreq string) *http.Request {
40 buf := bufio.NewReader(strings.NewReader(httpreq))
41 req, err := http.ReadRequest(buf)
43 panic("cgi: bogus http request in test: " + httpreq)
45 req.RemoteAddr = "1.2.3.4:1234"
49 func runCgiTest(t *testing.T, h *Handler,
51 expectedMap map[string]string, checks ...func(reqInfo map[string]string)) *httptest.ResponseRecorder {
52 rw := httptest.NewRecorder()
53 req := newRequest(httpreq)
55 runResponseChecks(t, rw, expectedMap, checks...)
59 func runResponseChecks(t *testing.T, rw *httptest.ResponseRecorder,
60 expectedMap map[string]string, checks ...func(reqInfo map[string]string)) {
61 // Make a map to hold the test map that the CGI returns.
62 m := make(map[string]string)
63 m["_body"] = rw.Body.String()
67 line, err := rw.Body.ReadString('\n')
72 t.Fatalf("unexpected error reading from CGI: %v", err)
75 trimmedLine := strings.TrimRight(line, "\r\n")
76 k, v, ok := strings.Cut(trimmedLine, "=")
78 t.Fatalf("Unexpected response from invalid line number %v: %q; existing map=%v",
84 for key, expected := range expectedMap {
87 // For Windows. golang.org/issue/4645.
88 fi1, _ := os.Stat(got)
89 fi2, _ := os.Stat(expected)
90 if os.SameFile(fi1, fi2) {
95 t.Errorf("for key %q got %q; expected %q", key, got, expected)
98 for _, check := range checks {
103 func TestCGIBasicGet(t *testing.T) {
104 testenv.MustHaveExec(t)
109 expectedMap := map[string]string{
113 "env-GATEWAY_INTERFACE": "CGI/1.1",
114 "env-HTTP_HOST": "example.com:80",
116 "env-QUERY_STRING": "foo=bar&a=b",
117 "env-REMOTE_ADDR": "1.2.3.4",
118 "env-REMOTE_HOST": "1.2.3.4",
119 "env-REMOTE_PORT": "1234",
120 "env-REQUEST_METHOD": "GET",
121 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
122 "env-SCRIPT_FILENAME": os.Args[0],
123 "env-SCRIPT_NAME": "/test.cgi",
124 "env-SERVER_NAME": "example.com",
125 "env-SERVER_PORT": "80",
126 "env-SERVER_SOFTWARE": "go",
128 replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com:80\n\n", expectedMap)
130 if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected {
131 t.Errorf("got a Content-Type of %q; expected %q", got, expected)
133 if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
134 t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
138 func TestCGIEnvIPv6(t *testing.T) {
139 testenv.MustHaveExec(t)
144 expectedMap := map[string]string{
148 "env-GATEWAY_INTERFACE": "CGI/1.1",
149 "env-HTTP_HOST": "example.com",
151 "env-QUERY_STRING": "foo=bar&a=b",
152 "env-REMOTE_ADDR": "2000::3000",
153 "env-REMOTE_HOST": "2000::3000",
154 "env-REMOTE_PORT": "12345",
155 "env-REQUEST_METHOD": "GET",
156 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
157 "env-SCRIPT_FILENAME": os.Args[0],
158 "env-SCRIPT_NAME": "/test.cgi",
159 "env-SERVER_NAME": "example.com",
160 "env-SERVER_PORT": "80",
161 "env-SERVER_SOFTWARE": "go",
164 rw := httptest.NewRecorder()
165 req := newRequest("GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n")
166 req.RemoteAddr = "[2000::3000]:12345"
168 runResponseChecks(t, rw, expectedMap)
171 func TestCGIBasicGetAbsPath(t *testing.T) {
172 absPath, err := filepath.Abs(os.Args[0])
176 testenv.MustHaveExec(t)
181 expectedMap := map[string]string{
182 "env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
183 "env-SCRIPT_FILENAME": absPath,
184 "env-SCRIPT_NAME": "/test.cgi",
186 runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
189 func TestPathInfo(t *testing.T) {
190 testenv.MustHaveExec(t)
195 expectedMap := map[string]string{
197 "env-PATH_INFO": "/extrapath",
198 "env-QUERY_STRING": "a=b",
199 "env-REQUEST_URI": "/test.cgi/extrapath?a=b",
200 "env-SCRIPT_FILENAME": os.Args[0],
201 "env-SCRIPT_NAME": "/test.cgi",
203 runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
206 func TestPathInfoDirRoot(t *testing.T) {
207 testenv.MustHaveExec(t)
212 expectedMap := map[string]string{
213 "env-PATH_INFO": "/bar",
214 "env-QUERY_STRING": "a=b",
215 "env-REQUEST_URI": "/myscript/bar?a=b",
216 "env-SCRIPT_FILENAME": os.Args[0],
217 "env-SCRIPT_NAME": "/myscript",
219 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
222 func TestDupHeaders(t *testing.T) {
223 testenv.MustHaveExec(t)
227 expectedMap := map[string]string{
228 "env-REQUEST_URI": "/myscript/bar?a=b",
229 "env-SCRIPT_FILENAME": os.Args[0],
230 "env-HTTP_COOKIE": "nom=NOM; yum=YUM",
231 "env-HTTP_X_FOO": "val1, val2",
233 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
238 "Host: example.com\n\n",
242 // Issue 16405: CGI+http.Transport differing uses of HTTP_PROXY.
243 // Verify we don't set the HTTP_PROXY environment variable.
244 // Hope nobody was depending on it. It's not a known header, though.
245 func TestDropProxyHeader(t *testing.T) {
246 testenv.MustHaveExec(t)
250 expectedMap := map[string]string{
251 "env-REQUEST_URI": "/myscript/bar?a=b",
252 "env-SCRIPT_FILENAME": os.Args[0],
253 "env-HTTP_X_FOO": "a",
255 runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
257 "Proxy: should_be_stripped\n"+
258 "Host: example.com\n\n",
260 func(reqInfo map[string]string) {
261 if v, ok := reqInfo["env-HTTP_PROXY"]; ok {
262 t.Errorf("HTTP_PROXY = %q; should be absent", v)
267 func TestPathInfoNoRoot(t *testing.T) {
268 testenv.MustHaveExec(t)
273 expectedMap := map[string]string{
274 "env-PATH_INFO": "/bar",
275 "env-QUERY_STRING": "a=b",
276 "env-REQUEST_URI": "/bar?a=b",
277 "env-SCRIPT_FILENAME": os.Args[0],
278 "env-SCRIPT_NAME": "",
280 runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
283 func TestCGIBasicPost(t *testing.T) {
284 testenv.MustHaveExec(t)
285 postReq := `POST /test.cgi?a=b HTTP/1.0
287 Content-Type: application/x-www-form-urlencoded
295 expectedMap := map[string]string{
297 "param-postfoo": "postbar",
298 "env-REQUEST_METHOD": "POST",
299 "env-CONTENT_LENGTH": "15",
300 "env-REQUEST_URI": "/test.cgi?a=b",
302 runCgiTest(t, h, postReq, expectedMap)
305 func chunk(s string) string {
306 return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
309 // The CGI spec doesn't allow chunked requests.
310 func TestCGIPostChunked(t *testing.T) {
311 testenv.MustHaveExec(t)
312 postReq := `POST /test.cgi?a=b HTTP/1.1
314 Content-Type: application/x-www-form-urlencoded
315 Transfer-Encoding: chunked
317 ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
323 expectedMap := map[string]string{}
324 resp := runCgiTest(t, h, postReq, expectedMap)
325 if got, expected := resp.Code, http.StatusBadRequest; got != expected {
326 t.Fatalf("Expected %v response code from chunked request body; got %d",
331 func TestRedirect(t *testing.T) {
332 testenv.MustHaveExec(t)
337 rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
338 if e, g := 302, rec.Code; e != g {
339 t.Errorf("expected status code %d; got %d", e, g)
341 if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g {
342 t.Errorf("expected Location header of %q; got %q", e, g)
346 func TestInternalRedirect(t *testing.T) {
347 testenv.MustHaveExec(t)
348 baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
349 fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
350 fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
355 PathLocationHandler: baseHandler,
357 expectedMap := map[string]string{
359 "remoteaddr": "1.2.3.4:1234",
361 runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap)
364 // TestCopyError tests that we kill the process if there's an error copying
365 // its output. (for example, from the client having gone away)
366 func TestCopyError(t *testing.T) {
367 testenv.MustHaveExec(t)
368 if runtime.GOOS == "windows" {
369 t.Skipf("skipping test on %q", runtime.GOOS)
375 ts := httptest.NewServer(h)
378 conn, err := net.Dial("tcp", ts.Listener.Addr().String())
382 req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
383 err = req.Write(conn)
385 t.Fatalf("Write: %v", err)
388 res, err := http.ReadResponse(bufio.NewReader(conn), req)
390 t.Fatalf("ReadResponse: %v", err)
393 pidstr := res.Header.Get("X-CGI-Pid")
395 t.Fatalf("expected an X-CGI-Pid header in response")
397 pid, err := strconv.Atoi(pidstr)
399 t.Fatalf("invalid X-CGI-Pid value")
403 n, err := io.ReadFull(res.Body, buf[:])
405 t.Fatalf("ReadFull: %d bytes, %v", n, err)
408 childRunning := func() bool {
409 return isProcessRunning(pid)
413 t.Fatalf("pre-conn.Close, expected child to be running")
418 for tries < 25 && childRunning() {
419 time.Sleep(50 * time.Millisecond * time.Duration(tries))
423 t.Fatalf("post-conn.Close, expected child to be gone")
427 func TestDir(t *testing.T) {
428 testenv.MustHaveExec(t)
435 expectedMap := map[string]string{
438 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
441 cwd, _ = filepath.Split(os.Args[0])
446 expectedMap = map[string]string{
449 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
452 func TestEnvOverride(t *testing.T) {
453 testenv.MustHaveExec(t)
454 cgifile, _ := filepath.Abs("testdata/test.cgi")
462 "SCRIPT_FILENAME=" + cgifile,
463 "REQUEST_URI=/foo/bar",
466 expectedMap := map[string]string{
468 "env-SCRIPT_FILENAME": cgifile,
469 "env-REQUEST_URI": "/foo/bar",
470 "env-PATH": "/wibble",
472 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
475 func TestHandlerStderr(t *testing.T) {
476 testenv.MustHaveExec(t)
477 var stderr strings.Builder
484 rw := httptest.NewRecorder()
485 req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
487 if got, want := stderr.String(), "Hello, stderr!\n"; got != want {
488 t.Errorf("Stderr = %q; want %q", got, want)
492 func TestRemoveLeadingDuplicates(t *testing.T) {
498 env: []string{"a=b", "b=c", "a=b2"},
499 want: []string{"b=c", "a=b2"},
502 env: []string{"a=b", "b=c", "d", "e=f"},
503 want: []string{"a=b", "b=c", "d", "e=f"},
506 for _, tt := range tests {
507 got := removeLeadingDuplicates(tt.env)
508 if !reflect.DeepEqual(got, tt.want) {
509 t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want)