summaryrefslogtreecommitdiff
path: root/contrib/cgi
diff options
context:
space:
mode:
Diffstat (limited to 'contrib/cgi')
-rw-r--r--contrib/cgi/cgi.go16
-rw-r--r--contrib/cgi/gopher.go139
2 files changed, 135 insertions, 20 deletions
diff --git a/contrib/cgi/cgi.go b/contrib/cgi/cgi.go
index bcdd5e1..749a284 100644
--- a/contrib/cgi/cgi.go
+++ b/contrib/cgi/cgi.go
@@ -25,11 +25,11 @@ import (
// a request for /foo/bar/baz can run an executable found at /foo or /foo/bar. In such
// a case the PATH_INFO would include the remaining portion of the URI path.
func ResolveCGI(requestPath, fsRoot string) (string, string, error) {
+ fsRoot = strings.TrimRight(fsRoot, "/")
segments := strings.Split(strings.TrimLeft(requestPath, "/"), "/")
for i := range append(segments, "") {
filepath := strings.Join(append([]string{fsRoot}, segments[:i]...), "/")
- filepath = strings.TrimRight(filepath, "/")
isDir, isExecutable, err := executableFile(filepath)
if err != nil {
return "", "", err
@@ -52,26 +52,20 @@ func ResolveCGI(requestPath, fsRoot string) (string, string, error) {
}
func executableFile(filepath string) (bool, bool, error) {
- file, err := os.Open(filepath)
+ info, err := os.Stat(filepath)
if isNotExistError(err) {
return false, false, nil
}
if err != nil {
return false, false, err
}
- defer file.Close()
-
- info, err := file.Stat()
- if err != nil {
- return false, false, err
- }
if info.IsDir() {
return true, false, nil
}
// readable + executable by anyone
- return false, info.Mode()&0005 == 0005, nil
+ return false, info.Mode()&5 == 5, nil
}
func isNotExistError(err error) bool {
@@ -94,7 +88,7 @@ func RunCGI(
request *sr.Request,
executable string,
pathInfo string,
-) (io.Reader, int, error) {
+) (*bytes.Buffer, int, error) {
pathSegments := strings.Split(executable, "/")
dirPath := "."
@@ -105,7 +99,7 @@ func RunCGI(
infoLen := len(pathInfo)
if pathInfo == "/" {
- infoLen -= 1
+ infoLen = 0
}
scriptName := request.Path[:len(request.Path)-infoLen]
diff --git a/contrib/cgi/gopher.go b/contrib/cgi/gopher.go
index 290adfa..98a3c75 100644
--- a/contrib/cgi/gopher.go
+++ b/contrib/cgi/gopher.go
@@ -3,10 +3,14 @@ package cgi
import (
"context"
"fmt"
+ "os"
+ "path"
+ "path/filepath"
"strings"
sr "tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/gopher"
+ "tildegit.org/tjp/sliderule/gopher/gophermap"
)
// GopherCGIDirectory runs any executable files relative to a root directory on the file system.
@@ -15,31 +19,148 @@ import (
// 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 GopherCGIDirectory(pathRoot, fsRoot string) sr.Handler {
+func GopherCGIDirectory(pathRoot, fsRoot string, settings *gophermap.FileSystemSettings) sr.Handler {
+ if settings == nil {
+ settings = &gophermap.FileSystemSettings{}
+ }
+
+ if !settings.Exec {
+ return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { return nil })
+ }
+
fsRoot = strings.TrimRight(fsRoot, "/")
return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
if !strings.HasPrefix(request.Path, pathRoot) {
return nil
}
- filepath, pathinfo, err := ResolveCGI(request.Path[len(pathRoot):], fsRoot)
+ fullpath, pathinfo, err := resolveGopherCGI(fsRoot, request)
if err != nil {
return gopher.Error(err).Response()
}
- if filepath == "" {
+ if fullpath == "" {
+ return nil
+ }
+
+ return runGopherCGI(ctx, request, fullpath, pathinfo, *settings)
+ })
+}
+
+// ExecGopherMaps runs any gophermaps
+func ExecGopherMaps(pathRoot, fsRoot string, settings *gophermap.FileSystemSettings) sr.Handler {
+ if settings == nil {
+ settings = &gophermap.FileSystemSettings{}
+ }
+
+ if !settings.Exec {
+ return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { return nil })
+ }
+
+ fsRoot = strings.TrimRight(fsRoot, "/")
+ return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
+ if !strings.HasPrefix(request.Path, pathRoot) {
return nil
}
- stdout, exitCode, err := RunCGI(ctx, request, filepath, pathinfo)
+ fullpath := filepath.Join(fsRoot, strings.Trim(request.Path, "/"))
+ info, err := os.Stat(fullpath)
+ if isNotExistError(err) {
+ return nil
+ }
if err != nil {
return gopher.Error(err).Response()
}
- if exitCode != 0 {
- return gopher.Error(
- fmt.Errorf("CGI process exited with status %d", exitCode),
- ).Response()
+
+ if info.IsDir() {
+ for _, fname := range settings.DirMaps {
+ fpath := filepath.Join(fullpath, fname)
+ finfo, err := os.Stat(fpath)
+ if isNotExistError(err) {
+ continue
+ }
+ if err != nil {
+ return gopher.Error(err).Response()
+ }
+
+ m := finfo.Mode()
+ if m.IsDir() {
+ continue
+ }
+ if !m.IsRegular() || m&5 != 5 {
+ continue
+ }
+ return runGopherCGI(ctx, request, fpath, "/", *settings)
+ }
+
+ return nil
+ }
+
+ m := info.Mode()
+ if !m.IsRegular() || m&5 != 5 {
+ return nil
}
- return gopher.File(0, stdout)
+ return runGopherCGI(ctx, request, fullpath, "/", *settings)
})
}
+
+func runGopherCGI(
+ ctx context.Context,
+ request *sr.Request,
+ fullpath string,
+ pathinfo string,
+ settings gophermap.FileSystemSettings,
+) *sr.Response {
+ stdout, exitCode, err := RunCGI(ctx, request, fullpath, pathinfo)
+ if err != nil {
+ return gopher.Error(err).Response()
+ }
+ if exitCode != 0 {
+ return gopher.Error(
+ fmt.Errorf("CGI process exited with status %d", exitCode),
+ ).Response()
+ }
+
+ if settings.ParseExtended {
+ edoc, err := gophermap.ParseExtended(stdout, request.URL)
+ if err != nil {
+ return gopher.Error(err).Response()
+ }
+
+ doc, _, err := edoc.Compatible(filepath.Dir(fullpath), settings)
+ return doc.Response()
+ }
+
+ return gopher.File(gopher.MenuType, stdout)
+}
+
+func resolveGopherCGI(fsRoot string, request *sr.Request) (string, string, error) {
+ reqPath := strings.TrimLeft(request.Path, "/")
+
+ segments := append([]string{""}, strings.Split(reqPath, "/")...)
+ fullpath := fsRoot
+ for i, segment := range segments {
+ fullpath = filepath.Join(fullpath, segment)
+
+ info, err := os.Stat(fullpath)
+ if isNotExistError(err) {
+ return "", "", nil
+ }
+ if err != nil {
+ return "", "", err
+ }
+
+ if !info.IsDir() {
+ if info.Mode()&5 == 5 {
+ pathinfo := "/"
+ if len(segments) > i+1 {
+ pathinfo = path.Join(segments[i:]...)
+ }
+ return fullpath, pathinfo, nil
+ }
+ break
+ }
+ }
+
+ return "", "", nil
+}