Source file src/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  	"net"
    17  	"net/http"
    18  	"net/url"
    19  	"os"
    20  	"strconv"
    21  	"strings"
    22  )
    23  
    24  // Request returns the HTTP request as represented in the current
    25  // environment. This assumes the current program is being run
    26  // by a web server in a CGI environment.
    27  // The returned Request's Body is populated, if applicable.
    28  func Request() (*http.Request, error) {
    29  	r, err := RequestFromMap(envMap(os.Environ()))
    30  	if err != nil {
    31  		return nil, err
    32  	}
    33  	if r.ContentLength > 0 {
    34  		r.Body = io.NopCloser(io.LimitReader(os.Stdin, r.ContentLength))
    35  	}
    36  	return r, nil
    37  }
    38  
    39  func envMap(env []string) map[string]string {
    40  	m := make(map[string]string)
    41  	for _, kv := range env {
    42  		if k, v, ok := strings.Cut(kv, "="); ok {
    43  			m[k] = v
    44  		}
    45  	}
    46  	return m
    47  }
    48  
    49  // RequestFromMap creates an [http.Request] from CGI variables.
    50  // The returned Request's Body field is not populated.
    51  func RequestFromMap(params map[string]string) (*http.Request, error) {
    52  	r := new(http.Request)
    53  	r.Method = params["REQUEST_METHOD"]
    54  	if r.Method == "" {
    55  		return nil, errors.New("cgi: no REQUEST_METHOD in environment")
    56  	}
    57  
    58  	r.Proto = params["SERVER_PROTOCOL"]
    59  	var ok bool
    60  	if r.Proto == "INCLUDED" {
    61  		// SSI (Server Side Include) use case
    62  		// CGI Specification RFC 3875 - section 4.1.16
    63  		r.ProtoMajor, r.ProtoMinor = 1, 0
    64  	} else if r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto); !ok {
    65  		return nil, errors.New("cgi: invalid SERVER_PROTOCOL version")
    66  	}
    67  
    68  	r.Close = true
    69  	r.Trailer = http.Header{}
    70  	r.Header = http.Header{}
    71  
    72  	r.Host = params["HTTP_HOST"]
    73  
    74  	if lenstr := params["CONTENT_LENGTH"]; lenstr != "" {
    75  		clen, err := strconv.ParseInt(lenstr, 10, 64)
    76  		if err != nil {
    77  			return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr)
    78  		}
    79  		r.ContentLength = clen
    80  	}
    81  
    82  	if ct := params["CONTENT_TYPE"]; ct != "" {
    83  		r.Header.Set("Content-Type", ct)
    84  	}
    85  
    86  	// Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers
    87  	for k, v := range params {
    88  		if k == "HTTP_HOST" {
    89  			continue
    90  		}
    91  		if after, found := strings.CutPrefix(k, "HTTP_"); found {
    92  			r.Header.Add(strings.ReplaceAll(after, "_", "-"), v)
    93  		}
    94  	}
    95  
    96  	uriStr := params["REQUEST_URI"]
    97  	if uriStr == "" {
    98  		// Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING.
    99  		uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"]
   100  		s := params["QUERY_STRING"]
   101  		if s != "" {
   102  			uriStr += "?" + s
   103  		}
   104  	}
   105  
   106  	// There's apparently a de-facto standard for this.
   107  	// https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
   108  	if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
   109  		r.TLS = &tls.ConnectionState{HandshakeComplete: true}
   110  	}
   111  
   112  	if r.Host != "" {
   113  		// Hostname is provided, so we can reasonably construct a URL.
   114  		rawurl := r.Host + uriStr
   115  		if r.TLS == nil {
   116  			rawurl = "http://" + rawurl
   117  		} else {
   118  			rawurl = "https://" + rawurl
   119  		}
   120  		url, err := url.Parse(rawurl)
   121  		if err != nil {
   122  			return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
   123  		}
   124  		r.URL = url
   125  	}
   126  	// Fallback logic if we don't have a Host header or the URL
   127  	// failed to parse
   128  	if r.URL == nil {
   129  		url, err := url.Parse(uriStr)
   130  		if err != nil {
   131  			return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
   132  		}
   133  		r.URL = url
   134  	}
   135  
   136  	// Request.RemoteAddr has its port set by Go's standard http
   137  	// server, so we do here too.
   138  	remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid
   139  	r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort))
   140  
   141  	return r, nil
   142  }
   143  
   144  // Serve executes the provided [Handler] on the currently active CGI
   145  // request, if any. If there's no current CGI environment
   146  // an error is returned. The provided handler may be nil to use
   147  // [http.DefaultServeMux].
   148  func Serve(handler http.Handler) error {
   149  	req, err := Request()
   150  	if err != nil {
   151  		return err
   152  	}
   153  	if req.Body == nil {
   154  		req.Body = http.NoBody
   155  	}
   156  	if handler == nil {
   157  		handler = http.DefaultServeMux
   158  	}
   159  	rw := &response{
   160  		req:    req,
   161  		header: make(http.Header),
   162  		bufw:   bufio.NewWriter(os.Stdout),
   163  	}
   164  	handler.ServeHTTP(rw, req)
   165  	rw.Write(nil) // make sure a response is sent
   166  	if err = rw.bufw.Flush(); err != nil {
   167  		return err
   168  	}
   169  	return nil
   170  }
   171  
   172  type response struct {
   173  	req            *http.Request
   174  	header         http.Header
   175  	code           int
   176  	wroteHeader    bool
   177  	wroteCGIHeader bool
   178  	bufw           *bufio.Writer
   179  }
   180  
   181  func (r *response) Flush() {
   182  	r.bufw.Flush()
   183  }
   184  
   185  func (r *response) Header() http.Header {
   186  	return r.header
   187  }
   188  
   189  func (r *response) Write(p []byte) (n int, err error) {
   190  	if !r.wroteHeader {
   191  		r.WriteHeader(http.StatusOK)
   192  	}
   193  	if !r.wroteCGIHeader {
   194  		r.writeCGIHeader(p)
   195  	}
   196  	return r.bufw.Write(p)
   197  }
   198  
   199  func (r *response) WriteHeader(code int) {
   200  	if r.wroteHeader {
   201  		// Note: explicitly using Stderr, as Stdout is our HTTP output.
   202  		fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
   203  		return
   204  	}
   205  	r.wroteHeader = true
   206  	r.code = code
   207  }
   208  
   209  // writeCGIHeader finalizes the header sent to the client and writes it to the output.
   210  // p is not written by writeHeader, but is the first chunk of the body
   211  // that will be written. It is sniffed for a Content-Type if none is
   212  // set explicitly.
   213  func (r *response) writeCGIHeader(p []byte) {
   214  	if r.wroteCGIHeader {
   215  		return
   216  	}
   217  	r.wroteCGIHeader = true
   218  	fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code))
   219  	if _, hasType := r.header["Content-Type"]; !hasType {
   220  		r.header.Set("Content-Type", http.DetectContentType(p))
   221  	}
   222  	r.header.Write(r.bufw)
   223  	r.bufw.WriteString("\r\n")
   224  	r.bufw.Flush()
   225  }
   226  

View as plain text