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
9package cgi
10
11import (
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).
26func 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
63type customWriterRecorder struct {
64	w io.Writer
65	*httptest.ResponseRecorder
66}
67
68func (r *customWriterRecorder) Write(p []byte) (n int, err error) {
69	return r.w.Write(p)
70}
71
72type limitWriter struct {
73	w io.Writer
74	n int
75}
76
77func (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.
93func 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
138func 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
156func Test500WithNoHeaders(t *testing.T)     { want500Test(t, "/immediate-disconnect") }
157func Test500WithNoContentType(t *testing.T) { want500Test(t, "/no-content-type") }
158func Test500WithEmptyHeaders(t *testing.T)  { want500Test(t, "/empty-headers") }
159
160func 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
175type neverEnding byte
176
177func (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.
185func 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