CGI improvements
continuous-integration/drone/push Build is passing
Details
continuous-integration/drone/push Build is passing
Details
This commit is contained in:
parent
96f3a7607f
commit
474a28663f
|
@ -16,18 +16,24 @@ import (
|
|||
"tildegit.org/tjp/gus/gemini"
|
||||
)
|
||||
|
||||
func CGIHandler(pathPrefix, rootDir string) gemini.Handler {
|
||||
rootDir = strings.TrimRight(rootDir, "/")
|
||||
// CGIDirectory runs any executable files relative to a root directory on the file system.
|
||||
//
|
||||
// It will also find and run any executables _part way_ through the path, so for example
|
||||
// a request for /foo/bar/baz can also run an executable found at /foo or /foo/bar. In
|
||||
// such a case the PATH_INFO environment variable will include the remaining portion of
|
||||
// the URI path.
|
||||
func CGIDirectory(pathRoot, fsRoot string) gemini.Handler {
|
||||
fsRoot = strings.TrimRight(fsRoot, "/")
|
||||
|
||||
return func(ctx context.Context, req *gemini.Request) *gemini.Response {
|
||||
if !strings.HasPrefix(req.Path, pathPrefix) {
|
||||
if !strings.HasPrefix(req.Path, pathRoot) {
|
||||
return gemini.NotFound("Resource does not exist.")
|
||||
}
|
||||
|
||||
path := req.Path[len(pathPrefix):]
|
||||
path := req.Path[len(pathRoot):]
|
||||
segments := strings.Split(strings.TrimLeft(path, "/"), "/")
|
||||
for i := range append(segments, "") {
|
||||
path := strings.Join(append([]string{rootDir}, segments[:i]...), "/")
|
||||
path := strings.Join(append([]string{fsRoot}, segments[:i]...), "/")
|
||||
path = strings.TrimRight(path, "/")
|
||||
isDir, isExecutable, err := executableFile(path)
|
||||
if err != nil {
|
||||
|
@ -35,11 +41,11 @@ func CGIHandler(pathPrefix, rootDir string) gemini.Handler {
|
|||
}
|
||||
|
||||
if isExecutable {
|
||||
pathInfo := ""
|
||||
pathInfo := "/"
|
||||
if len(segments) > i+1 {
|
||||
pathInfo = strings.Join(segments[i+1:], "/")
|
||||
pathInfo = strings.Join(segments[i:], "/")
|
||||
}
|
||||
return runCGI(ctx, req.Server, req, path, pathInfo)
|
||||
return RunCGI(ctx, req, path, pathInfo)
|
||||
}
|
||||
|
||||
if !isDir {
|
||||
|
@ -88,38 +94,46 @@ func isNotExistError(err error) bool {
|
|||
return false
|
||||
}
|
||||
|
||||
func runCGI(
|
||||
// RunCGI runs a specific program as a CGI script.
|
||||
func RunCGI(
|
||||
ctx context.Context,
|
||||
server *gemini.Server,
|
||||
req *gemini.Request,
|
||||
filePath string,
|
||||
executable string,
|
||||
pathInfo string,
|
||||
) *gemini.Response {
|
||||
pathSegments := strings.Split(filePath, "/")
|
||||
pathSegments := strings.Split(executable, "/")
|
||||
|
||||
dirPath := "."
|
||||
if len(pathSegments) > 1 {
|
||||
dirPath = strings.Join(pathSegments[:len(pathSegments)-1], "/")
|
||||
}
|
||||
filePath = "./" + pathSegments[len(pathSegments)-1]
|
||||
basename := pathSegments[len(pathSegments)-1]
|
||||
|
||||
cmd := exec.CommandContext(ctx, filePath)
|
||||
cmd.Env = prepareCGIEnv(ctx, server, req, filePath, pathInfo)
|
||||
scriptName := req.Path[:len(req.Path)-len(pathInfo)]
|
||||
if strings.HasSuffix(scriptName, "/") {
|
||||
scriptName = scriptName[:len(scriptName)-1]
|
||||
}
|
||||
|
||||
cmd := exec.CommandContext(ctx, "./" + basename)
|
||||
cmd.Env = prepareCGIEnv(ctx, req, scriptName, pathInfo)
|
||||
cmd.Dir = dirPath
|
||||
|
||||
responseBuffer := &bytes.Buffer{}
|
||||
cmd.Stdout = responseBuffer
|
||||
|
||||
fmt.Printf("running %s in %s\n", basename, dirPath)
|
||||
if err := cmd.Run(); err != nil {
|
||||
var exErr *exec.ExitError
|
||||
if errors.As(err, &exErr) {
|
||||
return gemini.CGIError(fmt.Sprintf("CGI returned with exit code %d", exErr.ExitCode()))
|
||||
errMsg := fmt.Sprintf("CGI returned exit code %d", exErr.ExitCode())
|
||||
return gemini.CGIError(errMsg)
|
||||
}
|
||||
return gemini.Failure(err)
|
||||
}
|
||||
|
||||
response, err := gemini.ParseResponse(responseBuffer)
|
||||
if err != nil {
|
||||
fmt.Printf("response: %q\n", responseBuffer)
|
||||
return gemini.Failure(err)
|
||||
}
|
||||
return response
|
||||
|
@ -127,7 +141,6 @@ func runCGI(
|
|||
|
||||
func prepareCGIEnv(
|
||||
ctx context.Context,
|
||||
server *gemini.Server,
|
||||
req *gemini.Request,
|
||||
scriptName string,
|
||||
pathInfo string,
|
||||
|
@ -136,7 +149,6 @@ func prepareCGIEnv(
|
|||
if len(req.TLSState.PeerCertificates) > 0 {
|
||||
authType = "Certificate"
|
||||
}
|
||||
|
||||
environ := []string{
|
||||
"AUTH_TYPE=" + authType,
|
||||
"CONTENT_LENGTH=",
|
||||
|
@ -155,8 +167,8 @@ func prepareCGIEnv(
|
|||
"REMOTE_HOST=",
|
||||
"REMOTE_IDENT=",
|
||||
"SCRIPT_NAME="+scriptName,
|
||||
"SERVER_NAME="+server.Hostname(),
|
||||
"SERVER_PORT="+server.Port(),
|
||||
"SERVER_NAME="+req.Server.Hostname(),
|
||||
"SERVER_PORT="+req.Server.Port(),
|
||||
"SERVER_PROTOCOL=GEMINI",
|
||||
"SERVER_SOFTWARE=GUS",
|
||||
)
|
||||
|
@ -166,6 +178,10 @@ func prepareCGIEnv(
|
|||
environ = append(
|
||||
environ,
|
||||
"TLS_CLIENT_HASH="+fingerprint(cert.Raw),
|
||||
"TLS_CLIENT_ISSUER="+cert.Issuer.String(),
|
||||
"TLS_CLIENT_ISSUER_CN="+cert.Issuer.CommonName,
|
||||
"TLS_CLIENT_SUBJECT="+cert.Subject.String(),
|
||||
"TLS_CLIENT_SUBJECT_CN="+cert.Subject.CommonName,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -2,8 +2,7 @@
|
|||
|
||||
set -euo pipefail
|
||||
|
||||
if [ -z "$QUERY_STRING" ];
|
||||
then
|
||||
if [[ -z "$QUERY_STRING" ]]; then
|
||||
printf "10 Enter a phrase.\r\n"
|
||||
exit 0
|
||||
fi
|
||||
|
|
|
@ -0,0 +1,8 @@
|
|||
#!/usr/bin/env sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
printf "20 text/gemini\r\n"
|
||||
echo "\`\`\`env(1) output"
|
||||
env
|
||||
echo "\`\`\`"
|
|
@ -21,7 +21,7 @@ func main() {
|
|||
}
|
||||
|
||||
// make use of a CGI request handler
|
||||
cgiHandler := cgi.CGIHandler("/cgi-bin", "cgi-bin")
|
||||
cgiHandler := cgi.CGIDirectory("/cgi-bin", "./cgi-bin")
|
||||
|
||||
// add stdout logging to the request handler
|
||||
handler := guslog.Requests(os.Stdout, nil)(cgiHandler)
|
||||
|
|
Reference in New Issue