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