diff options
Diffstat (limited to 'gopher/request.go')
| -rw-r--r-- | gopher/request.go | 50 |
1 files changed, 35 insertions, 15 deletions
diff --git a/gopher/request.go b/gopher/request.go index fdd7480..3aeaf74 100644 --- a/gopher/request.go +++ b/gopher/request.go @@ -11,6 +11,11 @@ import ( "tildegit.org/tjp/sliderule/internal/types" ) +const ( + maxSelectorLen = 255 + maxRequestLen = 4096 +) + // ParseRequest parses a gopher protocol request into a sliderule.Request object. func ParseRequest(rdr io.Reader) (*types.Request, error) { selector, search, err := readFullRequest(rdr) @@ -34,9 +39,7 @@ func ParseRequest(rdr io.Reader) (*types.Request, error) { } 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) + buf := make([]byte, maxSelectorLen+len("\r\n")) n, err := rdr.Read(buf) if err != nil && !errors.Is(err, io.EOF) { @@ -44,29 +47,46 @@ func readFullRequest(rdr io.Reader) (string, string, error) { } 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 + if lineEnd := bytes.IndexByte(buf, '\n'); lineEnd >= 0 { + buf = buf[:lineEnd+1] + } else if tab := bytes.IndexByte(buf, '\t'); tab >= 0 { + // Full-text search transactions append TAB and a search string. + // RFC 1436 still caps the selector at 255 bytes, but gives no + // search-string limit, so keep a small implementation cap. for { - intake = append(intake, 0) - intake = intake[:cap(intake)] + if len(buf) >= maxRequestLen { + return "", "", errors.New("gopher request too long") + } + intake := make([]byte, maxRequestLen-len(buf)) n, err = rdr.Read(intake) if err != nil && err != io.EOF { return "", "", err } - total += n - if n < cap(intake) || intake[cap(intake)-1] == '\n' { + buf = append(buf, intake[:n]...) + if lineEnd := bytes.IndexByte(intake[:n], '\n'); lineEnd >= 0 { + buf = buf[:len(buf)-n+lineEnd+1] + break + } + if err == io.EOF { break } - intake = intake[n:] } - buf = buf[:total] + } else if len(buf) > maxSelectorLen { + return "", "", errors.New("gopher selector too long") } selector, search, _ := bytes.Cut(buf, []byte{'\t'}) - return strings.TrimRight(string(selector), "\r\n"), strings.TrimRight(string(search), "\r\n"), nil + selector = bytes.TrimRight(selector, "\r\n") + search = bytes.TrimRight(search, "\r\n") + + if len(selector) > maxSelectorLen { + return "", "", errors.New("gopher selector too long") + } + if bytes.ContainsAny(selector, "\x00\r\n") || bytes.ContainsAny(search, "\x00\t\r\n") { + return "", "", errors.New("malformed gopher request") + } + + return string(selector), string(search), nil } |
