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.Replace(k[5:], "_", "-", -1), v)
90 }
91
92 // TODO: cookies. parsing them isn't exported, though.
93
94 if r.Host != "" {
95 // Hostname is provided, so we can reasonably construct a URL,
96 // even if we have to assume 'http' for the scheme.
97 rawurl := "http://" + r.Host + params["REQUEST_URI"]
98 url, err := url.Parse(rawurl)
99 if err != nil {
100 return nil, errors.New("cgi: failed to parse host and REQUEST_URI into a URL: " + rawurl)
101 }
102 r.URL = url
103 }
104 // Fallback logic if we don't have a Host header or the URL
105 // failed to parse
106 if r.URL == nil {
107 uriStr := params["REQUEST_URI"]
108 url, err := url.Parse(uriStr)
109 if err != nil {
110 return nil, errors.New("cgi: failed to parse REQUEST_URI into a URL: " + uriStr)
111 }
112 r.URL = url
113 }
114
115 // There's apparently a de-facto standard for this.
116 // http://docstore.mik.ua/orelly/linux/cgi/ch03_02.htm#ch03-35636
117 if s := params["HTTPS"]; s == "on" || s == "ON" || s == "1" {
118 r.TLS = &tls.ConnectionState{HandshakeComplete: true}
119 }
120
121 // Request.RemoteAddr has its port set by Go's standard http
122 // server, so we do here too. We don't have one, though, so we
123 // use a dummy one.
124 r.RemoteAddr = net.JoinHostPort(params["REMOTE_ADDR"], "0")
125
126 return r, nil
127 }
128
129 // Serve executes the provided Handler on the currently active CGI
130 // request, if any. If there's no current CGI environment
131 // an error is returned. The provided handler may be nil to use
132 // http.DefaultServeMux.
133 func Serve(handler http.Handler) error {
134 req, err := Request()
135 if err != nil {
136 return err
137 }
138 if handler == nil {
139 handler = http.DefaultServeMux
140 }
141 rw := &response{
142 req: req,
143 header: make(http.Header),
144 bufw: bufio.NewWriter(os.Stdout),
145 }
146 handler.ServeHTTP(rw, req)
147 rw.Write(nil) // make sure a response is sent
148 if err = rw.bufw.Flush(); err != nil {
149 return err
150 }
151 return nil
152 }
153
154 type response struct {
155 req *http.Request
156 header http.Header
157 bufw *bufio.Writer
158 headerSent bool
159 }
160
161 func (r *response) Flush() {
162 r.bufw.Flush()
163 }
164
165 func (r *response) Header() http.Header {
166 return r.header
167 }
168
169 func (r *response) Write(p []byte) (n int, err error) {
170 if !r.headerSent {
171 r.WriteHeader(http.StatusOK)
172 }
173 return r.bufw.Write(p)
174 }
175
176 func (r *response) WriteHeader(code int) {
177 if r.headerSent {
178 // Note: explicitly using Stderr, as Stdout is our HTTP output.
179 fmt.Fprintf(os.Stderr, "CGI attempted to write header twice on request for %s", r.req.URL)
180 return
181 }
182 r.headerSent = true
183 fmt.Fprintf(r.bufw, "Status: %d %s\r\n", code, http.StatusText(code))
184
185 // Set a default Content-Type
186 if _, hasType := r.header["Content-Type"]; !hasType {
187 r.header.Add("Content-Type", "text/html; charset=utf-8")
188 }
189
190 r.header.Write(r.bufw)
191 r.bufw.WriteString("\r\n")
192 r.bufw.Flush()
193 }