summaryrefslogtreecommitdiff
path: root/finger/request.go
diff options
context:
space:
mode:
Diffstat (limited to 'finger/request.go')
-rw-r--r--finger/request.go76
1 files changed, 76 insertions, 0 deletions
diff --git a/finger/request.go b/finger/request.go
new file mode 100644
index 0000000..833072d
--- /dev/null
+++ b/finger/request.go
@@ -0,0 +1,76 @@
+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
+}