diff options
Diffstat (limited to 'gemini')
-rw-r--r-- | gemini/request.go | 5 | ||||
-rw-r--r-- | gemini/response.go | 35 | ||||
-rw-r--r-- | gemini/serve.go | 52 |
3 files changed, 83 insertions, 9 deletions
diff --git a/gemini/request.go b/gemini/request.go index 248ce67..43ee69b 100644 --- a/gemini/request.go +++ b/gemini/request.go @@ -5,6 +5,7 @@ import ( "crypto/tls" "errors" "io" + "net" "net/url" ) @@ -15,7 +16,9 @@ var InvalidRequestLineEnding = errors.New("invalid request line ending") type Request struct { *url.URL - TLSState *tls.ConnectionState + Server *Server + RemoteAddr net.Addr + TLSState *tls.ConnectionState } // ParseRequest parses a single gemini request from a reader. diff --git a/gemini/response.go b/gemini/response.go index 90340a5..478913b 100644 --- a/gemini/response.go +++ b/gemini/response.go @@ -1,7 +1,9 @@ package gemini import ( + "bufio" "bytes" + "errors" "io" "strconv" ) @@ -262,6 +264,39 @@ func CertInvalid(msg string) *Response { } } +// InvalidResponseLineEnding indicates that a gemini response header didn't end with "\r\n". +var InvalidResponseLineEnding = errors.New("Invalid response line ending.") + +// InvalidResponseHeaderLine indicates a malformed gemini response header line. +var InvalidResponseHeaderLine = errors.New("Invalid response header line.") + +// ParseResponse parses a complete gemini response from a reader. +// +// The reader must contain only one gemini response. +func ParseResponse(rdr io.Reader) (*Response, error) { + bufrdr := bufio.NewReader(rdr) + + hdrLine, err := bufrdr.ReadBytes('\n') + if err != nil { + return nil, InvalidResponseLineEnding + } + if hdrLine[len(hdrLine)-2] != '\r' { + return nil, InvalidResponseLineEnding + } + hdrLine = hdrLine[:len(hdrLine)-2] + + status, err := strconv.Atoi(string(hdrLine[:2])) + if err != nil { + return nil, InvalidResponseHeaderLine + } + + return &Response{ + Status: Status(status), + Meta: string(hdrLine[2:]), + Body: bufrdr, + }, nil +} + // Read implements io.Reader for Response. func (r *Response) Read(b []byte) (int, error) { r.ensureReader() diff --git a/gemini/serve.go b/gemini/serve.go index d439472..8fd6b57 100644 --- a/gemini/serve.go +++ b/gemini/serve.go @@ -10,17 +10,33 @@ import ( type Server struct { ctx context.Context + network string + address string cancel context.CancelFunc wg *sync.WaitGroup listener net.Listener handler Handler } -func NewServer(ctx context.Context, tlsConfig *tls.Config, listener net.Listener, handler Handler) *Server { +func NewServer( + ctx context.Context, + tlsConfig *tls.Config, + network string, + address string, + handler Handler, +) (*Server, error) { ctx, cancel := context.WithCancel(ctx) + listener, err := net.Listen(network, address) + if err != nil { + cancel() + return nil, err + } + s := &Server{ ctx: ctx, + network: network, + address: address, cancel: cancel, wg: &sync.WaitGroup{}, listener: tls.NewListener(listener, tlsConfig), @@ -28,7 +44,7 @@ func NewServer(ctx context.Context, tlsConfig *tls.Config, listener net.Listener } go s.propagateCancel() - return s + return s, nil } func (s *Server) Close() { @@ -51,22 +67,42 @@ func (s *Server) Serve() { } } +func (s *Server) Network() string { + return s.network +} + +func (s *Server) Address() string { + return s.address +} + +func (s *Server) Hostname() string { + host, _, _ := net.SplitHostPort(s.address) + return host +} + +func (s *Server) Port() string { + _, portStr, _ := net.SplitHostPort(s.address) + return portStr +} + func (s *Server) handleConn(conn net.Conn) { defer s.wg.Done() defer conn.Close() req, err := ParseRequest(conn) + if err != nil { + _, _ = io.Copy(conn, BadRequest(err.Error())) + return + } + + req.Server = s + req.RemoteAddr = conn.RemoteAddr() if tlsconn, ok := conn.(*tls.Conn); req != nil && ok { state := tlsconn.ConnectionState() req.TLSState = &state } - var resp *Response - if err == nil { - resp = s.handler(s.ctx, req) - } else { - resp = BadRequest(err.Error()) - } + resp := s.handler(s.ctx, req) defer resp.Close() _, _ = io.Copy(conn, resp) |