Blame


1 1d5ddf5d 2024-02-20 o package apub
2 1d5ddf5d 2024-02-20 o
3 1d5ddf5d 2024-02-20 o import (
4 1d5ddf5d 2024-02-20 o "bytes"
5 1d5ddf5d 2024-02-20 o "fmt"
6 77918b00 2024-03-07 o "io"
7 1d5ddf5d 2024-02-20 o "net/mail"
8 71191436 2024-02-28 o "net/smtp"
9 1d5ddf5d 2024-02-20 o "strings"
10 1d5ddf5d 2024-02-20 o "time"
11 1d5ddf5d 2024-02-20 o )
12 1d5ddf5d 2024-02-20 o
13 d3dfb672 2024-03-18 o func MarshalMail(activity *Activity, client *Client) ([]byte, error) {
14 d3dfb672 2024-03-18 o msg, err := marshalMail(activity, client)
15 d3dfb672 2024-03-18 o if err != nil {
16 d3dfb672 2024-03-18 o return nil, err
17 d3dfb672 2024-03-18 o }
18 d3dfb672 2024-03-18 o return encodeMsg(msg), nil
19 d3dfb672 2024-03-18 o }
20 1d5ddf5d 2024-02-20 o
21 d3dfb672 2024-03-18 o func marshalMail(activity *Activity, client *Client) (*mail.Message, error) {
22 d3dfb672 2024-03-18 o if client == nil {
23 d3dfb672 2024-03-18 o client = &DefaultClient
24 d3dfb672 2024-03-18 o }
25 d3dfb672 2024-03-18 o
26 d3dfb672 2024-03-18 o msg := new(mail.Message)
27 d3dfb672 2024-03-18 o msg.Header = make(mail.Header)
28 d3dfb672 2024-03-18 o var actors []Actor
29 d3dfb672 2024-03-18 o from, err := client.LookupActor(activity.AttributedTo)
30 1d5ddf5d 2024-02-20 o if err != nil {
31 d3dfb672 2024-03-18 o return nil, fmt.Errorf("build From: lookup actor %s: %w", activity.AttributedTo, err)
32 1d5ddf5d 2024-02-20 o }
33 d3dfb672 2024-03-18 o actors = append(actors, *from)
34 d3dfb672 2024-03-18 o msg.Header["From"] = []string{from.Address().String()}
35 1d5ddf5d 2024-02-20 o
36 d3dfb672 2024-03-18 o var addrs, collections []string
37 d3dfb672 2024-03-18 o for _, id := range activity.To {
38 d3dfb672 2024-03-18 o if id == PublicCollection {
39 77918b00 2024-03-07 o continue
40 77918b00 2024-03-07 o }
41 d3dfb672 2024-03-18 o
42 d3dfb672 2024-03-18 o a, err := client.LookupActor(id)
43 77918b00 2024-03-07 o if err != nil {
44 d3dfb672 2024-03-18 o return nil, fmt.Errorf("build To: lookup actor %s: %w", id, err)
45 77918b00 2024-03-07 o }
46 d3dfb672 2024-03-18 o if a.Type == "Collection" || a.Type == "OrderedCollection" {
47 d3dfb672 2024-03-18 o collections = append(collections, a.ID)
48 d3dfb672 2024-03-18 o } else {
49 d3dfb672 2024-03-18 o addrs = append(addrs, a.Address().String())
50 d3dfb672 2024-03-18 o actors = append(actors, *a)
51 d3dfb672 2024-03-18 o }
52 77918b00 2024-03-07 o }
53 d3dfb672 2024-03-18 o for _, id := range collections {
54 d3dfb672 2024-03-18 o if i := indexFollowers(actors, id); i >= 0 {
55 d3dfb672 2024-03-18 o addrs = append(addrs, actors[i].FollowersAddress().String())
56 d3dfb672 2024-03-18 o }
57 d3dfb672 2024-03-18 o }
58 d3dfb672 2024-03-18 o msg.Header["To"] = addrs
59 77918b00 2024-03-07 o
60 d3dfb672 2024-03-18 o addrs, collections = []string{}, []string{}
61 d3dfb672 2024-03-18 o for _, id := range activity.CC {
62 d3dfb672 2024-03-18 o if id == PublicCollection {
63 d3dfb672 2024-03-18 o continue
64 1d5ddf5d 2024-02-20 o }
65 d3dfb672 2024-03-18 o
66 d3dfb672 2024-03-18 o a, err := client.LookupActor(id)
67 d3dfb672 2024-03-18 o if err != nil {
68 d3dfb672 2024-03-18 o return nil, fmt.Errorf("build CC: lookup actor %s: %w", id, err)
69 d3dfb672 2024-03-18 o }
70 d3dfb672 2024-03-18 o if a.Type == "Collection" || a.Type == "OrderedCollection" {
71 d3dfb672 2024-03-18 o collections = append(collections, a.ID)
72 d3dfb672 2024-03-18 o continue
73 d3dfb672 2024-03-18 o }
74 d3dfb672 2024-03-18 o addrs = append(addrs, a.Address().String())
75 d3dfb672 2024-03-18 o actors = append(actors, *a)
76 1d5ddf5d 2024-02-20 o }
77 d3dfb672 2024-03-18 o for _, id := range collections {
78 d3dfb672 2024-03-18 o if i := indexFollowers(actors, id); i >= 0 {
79 d3dfb672 2024-03-18 o addrs = append(addrs, actors[i].FollowersAddress().String())
80 d3dfb672 2024-03-18 o }
81 d3dfb672 2024-03-18 o }
82 d3dfb672 2024-03-18 o msg.Header["CC"] = addrs
83 1d5ddf5d 2024-02-20 o
84 af1bda8f 2024-03-18 o msg.Header["Date"] = []string{activity.Published.Format(time.RFC1123Z)}
85 d3dfb672 2024-03-18 o msg.Header["Message-ID"] = []string{"<" + activity.ID + ">"}
86 d3dfb672 2024-03-18 o msg.Header["Subject"] = []string{activity.Name}
87 1d5ddf5d 2024-02-20 o if activity.Audience != "" {
88 d3dfb672 2024-03-18 o msg.Header["List-ID"] = []string{"<" + activity.Audience + ">"}
89 1d5ddf5d 2024-02-20 o }
90 1d5ddf5d 2024-02-20 o if activity.InReplyTo != "" {
91 d3dfb672 2024-03-18 o msg.Header["In-Reply-To"] = []string{"<" + activity.InReplyTo + ">"}
92 1d5ddf5d 2024-02-20 o }
93 1d5ddf5d 2024-02-20 o
94 d3dfb672 2024-03-18 o msg.Body = strings.NewReader(activity.Content)
95 d3dfb672 2024-03-18 o msg.Header["Content-Type"] = []string{"text/html; charset=utf-8"}
96 1d5ddf5d 2024-02-20 o if activity.Source.Content != "" && activity.Source.MediaType == "text/markdown" {
97 d3dfb672 2024-03-18 o msg.Body = strings.NewReader(activity.Source.Content)
98 d3dfb672 2024-03-18 o msg.Header["Content-Type"] = []string{"text/plain; charset=utf-8"}
99 184e4de6 2024-03-13 o } else if activity.MediaType == "text/markdown" {
100 d3dfb672 2024-03-18 o msg.Header["Content-Type"] = []string{"text/plain; charset=utf-8"}
101 1d5ddf5d 2024-02-20 o }
102 d3dfb672 2024-03-18 o return msg, nil
103 1d5ddf5d 2024-02-20 o }
104 71191436 2024-02-28 o
105 d3dfb672 2024-03-18 o func indexFollowers(actors []Actor, id string) int {
106 d3dfb672 2024-03-18 o for i := range actors {
107 d3dfb672 2024-03-18 o if actors[i].Followers == id {
108 d3dfb672 2024-03-18 o return i
109 d3dfb672 2024-03-18 o }
110 d3dfb672 2024-03-18 o }
111 d3dfb672 2024-03-18 o return -1
112 d3dfb672 2024-03-18 o }
113 d3dfb672 2024-03-18 o
114 d3dfb672 2024-03-18 o func UnmarshalMail(msg *mail.Message, client *Client) (*Activity, error) {
115 d3dfb672 2024-03-18 o if client == nil {
116 d3dfb672 2024-03-18 o client = &DefaultClient
117 d3dfb672 2024-03-18 o }
118 662ec973 2024-03-14 o ct := msg.Header.Get("Content-Type")
119 662ec973 2024-03-14 o if strings.HasPrefix(ct, "multipart") {
120 662ec973 2024-03-14 o return nil, fmt.Errorf("cannot unmarshal from multipart message")
121 662ec973 2024-03-14 o }
122 662ec973 2024-03-14 o enc := msg.Header.Get("Content-Transfer-Encoding")
123 662ec973 2024-03-14 o if enc == "quoted-printable" {
124 662ec973 2024-03-14 o return nil, fmt.Errorf("cannot decode message with transfer encoding: %s", enc)
125 662ec973 2024-03-14 o }
126 662ec973 2024-03-14 o
127 77918b00 2024-03-07 o date, err := msg.Header.Date()
128 71191436 2024-02-28 o if err != nil {
129 77918b00 2024-03-07 o return nil, fmt.Errorf("parse message date: %w", err)
130 77918b00 2024-03-07 o }
131 77918b00 2024-03-07 o from, err := msg.Header.AddressList("From")
132 77918b00 2024-03-07 o if err != nil {
133 77918b00 2024-03-07 o return nil, fmt.Errorf("parse From: %w", err)
134 77918b00 2024-03-07 o }
135 d3dfb672 2024-03-18 o wfrom, err := client.Finger(from[0].Address)
136 77918b00 2024-03-07 o if err != nil {
137 77918b00 2024-03-07 o return nil, fmt.Errorf("webfinger From: %w", err)
138 77918b00 2024-03-07 o }
139 77918b00 2024-03-07 o
140 25fed994 2024-03-12 o var wto, wcc []string
141 d802abd8 2024-03-12 o var tags []Activity
142 25fed994 2024-03-12 o if msg.Header.Get("To") != "" {
143 25fed994 2024-03-12 o to, err := msg.Header.AddressList("To")
144 25fed994 2024-03-12 o // ignore missing To line. Some ActivityPub servers only have the
145 25fed994 2024-03-12 o // PublicCollection listed, which we don't care about.
146 25fed994 2024-03-12 o if err != nil {
147 25fed994 2024-03-12 o return nil, fmt.Errorf("parse To address list: %w", err)
148 25fed994 2024-03-12 o }
149 d3dfb672 2024-03-18 o actors, err := client.fingerAll(to)
150 25fed994 2024-03-12 o if err != nil {
151 25fed994 2024-03-12 o return nil, fmt.Errorf("webfinger To addresses: %w", err)
152 25fed994 2024-03-12 o }
153 2469f664 2024-03-12 o wto = make([]string, len(actors))
154 2469f664 2024-03-12 o for i, a := range actors {
155 d802abd8 2024-03-12 o addr := strings.Trim(to[i].Address, "<>")
156 eec64935 2024-03-16 o if strings.Contains(addr, "+followers") {
157 eec64935 2024-03-16 o wto[i] = a.Followers
158 eec64935 2024-03-16 o continue
159 eec64935 2024-03-16 o }
160 eec64935 2024-03-16 o tags = append(tags, Activity{Type: "Mention", Href: a.ID, Name: "@" + addr})
161 2469f664 2024-03-12 o wto[i] = a.ID
162 2469f664 2024-03-12 o }
163 77918b00 2024-03-07 o }
164 77918b00 2024-03-07 o if msg.Header.Get("CC") != "" {
165 77918b00 2024-03-07 o cc, err := msg.Header.AddressList("CC")
166 77918b00 2024-03-07 o if err != nil {
167 77918b00 2024-03-07 o return nil, fmt.Errorf("parse CC address list: %w", err)
168 77918b00 2024-03-07 o }
169 d3dfb672 2024-03-18 o actors, err := client.fingerAll(cc)
170 77918b00 2024-03-07 o if err != nil {
171 77918b00 2024-03-07 o return nil, fmt.Errorf("webfinger CC addresses: %w", err)
172 77918b00 2024-03-07 o }
173 2469f664 2024-03-12 o wcc = make([]string, len(actors))
174 2469f664 2024-03-12 o for i, a := range actors {
175 eec64935 2024-03-16 o if strings.Contains(cc[i].Address, "+followers") {
176 eec64935 2024-03-16 o wcc[i] = a.Followers
177 eec64935 2024-03-16 o continue
178 eec64935 2024-03-16 o }
179 2469f664 2024-03-12 o wcc[i] = a.ID
180 2469f664 2024-03-12 o }
181 77918b00 2024-03-07 o }
182 77918b00 2024-03-07 o
183 77918b00 2024-03-07 o buf := &bytes.Buffer{}
184 77918b00 2024-03-07 o if _, err := io.Copy(buf, msg.Body); err != nil {
185 77918b00 2024-03-07 o return nil, fmt.Errorf("read message body: %v", err)
186 77918b00 2024-03-07 o }
187 d3dfb672 2024-03-18 o content := strings.TrimSpace(strings.ReplaceAll(buf.String(), "\r", ""))
188 77918b00 2024-03-07 o
189 77918b00 2024-03-07 o return &Activity{
190 186ac3bf 2024-03-12 o AtContext: NormContext,
191 77918b00 2024-03-07 o Type: "Note",
192 77918b00 2024-03-07 o AttributedTo: wfrom.ID,
193 77918b00 2024-03-07 o To: wto,
194 77918b00 2024-03-07 o CC: wcc,
195 77918b00 2024-03-07 o MediaType: "text/markdown",
196 77918b00 2024-03-07 o Name: strings.TrimSpace(msg.Header.Get("Subject")),
197 d3dfb672 2024-03-18 o Content: content,
198 77918b00 2024-03-07 o InReplyTo: strings.Trim(msg.Header.Get("In-Reply-To"), "<>"),
199 77918b00 2024-03-07 o Published: &date,
200 d802abd8 2024-03-12 o Tag: tags,
201 77918b00 2024-03-07 o }, nil
202 77918b00 2024-03-07 o }
203 77918b00 2024-03-07 o
204 77918b00 2024-03-07 o func SendMail(addr string, auth smtp.Auth, from string, to []string, activity *Activity) error {
205 d3dfb672 2024-03-18 o msg, err := MarshalMail(activity, nil)
206 77918b00 2024-03-07 o if err != nil {
207 71191436 2024-02-28 o return fmt.Errorf("marshal to mail message: %w", err)
208 71191436 2024-02-28 o }
209 77918b00 2024-03-07 o return smtp.SendMail(addr, auth, from, to, msg)
210 71191436 2024-02-28 o }
211 d3dfb672 2024-03-18 o
212 d3dfb672 2024-03-18 o func encodeMsg(msg *mail.Message) []byte {
213 d3dfb672 2024-03-18 o buf := &bytes.Buffer{}
214 d3dfb672 2024-03-18 o // Lead with "From", end with "Subject" to make some mail clients happy.
215 d3dfb672 2024-03-18 o fmt.Fprintln(buf, "From:", msg.Header.Get("From"))
216 d3dfb672 2024-03-18 o for k, v := range msg.Header {
217 d3dfb672 2024-03-18 o switch k {
218 d3dfb672 2024-03-18 o case "Subject", "From":
219 d3dfb672 2024-03-18 o continue
220 d3dfb672 2024-03-18 o default:
221 d3dfb672 2024-03-18 o fmt.Fprintf(buf, "%s: %s\n", k, strings.Join(v, ", "))
222 d3dfb672 2024-03-18 o }
223 d3dfb672 2024-03-18 o }
224 d3dfb672 2024-03-18 o fmt.Fprintln(buf, "Subject:", msg.Header.Get("Subject"))
225 d3dfb672 2024-03-18 o fmt.Fprintln(buf)
226 d3dfb672 2024-03-18 o io.Copy(buf, msg.Body)
227 d3dfb672 2024-03-18 o return buf.Bytes()
228 d3dfb672 2024-03-18 o }