Source file
src/net/http/cgi/host_test.go
1
2
3
4
5
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,
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
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
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 = exec.Command("./testdata/test.cgi").Run() == nil
98 }
99 if !cgiWorks {
100
101
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",
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\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
246
247
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
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
368
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 := exec.Command(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
498
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 bytes.Buffer
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 }
579
View as plain text