1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
|
package gemini
import (
"bufio"
"context"
"crypto/tls"
"errors"
"fmt"
"io"
"net"
"strconv"
"strings"
sr "tildegit.org/tjp/sliderule"
"tildegit.org/tjp/sliderule/internal"
"tildegit.org/tjp/sliderule/logging"
)
type titanRequestBodyKey struct{}
type server struct {
internal.Server
handler sr.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 sr.Handler,
errorLog logging.Logger,
tlsConfig *tls.Config,
) (sr.Server, error) {
s := &server{handler: handler}
hostname = internal.JoinDefaultPort(hostname, "1965")
address = internal.JoinDefaultPort(address, "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 *sr.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 {
request.Meta = 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.Handle(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")
}
// GeminiOnly filters requests down to just those on the gemini:// protocol.
//
// Optionally, it will also allow through titan:// requests.
//
// Filtered requests will be turned away with a 53 response "proxy request refused".
func GeminiOnly(allowTitan bool) sr.Middleware {
return func(inner sr.Handler) sr.Handler {
return sr.HandlerFunc(func(ctx context.Context, request *sr.Request) *sr.Response {
if request.Scheme == "gemini" || (allowTitan && request.Scheme == "titan") {
return inner.Handle(ctx, request)
}
return RefuseProxy("Non-gemini protocol requests are not supported.")
})
}
}
|