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