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