diff options
Diffstat (limited to 'gopher/request.go')
-rw-r--r-- | gopher/request.go | 72 |
1 files changed, 72 insertions, 0 deletions
diff --git a/gopher/request.go b/gopher/request.go new file mode 100644 index 0000000..6c708c0 --- /dev/null +++ b/gopher/request.go @@ -0,0 +1,72 @@ +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.TrimRight(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.TrimRight(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 +} |