// 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" "fmt" "http" "io" "io/ioutil" "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. func Request() (*http.Request, os.Error) { return requestFromEnvironment(envMap(os.Environ())) } 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 } // These environment variables are manually copied into Request var skipHeader = map[string]bool{ "HTTP_HOST": true, "HTTP_REFERER": true, "HTTP_USER_AGENT": true, } func requestFromEnvironment(env map[string]string) (*http.Request, os.Error) { r := new(http.Request) r.Method = env["REQUEST_METHOD"] if r.Method == "" { return nil, os.NewError("cgi: no REQUEST_METHOD in environment") } r.Close = true r.Trailer = http.Header{} r.Header = http.Header{} r.Host = env["HTTP_HOST"] r.Referer = env["HTTP_REFERER"] r.UserAgent = env["HTTP_USER_AGENT"] // CGI doesn't allow chunked requests, so these should all be accurate: r.Proto = "HTTP/1.0" r.ProtoMajor = 1 r.ProtoMinor = 0 r.TransferEncoding = nil if lenstr := env["CONTENT_LENGTH"]; lenstr != "" { clen, err := strconv.Atoi64(lenstr) if err != nil { return nil, os.NewError("cgi: bad CONTENT_LENGTH in environment: " + lenstr) } r.ContentLength = clen r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, clen)) } // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers for k, v := range env { if !strings.HasPrefix(k, "HTTP_") || skipHeader[k] { continue } r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v) } // TODO: cookies. parsing them isn't exported, though. if r.Host != "" { // Hostname is provided, so we can reasonably construct a URL, // even if we have to assume 'http' for the scheme. r.RawURL = "http://" + r.Host + env["REQUEST_URI"] url, err := http.ParseURL(r.RawURL) if err != nil { return nil, os.NewError("cgi: failed to parse host and REQUEST_URI into a URL: " + r.RawURL) } r.URL = url } // Fallback logic if we don't have a Host header or the URL // failed to parse if r.URL == nil { r.RawURL = env["REQUEST_URI"] url, err := http.ParseURL(r.RawURL) if err != nil { return nil, os.NewError("cgi: failed to parse REQUEST_URI into a URL: " + r.RawURL) } r.URL = url } 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) os.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) 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) RemoteAddr() string { return os.Getenv("REMOTE_ADDR") } func (r *response) Header() http.Header { return r.header } func (r *response) Write(p []byte) (n int, err os.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") } // TODO: add a method on http.Header to write itself to an io.Writer? // This is duplicated code. for k, vv := range r.header { for _, v := range vv { v = strings.Replace(v, "\n", "", -1) v = strings.Replace(v, "\r", "", -1) v = strings.TrimSpace(v) fmt.Fprintf(r.bufw, "%s: %s\r\n", k, v) } } r.bufw.Write([]byte("\r\n")) r.bufw.Flush() } func (r *response) UsingTLS() bool { // There's apparently a de-facto standard for this. // http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 if s := os.Getenv("HTTPS"); s == "on" || s == "ON" || s == "1" { return true } return false }