...

Source file src/pkg/net/http/cgi/child.go

     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.ReplaceAll(k[5:], "_", "-"), 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		// https://web.archive.org/web/20170105004655/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	

View as plain text