diff options
author | tjpcc <tjp@ctrl-c.club> | 2023-01-24 07:36:28 -0700 |
---|---|---|
committer | tjpcc <tjp@ctrl-c.club> | 2023-01-24 07:36:28 -0700 |
commit | 23d705b93a89cb0aee582eda819a76257f42dffc (patch) | |
tree | 94091c5915a9c1dc9914622838394d32dccf2fed /gemini/serve.go | |
parent | 0480e066a3f1ae97dbab8fcb6303589eb0fa724c (diff) |
Add support for titan:// to the gemini server
Titan is a gemini add-on protocol so it really didn't make sense to
build it out in a separate package. The most significant difference in
titan for the purposes of implementation here is that requests can have
bodies following the URL line.
Since gus.Request is a struct, the only way to smuggle in the new field
(a reader for the body) was to stash it in the context.
Diffstat (limited to 'gemini/serve.go')
-rw-r--r-- | gemini/serve.go | 53 |
1 files changed, 48 insertions, 5 deletions
diff --git a/gemini/serve.go b/gemini/serve.go index cd51370..dd7ad52 100644 --- a/gemini/serve.go +++ b/gemini/serve.go @@ -1,16 +1,26 @@ package gemini import ( + "bufio" "context" "crypto/tls" + "errors" "io" "net" + "strconv" + "strings" "sync" "tildegit.org/tjp/gus" "tildegit.org/tjp/gus/logging" ) +// TitanRequestBody is the key set in a handler's context for titan requests. +// +// When this key is present in the context (request.URL.Scheme will be "titan"), the +// corresponding value is a *bufio.Reader from which the request body can be read. +const TitanRequestBody = "titan_request_body" + type server struct { ctx context.Context errorLog logging.Logger @@ -59,6 +69,10 @@ func NewServer( // It will respect cancellation of the context the server was created with, // but be aware that Close() must still be called in that case to avoid // dangling goroutines. +// +// On titan protocol requests, it sets a key/value pair in the context. The +// key is TitanRequestBody, and the value is a *bufio.Reader from which the +// request body can be read. func (s *server) Serve() error { s.wg.Add(1) defer s.wg.Done() @@ -74,7 +88,7 @@ func (s *server) Serve() error { if s.Closed() { err = nil } else { - s.errorLog.Log("msg", "accept_error", "error", err) + s.errorLog.Log("msg", "accept_error", "error", err) } return err @@ -112,11 +126,12 @@ func (s *server) handleConn(conn net.Conn) { defer s.wg.Done() defer conn.Close() + buf := bufio.NewReader(conn) + var response *gus.Response - req, err := ParseRequest(conn) + req, err := ParseRequest(buf) if err != nil { response = BadRequest(err.Error()) - return } else { req.Server = s req.RemoteAddr = conn.RemoteAddr() @@ -125,13 +140,25 @@ func (s *server) handleConn(conn net.Conn) { req.TLSState = &state } - response = s.handler(s.ctx, req) + ctx := s.ctx + if req.Scheme == "titan" { + len, err := sizeParam(req.Path) + if err == nil { + ctx = context.WithValue( + ctx, + "titan_request_body", + io.LimitReader(buf, int64(len)), + ) + } + } + + response = s.handler(ctx, req) if response == nil { response = NotFound("Resource does not exist.") } - defer response.Close() } + defer response.Close() _, _ = io.Copy(conn, NewResponseReader(response)) } @@ -152,3 +179,19 @@ func (s *server) Closed() bool { return false } } + +func sizeParam(path string) (int, error) { + _, rest, found := strings.Cut(path, ";") + if !found { + return 0, errors.New("no params in path") + } + + for _, piece := range strings.Split(rest, ";") { + key, val, _ := strings.Cut(piece, "=") + if key == "size" { + return strconv.Atoi(val) + } + } + + return 0, errors.New("no size param found") +} |