1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package cgi
16
17 import (
18 "bufio"
19 "fmt"
20 "io"
21 "log"
22 "net"
23 "net/http"
24 "net/textproto"
25 "os"
26 "os/exec"
27 "path/filepath"
28 "regexp"
29 "runtime"
30 "strconv"
31 "strings"
32
33 "golang.org/x/net/http/httpguts"
34 )
35
36 var trailingPort = regexp.MustCompile(`:([0-9]+)$`)
37
38 var osDefaultInheritEnv = func() []string {
39 switch runtime.GOOS {
40 case "darwin", "ios":
41 return []string{"DYLD_LIBRARY_PATH"}
42 case "linux", "freebsd", "netbsd", "openbsd":
43 return []string{"LD_LIBRARY_PATH"}
44 case "hpux":
45 return []string{"LD_LIBRARY_PATH", "SHLIB_PATH"}
46 case "irix":
47 return []string{"LD_LIBRARY_PATH", "LD_LIBRARYN32_PATH", "LD_LIBRARY64_PATH"}
48 case "illumos", "solaris":
49 return []string{"LD_LIBRARY_PATH", "LD_LIBRARY_PATH_32", "LD_LIBRARY_PATH_64"}
50 case "windows":
51 return []string{"SystemRoot", "COMSPEC", "PATHEXT", "WINDIR"}
52 }
53 return nil
54 }()
55
56
57 type Handler struct {
58 Path string
59 Root string
60
61
62
63
64
65 Dir string
66
67 Env []string
68 InheritEnv []string
69 Logger *log.Logger
70 Args []string
71 Stderr io.Writer
72
73
74
75
76
77
78
79
80
81 PathLocationHandler http.Handler
82 }
83
84 func (h *Handler) stderr() io.Writer {
85 if h.Stderr != nil {
86 return h.Stderr
87 }
88 return os.Stderr
89 }
90
91
92
93
94
95
96
97 func removeLeadingDuplicates(env []string) (ret []string) {
98 for i, e := range env {
99 found := false
100 if eq := strings.IndexByte(e, '='); eq != -1 {
101 keq := e[:eq+1]
102 for _, e2 := range env[i+1:] {
103 if strings.HasPrefix(e2, keq) {
104 found = true
105 break
106 }
107 }
108 }
109 if !found {
110 ret = append(ret, e)
111 }
112 }
113 return
114 }
115
116 func (h *Handler) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
117 root := h.Root
118 if root == "" {
119 root = "/"
120 }
121
122 if len(req.TransferEncoding) > 0 && req.TransferEncoding[0] == "chunked" {
123 rw.WriteHeader(http.StatusBadRequest)
124 rw.Write([]byte("Chunked request bodies are not supported by CGI."))
125 return
126 }
127
128 pathInfo := req.URL.Path
129 if root != "/" && strings.HasPrefix(pathInfo, root) {
130 pathInfo = pathInfo[len(root):]
131 }
132
133 port := "80"
134 if matches := trailingPort.FindStringSubmatch(req.Host); len(matches) != 0 {
135 port = matches[1]
136 }
137
138 env := []string{
139 "SERVER_SOFTWARE=go",
140 "SERVER_NAME=" + req.Host,
141 "SERVER_PROTOCOL=HTTP/1.1",
142 "HTTP_HOST=" + req.Host,
143 "GATEWAY_INTERFACE=CGI/1.1",
144 "REQUEST_METHOD=" + req.Method,
145 "QUERY_STRING=" + req.URL.RawQuery,
146 "REQUEST_URI=" + req.URL.RequestURI(),
147 "PATH_INFO=" + pathInfo,
148 "SCRIPT_NAME=" + root,
149 "SCRIPT_FILENAME=" + h.Path,
150 "SERVER_PORT=" + port,
151 }
152
153 if remoteIP, remotePort, err := net.SplitHostPort(req.RemoteAddr); err == nil {
154 env = append(env, "REMOTE_ADDR="+remoteIP, "REMOTE_HOST="+remoteIP, "REMOTE_PORT="+remotePort)
155 } else {
156
157 env = append(env, "REMOTE_ADDR="+req.RemoteAddr, "REMOTE_HOST="+req.RemoteAddr)
158 }
159
160 if req.TLS != nil {
161 env = append(env, "HTTPS=on")
162 }
163
164 for k, v := range req.Header {
165 k = strings.Map(upperCaseAndUnderscore, k)
166 if k == "PROXY" {
167
168 continue
169 }
170 joinStr := ", "
171 if k == "COOKIE" {
172 joinStr = "; "
173 }
174 env = append(env, "HTTP_"+k+"="+strings.Join(v, joinStr))
175 }
176
177 if req.ContentLength > 0 {
178 env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", req.ContentLength))
179 }
180 if ctype := req.Header.Get("Content-Type"); ctype != "" {
181 env = append(env, "CONTENT_TYPE="+ctype)
182 }
183
184 envPath := os.Getenv("PATH")
185 if envPath == "" {
186 envPath = "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin"
187 }
188 env = append(env, "PATH="+envPath)
189
190 for _, e := range h.InheritEnv {
191 if v := os.Getenv(e); v != "" {
192 env = append(env, e+"="+v)
193 }
194 }
195
196 for _, e := range osDefaultInheritEnv {
197 if v := os.Getenv(e); v != "" {
198 env = append(env, e+"="+v)
199 }
200 }
201
202 if h.Env != nil {
203 env = append(env, h.Env...)
204 }
205
206 env = removeLeadingDuplicates(env)
207
208 var cwd, path string
209 if h.Dir != "" {
210 path = h.Path
211 cwd = h.Dir
212 } else {
213 cwd, path = filepath.Split(h.Path)
214 }
215 if cwd == "" {
216 cwd = "."
217 }
218
219 internalError := func(err error) {
220 rw.WriteHeader(http.StatusInternalServerError)
221 h.printf("CGI error: %v", err)
222 }
223
224 cmd := &exec.Cmd{
225 Path: path,
226 Args: append([]string{h.Path}, h.Args...),
227 Dir: cwd,
228 Env: env,
229 Stderr: h.stderr(),
230 }
231 if req.ContentLength != 0 {
232 cmd.Stdin = req.Body
233 }
234 stdoutRead, err := cmd.StdoutPipe()
235 if err != nil {
236 internalError(err)
237 return
238 }
239
240 err = cmd.Start()
241 if err != nil {
242 internalError(err)
243 return
244 }
245 if hook := testHookStartProcess; hook != nil {
246 hook(cmd.Process)
247 }
248 defer cmd.Wait()
249 defer stdoutRead.Close()
250
251 linebody := bufio.NewReaderSize(stdoutRead, 1024)
252 headers := make(http.Header)
253 statusCode := 0
254 headerLines := 0
255 sawBlankLine := false
256 for {
257 line, isPrefix, err := linebody.ReadLine()
258 if isPrefix {
259 rw.WriteHeader(http.StatusInternalServerError)
260 h.printf("cgi: long header line from subprocess.")
261 return
262 }
263 if err == io.EOF {
264 break
265 }
266 if err != nil {
267 rw.WriteHeader(http.StatusInternalServerError)
268 h.printf("cgi: error reading headers: %v", err)
269 return
270 }
271 if len(line) == 0 {
272 sawBlankLine = true
273 break
274 }
275 headerLines++
276 parts := strings.SplitN(string(line), ":", 2)
277 if len(parts) < 2 {
278 h.printf("cgi: bogus header line: %s", string(line))
279 continue
280 }
281 header, val := parts[0], parts[1]
282 if !httpguts.ValidHeaderFieldName(header) {
283 h.printf("cgi: invalid header name: %q", header)
284 continue
285 }
286 val = textproto.TrimString(val)
287 switch {
288 case header == "Status":
289 if len(val) < 3 {
290 h.printf("cgi: bogus status (short): %q", val)
291 return
292 }
293 code, err := strconv.Atoi(val[0:3])
294 if err != nil {
295 h.printf("cgi: bogus status: %q", val)
296 h.printf("cgi: line was %q", line)
297 return
298 }
299 statusCode = code
300 default:
301 headers.Add(header, val)
302 }
303 }
304 if headerLines == 0 || !sawBlankLine {
305 rw.WriteHeader(http.StatusInternalServerError)
306 h.printf("cgi: no headers")
307 return
308 }
309
310 if loc := headers.Get("Location"); loc != "" {
311 if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil {
312 h.handleInternalRedirect(rw, req, loc)
313 return
314 }
315 if statusCode == 0 {
316 statusCode = http.StatusFound
317 }
318 }
319
320 if statusCode == 0 && headers.Get("Content-Type") == "" {
321 rw.WriteHeader(http.StatusInternalServerError)
322 h.printf("cgi: missing required Content-Type in headers")
323 return
324 }
325
326 if statusCode == 0 {
327 statusCode = http.StatusOK
328 }
329
330
331
332
333 for k, vv := range headers {
334 for _, v := range vv {
335 rw.Header().Add(k, v)
336 }
337 }
338
339 rw.WriteHeader(statusCode)
340
341 _, err = io.Copy(rw, linebody)
342 if err != nil {
343 h.printf("cgi: copy error: %v", err)
344
345
346
347
348
349
350 cmd.Process.Kill()
351 }
352 }
353
354 func (h *Handler) printf(format string, v ...interface{}) {
355 if h.Logger != nil {
356 h.Logger.Printf(format, v...)
357 } else {
358 log.Printf(format, v...)
359 }
360 }
361
362 func (h *Handler) handleInternalRedirect(rw http.ResponseWriter, req *http.Request, path string) {
363 url, err := req.URL.Parse(path)
364 if err != nil {
365 rw.WriteHeader(http.StatusInternalServerError)
366 h.printf("cgi: error resolving local URI path %q: %v", path, err)
367 return
368 }
369
370
371
372
373
374
375
376
377
378 newReq := &http.Request{
379 Method: "GET",
380 URL: url,
381 Proto: "HTTP/1.1",
382 ProtoMajor: 1,
383 ProtoMinor: 1,
384 Header: make(http.Header),
385 Host: url.Host,
386 RemoteAddr: req.RemoteAddr,
387 TLS: req.TLS,
388 }
389 h.PathLocationHandler.ServeHTTP(rw, newReq)
390 }
391
392 func upperCaseAndUnderscore(r rune) rune {
393 switch {
394 case r >= 'a' && r <= 'z':
395 return r - ('a' - 'A')
396 case r == '-':
397 return '_'
398 case r == '=':
399
400
401
402 return '_'
403 }
404
405 return r
406 }
407
408 var testHookStartProcess func(*os.Process)
409
View as plain text