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