diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-05-03 14:25:29 -0600 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-05-03 15:05:19 -0600 |
commit | e06214b4f62173fa4be23e980bcb4d9680fd8c95 (patch) | |
tree | 53014b449bb3d01f7f67b03099295403d4fcb0bb /contrib/fs | |
parent | e42716c43e0241e9d6d2ca7f7efe8d33a27127c2 (diff) |
TitanUpload middleware in contrib/fs
Diffstat (limited to 'contrib/fs')
-rw-r--r-- | contrib/fs/gemini.go | 65 |
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. |