summaryrefslogtreecommitdiff
path: root/contrib/fs/gemini.go
diff options
context:
space:
mode:
authortjpcc <tjp@ctrl-c.club>2023-05-03 14:25:29 -0600
committertjpcc <tjp@ctrl-c.club>2023-05-03 15:05:19 -0600
commite06214b4f62173fa4be23e980bcb4d9680fd8c95 (patch)
tree53014b449bb3d01f7f67b03099295403d4fcb0bb /contrib/fs/gemini.go
parente42716c43e0241e9d6d2ca7f7efe8d33a27127c2 (diff)
TitanUpload middleware in contrib/fs
Diffstat (limited to 'contrib/fs/gemini.go')
-rw-r--r--contrib/fs/gemini.go65
1 files changed, 65 insertions, 0 deletions
diff --git a/contrib/fs/gemini.go b/contrib/fs/gemini.go
index fbf8d08..7549ce6 100644
--- a/contrib/fs/gemini.go
+++ b/contrib/fs/gemini.go
@@ -2,14 +2,79 @@ package fs
import (
"context"
+ "crypto/tls"
+ "io"
"io/fs"
+ "net/url"
+ "os"
+ "path"
"strings"
"text/template"
sr "tildegit.org/tjp/sliderule"
+ "tildegit.org/tjp/sliderule/contrib/tlsauth"
"tildegit.org/tjp/sliderule/gemini"
)
+// TitanUpload decorates a handler to implement uploads via the titan protocol.
+//
+// It is a middleware rather than a handler because after the upload is processed,
+// the server is still responsible for generating a response.
+func TitanUpload(approver tlsauth.Approver, rootdir string) sr.Middleware {
+ rootdir = strings.TrimSuffix(rootdir, "/")
+
+ return func(responder sr.Handler) sr.Handler {
+ handler := sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
+ body := gemini.GetTitanRequestBody(request)
+
+ tmpf, err := os.CreateTemp("", "titan_upload_")
+ if err != nil {
+ return gemini.PermanentFailure(err)
+ }
+
+ if _, err := io.Copy(tmpf, body); err != nil {
+ _ = os.Remove(tmpf.Name())
+ return gemini.PermanentFailure(err)
+ }
+
+ request = cloneRequest(request)
+ request.Path = strings.SplitN(request.Path, ";", 2)[0]
+
+ filepath := strings.TrimPrefix(request.Path, "/")
+ filepath = path.Join(rootdir, filepath)
+ if err := os.Rename(tmpf.Name(), filepath); err != nil {
+ _ = os.Remove(tmpf.Name())
+ return gemini.PermanentFailure(err)
+ }
+
+ return responder.Handle(ctx, request)
+ })
+
+ handler = tlsauth.GeminiAuth(approver)(handler)
+
+ handler = sr.Filter(func(_ context.Context, r *sr.Request) bool {
+ return gemini.GetTitanRequestBody(r) != nil
+ }, nil)(handler)
+
+ return handler
+ }
+}
+
+func cloneRequest(start *sr.Request) *sr.Request {
+ next := &sr.Request{}
+ *next = *start
+
+ next.URL = &url.URL{}
+ *next.URL = *start.URL
+
+ if start.TLSState != nil {
+ next.TLSState = &tls.ConnectionState{}
+ *next.TLSState = *start.TLSState
+ }
+
+ return next
+}
+
// GeminiFileHandler builds a handler which serves up files from a file system.
//
// It only serves responses for paths which do not correspond to directories on disk.