From 23d705b93a89cb0aee582eda819a76257f42dffc Mon Sep 17 00:00:00 2001 From: tjpcc Date: Tue, 24 Jan 2023 07:36:28 -0700 Subject: 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. --- gemini/serve.go | 53 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 48 insertions(+), 5 deletions(-) (limited to 'gemini/serve.go') 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") +} -- cgit v1.2.3