github.com/slayercat/go@v0.0.0-20170428012452-c51559813f61/src/net/http/cgi/child.go (about)

     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  }