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
|
package gemini
import (
"bufio"
"errors"
"io"
"net/url"
"strconv"
"strings"
"tildegit.org/tjp/sliderule/internal/types"
)
// ErrInvalidRequestLineEnding indicates that a gemini request didn't end with "\r\n".
var ErrInvalidRequestLineEnding = errors.New("invalid request line ending")
// ParseRequest parses a single gemini/titan request from a reader.
//
// If the reader argument is a *bufio.Reader, it will only read a single line from it.
func ParseRequest(rdr io.Reader) (*types.Request, error) {
bufrdr, ok := rdr.(*bufio.Reader)
if !ok {
bufrdr = bufio.NewReader(rdr)
}
line, err := bufrdr.ReadString('\n')
if err != io.EOF && err != nil {
return nil, err
}
if len(line) < 2 || line[len(line)-2:] != "\r\n" {
return nil, ErrInvalidRequestLineEnding
}
u, err := url.Parse(line[:len(line)-2])
if err != nil {
return nil, err
}
if u.Scheme == "" {
u.Scheme = "gemini"
}
req := &types.Request{URL: u}
if u.Scheme == "titan" {
length, err := sizeParam(u.Path)
if err != nil {
return nil, err
}
req.Meta = io.LimitReader(bufrdr, int64(length))
}
return req, nil
}
// GetTitanRequestBody fetches the request body from a titan request.
//
// It returns nil if the argument is not a titan request or it otherwise
// does not have a request body set.
func GetTitanRequestBody(request *types.Request) io.Reader {
if request.Scheme != "titan" {
return nil
}
if rdr, ok := request.Meta.(io.Reader); ok {
return rdr
}
return nil
}
func sizeParam(path string) (int, error) {
_, rest, found := strings.Cut(path, ";")
if !found {
return 0, errors.New("no params in titan request 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 in titan request")
}
|