--- /dev/null
+// Copyright 2023 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package cgi
+
+import (
+ "fmt"
+ "io"
+ "net/http"
+ "os"
+ "path"
+ "sort"
+ "strings"
+ "time"
+)
+
+func cgiMain() {
+ switch path.Join(os.Getenv("SCRIPT_NAME"), os.Getenv("PATH_INFO")) {
+ case "/bar", "/test.cgi", "/myscript/bar", "/test.cgi/extrapath":
+ testCGI()
+ return
+ }
+ childCGIProcess()
+}
+
+// testCGI is a CGI program translated from a Perl program to complete host_test.
+// test cases in host_test should be provided by testCGI.
+func testCGI() {
+ req, err := Request()
+ if err != nil {
+ panic(err)
+ }
+
+ err = req.ParseForm()
+ if err != nil {
+ panic(err)
+ }
+
+ params := req.Form
+ if params.Get("loc") != "" {
+ fmt.Printf("Location: %s\r\n\r\n", params.Get("loc"))
+ return
+ }
+
+ fmt.Printf("Content-Type: text/html\r\n")
+ fmt.Printf("X-CGI-Pid: %d\r\n", os.Getpid())
+ fmt.Printf("X-Test-Header: X-Test-Value\r\n")
+ fmt.Printf("\r\n")
+
+ if params.Get("writestderr") != "" {
+ fmt.Fprintf(os.Stderr, "Hello, stderr!\n")
+ }
+
+ if params.Get("bigresponse") != "" {
+ // 17 MB, for OS X: golang.org/issue/4958
+ line := strings.Repeat("A", 1024)
+ for i := 0; i < 17*1024; i++ {
+ fmt.Printf("%s\r\n", line)
+ }
+ return
+ }
+
+ fmt.Printf("test=Hello CGI\r\n")
+
+ keys := make([]string, 0, len(params))
+ for k := range params {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ fmt.Printf("param-%s=%s\r\n", key, params.Get(key))
+ }
+
+ envs := envMap(os.Environ())
+ keys = make([]string, 0, len(envs))
+ for k := range envs {
+ keys = append(keys, k)
+ }
+ sort.Strings(keys)
+ for _, key := range keys {
+ fmt.Printf("env-%s=%s\r\n", key, envs[key])
+ }
+
+ cwd, _ := os.Getwd()
+ fmt.Printf("cwd=%s\r\n", cwd)
+}
+
+type neverEnding byte
+
+func (b neverEnding) Read(p []byte) (n int, err error) {
+ for i := range p {
+ p[i] = byte(b)
+ }
+ return len(p), nil
+}
+
+// childCGIProcess is used by integration_test to complete unit tests.
+func childCGIProcess() {
+ if os.Getenv("REQUEST_METHOD") == "" {
+ // Not in a CGI environment; skipping test.
+ return
+ }
+ switch os.Getenv("REQUEST_URI") {
+ case "/immediate-disconnect":
+ os.Exit(0)
+ case "/no-content-type":
+ fmt.Printf("Content-Length: 6\n\nHello\n")
+ os.Exit(0)
+ case "/empty-headers":
+ fmt.Printf("\nHello")
+ os.Exit(0)
+ }
+ Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
+ if req.FormValue("nil-request-body") == "1" {
+ fmt.Fprintf(rw, "nil-request-body=%v\n", req.Body == nil)
+ return
+ }
+ rw.Header().Set("X-Test-Header", "X-Test-Value")
+ req.ParseForm()
+ if req.FormValue("no-body") == "1" {
+ return
+ }
+ if eb, ok := req.Form["exact-body"]; ok {
+ io.WriteString(rw, eb[0])
+ return
+ }
+ if req.FormValue("write-forever") == "1" {
+ io.Copy(rw, neverEnding('a'))
+ for {
+ time.Sleep(5 * time.Second) // hang forever, until killed
+ }
+ }
+ fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
+ for k, vv := range req.Form {
+ for _, v := range vv {
+ fmt.Fprintf(rw, "param-%s=%s\n", k, v)
+ }
+ }
+ for _, kv := range os.Environ() {
+ fmt.Fprintf(rw, "env-%s\n", kv)
+ }
+ }))
+ os.Exit(0)
+}
"net/http"
"net/http/httptest"
"os"
- "os/exec"
"path/filepath"
"reflect"
"runtime"
"time"
)
+// TestMain executes the test binary as the cgi server if
+// SERVER_SOFTWARE is set, and runs the tests otherwise.
+func TestMain(m *testing.M) {
+ // SERVER_SOFTWARE swap variable is set when starting the cgi server.
+ if os.Getenv("SERVER_SOFTWARE") != "" {
+ cgiMain()
+ os.Exit(0)
+ }
+
+ os.Exit(m.Run())
+}
+
func newRequest(httpreq string) *http.Request {
buf := bufio.NewReader(strings.NewReader(httpreq))
req, err := http.ReadRequest(buf)
}
}
-var cgiTested, cgiWorks bool
-
-func check(t *testing.T) {
- if !cgiTested {
- cgiTested = true
- cgiWorks = testenv.Command(t, "./testdata/test.cgi").Run() == nil
- }
- if !cgiWorks {
- // No Perl on Windows, needed by test.cgi
- // TODO: make the child process be Go, not Perl.
- t.Skip("Skipping test: test.cgi failed.")
- }
-}
-
func TestCGIBasicGet(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap := map[string]string{
"env-REMOTE_PORT": "1234",
"env-REQUEST_METHOD": "GET",
"env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-SCRIPT_NAME": "/test.cgi",
"env-SERVER_NAME": "example.com",
"env-SERVER_PORT": "80",
}
func TestCGIEnvIPv6(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap := map[string]string{
"env-REMOTE_PORT": "12345",
"env-REQUEST_METHOD": "GET",
"env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-SCRIPT_NAME": "/test.cgi",
"env-SERVER_NAME": "example.com",
"env-SERVER_PORT": "80",
}
func TestCGIBasicGetAbsPath(t *testing.T) {
- check(t)
- pwd, err := os.Getwd()
+ absPath, err := filepath.Abs(os.Args[0])
if err != nil {
- t.Fatalf("getwd error: %v", err)
+ t.Fatal(err)
}
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: pwd + "/testdata/test.cgi",
+ Path: absPath,
Root: "/test.cgi",
}
expectedMap := map[string]string{
"env-REQUEST_URI": "/test.cgi?foo=bar&a=b",
- "env-SCRIPT_FILENAME": pwd + "/testdata/test.cgi",
+ "env-SCRIPT_FILENAME": absPath,
"env-SCRIPT_NAME": "/test.cgi",
}
runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
}
func TestPathInfo(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap := map[string]string{
"env-PATH_INFO": "/extrapath",
"env-QUERY_STRING": "a=b",
"env-REQUEST_URI": "/test.cgi/extrapath?a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-SCRIPT_NAME": "/test.cgi",
}
runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
}
func TestPathInfoDirRoot(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/myscript//",
}
expectedMap := map[string]string{
"env-PATH_INFO": "/bar",
"env-QUERY_STRING": "a=b",
"env-REQUEST_URI": "/myscript/bar?a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-SCRIPT_NAME": "/myscript",
}
runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
}
func TestDupHeaders(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
}
expectedMap := map[string]string{
"env-REQUEST_URI": "/myscript/bar?a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-HTTP_COOKIE": "nom=NOM; yum=YUM",
"env-HTTP_X_FOO": "val1, val2",
}
// Verify we don't set the HTTP_PROXY environment variable.
// Hope nobody was depending on it. It's not a known header, though.
func TestDropProxyHeader(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
}
expectedMap := map[string]string{
"env-REQUEST_URI": "/myscript/bar?a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-HTTP_X_FOO": "a",
}
runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
}
func TestPathInfoNoRoot(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "",
}
expectedMap := map[string]string{
"env-PATH_INFO": "/bar",
"env-QUERY_STRING": "a=b",
"env-REQUEST_URI": "/bar?a=b",
- "env-SCRIPT_FILENAME": "testdata/test.cgi",
+ "env-SCRIPT_FILENAME": os.Args[0],
"env-SCRIPT_NAME": "",
}
runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
}
func TestCGIBasicPost(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
postReq := `POST /test.cgi?a=b HTTP/1.0
Host: example.com
Content-Type: application/x-www-form-urlencoded
postfoo=postbar`
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap := map[string]string{
// The CGI spec doesn't allow chunked requests.
func TestCGIPostChunked(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
postReq := `POST /test.cgi?a=b HTTP/1.1
Host: example.com
Content-Type: application/x-www-form-urlencoded
` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap := map[string]string{}
}
func TestRedirect(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
}
func TestInternalRedirect(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
})
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
PathLocationHandler: baseHandler,
}
// TestCopyError tests that we kill the process if there's an error copying
// its output. (for example, from the client having gone away)
func TestCopyError(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
if runtime.GOOS == "windows" {
t.Skipf("skipping test on %q", runtime.GOOS)
}
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
ts := httptest.NewServer(h)
}
}
-func TestDirUnix(t *testing.T) {
- check(t)
- if runtime.GOOS == "windows" {
- t.Skipf("skipping test on %q", runtime.GOOS)
- }
+func TestDir(t *testing.T) {
+ testenv.MustHaveExec(t)
cwd, _ := os.Getwd()
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
Dir: cwd,
}
runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
cwd, _ = os.Getwd()
- cwd = filepath.Join(cwd, "testdata")
+ cwd, _ = filepath.Split(os.Args[0])
h = &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
}
expectedMap = map[string]string{
runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
}
-func findPerl(t *testing.T) string {
- t.Helper()
- perl, err := exec.LookPath("perl")
- if err != nil {
- t.Skip("Skipping test: perl not found.")
- }
- perl, _ = filepath.Abs(perl)
-
- cmd := testenv.Command(t, perl, "-e", "print 123")
- cmd.Env = []string{"PATH=/garbage"}
- out, err := cmd.Output()
- if err != nil || string(out) != "123" {
- t.Skipf("Skipping test: %s is not functional", perl)
- }
- return perl
-}
-
-func TestDirWindows(t *testing.T) {
- if runtime.GOOS != "windows" {
- t.Skip("Skipping windows specific test.")
- }
-
- cgifile, _ := filepath.Abs("testdata/test.cgi")
-
- perl := findPerl(t)
-
- cwd, _ := os.Getwd()
- h := &Handler{
- Path: perl,
- Root: "/test.cgi",
- Dir: cwd,
- Args: []string{cgifile},
- Env: []string{"SCRIPT_FILENAME=" + cgifile},
- }
- expectedMap := map[string]string{
- "cwd": cwd,
- }
- runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
-
- // If not specify Dir on windows, working directory should be
- // base directory of perl.
- cwd, _ = filepath.Split(perl)
- if cwd != "" && cwd[len(cwd)-1] == filepath.Separator {
- cwd = cwd[:len(cwd)-1]
- }
- h = &Handler{
- Path: perl,
- Root: "/test.cgi",
- Args: []string{cgifile},
- Env: []string{"SCRIPT_FILENAME=" + cgifile},
- }
- expectedMap = map[string]string{
- "cwd": cwd,
- }
- runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
-}
-
func TestEnvOverride(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
cgifile, _ := filepath.Abs("testdata/test.cgi")
- perl := findPerl(t)
-
cwd, _ := os.Getwd()
h := &Handler{
- Path: perl,
+ Path: os.Args[0],
Root: "/test.cgi",
Dir: cwd,
- Args: []string{cgifile},
Env: []string{
"SCRIPT_FILENAME=" + cgifile,
"REQUEST_URI=/foo/bar",
}
func TestHandlerStderr(t *testing.T) {
- check(t)
+ testenv.MustHaveExec(t)
var stderr strings.Builder
h := &Handler{
- Path: "testdata/test.cgi",
+ Path: os.Args[0],
Root: "/test.cgi",
Stderr: &stderr,
}
"os"
"strings"
"testing"
- "time"
)
// This test is a CGI host (testing host.go) that runs its own binary
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
expectedMap := map[string]string{
"test": "Hello CGI-in-CGI",
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
- req, _ := http.NewRequest("GET", "http://example.com/test.cgi?write-forever=1", nil)
+ req, _ := http.NewRequest("GET", "http://example.com/test.go?write-forever=1", nil)
rec := httptest.NewRecorder()
var out bytes.Buffer
const writeLen = 50 << 10
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
expectedMap := map[string]string{
"_body": "",
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
expectedMap := map[string]string{
"nil-request-body": "false",
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
var tests = []struct {
name string
h := &Handler{
Path: os.Args[0],
Root: "/test.go",
- Args: []string{"-test.run=^TestBeChildCGIProcess$"},
}
expectedMap := map[string]string{
"_body": "",
t.Errorf("Got code %d; want 500", replay.Code)
}
}
-
-type neverEnding byte
-
-func (b neverEnding) Read(p []byte) (n int, err error) {
- for i := range p {
- p[i] = byte(b)
- }
- return len(p), nil
-}
-
-// Note: not actually a test.
-func TestBeChildCGIProcess(t *testing.T) {
- if os.Getenv("REQUEST_METHOD") == "" {
- // Not in a CGI environment; skipping test.
- return
- }
- switch os.Getenv("REQUEST_URI") {
- case "/immediate-disconnect":
- os.Exit(0)
- case "/no-content-type":
- fmt.Printf("Content-Length: 6\n\nHello\n")
- os.Exit(0)
- case "/empty-headers":
- fmt.Printf("\nHello")
- os.Exit(0)
- }
- Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
- if req.FormValue("nil-request-body") == "1" {
- fmt.Fprintf(rw, "nil-request-body=%v\n", req.Body == nil)
- return
- }
- rw.Header().Set("X-Test-Header", "X-Test-Value")
- req.ParseForm()
- if req.FormValue("no-body") == "1" {
- return
- }
- if eb, ok := req.Form["exact-body"]; ok {
- io.WriteString(rw, eb[0])
- return
- }
- if req.FormValue("write-forever") == "1" {
- io.Copy(rw, neverEnding('a'))
- for {
- time.Sleep(5 * time.Second) // hang forever, until killed
- }
- }
- fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
- for k, vv := range req.Form {
- for _, v := range vv {
- fmt.Fprintf(rw, "param-%s=%s\n", k, v)
- }
- }
- for _, kv := range os.Environ() {
- fmt.Fprintf(rw, "env-%s\n", kv)
- }
- }))
- os.Exit(0)
-}