diff options
Diffstat (limited to 'contrib/cgi')
-rw-r--r-- | contrib/cgi/cgi.go | 16 | ||||
-rw-r--r-- | contrib/cgi/gopher.go | 139 |
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 +} |