Source file src/net/http/cgi/host_test.go

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

View as plain text