// Copyright 2011 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

// This file implements the host side of CGI (being the webserver
// parent process).

// Package cgi implements CGI (Common Gateway Interface) as specified // in RFC 3875. // // Note that using CGI means starting a new process to handle each // request, which is typically less efficient than using a // long-running server. This package is intended primarily for // compatibility with existing systems.
package cgi import ( ) var trailingPort = regexp.MustCompile(`:([0-9]+)$`) var osDefaultInheritEnv = func() []string { switch runtime.GOOS { case "darwin", "ios": return []string{"DYLD_LIBRARY_PATH"} case "android", "linux", "freebsd", "netbsd", "openbsd": return []string{"LD_LIBRARY_PATH"} case "hpux": return []string{"LD_LIBRARY_PATH", "SHLIB_PATH"} case "irix": return []string{"LD_LIBRARY_PATH", "LD_LIBRARYN32_PATH", "LD_LIBRARY64_PATH"} case "illumos", "solaris": return []string{"LD_LIBRARY_PATH", "LD_LIBRARY_PATH_32", "LD_LIBRARY_PATH_64"} case "windows": return []string{"SystemRoot", "COMSPEC", "PATHEXT", "WINDIR"} } return nil }() // Handler runs an executable in a subprocess with a CGI environment. type Handler struct { Path string // path to the CGI executable Root string // root URI prefix of handler or empty for "/" // Dir specifies the CGI executable's working directory. // If Dir is empty, the base directory of Path is used. // If Path has no base directory, the current working // directory is used. Dir string Env []string // extra environment variables to set, if any, as "key=value" InheritEnv []string // environment variables to inherit from host, as "key" Logger *log.Logger // optional log for errors or nil to use log.Print Args []string // optional arguments to pass to child process Stderr io.Writer // optional stderr for the child process; nil means os.Stderr // PathLocationHandler specifies the root http Handler that // should handle internal redirects when the CGI process // returns a Location header value starting with a "/", as // specified in RFC 3875 ยง 6.3.2. This will likely be // http.DefaultServeMux. // // If nil, a CGI response with a local URI path is instead sent // back to the client and not redirected internally. PathLocationHandler http.Handler } func ( *Handler) () io.Writer { if .Stderr != nil { return .Stderr } return os.Stderr } // removeLeadingDuplicates remove leading duplicate in environments. // It's possible to override environment like following. // // cgi.Handler{ // ... // Env: []string{"SCRIPT_FILENAME=foo.php"}, // } func removeLeadingDuplicates( []string) ( []string) { for , := range { := false if := strings.IndexByte(, '='); != -1 { := [:+1] // "key=" for , := range [+1:] { if strings.HasPrefix(, ) { = true break } } } if ! { = append(, ) } } return } func ( *Handler) ( http.ResponseWriter, *http.Request) { if len(.TransferEncoding) > 0 && .TransferEncoding[0] == "chunked" { .WriteHeader(http.StatusBadRequest) .Write([]byte("Chunked request bodies are not supported by CGI.")) return } := strings.TrimRight(.Root, "/") := strings.TrimPrefix(.URL.Path, ) := "80" if .TLS != nil { = "443" } if := trailingPort.FindStringSubmatch(.Host); len() != 0 { = [1] } := []string{ "SERVER_SOFTWARE=go", "SERVER_PROTOCOL=HTTP/1.1", "HTTP_HOST=" + .Host, "GATEWAY_INTERFACE=CGI/1.1", "REQUEST_METHOD=" + .Method, "QUERY_STRING=" + .URL.RawQuery, "REQUEST_URI=" + .URL.RequestURI(), "PATH_INFO=" + , "SCRIPT_NAME=" + , "SCRIPT_FILENAME=" + .Path, "SERVER_PORT=" + , } if , , := net.SplitHostPort(.RemoteAddr); == nil { = append(, "REMOTE_ADDR="+, "REMOTE_HOST="+, "REMOTE_PORT="+) } else { // could not parse ip:port, let's use whole RemoteAddr and leave REMOTE_PORT undefined = append(, "REMOTE_ADDR="+.RemoteAddr, "REMOTE_HOST="+.RemoteAddr) } if , , := net.SplitHostPort(.Host); == nil { = append(, "SERVER_NAME="+) } else { = append(, "SERVER_NAME="+.Host) } if .TLS != nil { = append(, "HTTPS=on") } for , := range .Header { = strings.Map(upperCaseAndUnderscore, ) if == "PROXY" { // See Issue 16405 continue } := ", " if == "COOKIE" { = "; " } = append(, "HTTP_"++"="+strings.Join(, )) } if .ContentLength > 0 { = append(, fmt.Sprintf("CONTENT_LENGTH=%d", .ContentLength)) } if := .Header.Get("Content-Type"); != "" { = append(, "CONTENT_TYPE="+) } := os.Getenv("PATH") if == "" { = "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin" } = append(, "PATH="+) for , := range .InheritEnv { if := os.Getenv(); != "" { = append(, +"="+) } } for , := range osDefaultInheritEnv { if := os.Getenv(); != "" { = append(, +"="+) } } if .Env != nil { = append(, .Env...) } = removeLeadingDuplicates() var , string if .Dir != "" { = .Path = .Dir } else { , = filepath.Split(.Path) } if == "" { = "." } := func( error) { .WriteHeader(http.StatusInternalServerError) .printf("CGI error: %v", ) } := &exec.Cmd{ Path: , Args: append([]string{.Path}, .Args...), Dir: , Env: , Stderr: .stderr(), } if .ContentLength != 0 { .Stdin = .Body } , := .StdoutPipe() if != nil { () return } = .Start() if != nil { () return } if := testHookStartProcess; != nil { (.Process) } defer .Wait() defer .Close() := bufio.NewReaderSize(, 1024) := make(http.Header) := 0 := 0 := false for { , , := .ReadLine() if { .WriteHeader(http.StatusInternalServerError) .printf("cgi: long header line from subprocess.") return } if == io.EOF { break } if != nil { .WriteHeader(http.StatusInternalServerError) .printf("cgi: error reading headers: %v", ) return } if len() == 0 { = true break } ++ , , := strings.Cut(string(), ":") if ! { .printf("cgi: bogus header line: %s", string()) continue } if !httpguts.ValidHeaderFieldName() { .printf("cgi: invalid header name: %q", ) continue } = textproto.TrimString() switch { case == "Status": if len() < 3 { .printf("cgi: bogus status (short): %q", ) return } , := strconv.Atoi([0:3]) if != nil { .printf("cgi: bogus status: %q", ) .printf("cgi: line was %q", ) return } = default: .Add(, ) } } if == 0 || ! { .WriteHeader(http.StatusInternalServerError) .printf("cgi: no headers") return } if := .Get("Location"); != "" { if strings.HasPrefix(, "/") && .PathLocationHandler != nil { .handleInternalRedirect(, , ) return } if == 0 { = http.StatusFound } } if == 0 && .Get("Content-Type") == "" { .WriteHeader(http.StatusInternalServerError) .printf("cgi: missing required Content-Type in headers") return } if == 0 { = http.StatusOK } // Copy headers to rw's headers, after we've decided not to // go into handleInternalRedirect, which won't want its rw // headers to have been touched. for , := range { for , := range { .Header().Add(, ) } } .WriteHeader() _, = io.Copy(, ) if != nil { .printf("cgi: copy error: %v", ) // And kill the child CGI process so we don't hang on // the deferred cmd.Wait above if the error was just // the client (rw) going away. If it was a read error // (because the child died itself), then the extra // kill of an already-dead process is harmless (the PID // won't be reused until the Wait above). .Process.Kill() } } func ( *Handler) ( string, ...any) { if .Logger != nil { .Logger.Printf(, ...) } else { log.Printf(, ...) } } func ( *Handler) ( http.ResponseWriter, *http.Request, string) { , := .URL.Parse() if != nil { .WriteHeader(http.StatusInternalServerError) .printf("cgi: error resolving local URI path %q: %v", , ) return } // TODO: RFC 3875 isn't clear if only GET is supported, but it // suggests so: "Note that any message-body attached to the // request (such as for a POST request) may not be available // to the resource that is the target of the redirect." We // should do some tests against Apache to see how it handles // POST, HEAD, etc. Does the internal redirect get the same // method or just GET? What about incoming headers? // (e.g. Cookies) Which headers, if any, are copied into the // second request? := &http.Request{ Method: "GET", URL: , Proto: "HTTP/1.1", ProtoMajor: 1, ProtoMinor: 1, Header: make(http.Header), Host: .Host, RemoteAddr: .RemoteAddr, TLS: .TLS, } .PathLocationHandler.ServeHTTP(, ) } func upperCaseAndUnderscore( rune) rune { switch { case >= 'a' && <= 'z': return - ('a' - 'A') case == '-': return '_' case == '=': // Maybe not part of the CGI 'spec' but would mess up // the environment in any case, as Go represents the // environment as a slice of "key=value" strings. return '_' } // TODO: other transformations in spec or practice? return } var testHookStartProcess func(*os.Process) // nil except for some tests