Blob


1 package apub
3 import (
4 "bytes"
5 "crypto/rsa"
6 "encoding/json"
7 "fmt"
8 "io"
9 "net/http"
10 "os"
11 "strings"
12 )
14 var DefaultClient Client = Client{Client: http.DefaultClient}
16 func Lookup(id string) (*Activity, error) {
17 return DefaultClient.Lookup(id)
18 }
20 func LookupActor(id string) (*Actor, error) {
21 return DefaultClient.LookupActor(id)
22 }
24 type Client struct {
25 *http.Client
26 // Key is a RSA private key which will be used to sign requests.
27 Key *rsa.PrivateKey
28 // PubKeyID is a URL where the corresponding public key of Key
29 // may be accessed. This must be set if Key is also set.
30 PubKeyID string // actor.PublicKey.ID
31 }
33 func (c *Client) Lookup(id string) (*Activity, error) {
34 if !strings.HasPrefix(id, "http") {
35 return nil, fmt.Errorf("id is not a HTTP URL")
36 }
37 if c.Client == nil {
38 c.Client = http.DefaultClient
39 }
41 req, err := newRequest(http.MethodGet, id, nil, c.Key, c.PubKeyID)
42 if err != nil {
43 return nil, fmt.Errorf("new request: %w", err)
44 }
45 resp, err := c.Do(req)
46 if err != nil {
47 return nil, err
48 }
49 defer resp.Body.Close()
50 if resp.StatusCode == http.StatusNotFound {
51 return nil, ErrNotExist
52 } else if resp.StatusCode >= 400 {
53 return nil, fmt.Errorf("non-ok response status %s", resp.Status)
54 }
55 return Decode(resp.Body)
56 }
58 func (c *Client) LookupActor(id string) (*Actor, error) {
59 activity, err := c.Lookup(id)
60 if err != nil {
61 return nil, err
62 }
63 switch activity.Type {
64 case "Application", "Group", "Organization", "Person", "Service":
65 return activityToActor(activity), nil
66 case "Collection", "OrderedCollection":
67 // probably followers. let caller work out what it wants to do
68 return activityToActor(activity), nil
69 }
70 return nil, fmt.Errorf("bad object Type %s", activity.Type)
71 }
73 func activityToActor(activity *Activity) *Actor {
74 actor := &Actor{
75 AtContext: activity.AtContext,
76 ID: activity.ID,
77 Type: activity.Type,
78 Name: activity.Name,
79 Username: activity.Username,
80 Inbox: activity.Inbox,
81 Outbox: activity.Outbox,
82 Followers: activity.Followers,
83 Published: activity.Published,
84 Summary: activity.Summary,
85 Endpoints: activity.Endpoints,
86 }
87 if activity.PublicKey != nil {
88 actor.PublicKey = *activity.PublicKey
89 }
90 return actor
91 }
93 func (c *Client) Send(inbox string, activity *Activity) (*Activity, error) {
94 b, err := json.Marshal(activity)
95 if err != nil {
96 return nil, fmt.Errorf("encode outgoing activity: %w", err)
97 }
98 req, err := newRequest(http.MethodPost, inbox, bytes.NewReader(b), c.Key, c.PubKeyID)
99 if err != nil {
100 return nil, err
102 resp, err := c.Do(req)
103 if err != nil {
104 return nil, err
106 switch resp.StatusCode {
107 case http.StatusOK, http.StatusAccepted, http.StatusNoContent:
108 return nil, nil
109 case http.StatusNotFound:
110 return nil, fmt.Errorf("no such inbox %s", inbox)
111 default:
112 io.Copy(os.Stderr, resp.Body)
113 resp.Body.Close()
114 return nil, fmt.Errorf("non-ok response status %s", resp.Status)
118 func newRequest(method, url string, body io.Reader, key *rsa.PrivateKey, pubkeyURL string) (*http.Request, error) {
119 req, err := http.NewRequest(method, url, body)
120 if err != nil {
121 return nil, err
123 req.Header.Set("Accept", ContentType)
124 if body != nil {
125 req.Header.Set("Content-Type", ContentType)
127 if key != nil {
128 if err := Sign(req, key, pubkeyURL); err != nil {
129 return nil, fmt.Errorf("sign request: %w", err)
132 return req, nil