// 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 CGI from the perspective of a child // process. package cgi import ( "bufio" "crypto/tls" "errors" "fmt" "io" "io/ioutil" "net" "net/http" "net/url" "os" "strconv" "strings" ) // Request returns the HTTP request as represented in the current // environment. This assumes the current program is being run // by a web server in a CGI environment. // The returned Request's Body is populated, if applicable. func Request() (*http.Request, error) { r, err := RequestFromMap(envMap(os.Environ())) if err != nil { return nil, err } if r.ContentLength > 0 { r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, r.ContentLength)) } return r, nil } func envMap(env []string) map[string]string { m := make(map[string]string) for _, kv := range env { if idx := strings.Index(kv, "="); idx != -1 { m[kv[:idx]] = kv[idx+1:] } } return m } // RequestFromMap creates an http.Request from CGI variables. // The returned Request's Body field is not populated. func RequestFromMap(params map[string]string) (*http.Request, error) { r := new(http.Request) r.Method = params["REQUEST_METHOD"] if r.Method == "" { return nil, errors.New("cgi: no REQUEST_METHOD in environment") } r.Proto = params["SERVER_PROTOCOL"] var ok bool r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto) if !ok { return nil, errors.New("cgi: invalid SERVER_PROTOCOL version") } r.Close = true r.Trailer = http.Header{} r.Header = http.Header{} r.Host = params["HTTP_HOST"] if lenstr := params["CONTENT_LENGTH"]; lenstr != "" { clen, err := strconv.ParseInt(lenstr, 10, 64) if err != nil { return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr) } r.ContentLength = clen } if ct := params["CONTENT_TYPE"]; ct != "" { r.Header.Set("Content-Type", ct) } // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers for k, v := range params { if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" { continue } r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v) } // TODO: cookies. parsing them isn't exported, though. uriStr := params["REQUEST_URI"] if uriStr == "" { // Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"] s := params["QUERY_STRING"] if s != "" { uriStr += "?" + s } } // There's apparently a de-facto standard for this. // http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" { r.TLS = &tls.ConnectionState{HandshakeComplete: true} } if r.Host != "" { // Hostname is provided, so we can reasonably construct a URL. rawurl := r.Host + uriStr if r.TLS == nil { rawurl = "http://" + rawurl } else { rawurl = "https://" + rawurl } url, err := url.Parse(rawurl) if err != nil { return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl) } r.URL = url } // Fallback logic if we don't have a Host header or the URL // failed to parse if r.URL == nil { url, err := url.Parse(uriStr) if err != nil { return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr) } r.URL = url } // Request.RemoteAddr has its port set by Go's standard http // server, so we do here too. remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort)) return r, nil } // Serve executes the provided Handler on the currently active CGI // request, if any. If there's no current CGI environment // an error is returned. The provided handler may be nil to use // http.DefaultServeMux. func Serve(handler http.Handler) error { req, err := Request() if err != nil { return err } if handler == nil { handler = http.DefaultServeMux } rw := &response{ req: req, header: make(http.Header), bufw: bufio.NewWriter(os.Stdout), } handler.ServeHTTP(rw, req) rw.Write(nil) // make sure a response is sent if err = rw.bufw.Flush(); err != nil { return err } return nil } type response struct { req *http.Request header http.Header bufw *bufio.Writer headerSent bool } func (r *response) Flush() { r.bufw.Flush() } func (r *response) Header() http.Header { return r.header } func (r *response) Write(p []byte) (n int, err error) { if !r.headerSent { r.WriteHeader(http.StatusOK) } return r.bufw.Write(p) } func (r *response) WriteHeader(code int) { if r.headerSent { // Note: explicitly using Stderr, as Stdout is our HTTP output. fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL) return } r.headerSent = true fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code)) // Set a default Content-Type if _, hasType := r.header["Content-Type"]; !hasType { r.header.Add("Content-Type", "text/html; charset=utf-8") } r.header.Write(r.bufw) r.bufw.WriteString("\r\n") r.bufw.Flush() }