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