...

Source file src/net/http/cgi/host.go

Documentation: net/http/cgi

		 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  	"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  // Handler runs an executable in a subprocess with a CGI environment.
		57  type Handler struct {
		58  	Path string // path to the CGI executable
		59  	Root string // root URI prefix of handler or empty for "/"
		60  
		61  	// Dir specifies the CGI executable's working directory.
		62  	// If Dir is empty, the base directory of Path is used.
		63  	// If Path has no base directory, the current working
		64  	// directory is used.
		65  	Dir string
		66  
		67  	Env				[]string		// extra environment variables to set, if any, as "key=value"
		68  	InheritEnv []string		// environment variables to inherit from host, as "key"
		69  	Logger		 *log.Logger // optional log for errors or nil to use log.Print
		70  	Args			 []string		// optional arguments to pass to child process
		71  	Stderr		 io.Writer	 // optional stderr for the child process; nil means os.Stderr
		72  
		73  	// PathLocationHandler specifies the root http Handler that
		74  	// should handle internal redirects when the CGI process
		75  	// returns a Location header value starting with a "/", as
		76  	// specified in RFC 3875 § 6.3.2. This will likely be
		77  	// http.DefaultServeMux.
		78  	//
		79  	// If nil, a CGI response with a local URI path is instead sent
		80  	// back to the client and not redirected internally.
		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  // removeLeadingDuplicates remove leading duplicate in environments.
		92  // It's possible to override environment like following.
		93  //		cgi.Handler{
		94  //			...
		95  //			Env: []string{"SCRIPT_FILENAME=foo.php"},
		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] // "key="
	 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  		// could not parse ip:port, let's use whole RemoteAddr and leave REMOTE_PORT undefined
	 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  			// See Issue 16405
	 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  	// Copy headers to rw's headers, after we've decided not to
	 331  	// go into handleInternalRedirect, which won't want its rw
	 332  	// headers to have been touched.
	 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  		// And kill the child CGI process so we don't hang on
	 345  		// the deferred cmd.Wait above if the error was just
	 346  		// the client (rw) going away. If it was a read error
	 347  		// (because the child died itself), then the extra
	 348  		// kill of an already-dead process is harmless (the PID
	 349  		// won't be reused until the Wait above).
	 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  	// TODO: RFC 3875 isn't clear if only GET is supported, but it
	 370  	// suggests so: "Note that any message-body attached to the
	 371  	// request (such as for a POST request) may not be available
	 372  	// to the resource that is the target of the redirect."	We
	 373  	// should do some tests against Apache to see how it handles
	 374  	// POST, HEAD, etc. Does the internal redirect get the same
	 375  	// method or just GET? What about incoming headers?
	 376  	// (e.g. Cookies) Which headers, if any, are copied into the
	 377  	// second request?
	 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  		// Maybe not part of the CGI 'spec' but would mess up
	 400  		// the environment in any case, as Go represents the
	 401  		// environment as a slice of "key=value" strings.
	 402  		return '_'
	 403  	}
	 404  	// TODO: other transformations in spec or practice?
	 405  	return r
	 406  }
	 407  
	 408  var testHookStartProcess func(*os.Process) // nil except for some tests
	 409  

View as plain text