Blob


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