diff options
Diffstat (limited to 'contrib/cgi')
-rw-r--r-- | contrib/cgi/cgi.go | 50 | ||||
-rw-r--r-- | contrib/cgi/gemini.go | 51 | ||||
-rw-r--r-- | contrib/cgi/gopher.go | 155 | ||||
-rw-r--r-- | contrib/cgi/handlers.go | 57 | ||||
-rw-r--r-- | contrib/cgi/spartan.go | 51 |
5 files changed, 85 insertions, 279 deletions
diff --git a/contrib/cgi/cgi.go b/contrib/cgi/cgi.go index b7dd14a..1b5bfcc 100644 --- a/contrib/cgi/cgi.go +++ b/contrib/cgi/cgi.go @@ -11,6 +11,7 @@ import ( "net" "os" "os/exec" + "path" "path/filepath" "strings" @@ -25,48 +26,37 @@ import ( // It will find executables which are just part way through the path, so for example // 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, "/"), "/") +func ResolveCGI(requestpath, fsroot string) (string, string, error) { + segments := append([]string{""}, strings.Split(requestpath, "/")...) - for i := range append(segments, "") { - filepath := strings.Join(append([]string{fsRoot}, segments[:i]...), "/") - isDir, isExecutable, err := executableFile(filepath) + fullpath := fsroot + for i, segment := range segments { + fullpath = filepath.Join(fullpath, segment) + + info, err := os.Stat(fullpath) + if isNotExistError(err) { + break + } if err != nil { return "", "", err } - if isExecutable { - pathinfo := "/" - if len(segments) > i+1 { - pathinfo = strings.Join(segments[i:], "/") - } - return filepath, pathinfo, nil + if info.IsDir() { + continue } - if !isDir { + if info.Mode()&5 != 5 { break } - } - - return "", "", nil -} -func executableFile(filepath string) (bool, bool, error) { - info, err := os.Stat(filepath) - if isNotExistError(err) { - return false, false, nil - } - if err != nil { - return false, false, err - } - - if info.IsDir() { - return true, false, nil + pathinfo := "/" + if len(segments) > i+1 { + pathinfo = path.Join(segments[i:]...) + } + return fullpath, pathinfo, nil } - // readable + executable by anyone - return false, info.Mode()&5 == 5, nil + return "", "", nil } func isNotExistError(err error) bool { diff --git a/contrib/cgi/gemini.go b/contrib/cgi/gemini.go index 0aa3044..9e4d68f 100644 --- a/contrib/cgi/gemini.go +++ b/contrib/cgi/gemini.go @@ -1,15 +1,8 @@ package cgi import ( - "bytes" - "context" - "fmt" - "path/filepath" - "strings" - - sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/gemini" - "tildegit.org/tjp/sliderule/logging" ) // GeminiCGIDirectory runs any executable files relative to a root directory on the file system. @@ -18,44 +11,6 @@ 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 GeminiCGIDirectory(fsroot, urlroot, cmd string) sr.Handler { - fsroot = strings.TrimRight(fsroot, "/") - return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { - if !strings.HasPrefix(request.Path, urlroot) { - return nil - } - - execpath, pathinfo, err := ResolveCGI(request.Path[len(urlroot):], fsroot) - if err != nil { - return gemini.Failure(err) - } - if execpath == "" { - return nil - } - workdir := filepath.Dir(execpath) - - if cmd != "" { - execpath = cmd - } - - stderr := &bytes.Buffer{} - stdout, exitCode, err := RunCGI(ctx, request, execpath, pathinfo, workdir, stderr) - if err != nil { - return gemini.Failure(err) - } - if exitCode != 0 { - ctx.Value("warnlog").(logging.Logger).Log( - "msg", "cgi exited with non-zero exit code", - "code", exitCode, - "stderr", stderr.String(), - ) - return gemini.CGIError(fmt.Sprintf("CGI process exited with status %d", exitCode)) - } - - response, err := gemini.ParseResponse(stdout) - if err != nil { - return gemini.Failure(err) - } - return response - }) +func GeminiCGIDirectory(fsroot, urlroot, cmd string) sliderule.Handler { + return cgiDirectory(gemini.ServerProtocol, fsroot, urlroot, cmd) } diff --git a/contrib/cgi/gopher.go b/contrib/cgi/gopher.go index 7067a6d..8704904 100644 --- a/contrib/cgi/gopher.go +++ b/contrib/cgi/gopher.go @@ -1,18 +1,11 @@ package cgi import ( - "bytes" "context" - "fmt" - "os" - "path" - "path/filepath" - "strings" sr "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/gopher" "tildegit.org/tjp/sliderule/gopher/gophermap" - "tildegit.org/tjp/sliderule/logging" ) // GopherCGIDirectory runs any executable files relative to a root directory on the file system. @@ -25,151 +18,7 @@ func GopherCGIDirectory(fsroot, urlroot, cmd string, settings *gophermap.FileSys if settings == nil || !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, urlroot) { - return nil - } - requestpath := strings.Trim(strings.TrimPrefix(request.Path, urlroot), "/") - - fullpath, pathinfo, err := resolveGopherCGI(fsroot, requestpath) - if err != nil { - return gopher.Error(err).Response() - } - if fullpath == "" { - return nil - } - - return runGopherCGI(ctx, request, fullpath, pathinfo, cmd, *settings) - }) -} - -// ExecGopherMaps runs any gophermaps -func ExecGopherMaps(fsroot, urlroot, cmd string, settings *gophermap.FileSystemSettings) sr.Handler { - if settings == nil || !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, urlroot) { - return nil - } - requestpath := strings.Trim(strings.TrimPrefix(request.Path, urlroot), "/") - - fullpath := filepath.Join(fsroot, requestpath) - info, err := os.Stat(fullpath) - if isNotExistError(err) { - return nil - } - if err != nil { - return gopher.Error(err).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, "/", cmd, *settings) - } - - return nil - } - - m := info.Mode() - if !m.IsRegular() || m&5 != 5 { - return nil - } - - return runGopherCGI(ctx, request, fullpath, "/", cmd, *settings) - }) -} - -func runGopherCGI( - ctx context.Context, - request *sr.Request, - fullpath string, - pathinfo string, - cmd string, - settings gophermap.FileSystemSettings, -) *sr.Response { - workdir := filepath.Dir(fullpath) - if cmd != "" { - fullpath = cmd - } - - stderr := &bytes.Buffer{} - stdout, exitCode, err := RunCGI(ctx, request, fullpath, pathinfo, workdir, stderr) - if err != nil { - return gopher.Error(err).Response() - } - if exitCode != 0 { - ctx.Value("warnlog").(logging.Logger).Log( - "msg", "cgi exited with non-zero exit code", - "code", exitCode, - "stderr", stderr.String(), - ) - 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) - if err != nil { - return gopher.Error(err).Response() - } - return doc.Response() - } - - return gopher.File(gopher.MenuType, stdout) -} - -func resolveGopherCGI(fsRoot string, reqPath string) (string, string, error) { - 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 + handler := cgiDirectory(gopher.ServerProtocol, fsroot, urlroot, cmd) + return gophermap.ExtendMiddleware(fsroot, urlroot, settings)(handler) } diff --git a/contrib/cgi/handlers.go b/contrib/cgi/handlers.go new file mode 100644 index 0000000..03a1db7 --- /dev/null +++ b/contrib/cgi/handlers.go @@ -0,0 +1,57 @@ +package cgi + +import ( + "bytes" + "context" + "fmt" + "path/filepath" + "strings" + + sr "tildegit.org/tjp/sliderule" + "tildegit.org/tjp/sliderule/logging" +) + +func cgiDirectory(protocol sr.ServerProtocol, fsroot, urlroot, cmd string) sr.Handler { + fsroot = strings.TrimRight(fsroot, "/") + + return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { + if !strings.HasPrefix(request.Path, urlroot) { + return nil + } + + rpath := strings.TrimPrefix(request.Path, urlroot) + rpath = strings.Trim(rpath, "/") + execpath, pathinfo, err := ResolveCGI(rpath, fsroot) + if err != nil { + return protocol.TemporaryServerError(err) + } + if execpath == "" { + return nil + } + workdir := filepath.Dir(execpath) + + if cmd != "" { + execpath = cmd + } + + stderr := &bytes.Buffer{} + stdout, exitCode, err := RunCGI(ctx, request, execpath, pathinfo, workdir, stderr) + if err != nil { + return protocol.TemporaryServerError(err) + } + if exitCode != 0 { + _ = ctx.Value("warnlog").(logging.Logger).Log( + "msg", "cgi exited with non-zero exit code", + "code", exitCode, + "stderr", stderr.String(), + ) + return protocol.CGIFailure(fmt.Errorf("CGI process exited with status %d", exitCode)) + } + + response, err := protocol.ParseResponse(stdout) + if err != nil { + return protocol.TemporaryServerError(err) + } + return response + }) +} diff --git a/contrib/cgi/spartan.go b/contrib/cgi/spartan.go index 36aaa36..32ea66c 100644 --- a/contrib/cgi/spartan.go +++ b/contrib/cgi/spartan.go @@ -1,14 +1,7 @@ package cgi import ( - "bytes" - "context" - "fmt" - "path/filepath" - "strings" - - sr "tildegit.org/tjp/sliderule" - "tildegit.org/tjp/sliderule/logging" + "tildegit.org/tjp/sliderule" "tildegit.org/tjp/sliderule/spartan" ) @@ -18,44 +11,6 @@ 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 SpartanCGIDirectory(fsroot, urlroot, cmd string) sr.Handler { - fsroot = strings.TrimRight(fsroot, "/") - return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response { - if !strings.HasPrefix(request.Path, urlroot) { - return nil - } - - execpath, pathinfo, err := ResolveCGI(request.Path[len(urlroot):], fsroot) - if err != nil { - return spartan.ServerError(err) - } - if execpath == "" { - return nil - } - workdir := filepath.Dir(execpath) - - if cmd != "" { - execpath = cmd - } - - stderr := &bytes.Buffer{} - stdout, exitCode, err := RunCGI(ctx, request, execpath, pathinfo, workdir, stderr) - if err != nil { - return spartan.ServerError(err) - } - if exitCode != 0 { - ctx.Value("warnlog").(logging.Logger).Log( - "msg", "cgi exited with non-zero exit code", - "code", exitCode, - "stderr", stderr.String(), - ) - return spartan.ServerError(fmt.Errorf("CGI process exited with status %d", exitCode)) - } - - response, err := spartan.ParseResponse(stdout) - if err != nil { - return spartan.ServerError(err) - } - return response - }) +func SpartanCGIDirectory(fsroot, urlroot, cmd string) sliderule.Handler { + return cgiDirectory(spartan.ServerProtocol, fsroot, urlroot, cmd) } |