diff options
-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 } |