Browse Source

webfinger seems to be working

Benton Edmondson 2 years ago
parent
commit
67852d961d
4 changed files with 121 additions and 23 deletions
  1. 5 9
      kinds/actor.go
  2. 1 0
      kinds/link.go
  3. 108 4
      kinds/request.go
  4. 7 10
      main.go

+ 5 - 9
kinds/actor.go

@@ -32,10 +32,11 @@ func (a Actor) InlineName() (string, error) {
 	if err != nil {
 		return "", err
 	}
-	if kind != "person" {
-		return fmt.Sprintf("%s (%s, %s)", name, id.Hostname(), kind), nil
-	}
-	return fmt.Sprintf("%s (%s)", name, id.Hostname()), nil
+	// if kind != "person" {
+	// 	return fmt.Sprintf("%s (%s, %s)", name, id.Hostname(), kind), nil
+	// }
+	// return fmt.Sprintf("%s (%s)", name, id.Hostname()), nil
+	return fmt.Sprintf("%s (%s, %s)", name, id.Hostname(), kind), nil
 }
 
 func (a Actor) Category() string {
@@ -58,11 +59,6 @@ func (a Actor) String() (string, error) {
 	if err == nil {
 		output += style.Bold(name)
 	}
-	kind, err := a.Kind()
-	if err == nil {
-		output += " "
-		output += kind
-	}
 	bio, err := a.Bio()
 	if err == nil {
 		output += "\n"

+ 1 - 0
kinds/link.go

@@ -72,6 +72,7 @@ func (l Link) String() (string, error) {
 	return output, nil
 }
 
+// TODO: must test when list only has 1 link (probably works)
 func SelectBestLink(links []Link, supertype string) (Link, error) {
 	if len(links) == 0 {
 		return nil, errors.New("Can't select best link of type " + supertype + "/* from an empty list")

+ 108 - 4
kinds/request.go

@@ -13,13 +13,13 @@ import (
 var client = &http.Client{}
 //var cache = TODO
 
-func Fetch(url *url.URL) (Content, error) {
-	const requiredContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
-	const optionalContentType = "application/activity+json"
+const requiredContentType = `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`
+const optionalContentType = "application/activity+json"
 
+func Fetch(url *url.URL) (Content, error) {
 	link := url.String()
 
-	req, err := http.NewRequest("GET", link, nil)
+	req, err := http.NewRequest("GET", link, nil) // `nil` is body
 	if err != nil {
 		return nil, err
 	}
@@ -41,6 +41,10 @@ func Fetch(url *url.URL) (Content, error) {
 		return nil, errors.New("The server returned a status code of " + resp.Status)
 	}
 
+	// TODO: delete the pointless first if right here
+	// TODO: for the sake of static servers, accept application/json
+	//		 as well iff it contains the @context key (but double check
+	//		 that it is absolutely necessary)
 	if contentType := resp.Header.Get("Content-Type"); contentType == "" {
 		return nil, errors.New("The server's response did not contain a content type")
 	} else if !strings.Contains(contentType, requiredContentType) && !strings.Contains(contentType, optionalContentType) {
@@ -54,3 +58,103 @@ func Fetch(url *url.URL) (Content, error) {
 
 	return Construct(unstructured, url)
 }
+
+func FetchWebFinger(username string) (Actor, error) {
+	// description of WebFinger: https://www.rfc-editor.org/rfc/rfc7033.html
+
+	username = strings.TrimPrefix(username, "@")
+
+	split := strings.Split(username, "@")
+	var account, domain string
+	if len(split) != 2 {
+		return nil, errors.New("webfinger address must have a separating @ symbol")
+	} else {
+		account = split[0]
+		domain = split[1]
+	}
+
+	query := url.Values{}
+	query.Add("resource", fmt.Sprintf("acct:%s@%s", account, domain))
+	query.Add("rel", "self")
+
+	link := url.URL{
+		Scheme: "https",
+		Host: domain,
+		Path: "/.well-known/webfinger",
+		RawQuery: query.Encode(),
+	}
+
+	req, err := http.NewRequest("GET", link.String(), nil) // `nil` is body
+	if err != nil {
+		return nil, err
+	}
+
+	resp, err := client.Do(req)
+	if err != nil {
+		return nil, err
+	}
+
+	defer resp.Body.Close()
+	body, err := ioutil.ReadAll(resp.Body)
+
+	if resp.StatusCode != 200 {
+		return nil, errors.New(fmt.Sprintf("the server responded to the WebFinger query %s with %s", link.String(), resp.Status))
+	} else if contentType := resp.Header.Get("Content-Type"); !strings.Contains(contentType, "application/jrd+json") && !strings.Contains(contentType, "application/json") {
+		return nil, errors.New("the server responded to the WebFinger query with invalid Content-Type " + contentType)
+	}
+
+	var jrd Dict
+	if err := json.Unmarshal(body, &jrd); err != nil {
+		return nil, err
+	}
+
+	jrdLinks, err := GetList(jrd, "links")
+	if err != nil {
+		return nil, err
+	}
+
+	var underlyingLink *url.URL = nil
+
+	for _, el := range jrdLinks {
+		jrdLink, ok := el.(Dict)
+		if ok {
+			rel, err := Get[string](jrdLink, "rel")
+			if err != nil { continue }
+			if rel != "self" { continue }
+			mediaType, err := Get[string](jrdLink, "type")
+			if err != nil { continue }
+			if !strings.Contains(mediaType, requiredContentType) && !strings.Contains(mediaType, optionalContentType) {
+				continue
+			}
+			href, err := GetURL(jrdLink, "href")
+			if err != nil { continue }
+			underlyingLink = href
+			break
+		}
+	}
+
+	if underlyingLink == nil {
+		return nil, errors.New("no matching href was found in the links array of " + link.String())
+	}
+
+	content, err := Fetch(underlyingLink)
+	if err != nil { return nil, err }
+
+	actor, ok := content.(Actor)
+	if !ok { return nil, errors.New("content returned by the WebFinger request was not an Actor") }
+
+	return actor, nil
+}
+
+func FetchUnknown(unknown string) (Content, error) {
+	if strings.HasPrefix(unknown, "@") {
+		return FetchWebFinger(unknown)
+	}
+
+	url, err := url.Parse(unknown)
+	if err != nil {
+		return nil, err
+	}
+
+	return Fetch(url)
+}

+ 7 - 10
main.go

@@ -4,7 +4,6 @@ import (
 	"encoding/json"
 	"fmt"
 	"mimicry/kinds"
-	"net/url"
 	"os"
 )
 
@@ -16,24 +15,22 @@ func main() {
 	link := os.Args[len(os.Args)-1]
 	command := os.Args[1]
 
-	url, err := url.Parse(link)
-	if err != nil {
-		panic(err)
-	}
-
-	object, err := kinds.Fetch(url)
+	content, err := kinds.FetchUnknown(link)
 	if err != nil {
 		panic(err)
 	}
 
 	if command == "raw" {
 		enc := json.NewEncoder(os.Stdout)
-		if err := enc.Encode(object); err != nil {
+		if err := enc.Encode(content); err != nil {
 			panic(err)
 		}
 		return
 	}
 
-	str, _ := object.String()
-	fmt.Println(str)
+	if str, err := content.String(); err != nil {
+		panic(err)
+	} else {
+		fmt.Println(str)
+	}
 }