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