From 770c42463ec01396bd8f0c5719b725c4f2716392 Mon Sep 17 00:00:00 2001 From: Yotam Nachum Date: Fri, 20 Dec 2019 19:19:46 +0200 Subject: Execute requested file if it's executable This is only an MVP for this feature. The executable can't set the status and meta of the response and the whole response body must fit in memory. --- fileutils.go | 30 ++++++++++++++++++++++++++++++ handler.go | 54 +++++++++++++++++++++++++++++++++++++++--------------- input.go | 7 +++++++ 3 files changed, 76 insertions(+), 15 deletions(-) create mode 100644 fileutils.go diff --git a/fileutils.go b/fileutils.go new file mode 100644 index 0000000..03e71fb --- /dev/null +++ b/fileutils.go @@ -0,0 +1,30 @@ +package main + +import ( + "mime" + "os" + "path/filepath" +) + +func absPathMime(path string) string { + ext := filepath.Ext(path) + return mime.TypeByExtension(ext) +} + +func isFile(path string) bool { + fileInfo, err := os.Stat(path) + if err != nil { + return false + } + + return fileInfo.Mode().IsRegular() +} + +func isExecutable(path string) bool { + stat, err := os.Stat(path) + if err != nil { + return false + } + + return stat.Mode().Perm()&0111 == 0111 +} diff --git a/handler.go b/handler.go index d3b025d..0c81c2e 100644 --- a/handler.go +++ b/handler.go @@ -1,31 +1,21 @@ package main import ( + "bytes" + "context" "fmt" + "io/ioutil" "log" - "mime" "net/url" "os" + "os/exec" "path/filepath" "strings" + "time" gemini "git.sr.ht/~yotam/go-gemini" ) -func absPathMime(path string) string { - ext := filepath.Ext(path) - return mime.TypeByExtension(ext) -} - -func isFile(path string) bool { - fileInfo, err := os.Stat(path) - if err != nil { - return false - } - - return fileInfo.Mode().IsRegular() -} - // Handler is the main handler of the server type Handler struct { cfg Config @@ -83,6 +73,36 @@ func (h Handler) getFilePath(rawURL string) (string, error) { return "", gemini.Error{Err: fmt.Errorf("file not found"), Status: gemini.StatusNotFound} } +func (h Handler) serveExecutable(r gemini.Request, path string) gemini.Response { + ctx, cancel := context.WithTimeout(context.Background(), time.Duration(h.cfg.ExecTimeout)*time.Second) + defer cancel() + + cmd := exec.CommandContext(ctx, path) + + stdin, err := cmd.StdinPipe() + if err != nil { + return gemini.ErrorResponse(err) + } + defer stdin.Close() + + _, err = fmt.Fprintf(stdin, "%s\r\n", r.URL) + if err != nil { + return gemini.ErrorResponse(err) + } + + // The gemini library api make it hard to stream stdout instead of reading it into memory + out, err := cmd.Output() + if err != nil { + return gemini.ErrorResponse(err) + } + + if ctx.Err() == context.DeadlineExceeded { + return gemini.ErrorResponse(ctx.Err()) + } + + return gemini.Response{Status: 20, Meta: "text/gemini", Body: ioutil.NopCloser(bytes.NewReader(out))} +} + func (h Handler) serveFile(path string) gemini.Response { log.Println("Serving file from", path) @@ -107,5 +127,9 @@ func (h Handler) Handle(r gemini.Request) gemini.Response { return gemini.ErrorResponse(err) } + if isExecutable(path) { + return h.serveExecutable(r, path) + } + return h.serveFile(path) } diff --git a/input.go b/input.go index 26e65f5..6b3d0e8 100644 --- a/input.go +++ b/input.go @@ -39,6 +39,9 @@ type Config struct { // default to ["index.gmi"] IndexFiles []string `toml:"index_files"` + // default to 5 + ExecTimeout int64 `toml:"exec_timeout"` + TLSCert string `toml:"tls_certificate"` TLSKey string `toml:"tls_key"` } @@ -64,5 +67,9 @@ func getConfig(path string) (Config, error) { cfg.IndexFiles = []string{"index.gmi"} } + if cfg.ExecTimeout == 0 { + cfg.ExecTimeout = 5 + } + return cfg, nil } -- cgit v1.2.3