You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

138 lines
4.0 KiB
Go

package core
import (
"os/exec"
"strings"
"syscall"
"github.com/grufwub/go-errors"
)
// setupInitialCGIEnv takes a safe PATH, uses other server variables and returns a slice of constant CGI environment variables
func setupInitialCGIEnv(safePath string) []string {
// Append the default environment
env := make([]string, 12)
env = append(env, "GATEWAY_INTERFACE=CGI/1.1")
env = append(env, "SERVER_SOFTWARE=Gophi "+Version)
env = append(env, "SERVER_PROTOCOL="+protocol)
env = append(env, "SERVER_NAME="+Hostname)
env = append(env, "SERVER_PORT="+Port)
env = append(env, "DOCUMENT_ROOT="+Root)
env = append(env, "PATH="+safePath)
// Return string slice of environment variables
return env
}
// generateCGIEnv takes a Client, and Request object, uses the global constant slice and generates a full set of CGI environment variables
func generateCGIEnv(client *Client, request *Request, pathInfo string) []string {
// Append
env := append(cgiEnv, "REMOTE_ADDR="+client.IP())
env = append(env, "QUERY_STRING="+request.Query())
env = append(env, "SCRIPT_NAME="+request.Path().Relative())
env = append(env, "SCRIPT_FILENAME="+request.Path().Absolute())
env = append(env, "REQUEST_URI="+request.Path().Selector())
env = append(env, "PATH_INFO="+pathInfo)
env = appendCgiEnv(client, request, env)
return env
}
// TryExecuteCGIScript attempts to execute supplied CGI script, finding shortest valid path and setting PATH_INFO accordingly
func TryExecuteCGIScript(client *Client, request *Request) error {
// Get relative path with CGI dir stripped
partial := request.Path().Relative()[len(cgiPath.Relative()):]
if len(partial) == 0 {
return ErrRestrictedPath.Extendf("%s is CGI dir", request.Path().Selector())
}
partial = partial[1:]
// Start with the CGI dir path
nextPath := cgiPath
// Get index of next '/' and resize view of partial
next := strings.IndexByte(partial, '/')
// If there's no slashes, we try straight-off with
// original request
if next == -1 {
stat, err := StatFile(request.Path())
switch {
case err != nil:
return err.(errors.Error).Extend("CGI error")
case !stat.Mode().IsRegular():
return ErrFileType.Extendf("%s CGI error", request.Path().Absolute())
}
return ExecuteCGIScript(client, request, "")
}
// Loop to find first valid path
for next != -1 {
// Join this path segment to current cgiPath
nextPath = nextPath.JoinPathUnsafe(partial[:next])
// Update view of partial
partial = partial[next+1:]
// Check file exists (and not a dir!)
stat, err := StatFile(nextPath)
if err == nil && stat.Mode().IsRegular() {
// Set the updated Path
request.path = nextPath
// Try execute!
return ExecuteCGIScript(client, request, partial)
}
// Get next '/' position
next = strings.IndexByte(partial, '/')
}
// No CGI script was found, return not-found error
return ErrFileStat.Extendf("%s CGI error", request.Path().Absolute())
}
// ExecuteCGIScript executes a CGI script, responding with stdout to client
func ExecuteCGIScript(client *Client, request *Request, pathInfo string) error {
// Create cmd object
cmd := exec.Command(request.Path().Absolute())
// Setup cmd environment
cmd.Env = generateCGIEnv(client, request, pathInfo)
cmd.Dir = request.Path().Root()
// Setup cmd out writer
cmd.Stdout = client.Conn().Writer()
// Not interested in err
cmd.Stderr = nil
// Start executing
err := cmd.Start()
if err != nil {
return errors.With(err).WrapWithin(ErrCGIStart).Extend(request.Path().Absolute())
}
// NOTE: we don't set a max CGI script run time anymore,
// we let the connection write deadline catch any longrunning
// scripts not sending content.
// Wait for command to finish
err = cmd.Wait()
if err != nil {
// Attempt to get exit code from error
var exitCode int
switch err.(type) {
case *exec.ExitError:
exitCode = err.(*exec.ExitError).Sys().(syscall.WaitStatus).ExitStatus()
default:
exitCode = 1
}
// Log and return
return ErrCGIExitCode.Extendf("%s: %d", request.Path().Absolute(), exitCode)
}
// Exit fine!
return nil
}