// 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 cgiimport ()var trailingPort = regexp.MustCompile(`:([0-9]+)$`)var osDefaultInheritEnv = func() []string {switchruntime.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"} }returnnil}()// Handler runs an executable in a subprocess with a CGI environment.typeHandlerstruct { 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 }returnos.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 { := falseif := strings.IndexByte(, '='); != -1 { := [:+1] // "key="for , := range [+1:] {ifstrings.HasPrefix(, ) { = truebreak } } }if ! { = append(, ) } }return}func ( *Handler) ( http.ResponseWriter, *http.Request) {iflen(.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 16405continue } := ", "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 , := rangeosDefaultInheritEnv {if := os.Getenv(); != "" { = append(, +"="+) } }if .Env != nil { = append(, .Env...) } = removeLeadingDuplicates()var , stringif .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 := falsefor { , , := .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 }iflen() == 0 { = truebreak } ++ , , := strings.Cut(string(), ":")if ! { .printf("cgi: bogus header line: %s", )continue }if !httpguts.ValidHeaderFieldName() { .printf("cgi: invalid header name: %q", )continue } = textproto.TrimString()switch {case == "Status":iflen() < 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"); != "" {ifstrings.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
The pages are generated with Goldsv0.7.0-preview. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.