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