github.com/d4l3k/go@v0.0.0-20151015000803-65fc379daeda/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  	"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  			"PATH=/wibble"},
   492  	}
   493  	expectedMap := map[string]string{
   494  		"cwd": cwd,
   495  		"env-SCRIPT_FILENAME": cgifile,
   496  		"env-REQUEST_URI":     "/foo/bar",
   497  		"env-PATH":            "/wibble",
   498  	}
   499  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   500  }