github.com/ice-blockchain/go/src@v0.0.0-20240403114104-1564d284e521/net/http/cgi/host_test.go (about) 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 "regexp" 21 "runtime" 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 // 367 // If we fail to do so, the test will time out (and dump its goroutines) with a 368 // call to [Handler.ServeHTTP] blocked on a deferred call to [exec.Cmd.Wait]. 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 // handlerRunning reports whether any goroutine is currently running 417 // [Handler.ServeHTTP]. 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 // Buffer wasn't large enough for a full goroutine dump. 427 // Resize it and try again. 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 !reflect.DeepEqual(got, tt.want) { 514 t.Errorf("removeLeadingDuplicates(%q) = %q; want %q", tt.env, got, tt.want) 515 } 516 } 517 }