Federated via graft
1.5 kB
45 lines
1package activitypub
2
3import (
4 "encoding/json"
5 "fmt"
6 "io"
7 "net/http"
8)
9
10// FetchActor retrieves a remote actor object. actorURL is attacker
11// influenced (it comes straight off an inbound activity's "actor" field),
12// so it's validated before any network call, and client is expected to be
13// one from NewSafeClient. An unsigned GET is used — acceptable here since
14// this is only for resolving a Follow request's actor (to get their
15// public key and inbox URL) and most servers permit unauthenticated actor
16// fetches; authenticated fetches ("secure mode") are out of scope for now.
17func FetchActor(client *http.Client, actorURL string) (*Actor, error) {
18 if err := ValidateExternalURL(actorURL); err != nil {
19 return nil, fmt.Errorf("actor URL: %w", err)
20 }
21
22 req, err := http.NewRequest(http.MethodGet, actorURL, nil)
23 if err != nil {
24 return nil, fmt.Errorf("build request: %w", err)
25 }
26 req.Header.Set("Accept", activityContentType)
27
28 resp, err := client.Do(req)
29 if err != nil {
30 return nil, fmt.Errorf("fetch actor %s: %w", actorURL, err)
31 }
32 defer resp.Body.Close()
33 if resp.StatusCode >= 300 {
34 return nil, fmt.Errorf("fetch actor %s: HTTP %d", actorURL, resp.StatusCode)
35 }
36
37 var a Actor
38 if err := json.NewDecoder(io.LimitReader(resp.Body, maxResponseBody)).Decode(&a); err != nil {
39 return nil, fmt.Errorf("decode actor %s: %w", actorURL, err)
40 }
41 if err := ValidateExternalURL(a.Inbox); err != nil {
42 return nil, fmt.Errorf("actor inbox URL: %w", err)
43 }
44 return &a, nil
45}