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
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
|
|
}
|