77 lines
3.0 KiB
Go
77 lines
3.0 KiB
Go
package finger
|
|
|
|
import (
|
|
"bufio"
|
|
"errors"
|
|
"io"
|
|
"net/url"
|
|
"strings"
|
|
|
|
"tildegit.org/tjp/gus"
|
|
)
|
|
|
|
// ForwardingDenied is returned in response to requests for forwarding service.
|
|
var ForwardingDenied = errors.New("Finger forwarding service denied.")
|
|
|
|
// InvalidFingerQuery is sent when a client doesn't properly format the query.
|
|
var InvalidFingerQuery = errors.New("Invalid finger query .")
|
|
|
|
// ParseRequest builds a gus.Request by reading a finger protocol request.
|
|
//
|
|
// At the time of writing, there is no firm standard on how to represent finger
|
|
// queries as URLs (the finger protocol itself predates URLs entirely), but there
|
|
// are a few helpful resources to go from.
|
|
// - The lynx browser supports finger URLs and documents the forms they may take:
|
|
// https://lynx.invisible-island.net/lynx_help/lynx_url_support.html#finger_url
|
|
// - There is an IETF draft:
|
|
// https://datatracker.ietf.org/doc/html/draft-ietf-uri-url-finger
|
|
//
|
|
// As this function builds a *gus.Request (which is mostly a wrapper around a URL)
|
|
// from nothing but an io.Reader, it doesn't have the context of the hostname which
|
|
// the receiving server was hosting. So it only has the host component if it
|
|
// arrived in the body of the query in the form username@hostname. Bear in mind that
|
|
// in gus handlers, request objects will also carry a reference to the server so
|
|
// that hostname is always available as request.Server.Hostname().
|
|
//
|
|
// The primary deviation from the IETF draft is that a query-specified host becomes
|
|
// the Host section of the URL, rather than remaining in the Path. Where the IETF draft
|
|
// would consider a query of "tjp@ctrl-c.club\r\n" to be "finger:/tjp@ctrl-c.club", this
|
|
// function will parse it into "finger://ctrl-c.club/tjp". This decision to separate the
|
|
// query-specified host from the username is intended to make it easier to avoid
|
|
// inadvertently acting as a jump host for example with:
|
|
// `exec.Command("/usr/bin/finger", request.Path[1:])`.
|
|
//
|
|
// Consistent with the IETF draft, the /W whois switch is dropped and not represented
|
|
// in the URL at all.
|
|
//
|
|
// In accordance with the recommendation of RFC 1288 section 3.2.1
|
|
// (https://datatracker.ietf.org/doc/html/rfc1288#section-3.2.1), any queries which
|
|
// include a jump-host (user@host1@host2) are rejected with the ForwardingDenied error.
|
|
func ParseRequest(rdr io.Reader) (*gus.Request, error) {
|
|
line, err := bufio.NewReader(rdr).ReadString('\n')
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if line[len(line)-2] != '\r' {
|
|
return nil, InvalidFingerQuery
|
|
}
|
|
|
|
line = strings.TrimSuffix(line, "\r\n")
|
|
line = strings.TrimPrefix(line, "/W")
|
|
line = strings.TrimLeft(line, " ")
|
|
|
|
username, hostname, _ := strings.Cut(line, "@")
|
|
if strings.Contains(hostname, "@") {
|
|
return nil, ForwardingDenied
|
|
}
|
|
|
|
return &gus.Request{URL: &url.URL{
|
|
Scheme: "finger",
|
|
Host: hostname,
|
|
Path: "/" + username,
|
|
OmitHost: true, //nolint:typecheck
|
|
// (for some reason typecheck on drone-ci doesn't realize OmitHost is a field in url.URL)
|
|
}}, nil
|
|
}
|