diff options
author | Yotam Nachum <me@yotam.net> | 2019-12-20 19:19:46 +0200 |
---|---|---|
committer | Yotam Nachum <me@yotam.net> | 2019-12-20 19:24:42 +0200 |
commit | 770c42463ec01396bd8f0c5719b725c4f2716392 (patch) | |
tree | d480cfbabcf539da16a83e4843559955b59360b6 | |
parent | Add systemd service file (diff) | |
download | shavit-770c42463ec01396bd8f0c5719b725c4f2716392.tar.gz shavit-770c42463ec01396bd8f0c5719b725c4f2716392.zip |
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.
-rw-r--r-- | fileutils.go | 30 | ||||
-rw-r--r-- | handler.go | 54 | ||||
-rw-r--r-- | input.go | 7 |
3 files changed, 76 insertions, 15 deletions
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 +} @@ -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) } @@ -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 } |