github.com/geraldss/go/src@v0.0.0-20210511222824-ac7d0ebfc235/net/http/cgi/host.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 // This file implements the host side of CGI (being the webserver 6 // parent process). 7 8 // Package cgi implements CGI (Common Gateway Interface) as specified 9 // in RFC 3875. 10 // 11 // Note that using CGI means starting a new process to handle each 12 // request, which is typically less efficient than using a 13 // long-running server. This package is intended primarily for 14 // compatibility with existing systems. 15 package cgi 16 17 import ( 18 "bufio" 19 "fmt" 20 "io" 21 "log" 22 "net" 23 "net/http" 24 "net/textproto" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "regexp" 29 "runtime" 30 "strconv" 31 "strings" 32 33 "golang.org/x/net/http/httpguts" 34 ) 35 36 var trailingPort = regexp.MustCompile(`:([0-9]+)$`) 37 38 var osDefaultInheritEnv = func() []string { 39 switch runtime.GOOS { 40 case "darwin", "ios": 41 return []string{"DYLD_LIBRARY_PATH"} 42 case "linux", "freebsd", "netbsd", "openbsd": 43 return []string{"LD_LIBRARY_PATH"} 44 case "hpux": 45 return []string{"LD_LIBRARY_PATH", "SHLIB_PATH"} 46 case "irix": 47 return []string{"LD_LIBRARY_PATH", "LD_LIBRARYN32_PATH", "LD_LIBRARY64_PATH"} 48 case "illumos", "solaris": 49 return []string{"LD_LIBRARY_PATH", "LD_LIBRARY_PATH_32", "LD_LIBRARY_PATH_64"} 50 case "windows": 51 return []string{"SystemRoot", "COMSPEC", "PATHEXT", "WINDIR"} 52 } 53 return nil 54 }() 55 56 // Handler runs an executable in a subprocess with a CGI environment. 57 type Handler struct { 58 Path string // path to the CGI executable 59 Root string // root URI prefix of handler or empty for "/" 60 61 // Dir specifies the CGI executable's working directory. 62 // If Dir is empty, the base directory of Path is used. 63 // If Path has no base directory, the current working 64 // directory is used. 65 Dir string 66 67 Env []string // extra environment variables to set, if any, as "key=value" 68 InheritEnv []string // environment variables to inherit from host, as "key" 69 Logger *log.Logger // optional log for errors or nil to use log.Print 70 Args []string // optional arguments to pass to child process 71 Stderr io.Writer // optional stderr for the child process; nil means os.Stderr 72 73 // PathLocationHandler specifies the root http Handler that 74 // should handle internal redirects when the CGI process 75 // returns a Location header value starting with a "/", as 76 // specified in RFC 3875 ยง 6.3.2. This will likely be 77 // http.DefaultServeMux. 78 // 79 // If nil, a CGI response with a local URI path is instead sent 80 // back to the client and not redirected internally. 81 PathLocationHandler http.Handler 82 } 83 84 func (h *Handler) stderr() io.Writer { 85 if h.Stderr != nil { 86 return h.Stderr 87 } 88 return os.Stderr 89 } 90 91 // removeLeadingDuplicates remove leading duplicate in environments. 92 // It's possible to override environment like following. 93 // cgi.Handler{ 94 // ... 95 // Env: []string{"SCRIPT_FILENAME=foo.php"}, 96 // } 97 func removeLeadingDuplicates(env []string) (ret []string) { 98 for i, e := range env { 99 found := false 100 if eq := strings.IndexByte(e, '='); eq != -1 { 101 keq := e[:eq+1] // "key=" 102 for _, e2 := range env[i+1:] { 103 if strings.HasPrefix(e2, keq) { 104 found = true 105 break 106 } 107 } 108 } 109 if !found { 110 ret = append(ret, e) 111 } 112 } 113 return 114 } 115 116 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { 117 root := h.Root 118 if root == "" { 119 root = "/" 120 } 121 122 if len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" { 123 rw.WriteHeader(http.StatusBadRequest) 124 rw.Write([]byte("Chunked request bodies are not supported by CGI.")) 125 return 126 } 127 128 pathInfo := req.URL.Path 129 if root != "/" && strings.HasPrefix(pathInfo, root) { 130 pathInfo = pathInfo[len(root):] 131 } 132 133 port := "80" 134 if matches := trailingPort.FindStringSubmatch(req.Host); len(matches) != 0 { 135 port = matches[1] 136 } 137 138 env := []string{ 139 "SERVER_SOFTWARE=go", 140 "SERVER_NAME=" + req.Host, 141 "SERVER_PROTOCOL=HTTP/1.1", 142 "HTTP_HOST=" + req.Host, 143 "GATEWAY_INTERFACE=CGI/1.1", 144 "REQUEST_METHOD=" + req.Method, 145 "QUERY_STRING=" + req.URL.RawQuery, 146 "REQUEST_URI=" + req.URL.RequestURI(), 147 "PATH_INFO=" + pathInfo, 148 "SCRIPT_NAME=" + root, 149 "SCRIPT_FILENAME=" + h.Path, 150 "SERVER_PORT=" + port, 151 } 152 153 if remoteIP, remotePort, err := net.SplitHostPort(req.RemoteAddr); err == nil { 154 env = append(env, "REMOTE_ADDR="+remoteIP, "REMOTE_HOST="+remoteIP, "REMOTE_PORT="+remotePort) 155 } else { 156 // could not parse ip:port, let's use whole RemoteAddr and leave REMOTE_PORT undefined 157 env = append(env, "REMOTE_ADDR="+req.RemoteAddr, "REMOTE_HOST="+req.RemoteAddr) 158 } 159 160 if req.TLS != nil { 161 env = append(env, "HTTPS=on") 162 } 163 164 for k, v := range req.Header { 165 k = strings.Map(upperCaseAndUnderscore, k) 166 if k == "PROXY" { 167 // See Issue 16405 168 continue 169 } 170 joinStr := ", " 171 if k == "COOKIE" { 172 joinStr = "; " 173 } 174 env = append(env, "HTTP_"+k+"="+strings.Join(v, joinStr)) 175 } 176 177 if req.ContentLength > 0 { 178 env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", req.ContentLength)) 179 } 180 if ctype := req.Header.Get("Content-Type"); ctype != "" { 181 env = append(env, "CONTENT_TYPE="+ctype) 182 } 183 184 envPath := os.Getenv("PATH") 185 if envPath == "" { 186 envPath = "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin" 187 } 188 env = append(env, "PATH="+envPath) 189 190 for _, e := range h.InheritEnv { 191 if v := os.Getenv(e); v != "" { 192 env = append(env, e+"="+v) 193 } 194 } 195 196 for _, e := range osDefaultInheritEnv { 197 if v := os.Getenv(e); v != "" { 198 env = append(env, e+"="+v) 199 } 200 } 201 202 if h.Env != nil { 203 env = append(env, h.Env...) 204 } 205 206 env = removeLeadingDuplicates(env) 207 208 var cwd, path string 209 if h.Dir != "" { 210 path = h.Path 211 cwd = h.Dir 212 } else { 213 cwd, path = filepath.Split(h.Path) 214 } 215 if cwd == "" { 216 cwd = "." 217 } 218 219 internalError := func(err error) { 220 rw.WriteHeader(http.StatusInternalServerError) 221 h.printf("CGI error: %v", err) 222 } 223 224 cmd := &exec.Cmd{ 225 Path: path, 226 Args: append([]string{h.Path}, h.Args...), 227 Dir: cwd, 228 Env: env, 229 Stderr: h.stderr(), 230 } 231 if req.ContentLength != 0 { 232 cmd.Stdin = req.Body 233 } 234 stdoutRead, err := cmd.StdoutPipe() 235 if err != nil { 236 internalError(err) 237 return 238 } 239 240 err = cmd.Start() 241 if err != nil { 242 internalError(err) 243 return 244 } 245 if hook := testHookStartProcess; hook != nil { 246 hook(cmd.Process) 247 } 248 defer cmd.Wait() 249 defer stdoutRead.Close() 250 251 linebody := bufio.NewReaderSize(stdoutRead, 1024) 252 headers := make(http.Header) 253 statusCode := 0 254 headerLines := 0 255 sawBlankLine := false 256 for { 257 line, isPrefix, err := linebody.ReadLine() 258 if isPrefix { 259 rw.WriteHeader(http.StatusInternalServerError) 260 h.printf("cgi: long header line from subprocess.") 261 return 262 } 263 if err == io.EOF { 264 break 265 } 266 if err != nil { 267 rw.WriteHeader(http.StatusInternalServerError) 268 h.printf("cgi: error reading headers: %v", err) 269 return 270 } 271 if len(line) == 0 { 272 sawBlankLine = true 273 break 274 } 275 headerLines++ 276 parts := strings.SplitN(string(line), ":", 2) 277 if len(parts) < 2 { 278 h.printf("cgi: bogus header line: %s", string(line)) 279 continue 280 } 281 header, val := parts[0], parts[1] 282 if !httpguts.ValidHeaderFieldName(header) { 283 h.printf("cgi: invalid header name: %q", header) 284 continue 285 } 286 val = textproto.TrimString(val) 287 switch { 288 case header == "Status": 289 if len(val) < 3 { 290 h.printf("cgi: bogus status (short): %q", val) 291 return 292 } 293 code, err := strconv.Atoi(val[0:3]) 294 if err != nil { 295 h.printf("cgi: bogus status: %q", val) 296 h.printf("cgi: line was %q", line) 297 return 298 } 299 statusCode = code 300 default: 301 headers.Add(header, val) 302 } 303 } 304 if headerLines == 0 || !sawBlankLine { 305 rw.WriteHeader(http.StatusInternalServerError) 306 h.printf("cgi: no headers") 307 return 308 } 309 310 if loc := headers.Get("Location"); loc != "" { 311 if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil { 312 h.handleInternalRedirect(rw, req, loc) 313 return 314 } 315 if statusCode == 0 { 316 statusCode = http.StatusFound 317 } 318 } 319 320 if statusCode == 0 && headers.Get("Content-Type") == "" { 321 rw.WriteHeader(http.StatusInternalServerError) 322 h.printf("cgi: missing required Content-Type in headers") 323 return 324 } 325 326 if statusCode == 0 { 327 statusCode = http.StatusOK 328 } 329 330 // Copy headers to rw's headers, after we've decided not to 331 // go into handleInternalRedirect, which won't want its rw 332 // headers to have been touched. 333 for k, vv := range headers { 334 for _, v := range vv { 335 rw.Header().Add(k, v) 336 } 337 } 338 339 rw.WriteHeader(statusCode) 340 341 _, err = io.Copy(rw, linebody) 342 if err != nil { 343 h.printf("cgi: copy error: %v", err) 344 // And kill the child CGI process so we don't hang on 345 // the deferred cmd.Wait above if the error was just 346 // the client (rw) going away. If it was a read error 347 // (because the child died itself), then the extra 348 // kill of an already-dead process is harmless (the PID 349 // won't be reused until the Wait above). 350 cmd.Process.Kill() 351 } 352 } 353 354 func (h *Handler) printf(format string, v ...interface{}) { 355 if h.Logger != nil { 356 h.Logger.Printf(format, v...) 357 } else { 358 log.Printf(format, v...) 359 } 360 } 361 362 func (h *Handler) handleInternalRedirect(rw http.ResponseWriter, req *http.Request, path string) { 363 url, err := req.URL.Parse(path) 364 if err != nil { 365 rw.WriteHeader(http.StatusInternalServerError) 366 h.printf("cgi: error resolving local URI path %q: %v", path, err) 367 return 368 } 369 // TODO: RFC 3875 isn't clear if only GET is supported, but it 370 // suggests so: "Note that any message-body attached to the 371 // request (such as for a POST request) may not be available 372 // to the resource that is the target of the redirect." We 373 // should do some tests against Apache to see how it handles 374 // POST, HEAD, etc. Does the internal redirect get the same 375 // method or just GET? What about incoming headers? 376 // (e.g. Cookies) Which headers, if any, are copied into the 377 // second request? 378 newReq := &http.Request{ 379 Method: "GET", 380 URL: url, 381 Proto: "HTTP/1.1", 382 ProtoMajor: 1, 383 ProtoMinor: 1, 384 Header: make(http.Header), 385 Host: url.Host, 386 RemoteAddr: req.RemoteAddr, 387 TLS: req.TLS, 388 } 389 h.PathLocationHandler.ServeHTTP(rw, newReq) 390 } 391 392 func upperCaseAndUnderscore(r rune) rune { 393 switch { 394 case r >= 'a' && r <= 'z': 395 return r - ('a' - 'A') 396 case r == '-': 397 return '_' 398 case r == '=': 399 // Maybe not part of the CGI 'spec' but would mess up 400 // the environment in any case, as Go represents the 401 // environment as a slice of "key=value" strings. 402 return '_' 403 } 404 // TODO: other transformations in spec or practice? 405 return r 406 } 407 408 var testHookStartProcess func(*os.Process) // nil except for some tests