]> Cypherpunks.ru repositories - gostls13.git/blob - src/net/http/cgi/host.go
net/http/cgi: allow CGI host to configure where child's stderr goes
[gostls13.git] / src / net / http / cgi / host.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 the host side of CGI (being the webserver
6 // parent process).
7
8 // Package cgi implements CGI (Common Gateway Interface) as specified
9 // in RFC 3875.
10 //
11 // Note that using CGI means starting a new process to handle each
12 // request, which is typically less efficient than using a
13 // long-running server. This package is intended primarily for
14 // compatibility with existing systems.
15 package cgi
16
17 import (
18         "bufio"
19         "fmt"
20         "io"
21         "log"
22         "net"
23         "net/http"
24         "os"
25         "os/exec"
26         "path/filepath"
27         "regexp"
28         "runtime"
29         "strconv"
30         "strings"
31 )
32
33 var trailingPort = regexp.MustCompile(`:([0-9]+)$`)
34
35 var osDefaultInheritEnv = map[string][]string{
36         "darwin":  {"DYLD_LIBRARY_PATH"},
37         "freebsd": {"LD_LIBRARY_PATH"},
38         "hpux":    {"LD_LIBRARY_PATH", "SHLIB_PATH"},
39         "irix":    {"LD_LIBRARY_PATH", "LD_LIBRARYN32_PATH", "LD_LIBRARY64_PATH"},
40         "linux":   {"LD_LIBRARY_PATH"},
41         "openbsd": {"LD_LIBRARY_PATH"},
42         "solaris": {"LD_LIBRARY_PATH", "LD_LIBRARY_PATH_32", "LD_LIBRARY_PATH_64"},
43         "windows": {"SystemRoot", "COMSPEC", "PATHEXT", "WINDIR"},
44 }
45
46 // Handler runs an executable in a subprocess with a CGI environment.
47 type Handler struct {
48         Path string // path to the CGI executable
49         Root string // root URI prefix of handler or empty for "/"
50
51         // Dir specifies the CGI executable's working directory.
52         // If Dir is empty, the base directory of Path is used.
53         // If Path has no base directory, the current working
54         // directory is used.
55         Dir string
56
57         Env        []string    // extra environment variables to set, if any, as "key=value"
58         InheritEnv []string    // environment variables to inherit from host, as "key"
59         Logger     *log.Logger // optional log for errors or nil to use log.Print
60         Args       []string    // optional arguments to pass to child process
61         Stderr     io.Writer   // optional stderr for the child process; nil means os.Stderr
62
63         // PathLocationHandler specifies the root http Handler that
64         // should handle internal redirects when the CGI process
65         // returns a Location header value starting with a "/", as
66         // specified in RFC 3875 ยง 6.3.2. This will likely be
67         // http.DefaultServeMux.
68         //
69         // If nil, a CGI response with a local URI path is instead sent
70         // back to the client and not redirected internally.
71         PathLocationHandler http.Handler
72 }
73
74 func (h *Handler) stderr() io.Writer {
75         if h.Stderr != nil {
76                 return h.Stderr
77         }
78         return os.Stderr
79 }
80
81 // removeLeadingDuplicates remove leading duplicate in environments.
82 // It's possible to override environment like following.
83 //    cgi.Handler{
84 //      ...
85 //      Env: []string{"SCRIPT_FILENAME=foo.php"},
86 //    }
87 func removeLeadingDuplicates(env []string) (ret []string) {
88         for i, e := range env {
89                 found := false
90                 if eq := strings.IndexByte(e, '='); eq != -1 {
91                         keq := e[:eq+1] // "key="
92                         for _, e2 := range env[i+1:] {
93                                 if strings.HasPrefix(e2, keq) {
94                                         found = true
95                                         break
96                                 }
97                         }
98                 }
99                 if !found {
100                         ret = append(ret, e)
101                 }
102         }
103         return
104 }
105
106 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
107         root := h.Root
108         if root == "" {
109                 root = "/"
110         }
111
112         if len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" {
113                 rw.WriteHeader(http.StatusBadRequest)
114                 rw.Write([]byte("Chunked request bodies are not supported by CGI."))
115                 return
116         }
117
118         pathInfo := req.URL.Path
119         if root != "/" && strings.HasPrefix(pathInfo, root) {
120                 pathInfo = pathInfo[len(root):]
121         }
122
123         port := "80"
124         if matches := trailingPort.FindStringSubmatch(req.Host); len(matches) != 0 {
125                 port = matches[1]
126         }
127
128         env := []string{
129                 "SERVER_SOFTWARE=go",
130                 "SERVER_NAME=" + req.Host,
131                 "SERVER_PROTOCOL=HTTP/1.1",
132                 "HTTP_HOST=" + req.Host,
133                 "GATEWAY_INTERFACE=CGI/1.1",
134                 "REQUEST_METHOD=" + req.Method,
135                 "QUERY_STRING=" + req.URL.RawQuery,
136                 "REQUEST_URI=" + req.URL.RequestURI(),
137                 "PATH_INFO=" + pathInfo,
138                 "SCRIPT_NAME=" + root,
139                 "SCRIPT_FILENAME=" + h.Path,
140                 "SERVER_PORT=" + port,
141         }
142
143         if remoteIP, remotePort, err := net.SplitHostPort(req.RemoteAddr); err == nil {
144                 env = append(env, "REMOTE_ADDR="+remoteIP, "REMOTE_HOST="+remoteIP, "REMOTE_PORT="+remotePort)
145         } else {
146                 // could not parse ip:port, let's use whole RemoteAddr and leave REMOTE_PORT undefined
147                 env = append(env, "REMOTE_ADDR="+req.RemoteAddr, "REMOTE_HOST="+req.RemoteAddr)
148         }
149
150         if req.TLS != nil {
151                 env = append(env, "HTTPS=on")
152         }
153
154         for k, v := range req.Header {
155                 k = strings.Map(upperCaseAndUnderscore, k)
156                 joinStr := ", "
157                 if k == "COOKIE" {
158                         joinStr = "; "
159                 }
160                 env = append(env, "HTTP_"+k+"="+strings.Join(v, joinStr))
161         }
162
163         if req.ContentLength > 0 {
164                 env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", req.ContentLength))
165         }
166         if ctype := req.Header.Get("Content-Type"); ctype != "" {
167                 env = append(env, "CONTENT_TYPE="+ctype)
168         }
169
170         envPath := os.Getenv("PATH")
171         if envPath == "" {
172                 envPath = "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin"
173         }
174         env = append(env, "PATH="+envPath)
175
176         for _, e := range h.InheritEnv {
177                 if v := os.Getenv(e); v != "" {
178                         env = append(env, e+"="+v)
179                 }
180         }
181
182         for _, e := range osDefaultInheritEnv[runtime.GOOS] {
183                 if v := os.Getenv(e); v != "" {
184                         env = append(env, e+"="+v)
185                 }
186         }
187
188         if h.Env != nil {
189                 env = append(env, h.Env...)
190         }
191
192         env = removeLeadingDuplicates(env)
193
194         var cwd, path string
195         if h.Dir != "" {
196                 path = h.Path
197                 cwd = h.Dir
198         } else {
199                 cwd, path = filepath.Split(h.Path)
200         }
201         if cwd == "" {
202                 cwd = "."
203         }
204
205         internalError := func(err error) {
206                 rw.WriteHeader(http.StatusInternalServerError)
207                 h.printf("CGI error: %v", err)
208         }
209
210         cmd := &exec.Cmd{
211                 Path:   path,
212                 Args:   append([]string{h.Path}, h.Args...),
213                 Dir:    cwd,
214                 Env:    env,
215                 Stderr: h.stderr(),
216         }
217         if req.ContentLength != 0 {
218                 cmd.Stdin = req.Body
219         }
220         stdoutRead, err := cmd.StdoutPipe()
221         if err != nil {
222                 internalError(err)
223                 return
224         }
225
226         err = cmd.Start()
227         if err != nil {
228                 internalError(err)
229                 return
230         }
231         if hook := testHookStartProcess; hook != nil {
232                 hook(cmd.Process)
233         }
234         defer cmd.Wait()
235         defer stdoutRead.Close()
236
237         linebody := bufio.NewReaderSize(stdoutRead, 1024)
238         headers := make(http.Header)
239         statusCode := 0
240         headerLines := 0
241         sawBlankLine := false
242         for {
243                 line, isPrefix, err := linebody.ReadLine()
244                 if isPrefix {
245                         rw.WriteHeader(http.StatusInternalServerError)
246                         h.printf("cgi: long header line from subprocess.")
247                         return
248                 }
249                 if err == io.EOF {
250                         break
251                 }
252                 if err != nil {
253                         rw.WriteHeader(http.StatusInternalServerError)
254                         h.printf("cgi: error reading headers: %v", err)
255                         return
256                 }
257                 if len(line) == 0 {
258                         sawBlankLine = true
259                         break
260                 }
261                 headerLines++
262                 parts := strings.SplitN(string(line), ":", 2)
263                 if len(parts) < 2 {
264                         h.printf("cgi: bogus header line: %s", string(line))
265                         continue
266                 }
267                 header, val := parts[0], parts[1]
268                 header = strings.TrimSpace(header)
269                 val = strings.TrimSpace(val)
270                 switch {
271                 case header == "Status":
272                         if len(val) < 3 {
273                                 h.printf("cgi: bogus status (short): %q", val)
274                                 return
275                         }
276                         code, err := strconv.Atoi(val[0:3])
277                         if err != nil {
278                                 h.printf("cgi: bogus status: %q", val)
279                                 h.printf("cgi: line was %q", line)
280                                 return
281                         }
282                         statusCode = code
283                 default:
284                         headers.Add(header, val)
285                 }
286         }
287         if headerLines == 0 || !sawBlankLine {
288                 rw.WriteHeader(http.StatusInternalServerError)
289                 h.printf("cgi: no headers")
290                 return
291         }
292
293         if loc := headers.Get("Location"); loc != "" {
294                 if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil {
295                         h.handleInternalRedirect(rw, req, loc)
296                         return
297                 }
298                 if statusCode == 0 {
299                         statusCode = http.StatusFound
300                 }
301         }
302
303         if statusCode == 0 && headers.Get("Content-Type") == "" {
304                 rw.WriteHeader(http.StatusInternalServerError)
305                 h.printf("cgi: missing required Content-Type in headers")
306                 return
307         }
308
309         if statusCode == 0 {
310                 statusCode = http.StatusOK
311         }
312
313         // Copy headers to rw's headers, after we've decided not to
314         // go into handleInternalRedirect, which won't want its rw
315         // headers to have been touched.
316         for k, vv := range headers {
317                 for _, v := range vv {
318                         rw.Header().Add(k, v)
319                 }
320         }
321
322         rw.WriteHeader(statusCode)
323
324         _, err = io.Copy(rw, linebody)
325         if err != nil {
326                 h.printf("cgi: copy error: %v", err)
327                 // And kill the child CGI process so we don't hang on
328                 // the deferred cmd.Wait above if the error was just
329                 // the client (rw) going away. If it was a read error
330                 // (because the child died itself), then the extra
331                 // kill of an already-dead process is harmless (the PID
332                 // won't be reused until the Wait above).
333                 cmd.Process.Kill()
334         }
335 }
336
337 func (h *Handler) printf(format string, v ...interface{}) {
338         if h.Logger != nil {
339                 h.Logger.Printf(format, v...)
340         } else {
341                 log.Printf(format, v...)
342         }
343 }
344
345 func (h *Handler) handleInternalRedirect(rw http.ResponseWriter, req *http.Request, path string) {
346         url, err := req.URL.Parse(path)
347         if err != nil {
348                 rw.WriteHeader(http.StatusInternalServerError)
349                 h.printf("cgi: error resolving local URI path %q: %v", path, err)
350                 return
351         }
352         // TODO: RFC 3875 isn't clear if only GET is supported, but it
353         // suggests so: "Note that any message-body attached to the
354         // request (such as for a POST request) may not be available
355         // to the resource that is the target of the redirect."  We
356         // should do some tests against Apache to see how it handles
357         // POST, HEAD, etc. Does the internal redirect get the same
358         // method or just GET? What about incoming headers?
359         // (e.g. Cookies) Which headers, if any, are copied into the
360         // second request?
361         newReq := &http.Request{
362                 Method:     "GET",
363                 URL:        url,
364                 Proto:      "HTTP/1.1",
365                 ProtoMajor: 1,
366                 ProtoMinor: 1,
367                 Header:     make(http.Header),
368                 Host:       url.Host,
369                 RemoteAddr: req.RemoteAddr,
370                 TLS:        req.TLS,
371         }
372         h.PathLocationHandler.ServeHTTP(rw, newReq)
373 }
374
375 func upperCaseAndUnderscore(r rune) rune {
376         switch {
377         case r >= 'a' && r <= 'z':
378                 return r - ('a' - 'A')
379         case r == '-':
380                 return '_'
381         case r == '=':
382                 // Maybe not part of the CGI 'spec' but would mess up
383                 // the environment in any case, as Go represents the
384                 // environment as a slice of "key=value" strings.
385                 return '_'
386         }
387         // TODO: other transformations in spec or practice?
388         return r
389 }
390
391 var testHookStartProcess func(*os.Process) // nil except for some tests