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 8package cgi 9 10import ( 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. 28func 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 39func envMap(env []string) map[string]string { 40 m := make(map[string]string) 41 for _, kv := range env { 42 if idx := strings.Index(kv, "="); idx != -1 { 43 m[kv[:idx]] = kv[idx+1:] 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. 51func 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 r.ProtoMajor, r.ProtoMinor, ok = http.ParseHTTPVersion(r.Proto) 61 if !ok { 62 return nil, errors.New("cgi: invalid SERVER_PROTOCOL version") 63 } 64 65 r.Close = true 66 r.Trailer = http.Header{} 67 r.Header = http.Header{} 68 69 r.Host = params["HTTP_HOST"] 70 71 if lenstr := params["CONTENT_LENGTH"]; lenstr != "" { 72 clen, err := strconv.ParseInt(lenstr, 10, 64) 73 if err != nil { 74 return nil, errors.New("cgi: bad CONTENT_LENGTH in environment: " + lenstr) 75 } 76 r.ContentLength = clen 77 } 78 79 if ct := params["CONTENT_TYPE"]; ct != "" { 80 r.Header.Set("Content-Type", ct) 81 } 82 83 // Copy "HTTP_FOO_BAR" variables to "Foo-Bar" Headers 84 for k, v := range params { 85 if !strings.HasPrefix(k, "HTTP_") || k == "HTTP_HOST" { 86 continue 87 } 88 r.Header.Add(strings.ReplaceAll(k[5:], "_", "-"), v) 89 } 90 91 uriStr := params["REQUEST_URI"] 92 if uriStr == "" { 93 // Fallback to SCRIPT_NAME, PATH_INFO and QUERY_STRING. 94 uriStr = params["SCRIPT_NAME"] + params["PATH_INFO"] 95 s := params["QUERY_STRING"] 96 if s != "" { 97 uriStr += "?" + s 98 } 99 } 100 101 // There's apparently a de-facto standard for this. 102 // https://web.archive.org/web/20170105004655/http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636 103 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" { 104 r.TLS = &tls.ConnectionState{HandshakeComplete: true} 105 } 106 107 if r.Host != "" { 108 // Hostname is provided, so we can reasonably construct a URL. 109 rawurl := r.Host + uriStr 110 if r.TLS == nil { 111 rawurl = "http://" + rawurl 112 } else { 113 rawurl = "https://" + rawurl 114 } 115 url, err := url.Parse(rawurl) 116 if err != nil { 117 return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl) 118 } 119 r.URL = url 120 } 121 // Fallback logic if we don't have a Host header or the URL 122 // failed to parse 123 if r.URL == nil { 124 url, err := url.Parse(uriStr) 125 if err != nil { 126 return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr) 127 } 128 r.URL = url 129 } 130 131 // Request.RemoteAddr has its port set by Go's standard http 132 // server, so we do here too. 133 remotePort, _ := strconv.Atoi(params["REMOTE_PORT"]) // zero if unset or invalid 134 r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], strconv.Itoa(remotePort)) 135 136 return r, nil 137} 138 139// Serve executes the provided Handler on the currently active CGI 140// request, if any. If there's no current CGI environment 141// an error is returned. The provided handler may be nil to use 142// http.DefaultServeMux. 143func Serve(handler http.Handler) error { 144 req, err := Request() 145 if err != nil { 146 return err 147 } 148 if req.Body == nil { 149 req.Body = http.NoBody 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 167type response struct { 168 req *http.Request 169 header http.Header 170 code int 171 wroteHeader bool 172 wroteCGIHeader bool 173 bufw *bufio.Writer 174} 175 176func (r *response) Flush() { 177 r.bufw.Flush() 178} 179 180func (r *response) Header() http.Header { 181 return r.header 182} 183 184func (r *response) Write(p []byte) (n int, err error) { 185 if !r.wroteHeader { 186 r.WriteHeader(http.StatusOK) 187 } 188 if !r.wroteCGIHeader { 189 r.writeCGIHeader(p) 190 } 191 return r.bufw.Write(p) 192} 193 194func (r *response) WriteHeader(code int) { 195 if r.wroteHeader { 196 // Note: explicitly using Stderr, as Stdout is our HTTP output. 197 fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL) 198 return 199 } 200 r.wroteHeader = true 201 r.code = code 202} 203 204// writeCGIHeader finalizes the header sent to the client and writes it to the output. 205// p is not written by writeHeader, but is the first chunk of the body 206// that will be written. It is sniffed for a Content-Type if none is 207// set explicitly. 208func (r *response) writeCGIHeader(p []byte) { 209 if r.wroteCGIHeader { 210 return 211 } 212 r.wroteCGIHeader = true 213 fmt.Fprintf(r.bufw, "Status: %d %s\r\n", r.code, http.StatusText(r.code)) 214 if _, hasType := r.header["Content-Type"]; !hasType { 215 r.header.Set("Content-Type", http.DetectContentType(p)) 216 } 217 r.header.Write(r.bufw) 218 r.bufw.WriteString("\r\n") 219 r.bufw.Flush() 220} 221