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