73 lines
1.6 KiB
Go
73 lines
1.6 KiB
Go
package gopher
|
|
|
|
import (
|
|
"bytes"
|
|
"errors"
|
|
"io"
|
|
"net/url"
|
|
"path"
|
|
"strings"
|
|
|
|
"tildegit.org/tjp/gus"
|
|
)
|
|
|
|
// ParseRequest parses a gopher protocol request into a gus.Request object.
|
|
func ParseRequest(rdr io.Reader) (*gus.Request, error) {
|
|
selector, search, err := readFullRequest(rdr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if !strings.HasPrefix(selector, "/") {
|
|
selector = "/" + selector
|
|
}
|
|
|
|
return &gus.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
|
|
}
|