Source file src/pkg/net/http/cgi/host.go
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 "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
47 type Handler struct {
48 Path string
49 Root string
50
51
52
53
54
55 Dir string
56
57 Env []string
58 InheritEnv []string
59 Logger *log.Logger
60 Args []string
61 Stderr io.Writer
62
63
64
65
66
67
68
69
70
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
82
83
84
85
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]
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
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 if k == "PROXY" {
157
158 continue
159 }
160 joinStr := ", "
161 if k == "COOKIE" {
162 joinStr = "; "
163 }
164 env = append(env, "HTTP_"+k+"="+strings.Join(v, joinStr))
165 }
166
167 if req.ContentLength > 0 {
168 env = append(env, fmt.Sprintf("CONTENT_LENGTH=%d", req.ContentLength))
169 }
170 if ctype := req.Header.Get("Content-Type"); ctype != "" {
171 env = append(env, "CONTENT_TYPE="+ctype)
172 }
173
174 envPath := os.Getenv("PATH")
175 if envPath == "" {
176 envPath = "/bin:/usr/bin:/usr/ucb:/usr/bsd:/usr/local/bin"
177 }
178 env = append(env, "PATH="+envPath)
179
180 for _, e := range h.InheritEnv {
181 if v := os.Getenv(e); v != "" {
182 env = append(env, e+"="+v)
183 }
184 }
185
186 for _, e := range osDefaultInheritEnv[runtime.GOOS] {
187 if v := os.Getenv(e); v != "" {
188 env = append(env, e+"="+v)
189 }
190 }
191
192 if h.Env != nil {
193 env = append(env, h.Env...)
194 }
195
196 env = removeLeadingDuplicates(env)
197
198 var cwd, path string
199 if h.Dir != "" {
200 path = h.Path
201 cwd = h.Dir
202 } else {
203 cwd, path = filepath.Split(h.Path)
204 }
205 if cwd == "" {
206 cwd = "."
207 }
208
209 internalError := func(err error) {
210 rw.WriteHeader(http.StatusInternalServerError)
211 h.printf("CGI error: %v", err)
212 }
213
214 cmd := &exec.Cmd{
215 Path: path,
216 Args: append([]string{h.Path}, h.Args...),
217 Dir: cwd,
218 Env: env,
219 Stderr: h.stderr(),
220 }
221 if req.ContentLength != 0 {
222 cmd.Stdin = req.Body
223 }
224 stdoutRead, err := cmd.StdoutPipe()
225 if err != nil {
226 internalError(err)
227 return
228 }
229
230 err = cmd.Start()
231 if err != nil {
232 internalError(err)
233 return
234 }
235 if hook := testHookStartProcess; hook != nil {
236 hook(cmd.Process)
237 }
238 defer cmd.Wait()
239 defer stdoutRead.Close()
240
241 linebody := bufio.NewReaderSize(stdoutRead, 1024)
242 headers := make(http.Header)
243 statusCode := 0
244 headerLines := 0
245 sawBlankLine := false
246 for {
247 line, isPrefix, err := linebody.ReadLine()
248 if isPrefix {
249 rw.WriteHeader(http.StatusInternalServerError)
250 h.printf("cgi: long header line from subprocess.")
251 return
252 }
253 if err == io.EOF {
254 break
255 }
256 if err != nil {
257 rw.WriteHeader(http.StatusInternalServerError)
258 h.printf("cgi: error reading headers: %v", err)
259 return
260 }
261 if len(line) == 0 {
262 sawBlankLine = true
263 break
264 }
265 headerLines++
266 parts := strings.SplitN(string(line), ":", 2)
267 if len(parts) < 2 {
268 h.printf("cgi: bogus header line: %s", string(line))
269 continue
270 }
271 header, val := parts[0], parts[1]
272 header = strings.TrimSpace(header)
273 val = strings.TrimSpace(val)
274 switch {
275 case header == "Status":
276 if len(val) < 3 {
277 h.printf("cgi: bogus status (short): %q", val)
278 return
279 }
280 code, err := strconv.Atoi(val[0:3])
281 if err != nil {
282 h.printf("cgi: bogus status: %q", val)
283 h.printf("cgi: line was %q", line)
284 return
285 }
286 statusCode = code
287 default:
288 headers.Add(header, val)
289 }
290 }
291 if headerLines == 0 || !sawBlankLine {
292 rw.WriteHeader(http.StatusInternalServerError)
293 h.printf("cgi: no headers")
294 return
295 }
296
297 if loc := headers.Get("Location"); loc != "" {
298 if strings.HasPrefix(loc, "/") && h.PathLocationHandler != nil {
299 h.handleInternalRedirect(rw, req, loc)
300 return
301 }
302 if statusCode == 0 {
303 statusCode = http.StatusFound
304 }
305 }
306
307 if statusCode == 0 && headers.Get("Content-Type") == "" {
308 rw.WriteHeader(http.StatusInternalServerError)
309 h.printf("cgi: missing required Content-Type in headers")
310 return
311 }
312
313 if statusCode == 0 {
314 statusCode = http.StatusOK
315 }
316
317
318
319
320 for k, vv := range headers {
321 for _, v := range vv {
322 rw.Header().Add(k, v)
323 }
324 }
325
326 rw.WriteHeader(statusCode)
327
328 _, err = io.Copy(rw, linebody)
329 if err != nil {
330 h.printf("cgi: copy error: %v", err)
331
332
333
334
335
336
337 cmd.Process.Kill()
338 }
339 }
340
341 func (h *Handler) printf(format string, v ...interface{}) {
342 if h.Logger != nil {
343 h.Logger.Printf(format, v...)
344 } else {
345 log.Printf(format, v...)
346 }
347 }
348
349 func (h *Handler) handleInternalRedirect(rw http.ResponseWriter, req *http.Request, path string) {
350 url, err := req.URL.Parse(path)
351 if err != nil {
352 rw.WriteHeader(http.StatusInternalServerError)
353 h.printf("cgi: error resolving local URI path %q: %v", path, err)
354 return
355 }
356
357
358
359
360
361
362
363
364
365 newReq := &http.Request{
366 Method: "GET",
367 URL: url,
368 Proto: "HTTP/1.1",
369 ProtoMajor: 1,
370 ProtoMinor: 1,
371 Header: make(http.Header),
372 Host: url.Host,
373 RemoteAddr: req.RemoteAddr,
374 TLS: req.TLS,
375 }
376 h.PathLocationHandler.ServeHTTP(rw, newReq)
377 }
378
379 func upperCaseAndUnderscore(r rune) rune {
380 switch {
381 case r >= 'a' && r <= 'z':
382 return r - ('a' - 'A')
383 case r == '-':
384 return '_'
385 case r == '=':
386
387
388
389 return '_'
390 }
391
392 return r
393 }
394
395 var testHookStartProcess func(*os.Process)
396
View as plain text