123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194 |
- package client
- import (
- "encoding/json"
- "errors"
- "fmt"
- "golang.org/x/sync/singleflight"
- "servitor/jtp"
- "servitor/object"
- "net/url"
- "os"
- "strings"
- )
- const MAX_REDIRECTS = 20
- func FetchUnknown(input any, source *url.URL) (object.Object, *url.URL, error) {
- var obj object.Object
- switch narrowed := input.(type) {
- case string:
- ref, err := url.Parse(narrowed)
- if err != nil {
- return nil, nil, err
- }
- if source != nil {
- obj, source, err = FetchURL(source.ResolveReference(ref))
- } else {
- obj, source, err = FetchURL(ref)
- }
- if err != nil {
- return nil, nil, err
- }
- case map[string]any:
- obj = object.Object(narrowed)
- default:
- return nil, nil, fmt.Errorf("can't turn non-string, non-object %T into Item", input)
- }
- id, err := obj.GetURL("id")
- if errors.Is(err, object.ErrKeyNotPresent) {
- id = nil
- } else if err != nil {
- return nil, nil, err
- }
-
- if id != nil && (source == nil || source.Host != id.Host || len(obj) <= 2) {
- obj, source, err = FetchURL(id)
- if err != nil {
- return nil, nil, err
- }
-
- id, err = obj.GetURL("id")
- if errors.Is(err, object.ErrKeyNotPresent) {
- id = nil
- } else if err != nil {
- return nil, nil, err
- }
- if id != nil && source.Host != id.Host {
- return nil, nil, errors.New("received response with forged ID")
- }
- }
- return obj, id, nil
- }
- var group singleflight.Group
- type bundle struct {
- item map[string]any
- source *url.URL
- err error
- }
- func FetchURL(uri *url.URL) (object.Object, *url.URL, error) {
- uriString := uri.String()
- b, _, _ := group.Do(uriString, func() (any, error) {
- json, source, err :=
- jtp.Get(
- uri,
- `application/activity+json,`+
- `application/ld+json; profile="https://www.w3.org/ns/activitystreams"`,
- []string{
- "application/activity+json",
- "application/ld+json",
- "application/json",
- },
- MAX_REDIRECTS,
- )
- return bundle{
- item: json,
- source: source,
- err: err,
- }, nil
- })
-
- group.Forget(uriString)
- return b.(bundle).item, b.(bundle).source, b.(bundle).err
- }
- func ResolveWebfinger(username string) (string, error) {
- split := strings.SplitN(username, "@", 2)
- var account, domain string
- if len(split) != 2 {
- return "", errors.New("webfinger address must have a separating @ symbol")
- }
- account = split[0]
- domain = split[1]
- link := &url.URL{
- Scheme: "https",
- Host: domain,
- Path: "/.well-known/webfinger",
- RawQuery: (url.Values{
- "resource": []string{"acct:" + account + "@" + domain},
- }).Encode(),
- }
- json, _, err := jtp.Get(link, "application/jrd+json", []string{
- "application/jrd+json",
- "application/json",
- }, MAX_REDIRECTS)
- if err != nil {
- return "", err
- }
- response := object.Object(json)
- jrdLinks, err := response.GetList("links")
- if err != nil {
- return "", err
- }
- found := false
- var underlyingLink string
- for _, el := range jrdLinks {
- asMap, ok := el.(map[string]any)
- o := object.Object(asMap)
- if ok {
- rel, err := o.GetString("rel")
- if err != nil {
- return "", err
- }
- if rel != "self" {
- continue
- }
- mediaType, err := o.GetMediaType("type")
- if errors.Is(err, object.ErrKeyNotPresent) {
- continue
- } else if err != nil {
- return "", err
- }
- if !mediaType.Matches([]string{
- "application/activity+json",
- "application/ld+json",
- }) {
- continue
- }
- href, err := o.GetString("href")
- if err != nil {
- return "", err
- }
- found = true
- underlyingLink = href
- break
- } else {
- return "", fmt.Errorf("unrecognized type %T found in webfinger response", el)
- }
- }
- if !found {
- return "", errors.New("no matching href was found in the links array of " + link.String())
- }
- return underlyingLink, nil
- }
- func FetchFromFile(name string) (object.Object, error) {
- file, err := os.Open(name)
- if err != nil {
- return nil, err
- }
- var obj object.Object
- json.NewDecoder(file).Decode(&obj)
- return obj, nil
- }
|