summaryrefslogtreecommitdiff
path: root/contrib/cgi/cgi.go
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cgi/cgi.go')
-rw-r--r--contrib/cgi/cgi.go56
1 files changed, 36 insertions, 20 deletions
diff --git a/contrib/cgi/cgi.go b/contrib/cgi/cgi.go
index 2e20485..b5dfbdd 100644
--- a/contrib/cgi/cgi.go
+++ b/contrib/cgi/cgi.go
@@ -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,
)
}