package gemini import ( "bufio" "context" "crypto/tls" "errors" "io" "net" "strconv" "strings" "tildegit.org/tjp/gus" "tildegit.org/tjp/gus/internal" "tildegit.org/tjp/gus/logging" ) type titanRequestBodyKey struct{} // 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. var TitanRequestBody = titanRequestBodyKey{} type server struct { internal.Server handler gus.Handler } func (s server) Protocol() string { return "GEMINI" } // NewServer builds a gemini server. func NewServer( ctx context.Context, hostname string, network string, address string, handler gus.Handler, errorLog logging.Logger, tlsConfig *tls.Config, ) (gus.Server, error) { s := &server{handler: handler} if strings.IndexByte(hostname, ':') < 0 { hostname = net.JoinHostPort(hostname, "1965") } internalServer, err := internal.NewServer(ctx, hostname, network, address, errorLog, s.handleConn) if err != nil { return nil, err } s.Server = internalServer s.Listener = tls.NewListener(s.Listener, tlsConfig) return s, nil } func (s *server) handleConn(conn net.Conn) { buf := bufio.NewReader(conn) var response *gus.Response request, err := ParseRequest(buf) if err != nil { response = BadRequest(err.Error()) } else { request.Server = s request.RemoteAddr = conn.RemoteAddr() if tlsconn, ok := conn.(*tls.Conn); ok { state := tlsconn.ConnectionState() request.TLSState = &state } ctx := s.Ctx if request.Scheme == "titan" { len, err := sizeParam(request.Path) if err == nil { ctx = context.WithValue( ctx, TitanRequestBody, io.LimitReader(buf, int64(len)), ) } } /* defer func() { if r := recover(); r != nil { err := fmt.Errorf("%s", r) _ = s.LogError("msg", "panic in handler", "err", err) _, _ = io.Copy(conn, NewResponseReader(Failure(err))) } }() */ response = s.handler(ctx, request) if response == nil { response = NotFound("Resource does not exist.") } } defer response.Close() _, _ = io.Copy(conn, NewResponseReader(response)) } 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") }