|
@@ -13,13 +13,13 @@ import (
|
|
|
var client = &http.Client{}
|
|
|
|
|
|
|
|
|
-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)
|
|
|
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)
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
+
|
|
|
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) {
|
|
|
+
|
|
|
+
|
|
|
+ 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)
|
|
|
+ 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)
|
|
|
+}
|