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