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