aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYotam Nachum <me@yotam.net>2019-12-20 19:19:46 +0200
committerYotam Nachum <me@yotam.net>2019-12-20 19:24:42 +0200
commit770c42463ec01396bd8f0c5719b725c4f2716392 (patch)
treed480cfbabcf539da16a83e4843559955b59360b6
parentAdd systemd service file (diff)
downloadshavit-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.go30
-rw-r--r--handler.go54
-rw-r--r--input.go7
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
+}
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
}