af146490bb
It is not needed due to the removal of the ctx field. Reviewed-on: https://go-review.googlesource.com/16525 From-SVN: r229616
207 lines
5.1 KiB
Go
207 lines
5.1 KiB
Go
// 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()
|
|
}
|