github.com/hxx258456/ccgo@v0.0.5-0.20230213014102-48b35f46f66f/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 "github.com/hxx258456/ccgo/gmhttp"
    26  	"github.com/hxx258456/ccgo/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  func TestRedirect(t *testing.T) {
   336  	check(t)
   337  	h := &Handler{
   338  		Path: "testdata/test.cgi",
   339  		Root: "/test.cgi",
   340  	}
   341  	rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
   342  	if e, g := 302, rec.Code; e != g {
   343  		t.Errorf("expected status code %d; got %d", e, g)
   344  	}
   345  	if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g {
   346  		t.Errorf("expected Location header of %q; got %q", e, g)
   347  	}
   348  }
   349  
   350  func TestInternalRedirect(t *testing.T) {
   351  	check(t)
   352  	baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
   353  		fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
   354  		fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
   355  	})
   356  	h := &Handler{
   357  		Path:                "testdata/test.cgi",
   358  		Root:                "/test.cgi",
   359  		PathLocationHandler: baseHandler,
   360  	}
   361  	expectedMap := map[string]string{
   362  		"basepath":   "/foo",
   363  		"remoteaddr": "1.2.3.4:1234",
   364  	}
   365  	runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap)
   366  }
   367  
   368  // TestCopyError tests that we kill the process if there's an error copying
   369  // its output. (for example, from the client having gone away)
   370  func TestCopyError(t *testing.T) {
   371  	check(t)
   372  	if runtime.GOOS == "windows" {
   373  		t.Skipf("skipping test on %q", runtime.GOOS)
   374  	}
   375  	h := &Handler{
   376  		Path: "testdata/test.cgi",
   377  		Root: "/test.cgi",
   378  	}
   379  	ts := httptest.NewServer(h)
   380  	defer ts.Close()
   381  
   382  	conn, err := net.Dial("tcp", ts.Listener.Addr().String())
   383  	if err != nil {
   384  		t.Fatal(err)
   385  	}
   386  	req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
   387  	err = req.Write(conn)
   388  	if err != nil {
   389  		t.Fatalf("Write: %v", err)
   390  	}
   391  
   392  	res, err := http.ReadResponse(bufio.NewReader(conn), req)
   393  	if err != nil {
   394  		t.Fatalf("ReadResponse: %v", err)
   395  	}
   396  
   397  	pidstr := res.Header.Get("X-CGI-Pid")
   398  	if pidstr == "" {
   399  		t.Fatalf("expected an X-CGI-Pid header in response")
   400  	}
   401  	pid, err := strconv.Atoi(pidstr)
   402  	if err != nil {
   403  		t.Fatalf("invalid X-CGI-Pid value")
   404  	}
   405  
   406  	var buf [5000]byte
   407  	n, err := io.ReadFull(res.Body, buf[:])
   408  	if err != nil {
   409  		t.Fatalf("ReadFull: %d bytes, %v", n, err)
   410  	}
   411  
   412  	childRunning := func() bool {
   413  		return isProcessRunning(pid)
   414  	}
   415  
   416  	if !childRunning() {
   417  		t.Fatalf("pre-conn.Close, expected child to be running")
   418  	}
   419  	conn.Close()
   420  
   421  	tries := 0
   422  	for tries < 25 && childRunning() {
   423  		time.Sleep(50 * time.Millisecond * time.Duration(tries))
   424  		tries++
   425  	}
   426  	if childRunning() {
   427  		t.Fatalf("post-conn.Close, expected child to be gone")
   428  	}
   429  }
   430  
   431  func TestDirUnix(t *testing.T) {
   432  	check(t)
   433  	if runtime.GOOS == "windows" {
   434  		t.Skipf("skipping test on %q", runtime.GOOS)
   435  	}
   436  	cwd, _ := os.Getwd()
   437  	h := &Handler{
   438  		Path: "testdata/test.cgi",
   439  		Root: "/test.cgi",
   440  		Dir:  cwd,
   441  	}
   442  	expectedMap := map[string]string{
   443  		"cwd": cwd,
   444  	}
   445  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   446  
   447  	cwd, _ = os.Getwd()
   448  	cwd = filepath.Join(cwd, "testdata")
   449  	h = &Handler{
   450  		Path: "testdata/test.cgi",
   451  		Root: "/test.cgi",
   452  	}
   453  	expectedMap = map[string]string{
   454  		"cwd": cwd,
   455  	}
   456  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   457  }
   458  
   459  func findPerl(t *testing.T) string {
   460  	t.Helper()
   461  	perl, err := exec.LookPath("perl")
   462  	if err != nil {
   463  		t.Skip("Skipping test: perl not found.")
   464  	}
   465  	perl, _ = filepath.Abs(perl)
   466  
   467  	cmd := exec.Command(perl, "-e", "print 123")
   468  	cmd.Env = []string{"PATH=/garbage"}
   469  	out, err := cmd.Output()
   470  	if err != nil || string(out) != "123" {
   471  		t.Skipf("Skipping test: %s is not functional", perl)
   472  	}
   473  	return perl
   474  }
   475  
   476  func TestDirWindows(t *testing.T) {
   477  	if runtime.GOOS != "windows" {
   478  		t.Skip("Skipping windows specific test.")
   479  	}
   480  
   481  	cgifile, _ := filepath.Abs("testdata/test.cgi")
   482  
   483  	perl := findPerl(t)
   484  
   485  	cwd, _ := os.Getwd()
   486  	h := &Handler{
   487  		Path: perl,
   488  		Root: "/test.cgi",
   489  		Dir:  cwd,
   490  		Args: []string{cgifile},
   491  		Env:  []string{"SCRIPT_FILENAME=" + cgifile},
   492  	}
   493  	expectedMap := map[string]string{
   494  		"cwd": cwd,
   495  	}
   496  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   497  
   498  	// If not specify Dir on windows, working directory should be
   499  	// base directory of perl.
   500  	cwd, _ = filepath.Split(perl)
   501  	if cwd != "" && cwd[len(cwd)-1] == filepath.Separator {
   502  		cwd = cwd[:len(cwd)-1]
   503  	}
   504  	h = &Handler{
   505  		Path: perl,
   506  		Root: "/test.cgi",
   507  		Args: []string{cgifile},
   508  		Env:  []string{"SCRIPT_FILENAME=" + cgifile},
   509  	}
   510  	expectedMap = map[string]string{
   511  		"cwd": cwd,
   512  	}
   513  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   514  }
   515  
   516  func TestEnvOverride(t *testing.T) {
   517  	check(t)
   518  	cgifile, _ := filepath.Abs("testdata/test.cgi")
   519  
   520  	perl := findPerl(t)
   521  
   522  	cwd, _ := os.Getwd()
   523  	h := &Handler{
   524  		Path: perl,
   525  		Root: "/test.cgi",
   526  		Dir:  cwd,
   527  		Args: []string{cgifile},
   528  		Env: []string{
   529  			"SCRIPT_FILENAME=" + cgifile,
   530  			"REQUEST_URI=/foo/bar",
   531  			"PATH=/wibble"},
   532  	}
   533  	expectedMap := map[string]string{
   534  		"cwd":                 cwd,
   535  		"env-SCRIPT_FILENAME": cgifile,
   536  		"env-REQUEST_URI":     "/foo/bar",
   537  		"env-PATH":            "/wibble",
   538  	}
   539  	runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
   540  }
   541  
   542  func TestHandlerStderr(t *testing.T) {
   543  	check(t)
   544  	var stderr bytes.Buffer
   545  	h := &Handler{
   546  		Path:   "testdata/test.cgi",
   547  		Root:   "/test.cgi",
   548  		Stderr: &stderr,
   549  	}
   550  
   551  	rw := httptest.NewRecorder()
   552  	req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
   553  	h.ServeHTTP(rw, req)
   554  	if got, want := stderr.String(), "Hello, stderr!\n"; got != want {
   555  		t.Errorf("Stderr = %q; want %q", got, want)
   556  	}
   557  }
   558  
   559  func TestRemoveLeadingDuplicates(t *testing.T) {
   560  	tests := []struct {
   561  		env  []string
   562  		want []string
   563  	}{
   564  		{
   565  			env:  []string{"a=b", "b=c", "a=b2"},
   566  			want: []string{"b=c", "a=b2"},
   567  		},
   568  		{
   569  			env:  []string{"a=b", "b=c", "d", "e=f"},
   570  			want: []string{"a=b", "b=c", "d", "e=f"},
   571  		},
   572  	}
   573  	for _, tt := range tests {
   574  		got := removeLeadingDuplicates(tt.env)
   575  		if !reflect.DeepEqual(got, tt.want) {
   576  			t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want)
   577  		}
   578  	}
   579  }