Home | History | Annotate | Download | only in cgi
      1 // Copyright 2011 The Go Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style
      3 // license that can be found in the LICENSE file.
      4 
      5 // This file implements CGI from the perspective of a child
      6 // process.
      7 
      8 package cgi
      9 
     10 import (
     11 	"bufio"
     12 	"crypto/tls"
     13 	"errors"
     14 	"fmt"
     15 	"io"
     16 	"io/ioutil"
     17 	"net"
     18 	"net/http"
     19 	"net/url"
     20 	"os"
     21 	"strconv"
     22 	"strings"
     23 )
     24 
     25 // Request returns the HTTP request as represented in the current
     26 // environment. This assumes the current program is being run
     27 // by a web server in a CGI environment.
     28 // The returned Request's Body is populated, if applicable.
     29 func Request() (*http.Request, error) {
     30 	r, err := RequestFromMap(envMap(os.Environ()))
     31 	if err != nil {
     32 		return nil, err
     33 	}
     34 	if r.ContentLength > 0 {
     35 		r.Body = ioutil.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
     36 	}
     37 	return r, nil
     38 }
     39 
     40 func envMap(env []string) map[string]string {
     41 	m := make(map[string]string)
     42 	for _, kv := range env {
     43 		if idx := strings.Index(kv, "="); idx != -1 {
     44 			m[kv[:idx]] = kv[idx+1:]
     45 		}
     46 	}
     47 	return m
     48 }
     49 
     50 // RequestFromMap creates an http.Request from CGI variables.
     51 // The returned Request's Body field is not populated.
     52 func RequestFromMap(params map[string]string) (*http.Request, error) {
     53 	r := new(http.Request)
     54 	r.Method = params["REQUEST_METHOD"]
     55 	if r.Method == "" {
     56 		return nil, errors.New("cgi: no REQUEST_METHOD in environment")
     57 	}
     58 
     59 	r.Proto = params["SERVER_PROTOCOL"]
     60 	var ok bool
     61 	r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto)
     62 	if !ok {
     63 		return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
     64 	}
     65 
     66 	r.Close = true
     67 	r.Trailer = http.Header{}
     68 	r.Header = http.Header{}
     69 
     70 	r.Host = params["HTTP_HOST"]
     71 
     72 	if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
     73 		clen, err := strconv.ParseInt(lenstr, 10, 64)
     74 		if err != nil {
     75 			return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
     76 		}
     77 		r.ContentLength = clen
     78 	}
     79 
     80 	if ct := params["CONTENT_TYPE"]; ct != "" {
     81 		r.Header.Set("Content-Type", ct)
     82 	}
     83 
     84 	// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
     85 	for k, v := range params {
     86 		if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" {
     87 			continue
     88 		}
     89 		r.Header.Add(strings.Replace(k[5:], "_", "-", -1), v)
     90 	}
     91 
     92 	// TODO: cookies.  parsing them isn't exported, though.
     93 
     94 	uriStr := params["REQUEST_URI"]
     95 	if uriStr == "" {
     96 		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
     97 		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
     98 		s := params["QUERY_STRING"]
     99 		if s != "" {
    100 			uriStr += "?" + s
    101 		}
    102 	}
    103 
    104 	// There's apparently a de-facto standard for this.
    105 	// http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
    106 	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
    107 		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
    108 	}
    109 
    110 	if r.Host != "" {
    111 		// Hostname is provided, so we can reasonably construct a URL.
    112 		rawurl := r.Host + uriStr
    113 		if r.TLS == nil {
    114 			rawurl = "http://" + rawurl
    115 		} else {
    116 			rawurl = "https://" + rawurl
    117 		}
    118 		url, err := url.Parse(rawurl)
    119 		if err != nil {
    120 			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
    121 		}
    122 		r.URL = url
    123 	}
    124 	// Fallback logic if we don't have a Host header or the URL
    125 	// failed to parse
    126 	if r.URL == nil {
    127 		url, err := url.Parse(uriStr)
    128 		if err != nil {
    129 			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
    130 		}
    131 		r.URL = url
    132 	}
    133 
    134 	// Request.RemoteAddr has its port set by Go's standard http
    135 	// server, so we do here too.
    136 	remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid
    137 	r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort))
    138 
    139 	return r, nil
    140 }
    141 
    142 // Serve executes the provided Handler on the currently active CGI
    143 // request, if any. If there's no current CGI environment
    144 // an error is returned. The provided handler may be nil to use
    145 // http.DefaultServeMux.
    146 func Serve(handler http.Handler) error {
    147 	req, err := Request()
    148 	if err != nil {
    149 		return err
    150 	}
    151 	if handler == nil {
    152 		handler = http.DefaultServeMux
    153 	}
    154 	rw := &response{
    155 		req:    req,
    156 		header: make(http.Header),
    157 		bufw:   bufio.NewWriter(os.Stdout),
    158 	}
    159 	handler.ServeHTTP(rw, req)
    160 	rw.Write(nil) // make sure a response is sent
    161 	if err = rw.bufw.Flush(); err != nil {
    162 		return err
    163 	}
    164 	return nil
    165 }
    166 
    167 type response struct {
    168 	req        *http.Request
    169 	header     http.Header
    170 	bufw       *bufio.Writer
    171 	headerSent bool
    172 }
    173 
    174 func (r *response) Flush() {
    175 	r.bufw.Flush()
    176 }
    177 
    178 func (r *response) Header() http.Header {
    179 	return r.header
    180 }
    181 
    182 func (r *response) Write(p []byte) (n int, err error) {
    183 	if !r.headerSent {
    184 		r.WriteHeader(http.StatusOK)
    185 	}
    186 	return r.bufw.Write(p)
    187 }
    188 
    189 func (r *response) WriteHeader(code int) {
    190 	if r.headerSent {
    191 		// Note: explicitly using Stderr, as Stdout is our HTTP output.
    192 		fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
    193 		return
    194 	}
    195 	r.headerSent = true
    196 	fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
    197 
    198 	// Set a default Content-Type
    199 	if _, hasType := r.header["Content-Type"]; !hasType {
    200 		r.header.Add("Content-Type", "text/html; charset=utf-8")
    201 	}
    202 
    203 	r.header.Write(r.bufw)
    204 	r.bufw.WriteString("\r\n")
    205 	r.bufw.Flush()
    206 }
    207