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.
5 // Tests for package cgi
28 func newRequest(httpreq
string) *http
.Request
{
29 buf
:= bufio
.NewReader(strings
.NewReader(httpreq
))
30 req
, err
:= http
.ReadRequest(buf
)
32 panic("cgi: bogus http request in test: " + httpreq
)
34 req
.RemoteAddr
= "1.2.3.4:1234"
38 func runCgiTest(t
*testing
.T
, h
*Handler
,
40 expectedMap
map[string]string, checks
...func(reqInfo
map[string]string)) *httptest
.ResponseRecorder
{
41 rw
:= httptest
.NewRecorder()
42 req
:= newRequest(httpreq
)
44 runResponseChecks(t
, rw
, expectedMap
, checks
...)
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()
56 line
, err
:= rw
.Body
.ReadString('\n')
61 t
.Fatalf("unexpected error reading from CGI: %v", err
)
64 trimmedLine
:= strings
.TrimRight(line
, "\r\n")
65 split
:= strings
.SplitN(trimmedLine
, "=", 2)
67 t
.Fatalf("Unexpected %d parts from invalid line number %v: %q; existing map=%v",
68 len(split
), linesRead
, line
, m
)
70 m
[split
[0]] = split
[1]
73 for key
, expected
:= range expectedMap
{
76 // For Windows. golang.org/issue/4645.
77 fi1
, _
:= os
.Stat(got
)
78 fi2
, _
:= os
.Stat(expected
)
79 if os
.SameFile(fi1
, fi2
) {
84 t
.Errorf("for key %q got %q; expected %q", key
, got
, expected
)
87 for _
, check
:= range checks
{
92 var cgiTested
, cgiWorks
bool
94 func check(t
*testing
.T
) {
97 cgiWorks
= exec
.Command("./testdata/test.cgi").Run() == nil
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.")
106 func TestCGIBasicGet(t
*testing
.T
) {
109 Path
: "testdata/test.cgi",
112 expectedMap
:= map[string]string{
116 "env-GATEWAY_INTERFACE": "CGI/1.1",
117 "env-HTTP_HOST": "example.com",
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",
131 replay
:= runCgiTest(t
, h
, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap
)
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
)
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
)
141 func TestCGIEnvIPv6(t
*testing
.T
) {
144 Path
: "testdata/test.cgi",
147 expectedMap
:= map[string]string{
151 "env-GATEWAY_INTERFACE": "CGI/1.1",
152 "env-HTTP_HOST": "example.com",
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",
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"
171 runResponseChecks(t
, rw
, expectedMap
)
174 func TestCGIBasicGetAbsPath(t
*testing
.T
) {
176 pwd
, err
:= os
.Getwd()
178 t
.Fatalf("getwd error: %v", err
)
181 Path
: pwd
+ "/testdata/test.cgi",
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",
189 runCgiTest(t
, h
, "GET /test.cgi?foo=bar&a=b HTTP/1.0\nHost: example.com\n\n", expectedMap
)
192 func TestPathInfo(t
*testing
.T
) {
195 Path
: "testdata/test.cgi",
198 expectedMap
:= map[string]string{
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",
206 runCgiTest(t
, h
, "GET /test.cgi/extrapath?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap
)
209 func TestPathInfoDirRoot(t
*testing
.T
) {
212 Path
: "testdata/test.cgi",
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/",
222 runCgiTest(t
, h
, "GET /myscript/bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap
)
225 func TestDupHeaders(t
*testing
.T
) {
228 Path
: "testdata/test.cgi",
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",
236 runCgiTest(t
, h
, "GET /myscript/bar?a=b HTTP/1.0\n"+
241 "Host: example.com\n\n",
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
) {
251 Path
: "testdata/test.cgi",
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",
258 runCgiTest(t
, h
, "GET /myscript/bar?a=b HTTP/1.0\n"+
260 "Proxy: should_be_stripped\n"+
261 "Host: example.com\n\n",
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
)
270 func TestPathInfoNoRoot(t
*testing
.T
) {
273 Path
: "testdata/test.cgi",
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": "/",
283 runCgiTest(t
, h
, "GET /bar?a=b HTTP/1.0\nHost: example.com\n\n", expectedMap
)
286 func TestCGIBasicPost(t
*testing
.T
) {
288 postReq
:= `POST /test.cgi?a=b HTTP/1.0
290 Content-Type: application/x-www-form-urlencoded
295 Path
: "testdata/test.cgi",
298 expectedMap
:= map[string]string{
300 "param-postfoo": "postbar",
301 "env-REQUEST_METHOD": "POST",
302 "env-CONTENT_LENGTH": "15",
303 "env-REQUEST_URI": "/test.cgi?a=b",
305 runCgiTest(t
, h
, postReq
, expectedMap
)
308 func chunk(s
string) string {
309 return fmt
.Sprintf("%x\r\n%s\r\n", len(s
), s
)
312 // The CGI spec doesn't allow chunked requests.
313 func TestCGIPostChunked(t
*testing
.T
) {
315 postReq
:= `POST /test.cgi?a=b HTTP/1.1
317 Content-Type: application/x-www-form-urlencoded
318 Transfer-Encoding: chunked
320 ` + chunk("postfoo") + chunk("=") + chunk("postbar") + chunk("")
323 Path
: "testdata/test.cgi",
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",
334 func TestRedirect(t
*testing
.T
) {
337 Path
: "testdata/test.cgi",
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
)
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
)
349 func TestInternalRedirect(t
*testing
.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
)
356 Path
: "testdata/test.cgi",
358 PathLocationHandler
: baseHandler
,
360 expectedMap
:= map[string]string{
362 "remoteaddr": "1.2.3.4:1234",
364 runCgiTest(t
, h
, "GET /test.cgi?loc=/foo HTTP/1.0\nHost: example.com\n\n", expectedMap
)
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
) {
371 if runtime
.GOOS
== "windows" {
372 t
.Skipf("skipping test on %q", runtime
.GOOS
)
375 Path
: "testdata/test.cgi",
378 ts
:= httptest
.NewServer(h
)
381 conn
, err
:= net
.Dial("tcp", ts
.Listener
.Addr().String())
385 req
, _
:= http
.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
386 err
= req
.Write(conn
)
388 t
.Fatalf("Write: %v", err
)
391 res
, err
:= http
.ReadResponse(bufio
.NewReader(conn
), req
)
393 t
.Fatalf("ReadResponse: %v", err
)
396 pidstr
:= res
.Header
.Get("X-CGI-Pid")
398 t
.Fatalf("expected an X-CGI-Pid header in response")
400 pid
, err
:= strconv
.Atoi(pidstr
)
402 t
.Fatalf("invalid X-CGI-Pid value")
406 n
, err
:= io
.ReadFull(res
.Body
, buf
[:])
408 t
.Fatalf("ReadFull: %d bytes, %v", n
, err
)
411 childRunning
:= func() bool {
412 return isProcessRunning(pid
)
416 t
.Fatalf("pre-conn.Close, expected child to be running")
421 for tries
< 25 && childRunning() {
422 time
.Sleep(50 * time
.Millisecond
* time
.Duration(tries
))
426 t
.Fatalf("post-conn.Close, expected child to be gone")
430 func TestDirUnix(t
*testing
.T
) {
432 if runtime
.GOOS
== "windows" {
433 t
.Skipf("skipping test on %q", runtime
.GOOS
)
437 Path
: "testdata/test.cgi",
441 expectedMap
:= map[string]string{
444 runCgiTest(t
, h
, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap
)
447 cwd
= filepath
.Join(cwd
, "testdata")
449 Path
: "testdata/test.cgi",
452 abswd
, _
:= filepath
.EvalSymlinks(cwd
)
453 expectedMap
= map[string]string{
456 runCgiTest(t
, h
, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap
)
459 func TestDirWindows(t
*testing
.T
) {
460 if runtime
.GOOS
!= "windows" {
461 t
.Skip("Skipping windows specific test.")
464 cgifile
, _
:= filepath
.Abs("testdata/test.cgi")
468 perl
, err
= exec
.LookPath("perl")
470 t
.Skip("Skipping test: perl not found.")
472 perl
, _
= filepath
.Abs(perl
)
479 Args
: []string{cgifile
},
480 Env
: []string{"SCRIPT_FILENAME=" + cgifile
},
482 expectedMap
:= map[string]string{
485 runCgiTest(t
, h
, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap
)
487 // If not specify Dir on windows, working directory should be
488 // base directory of perl.
489 cwd
, _
= filepath
.Split(perl
)
490 if cwd
!= "" && cwd
[len(cwd
)-1] == filepath
.Separator
{
491 cwd
= cwd
[:len(cwd
)-1]
496 Args
: []string{cgifile
},
497 Env
: []string{"SCRIPT_FILENAME=" + cgifile
},
499 expectedMap
= map[string]string{
502 runCgiTest(t
, h
, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap
)
505 func TestEnvOverride(t
*testing
.T
) {
506 cgifile
, _
:= filepath
.Abs("testdata/test.cgi")
510 perl
, err
= exec
.LookPath("perl")
512 t
.Skipf("Skipping test: perl not found.")
514 perl
, _
= filepath
.Abs(perl
)
521 Args
: []string{cgifile
},
523 "SCRIPT_FILENAME=" + cgifile
,
524 "REQUEST_URI=/foo/bar",
527 expectedMap
:= map[string]string{
529 "env-SCRIPT_FILENAME": cgifile
,
530 "env-REQUEST_URI": "/foo/bar",
531 "env-PATH": "/wibble",
533 runCgiTest(t
, h
, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap
)
536 func TestHandlerStderr(t
*testing
.T
) {
538 var stderr bytes
.Buffer
540 Path
: "testdata/test.cgi",
545 rw
:= httptest
.NewRecorder()
546 req
:= newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
548 if got
, want
:= stderr
.String(), "Hello, stderr!\n"; got
!= want
{
549 t
.Errorf("Stderr = %q; want %q", got
, want
)
553 func TestRemoveLeadingDuplicates(t
*testing
.T
) {
559 env
: []string{"a=b", "b=c", "a=b2"},
560 want
: []string{"b=c", "a=b2"},
563 env
: []string{"a=b", "b=c", "d", "e=f"},
564 want
: []string{"a=b", "b=c", "d", "e=f"},
567 for _
, tt
:= range tests
{
568 got
:= removeLeadingDuplicates(tt
.env
)
569 if !reflect
.DeepEqual(got
, tt
.want
) {
570 t
.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt
.env
, got
, tt
.want
)