Source file
src/net/http/cgi/host_test.go
1
2
3
4
5
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 "regexp"
20 "runtime"
21 "slices"
22 "strings"
23 "testing"
24 "time"
25 )
26
27
28
29 func TestMain(m *testing.M) {
30
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
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
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
243
244
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
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
365
366
367
368
369 func TestCopyError(t *testing.T) {
370 testenv.MustHaveExec(t)
371
372 h := &Handler{
373 Path: os.Args[0],
374 Root: "/test.cgi",
375 }
376 ts := httptest.NewServer(h)
377 defer ts.Close()
378
379 conn, err := net.Dial("tcp", ts.Listener.Addr().String())
380 if err != nil {
381 t.Fatal(err)
382 }
383 req, _ := http.NewRequest("GET", "http://example.com/test.cgi?bigresponse=1", nil)
384 err = req.Write(conn)
385 if err != nil {
386 t.Fatalf("Write: %v", err)
387 }
388 res, err := http.ReadResponse(bufio.NewReader(conn), req)
389 if err != nil {
390 t.Fatalf("ReadResponse: %v", err)
391 }
392 defer res.Body.Close()
393 var buf [5000]byte
394 n, err := io.ReadFull(res.Body, buf[:])
395 if err != nil {
396 t.Fatalf("ReadFull: %d bytes, %v", n, err)
397 }
398
399 if !handlerRunning() {
400 t.Fatalf("pre-conn.Close, expected handler to still be running")
401 }
402 conn.Close()
403 closed := time.Now()
404
405 nextSleep := 1 * time.Millisecond
406 for {
407 time.Sleep(nextSleep)
408 nextSleep *= 2
409 if !handlerRunning() {
410 break
411 }
412 t.Logf("handler still running %v after conn.Close", time.Since(closed))
413 }
414 }
415
416
417
418 func handlerRunning() bool {
419 r := regexp.MustCompile(`net/http/cgi\.\(\*Handler\)\.ServeHTTP`)
420 buf := make([]byte, 64<<10)
421 for {
422 n := runtime.Stack(buf, true)
423 if n < len(buf) {
424 return r.Match(buf[:n])
425 }
426
427
428 buf = make([]byte, 2*len(buf))
429 }
430 }
431
432 func TestDir(t *testing.T) {
433 testenv.MustHaveExec(t)
434 cwd, _ := os.Getwd()
435 h := &Handler{
436 Path: os.Args[0],
437 Root: "/test.cgi",
438 Dir: cwd,
439 }
440 expectedMap := map[string]string{
441 "cwd": cwd,
442 }
443 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
444
445 cwd, _ = os.Getwd()
446 cwd, _ = filepath.Split(os.Args[0])
447 h = &Handler{
448 Path: os.Args[0],
449 Root: "/test.cgi",
450 }
451 expectedMap = map[string]string{
452 "cwd": cwd,
453 }
454 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
455 }
456
457 func TestEnvOverride(t *testing.T) {
458 testenv.MustHaveExec(t)
459 cgifile, _ := filepath.Abs("testdata/test.cgi")
460
461 cwd, _ := os.Getwd()
462 h := &Handler{
463 Path: os.Args[0],
464 Root: "/test.cgi",
465 Dir: cwd,
466 Env: []string{
467 "SCRIPT_FILENAME=" + cgifile,
468 "REQUEST_URI=/foo/bar",
469 "PATH=/wibble"},
470 }
471 expectedMap := map[string]string{
472 "cwd": cwd,
473 "env-SCRIPT_FILENAME": cgifile,
474 "env-REQUEST_URI": "/foo/bar",
475 "env-PATH": "/wibble",
476 }
477 runCgiTest(t, h, "GET /test.cgi HTTP/1.0\nHost: example.com\n\n", expectedMap)
478 }
479
480 func TestHandlerStderr(t *testing.T) {
481 testenv.MustHaveExec(t)
482 var stderr strings.Builder
483 h := &Handler{
484 Path: os.Args[0],
485 Root: "/test.cgi",
486 Stderr: &stderr,
487 }
488
489 rw := httptest.NewRecorder()
490 req := newRequest("GET /test.cgi?writestderr=1 HTTP/1.0\nHost: example.com\n\n")
491 h.ServeHTTP(rw, req)
492 if got, want := stderr.String(), "Hello, stderr!\n"; got != want {
493 t.Errorf("Stderr = %q; want %q", got, want)
494 }
495 }
496
497 func TestRemoveLeadingDuplicates(t *testing.T) {
498 tests := []struct {
499 env []string
500 want []string
501 }{
502 {
503 env: []string{"a=b", "b=c", "a=b2"},
504 want: []string{"b=c", "a=b2"},
505 },
506 {
507 env: []string{"a=b", "b=c", "d", "e=f"},
508 want: []string{"a=b", "b=c", "d", "e=f"},
509 },
510 }
511 for _, tt := range tests {
512 got := removeLeadingDuplicates(tt.env)
513 if !slices.Equal(got, tt.want) {
514 t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want)
515 }
516 }
517 }
518
View as plain text