summaryrefslogtreecommitdiff
path: root/gemini
diff options
context:
space:
mode:
Diffstat (limited to 'gemini')
-rw-r--r--gemini/request.go5
-rw-r--r--gemini/response.go35
-rw-r--r--gemini/serve.go52
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)