Home | History | Annotate | Download | only in cgi
      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 a Go CGI program running under a Go CGI host process.
      6 // Further, the two programs are the same binary, just checking
      7 // their environment to figure out what mode to run in.
      8 
      9 package cgi
     10 
     11 import (
     12 	"bytes"
     13 	"errors"
     14 	"fmt"
     15 	"internal/testenv"
     16 	"io"
     17 	"net/http"
     18 	"net/http/httptest"
     19 	"os"
     20 	"testing"
     21 	"time"
     22 )
     23 
     24 // This test is a CGI host (testing host.go) that runs its own binary
     25 // as a child process testing the other half of CGI (child.go).
     26 func TestHostingOurselves(t *testing.T) {
     27 	testenv.MustHaveExec(t)
     28 
     29 	h := &Handler{
     30 		Path: os.Args[0],
     31 		Root: "/test.go",
     32 		Args: []string{"-test.run=TestBeChildCGIProcess"},
     33 	}
     34 	expectedMap := map[string]string{
     35 		"test":                  "Hello CGI-in-CGI",
     36 		"param-a":               "b",
     37 		"param-foo":             "bar",
     38 		"env-GATEWAY_INTERFACE": "CGI/1.1",
     39 		"env-HTTP_HOST":         "example.com",
     40 		"env-PATH_INFO":         "",
     41 		"env-QUERY_STRING":      "foo=bar&a=b",
     42 		"env-REMOTE_ADDR":       "1.2.3.4",
     43 		"env-REMOTE_HOST":       "1.2.3.4",
     44 		"env-REMOTE_PORT":       "1234",
     45 		"env-REQUEST_METHOD":    "GET",
     46 		"env-REQUEST_URI":       "/test.go?foo=bar&a=b",
     47 		"env-SCRIPT_FILENAME":   os.Args[0],
     48 		"env-SCRIPT_NAME":       "/test.go",
     49 		"env-SERVER_NAME":       "example.com",
     50 		"env-SERVER_PORT":       "80",
     51 		"env-SERVER_SOFTWARE":   "go",
     52 	}
     53 	replay := runCgiTest(t, h, "GET /test.go?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
     54 
     55 	if expected, got := "text/html; charset=utf-8", replay.Header().Get("Content-Type"); got != expected {
     56 		t.Errorf("got a Content-Type of %q; expected %q", got, expected)
     57 	}
     58 	if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
     59 		t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
     60 	}
     61 }
     62 
     63 type customWriterRecorder struct {
     64 	w io.Writer
     65 	*httptest.ResponseRecorder
     66 }
     67 
     68 func (r *customWriterRecorder) Write(p []byte) (n int, err error) {
     69 	return r.w.Write(p)
     70 }
     71 
     72 type limitWriter struct {
     73 	w io.Writer
     74 	n int
     75 }
     76 
     77 func (w *limitWriter) Write(p []byte) (n int, err error) {
     78 	if len(p) > w.n {
     79 		p = p[:w.n]
     80 	}
     81 	if len(p) > 0 {
     82 		n, err = w.w.Write(p)
     83 		w.n -= n
     84 	}
     85 	if w.n == 0 {
     86 		err = errors.New("past write limit")
     87 	}
     88 	return
     89 }
     90 
     91 // If there's an error copying the child's output to the parent, test
     92 // that we kill the child.
     93 func TestKillChildAfterCopyError(t *testing.T) {
     94 	testenv.MustHaveExec(t)
     95 
     96 	defer func() { testHookStartProcess = nil }()
     97 	proc := make(chan *os.Process, 1)
     98 	testHookStartProcess = func(p *os.Process) {
     99 		proc <- p
    100 	}
    101 
    102 	h := &Handler{
    103 		Path: os.Args[0],
    104 		Root: "/test.go",
    105 		Args: []string{"-test.run=TestBeChildCGIProcess"},
    106 	}
    107 	req, _ := http.NewRequest("GET", "http://example.com/test.cgi?write-forever=1", nil)
    108 	rec := httptest.NewRecorder()
    109 	var out bytes.Buffer
    110 	const writeLen = 50 << 10
    111 	rw := &customWriterRecorder{&limitWriter{&out, writeLen}, rec}
    112 
    113 	donec := make(chan bool, 1)
    114 	go func() {
    115 		h.ServeHTTP(rw, req)
    116 		donec <- true
    117 	}()
    118 
    119 	select {
    120 	case <-donec:
    121 		if out.Len() != writeLen || out.Bytes()[0] != 'a' {
    122 			t.Errorf("unexpected output: %q", out.Bytes())
    123 		}
    124 	case <-time.After(5 * time.Second):
    125 		t.Errorf("timeout. ServeHTTP hung and didn't kill the child process?")
    126 		select {
    127 		case p := <-proc:
    128 			p.Kill()
    129 			t.Logf("killed process")
    130 		default:
    131 			t.Logf("didn't kill process")
    132 		}
    133 	}
    134 }
    135 
    136 // Test that a child handler writing only headers works.
    137 // golang.org/issue/7196
    138 func TestChildOnlyHeaders(t *testing.T) {
    139 	testenv.MustHaveExec(t)
    140 
    141 	h := &Handler{
    142 		Path: os.Args[0],
    143 		Root: "/test.go",
    144 		Args: []string{"-test.run=TestBeChildCGIProcess"},
    145 	}
    146 	expectedMap := map[string]string{
    147 		"_body": "",
    148 	}
    149 	replay := runCgiTest(t, h, "GET /test.go?no-body=1 HTTP/1.0\nHost: example.com\n\n", expectedMap)
    150 	if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
    151 		t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
    152 	}
    153 }
    154 
    155 // golang.org/issue/7198
    156 func Test500WithNoHeaders(t *testing.T)     { want500Test(t, "/immediate-disconnect") }
    157 func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") }
    158 func Test500WithEmptyHeaders(t *testing.T)  { want500Test(t, "/empty-headers") }
    159 
    160 func want500Test(t *testing.T, path string) {
    161 	h := &Handler{
    162 		Path: os.Args[0],
    163 		Root: "/test.go",
    164 		Args: []string{"-test.run=TestBeChildCGIProcess"},
    165 	}
    166 	expectedMap := map[string]string{
    167 		"_body": "",
    168 	}
    169 	replay := runCgiTest(t, h, "GET "+path+" HTTP/1.0\nHost: example.com\n\n", expectedMap)
    170 	if replay.Code != 500 {
    171 		t.Errorf("Got code %d; want 500", replay.Code)
    172 	}
    173 }
    174 
    175 type neverEnding byte
    176 
    177 func (b neverEnding) Read(p []byte) (n int, err error) {
    178 	for i := range p {
    179 		p[i] = byte(b)
    180 	}
    181 	return len(p), nil
    182 }
    183 
    184 // Note: not actually a test.
    185 func TestBeChildCGIProcess(t *testing.T) {
    186 	if os.Getenv("REQUEST_METHOD") == "" {
    187 		// Not in a CGI environment; skipping test.
    188 		return
    189 	}
    190 	switch os.Getenv("REQUEST_URI") {
    191 	case "/immediate-disconnect":
    192 		os.Exit(0)
    193 	case "/no-content-type":
    194 		fmt.Printf("Content-Length: 6\n\nHello\n")
    195 		os.Exit(0)
    196 	case "/empty-headers":
    197 		fmt.Printf("\nHello")
    198 		os.Exit(0)
    199 	}
    200 	Serve(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
    201 		rw.Header().Set("X-Test-Header", "X-Test-Value")
    202 		req.ParseForm()
    203 		if req.FormValue("no-body") == "1" {
    204 			return
    205 		}
    206 		if req.FormValue("write-forever") == "1" {
    207 			io.Copy(rw, neverEnding('a'))
    208 			for {
    209 				time.Sleep(5 * time.Second) // hang forever, until killed
    210 			}
    211 		}
    212 		fmt.Fprintf(rw, "test=Hello CGI-in-CGI\n")
    213 		for k, vv := range req.Form {
    214 			for _, v := range vv {
    215 				fmt.Fprintf(rw, "param-%s=%s\n", k, v)
    216 			}
    217 		}
    218 		for _, kv := range os.Environ() {
    219 			fmt.Fprintf(rw, "env-%s\n", kv)
    220 		}
    221 	}))
    222 	os.Exit(0)
    223 }
    224