Home | History | Annotate | Download | only in cgi
      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