]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/http/cgi/host_test.go
build: merge the great pkg/ rename into dev.power64
[gostls13.git] / src / net / http / cgi / host_test.go
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         "fmt"
12         "io"
13         "net"
14         "net/http"
15         "net/http/httptest"
16         "os"
17         "os/exec"
18         "path/filepath"
19         "runtime"
20         "strconv"
21         "strings"
22         "testing"
23         "time"
24 )
25
26 func newRequest(httpreq string) *http.Request {
27         buf := bufio.NewReader(strings.NewReader(httpreq))
28         req, err := http.ReadRequest(buf)
29         if err != nil {
30                 panic("cgi: bogus http request in test: " + httpreq)
31         }
32         req.RemoteAddr = "1.2.3.4"
33         return req
34 }
35
36 func runCgiTest(t *testing.T, h *Handler, httpreq string, expectedMap map[string]string) *httptest.ResponseRecorder {
37         rw := httptest.NewRecorder()
38         req := newRequest(httpreq)
39         h.ServeHTTP(rw, req)
40
41         // Make a map to hold the test map that the CGI returns.
42         m := make(map[string]string)
43         m["_body"] = rw.Body.String()
44         linesRead := 0
45 readlines:
46         for {
47                 line, err := rw.Body.ReadString('\n')
48                 switch {
49                 case err == io.EOF:
50                         break readlines
51                 case err != nil:
52                         t.Fatalf("unexpected error reading from CGI: %v", err)
53                 }
54                 linesRead++
55                 trimmedLine := strings.TrimRight(line, "\r\n")
56                 split := strings.SplitN(trimmedLine, "=", 2)
57                 if len(split) != 2 {
58                         t.Fatalf("Unexpected %d parts from invalid line number %v: %q; existing map=%v",
59                                 len(split), linesRead, line, m)
60                 }
61                 m[split[0]] = split[1]
62         }
63
64         for key, expected := range expectedMap {
65                 got := m[key]
66                 if key == "cwd" {
67                         // For Windows. golang.org/issue/4645.
68                         fi1, _ := os.Stat(got)
69                         fi2, _ := os.Stat(expected)
70                         if os.SameFile(fi1, fi2) {
71                                 got = expected
72                         }
73                 }
74                 if got != expected {
75                         t.Errorf("for key %q got %q; expected %q", key, got, expected)
76                 }
77         }
78         return rw
79 }
80
81 var cgiTested, cgiWorks bool
82
83 func check(t *testing.T) {
84         if !cgiTested {
85                 cgiTested = true
86                 cgiWorks = exec.Command("./testdata/test.cgi").Run() == nil
87         }
88         if !cgiWorks {
89                 // No Perl on Windows, needed by test.cgi
90                 // TODO: make the child process be Go, not Perl.
91                 t.Skip("Skipping test: test.cgi failed.")
92         }
93 }
94
95 func TestCGIBasicGet(t *testing.T) {
96         check(t)
97         h := &Handler{
98                 Path: "testdata/test.cgi",
99                 Root: "/test.cgi",
100         }
101         expectedMap := map[string]string{
102                 "test":                  "Hello CGI",
103                 "param-a":               "b",
104                 "param-foo":             "bar",
105                 "env-GATEWAY_INTERFACE": "CGI/1.1",
106                 "env-HTTP_HOST":         "example.com",
107                 "env-PATH_INFO":         "",
108                 "env-QUERY_STRING":      "foo=bar&a=b",
109                 "env-REMOTE_ADDR":       "1.2.3.4",
110                 "env-REMOTE_HOST":       "1.2.3.4",
111                 "env-REQUEST_METHOD":    "GET",
112                 "env-REQUEST_URI":       "/test.cgi?foo=bar&a=b",
113                 "env-SCRIPT_FILENAME":   "testdata/test.cgi",
114                 "env-SCRIPT_NAME":       "/test.cgi",
115                 "env-SERVER_NAME":       "example.com",
116                 "env-SERVER_PORT":       "80",
117                 "env-SERVER_SOFTWARE":   "go",
118         }
119         replay := runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
120
121         if expected, got := "text/html", replay.Header().Get("Content-Type"); got != expected {
122                 t.Errorf("got a Content-Type of %q; expected %q", got, expected)
123         }
124         if expected, got := "X-Test-Value", replay.Header().Get("X-Test-Header"); got != expected {
125                 t.Errorf("got a X-Test-Header of %q; expected %q", got, expected)
126         }
127 }
128
129 func TestCGIBasicGetAbsPath(t *testing.T) {
130         check(t)
131         pwd, err := os.Getwd()
132         if err != nil {
133                 t.Fatalf("getwd error: %v", err)
134         }
135         h := &Handler{
136                 Path: pwd + "/testdata/test.cgi",
137                 Root: "/test.cgi",
138         }
139         expectedMap := map[string]string{
140                 "env-REQUEST_URI":     "/test.cgi?foo=bar&a=b",
141                 "env-SCRIPT_FILENAME": pwd + "/testdata/test.cgi",
142                 "env-SCRIPT_NAME":     "/test.cgi",
143         }
144         runCgiTest(t, h, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
145 }
146
147 func TestPathInfo(t *testing.T) {
148         check(t)
149         h := &Handler{
150                 Path: "testdata/test.cgi",
151                 Root: "/test.cgi",
152         }
153         expectedMap := map[string]string{
154                 "param-a":             "b",
155                 "env-PATH_INFO":       "/extrapath",
156                 "env-QUERY_STRING":    "a=b",
157                 "env-REQUEST_URI":     "/test.cgi/extrapath?a=b",
158                 "env-SCRIPT_FILENAME": "testdata/test.cgi",
159                 "env-SCRIPT_NAME":     "/test.cgi",
160         }
161         runCgiTest(t, h, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
162 }
163
164 func TestPathInfoDirRoot(t *testing.T) {
165         check(t)
166         h := &Handler{
167                 Path: "testdata/test.cgi",
168                 Root: "/myscript/",
169         }
170         expectedMap := map[string]string{
171                 "env-PATH_INFO":       "bar",
172                 "env-QUERY_STRING":    "a=b",
173                 "env-REQUEST_URI":     "/myscript/bar?a=b",
174                 "env-SCRIPT_FILENAME": "testdata/test.cgi",
175                 "env-SCRIPT_NAME":     "/myscript/",
176         }
177         runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
178 }
179
180 func TestDupHeaders(t *testing.T) {
181         check(t)
182         h := &Handler{
183                 Path: "testdata/test.cgi",
184         }
185         expectedMap := map[string]string{
186                 "env-REQUEST_URI":     "/myscript/bar?a=b",
187                 "env-SCRIPT_FILENAME": "testdata/test.cgi",
188                 "env-HTTP_COOKIE":     "nom=NOM; yum=YUM",
189                 "env-HTTP_X_FOO":      "val1, val2",
190         }
191         runCgiTest(t, h, "GET /myscript/bar?a=b HTTP/1.0\n"+
192                 "Cookie: nom=NOM\n"+
193                 "Cookie: yum=YUM\n"+
194                 "X-Foo: val1\n"+
195                 "X-Foo: val2\n"+
196                 "Host: example.com\n\n",
197                 expectedMap)
198 }
199
200 func TestPathInfoNoRoot(t *testing.T) {
201         check(t)
202         h := &Handler{
203                 Path: "testdata/test.cgi",
204                 Root: "",
205         }
206         expectedMap := map[string]string{
207                 "env-PATH_INFO":       "/bar",
208                 "env-QUERY_STRING":    "a=b",
209                 "env-REQUEST_URI":     "/bar?a=b",
210                 "env-SCRIPT_FILENAME": "testdata/test.cgi",
211                 "env-SCRIPT_NAME":     "/",
212         }
213         runCgiTest(t, h, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap)
214 }
215
216 func TestCGIBasicPost(t *testing.T) {
217         check(t)
218         postReq := `POST /test.cgi?a=b HTTP/1.0
219 Host: example.com
220 Content-Type: application/x-www-form-urlencoded
221 Content-Length: 15
222
223 postfoo=postbar`
224         h := &Handler{
225                 Path: "testdata/test.cgi",
226                 Root: "/test.cgi",
227         }
228         expectedMap := map[string]string{
229                 "test":               "Hello CGI",
230                 "param-postfoo":      "postbar",
231                 "env-REQUEST_METHOD": "POST",
232                 "env-CONTENT_LENGTH": "15",
233                 "env-REQUEST_URI":    "/test.cgi?a=b",
234         }
235         runCgiTest(t, h, postReq, expectedMap)
236 }
237
238 func chunk(s string) string {
239         return fmt.Sprintf("%x\r\n%s\r\n", len(s), s)
240 }
241
242 // The CGI spec doesn't allow chunked requests.
243 func TestCGIPostChunked(t *testing.T) {
244         check(t)
245         postReq := `POST /test.cgi?a=b HTTP/1.1
246 Host: example.com
247 Content-Type: application/x-www-form-urlencoded
248 Transfer-Encoding: chunked
249
250 ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
251
252         h := &Handler{
253                 Path: "testdata/test.cgi",
254                 Root: "/test.cgi",
255         }
256         expectedMap := map[string]string{}
257         resp := runCgiTest(t, h, postReq, expectedMap)
258         if got, expected := resp.Code, http.StatusBadRequest; got != expected {
259                 t.Fatalf("Expected %v response code from chunked request body; got %d",
260                         expected, got)
261         }
262 }
263
264 func TestRedirect(t *testing.T) {
265         check(t)
266         h := &Handler{
267                 Path: "testdata/test.cgi",
268                 Root: "/test.cgi",
269         }
270         rec := runCgiTest(t, h, "GET /test.cgi?loc=http://foo.com/ HTTP/1.0\nHost: example.com\n\n", nil)
271         if e, g := 302, rec.Code; e != g {
272                 t.Errorf("expected status code %d; got %d", e, g)
273         }
274         if e, g := "http://foo.com/", rec.Header().Get("Location"); e != g {
275                 t.Errorf("expected Location header of %q; got %q", e, g)
276         }
277 }
278
279 func TestInternalRedirect(t *testing.T) {
280         check(t)
281         baseHandler := http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
282                 fmt.Fprintf(rw, "basepath=%s\n", req.URL.Path)
283                 fmt.Fprintf(rw, "remoteaddr=%s\n", req.RemoteAddr)
284         })
285         h := &Handler{
286                 Path:                "testdata/test.cgi",
287                 Root:                "/test.cgi",
288                 PathLocationHandler: baseHandler,
289         }
290         expectedMap := map[string]string{
291                 "basepath":   "/foo",
292                 "remoteaddr": "1.2.3.4",
293         }
294         runCgiTest(t, h, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap)
295 }
296
297 // TestCopyError tests that we kill the process if there's an error copying
298 // its output. (for example, from the client having gone away)
299 func TestCopyError(t *testing.T) {
300         check(t)
301         if runtime.GOOS == "windows" {
302                 t.Skipf("skipping test on %q", runtime.GOOS)
303         }
304         h := &Handler{
305                 Path: "testdata/test.cgi",
306                 Root: "/test.cgi",
307         }
308         ts := httptest.NewServer(h)
309         defer ts.Close()
310
311         conn, err := net.Dial("tcp", ts.Listener.Addr().String())
312         if err != nil {
313                 t.Fatal(err)
314         }
315         req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
316         err = req.Write(conn)
317         if err != nil {
318                 t.Fatalf("Write: %v", err)
319         }
320
321         res, err := http.ReadResponse(bufio.NewReader(conn), req)
322         if err != nil {
323                 t.Fatalf("ReadResponse: %v", err)
324         }
325
326         pidstr := res.Header.Get("X-CGI-Pid")
327         if pidstr == "" {
328                 t.Fatalf("expected an X-CGI-Pid header in response")
329         }
330         pid, err := strconv.Atoi(pidstr)
331         if err != nil {
332                 t.Fatalf("invalid X-CGI-Pid value")
333         }
334
335         var buf [5000]byte
336         n, err := io.ReadFull(res.Body, buf[:])
337         if err != nil {
338                 t.Fatalf("ReadFull: %d bytes, %v", n, err)
339         }
340
341         childRunning := func() bool {
342                 return isProcessRunning(t, pid)
343         }
344
345         if !childRunning() {
346                 t.Fatalf("pre-conn.Close, expected child to be running")
347         }
348         conn.Close()
349
350         tries := 0
351         for tries < 25 && childRunning() {
352                 time.Sleep(50 * time.Millisecond * time.Duration(tries))
353                 tries++
354         }
355         if childRunning() {
356                 t.Fatalf("post-conn.Close, expected child to be gone")
357         }
358 }
359
360 func TestDirUnix(t *testing.T) {
361         check(t)
362         if runtime.GOOS == "windows" {
363                 t.Skipf("skipping test on %q", runtime.GOOS)
364         }
365         cwd, _ := os.Getwd()
366         h := &Handler{
367                 Path: "testdata/test.cgi",
368                 Root: "/test.cgi",
369                 Dir:  cwd,
370         }
371         expectedMap := map[string]string{
372                 "cwd": cwd,
373         }
374         runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
375
376         cwd, _ = os.Getwd()
377         cwd = filepath.Join(cwd, "testdata")
378         h = &Handler{
379                 Path: "testdata/test.cgi",
380                 Root: "/test.cgi",
381         }
382         expectedMap = map[string]string{
383                 "cwd": cwd,
384         }
385         runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
386 }
387
388 func TestDirWindows(t *testing.T) {
389         if runtime.GOOS != "windows" {
390                 t.Skip("Skipping windows specific test.")
391         }
392
393         cgifile, _ := filepath.Abs("testdata/test.cgi")
394
395         var perl string
396         var err error
397         perl, err = exec.LookPath("perl")
398         if err != nil {
399                 t.Skip("Skipping test: perl not found.")
400         }
401         perl, _ = filepath.Abs(perl)
402
403         cwd, _ := os.Getwd()
404         h := &Handler{
405                 Path: perl,
406                 Root: "/test.cgi",
407                 Dir:  cwd,
408                 Args: []string{cgifile},
409                 Env:  []string{"SCRIPT_FILENAME=" + cgifile},
410         }
411         expectedMap := map[string]string{
412                 "cwd": cwd,
413         }
414         runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
415
416         // If not specify Dir on windows, working directory should be
417         // base directory of perl.
418         cwd, _ = filepath.Split(perl)
419         if cwd != "" && cwd[len(cwd)-1] == filepath.Separator {
420                 cwd = cwd[:len(cwd)-1]
421         }
422         h = &Handler{
423                 Path: perl,
424                 Root: "/test.cgi",
425                 Args: []string{cgifile},
426                 Env:  []string{"SCRIPT_FILENAME=" + cgifile},
427         }
428         expectedMap = map[string]string{
429                 "cwd": cwd,
430         }
431         runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
432 }
433
434 func TestEnvOverride(t *testing.T) {
435         cgifile, _ := filepath.Abs("testdata/test.cgi")
436
437         var perl string
438         var err error
439         perl, err = exec.LookPath("perl")
440         if err != nil {
441                 t.Skipf("Skipping test: perl not found.")
442         }
443         perl, _ = filepath.Abs(perl)
444
445         cwd, _ := os.Getwd()
446         h := &Handler{
447                 Path: perl,
448                 Root: "/test.cgi",
449                 Dir:  cwd,
450                 Args: []string{cgifile},
451                 Env: []string{
452                         "SCRIPT_FILENAME=" + cgifile,
453                         "REQUEST_URI=/foo/bar"},
454         }
455         expectedMap := map[string]string{
456                 "cwd": cwd,
457                 "env-SCRIPT_FILENAME": cgifile,
458                 "env-REQUEST_URI":     "/foo/bar",
459         }
460         runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
461 }