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