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
|
package gopher
import (
"bytes"
"errors"
"io"
"net/url"
"path"
"strings"
sr "tildegit.org/tjp/sliderule"
)
// ParseRequest parses a gopher protocol request into a sliderule.Request object.
func ParseRequest(rdr io.Reader) (*sr.Request, error) {
selector, search, err := readFullRequest(rdr)
if err != nil {
return nil, err
}
if !strings.HasPrefix(selector, "/") {
selector = "/" + selector
}
return &sr.Request{
URL: &url.URL{
Scheme: "gopher",
Path: path.Clean(strings.TrimSuffix(selector, "\r\n")),
OmitHost: true, //nolint:typecheck
// (for some reason typecheck on drone-ci doesn't realize OmitHost is a field in url.URL)
RawQuery: url.QueryEscape(strings.TrimSuffix(search, "\r\n")),
},
}, nil
}
func readFullRequest(rdr io.Reader) (string, string, error) {
// The vast majority of requests will fit in this size:
// the specified 255 byte max for selector, then CRLF.
buf := make([]byte, 257)
n, err := rdr.Read(buf)
if err != nil && !errors.Is(err, io.EOF) {
return "", "", err
}
buf = buf[:n]
// Full-text search transactions are the exception, they
// may be longer because there is an additional search string
if n == 257 && buf[256] != '\n' {
intake := buf[n:cap(buf)]
total := n
for {
intake = append(intake, 0)
intake = intake[:cap(intake)]
n, err = rdr.Read(intake)
if err != nil && err != io.EOF {
return "", "", err
}
total += n
if n < cap(intake) || intake[cap(intake)-1] == '\n' {
break
}
intake = intake[n:]
}
buf = buf[:total]
}
selector, search, _ := bytes.Cut(buf, []byte{'\t'})
return string(selector), string(search), nil
}
|