diff options
author | 2024-01-26 14:17:10 +0100 | |
---|---|---|
committer | 2024-01-26 14:17:10 +0100 | |
commit | e3052e8c825da699162ea25367e860ac3c66f461 (patch) | |
tree | 3d13e83413d4a85ab694034d6c9772f9ec64268a /internal/util/namestring.go | |
parent | [performance] cache library performance enhancements (updates go-structr => v... (diff) | |
download | gotosocial-e3052e8c825da699162ea25367e860ac3c66f461.tar.xz |
[bugfix] Don't return Account or Status if new and dereferencing failed, other small fixes (#2563)
* tidy up account, status, webfingering logic a wee bit
* go fmt
* invert published check
* alter resp initialization
* get Published from account in typeutils
* don't instantiate error for no darn good reason
* shadow err
* don't repeat error codes in wrapped errors
* don't wrap error unnecessarily
Diffstat (limited to 'internal/util/namestring.go')
-rw-r--r-- | internal/util/namestring.go | 175 |
1 files changed, 121 insertions, 54 deletions
diff --git a/internal/util/namestring.go b/internal/util/namestring.go index e510fe43f..bb351c6e8 100644 --- a/internal/util/namestring.go +++ b/internal/util/namestring.go @@ -23,101 +23,168 @@ import ( "strings" "github.com/superseriousbusiness/gotosocial/internal/regexes" + "github.com/superseriousbusiness/gotosocial/internal/uris" ) // ExtractNamestringParts extracts the username test_user and // the domain example.org from a string like @test_user@example.org. // // If nothing is matched, it will return an error. -func ExtractNamestringParts(mention string) (username, host string, err error) { - matches := regexes.MentionName.FindStringSubmatch(mention) +func ExtractNamestringParts(namestring string) (username, host string, err error) { + matches := regexes.MentionName.FindStringSubmatch(namestring) switch len(matches) { case 2: return matches[1], "", nil case 3: return matches[1], matches[2], nil default: - return "", "", fmt.Errorf("couldn't match mention %s", mention) + return "", "", fmt.Errorf("couldn't match namestring %s", namestring) } } -// ExtractWebfingerParts returns the username and domain from either an -// account query or an actor URI. +// ExtractWebfingerParts returns the username and domain from the "subject" +// part of a webfinger response: either an account namestring or an actor URI. // -// All implementations in the wild generate webfinger account resource +// All AP implementations in the wild perform webfinger account resource // queries with the "acct" scheme and without a leading "@"" on the username. // This is also the format the "subject" in a webfinger response adheres to. // -// Despite this fact, we're being permissive about a single leading @. This -// makes a query for acct:user@domain.tld and acct:@user@domain.tld -// equivalent. But a query for acct:@@user@domain.tld will have its username -// returned with the @ prefix. +// Despite this fact, we're permissive about a single leading @. This makes +// a query for "acct:user@domain.tld" and "acct:@user@domain.tld" equivalent. // -// We also permit a resource of user@domain.tld or @user@domain.tld, without -// a scheme. In that case it gets interpreted as if it was using the "acct" -// scheme. +// We also permit a resource of "user@domain.tld" or "@user@domain.tld", without +// a scheme. In that case it gets interpreted as if it was using "acct:". // -// When parsing fails, an error is returned. -func ExtractWebfingerParts(webfinger string) (username, host string, err error) { - orig := webfinger - - u, oerr := url.ParseRequestURI(webfinger) - if oerr != nil { - // Most likely reason for failing to parse is if the "acct" scheme was - // missing but a :port was included. So try an extra time with the scheme. - u, err = url.ParseRequestURI("acct:" + webfinger) - if err != nil { - return "", "", fmt.Errorf("failed to parse %s with acct sheme: %w", orig, oerr) - } +// Will error if parsing fails, or if the extracted username or domain are empty. +func ExtractWebfingerParts(subject string) ( + string, // username + string, // domain + error, +) { + u, err := url.ParseRequestURI(subject) + if err != nil { + // Most likely reason for failing to parse is if + // the "acct" scheme was missing but a :port was + // included. So try an extra time with the scheme. + u, err = url.ParseRequestURI("acct:" + subject) } + if err != nil { + return "", "", fmt.Errorf("failed to parse %s: %w", subject, err) + } + + switch u.Scheme { + + // Subject looks like + // "https://example.org/users/whatever" + // or "https://example.org/@whatever". + case "http", "https": + return partsFromURI(u) - if u.Scheme == "http" || u.Scheme == "https" { - return ExtractWebfingerPartsFromURI(u) + // Subject looks like + // "acct:whatever@example.org" + // or "acct:@whatever@example.org". + case "acct": + // Pass string without "acct:" prefix. + return partsFromNamestring(u.Opaque) + + // Subject was probably a relative URL. + // Fail since we need the domain. + case "": + return "", "", fmt.Errorf("no scheme for resource %s", subject) } - if u.Scheme != "acct" { - return "", "", fmt.Errorf("unsupported scheme: %s for resource: %s", u.Scheme, orig) + return "", "", fmt.Errorf("unsupported scheme %s for resource %s", u.Scheme, subject) +} + +// partsFromNamestring returns the username +// and host parts extracted from a passed-in actor +// namestring of the format "whatever@example.org". +// +// The function returns an error if username or +// host cannot be extracted. +func partsFromNamestring(namestring string) ( + string, // username + string, // host + error, +) { + // Trim all leading "@" symbols, + // and then inject just one "@". + namestring = strings.TrimLeft(namestring, "@") + namestring = "@" + namestring + + username, host, err := ExtractNamestringParts(namestring) + if err != nil { + return "", "", err } - stripped := strings.TrimPrefix(u.Opaque, "@") - userDomain := strings.Split(stripped, "@") - if len(userDomain) != 2 { - return "", "", fmt.Errorf("failed to extract user and domain from: %s", orig) + if username == "" { + err := fmt.Errorf("failed to extract username from: %s", namestring) + return "", "", err } - return userDomain[0], userDomain[1], nil + + if host == "" { + err := fmt.Errorf("failed to extract domain from: %s", namestring) + return "", "", err + } + + return username, host, nil } -// ExtractWebfingerPartsFromURI returns the user and domain extracted from -// the passed in URI. The URI should be an actor URI. -// -// The domain returned is the hostname, and the user will be extracted -// from either /@test_user or /users/test_user. These two paths match the -// "aliasses" we include in our webfinger response and are also present in -// our "links". +// partsFromURI returns the username and host +// extracted from the passed in actor URI. // -// Like with ExtractWebfingerParts, we're being permissive about a single -// leading @. +// The username will be extracted from one of +// the patterns "/@whatever" or "/users/whatever". +// These paths match the "aliases" and "links" +// we include in our own webfinger responses. // -// Errors are returned in case we end up with an empty domain or username. -func ExtractWebfingerPartsFromURI(uri *url.URL) (username, host string, err error) { - host = uri.Host +// This function tries to be permissive with +// regard to leading "@" symbols. Nevertheless, +// an error will be returned if username or host +// cannot be extracted. +func partsFromURI(uri *url.URL) ( + string, // username + string, // host + error, +) { + host := uri.Host if host == "" { - return "", "", fmt.Errorf("failed to extract domain from: %s", uri) + err := fmt.Errorf("failed to extract domain from: %s", uri) + return "", "", err + } + + // Copy the URL, taking + // only the parts we need. + short := &url.URL{ + Path: uri.Path, } - // strip any leading slashes - path := strings.TrimLeft(uri.Path, "/") - segs := strings.Split(path, "/") + // Try "/users/whatever". + username, err := uris.ParseUserPath(short) + if err == nil && username != "" { + return username, host, nil + } + + // Try "/@whatever" + username, err = uris.ParseUserWebPath(short) + if err == nil && username != "" { + return username, host, nil + } + + // Try some exotic fallbacks like + // "/users/@whatever", "/@@whatever", etc. + short.Path = strings.TrimLeft(short.Path, "/") + segs := strings.Split(short.Path, "/") if segs[0] == "users" { username = segs[1] } else { username = segs[0] } - username = strings.TrimPrefix(username, "@") - if username == "" { - return "", "", fmt.Errorf("failed to extract username from: %s", uri) + username = strings.TrimLeft(username, "@") + if username != "" { + return username, host, nil } - return + return "", "", fmt.Errorf("failed to extract username from: %s", uri) } |