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