This repository has been archived on 2023-05-01. You can view files and clone it, but cannot push or open issues or pull requests.
gus/gopher/request.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
}