-
Notifications
You must be signed in to change notification settings - Fork 10
/
Copy pathtyper.go
396 lines (368 loc) · 12 KB
/
typer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
package activitypub
import (
"path/filepath"
"strings"
"github.com/go-ap/errors"
)
// CollectionPath
type CollectionPath string
// CollectionPaths
type CollectionPaths []CollectionPath
const (
Unknown = CollectionPath("")
// Outbox
//
// https://www.w3.org/TR/activitypub/#outbox
//
// The outbox is discovered through the outbox property of an actor's profile.
// The outbox MUST be an OrderedCollection.
//
// The outbox stream contains activities the user has published, subject to the ability of the requestor
// to retrieve the activity (that is, the contents of the outbox are filtered by the permissions
// of the person reading it). If a user submits a request without Authorization the server should respond
// with all of the Public posts. This could potentially be all relevant objects published by the user,
// though the number of available items is left to the discretion of those implementing and deploying the server.
//
// The outbox accepts HTTP POST requests, with behaviour described in Client to Server Interactions.
Outbox = CollectionPath("outbox")
// Inbox
//
// https://www.w3.org/TR/activitypub/#inbox
//
// The inbox is discovered through the inbox property of an actor's profile. The inbox MUST be an OrderedCollection.
//
// The inbox stream contains all activities received by the actor. The server SHOULD filter content according
// to the requester's permission. In general, the owner of an inbox is likely to be able to access
// all of their inbox contents. Depending on access control, some other content may be public,
// whereas other content may require authentication for non-owner users, if they can access the inbox at all.
//
// The server MUST perform de-duplication of activities returned by the inbox. Duplication can occur
// if an activity is addressed both to an actor's followers, and a specific actor who also follows
// the recipient actor, and the server has failed to de-duplicate the recipients list.
// Such deduplication MUST be performed by comparing the id of the activities and dropping any activities already seen.
//
// The inboxes of actors on federated servers accepts HTTP POST requests, with behaviour described in Delivery.
// Non-federated servers SHOULD return a 405 Method Not Allowed upon receipt of a POST request.
Inbox = CollectionPath("inbox")
// Followers
//
// https://www.w3.org/TR/activitypub/#followers
//
// Every actor SHOULD have a followers collection. This is a list of everyone who has sent a Follow activity
// for the actor, added as a side effect. This is where one would find a list of all the actors that are following
// the actor. The followers collection MUST be either an OrderedCollection or a Collection and MAY be filtered
// on privileges of an authenticated user or as appropriate when no authentication is given.
//
// NOTE: Default for notification targeting
// The follow activity generally is a request to see the objects an actor creates.
// This makes the Followers collection an appropriate default target for delivery of notifications.
Followers = CollectionPath("followers")
// Following
//
// https://www.w3.org/TR/activitypub/#following
//
// Every actor SHOULD have a following collection. This is a list of everybody that the actor has followed,
// added as a side effect. The following collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
Following = CollectionPath("following")
// Liked
//
// https://www.w3.org/TR/activitypub/#liked
//
// Every actor MAY have a liked collection. This is a list of every object from all of the actor's Like activities,
// added as a side effect. The liked collection MUST be either an OrderedCollection or a Collection and
// MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
Liked = CollectionPath("liked")
// Likes
//
// https://www.w3.org/TR/activitypub/#likes
//
// Every object MAY have a likes collection. This is a list of all Like activities with this object as the object
// property, added as a side effect. The likes collection MUST be either an OrderedCollection or a Collection
// and MAY be filtered on privileges of an authenticated user or as appropriate when no authentication is given.
//
// NOTE
// Care should be taken to not confuse the the likes collection with the similarly named but different liked
// collection. In sum:
//
// * liked: Specifically a property of actors. This is a collection of Like activities performed by the actor,
// added to the collection as a side effect of delivery to the outbox.
// * likes: May be a property of any object. This is a collection of Like activities referencing this object,
// added to the collection as a side effect of delivery to the inbox.
Likes = CollectionPath("likes")
// Shares
//
// https://www.w3.org/TR/activitypub/#shares
//
// Every object MAY have a shares collection. This is a list of all Announce activities with this object
// as the object property, added as a side effect. The shares collection MUST be either an OrderedCollection
// or a Collection and MAY be filtered on privileges of an authenticated user or as appropriate when
// no authentication is given.
Shares = CollectionPath("shares")
Replies = CollectionPath("replies") // activitystreams
)
var (
validActivityCollection = CollectionPaths{
Outbox,
Inbox,
Likes,
Shares,
Replies, // activitystreams
}
OfObject = CollectionPaths{
Likes,
Shares,
Replies,
}
OfActor = CollectionPaths{
Outbox,
Inbox,
Liked,
Following,
Followers,
}
ActivityPubCollections = CollectionPaths{
Outbox,
Inbox,
Liked,
Following,
Followers,
Likes,
Shares,
Replies,
}
)
func (t CollectionPaths) Contains(typ CollectionPath) bool {
for _, tt := range t {
if strings.EqualFold(string(typ), string(tt)) {
return true
}
}
return false
}
// Split splits the IRI in an actor IRI and its CollectionPath
// if the CollectionPath is found in the elements in the t CollectionPaths slice
func (t CollectionPaths) Split(i IRI) (IRI, CollectionPath) {
if u, err := i.URL(); err == nil {
maybeActor, maybeCol := filepath.Split(u.Path)
if len(maybeActor) == 0 {
return i, Unknown
}
tt := CollectionPath(maybeCol)
if !t.Contains(tt) {
tt = ""
}
u.Path = strings.TrimRight(maybeActor, "/")
iri := IRI(u.String())
return iri, tt
}
maybeActor, maybeCol := filepath.Split(i.String())
if len(maybeActor) == 0 {
return i, Unknown
}
tt := CollectionPath(maybeCol)
if !t.Contains(tt) {
return i, Unknown
}
maybeActor = strings.TrimRight(maybeActor, "/")
return IRI(maybeActor), tt
}
// IRIf formats an IRI from an existing IRI and the CollectionPath type
func IRIf(i IRI, t CollectionPath) IRI {
si := i.String()
s := strings.Builder{}
_, _ = s.WriteString(si)
if l := len(si); l == 0 || si[l-1] != '/' {
_, _ = s.WriteRune('/')
}
_, _ = s.WriteString(string(t))
return IRI(s.String())
}
// IRI gives us the IRI of the t CollectionPath type corresponding to the i Item,
// or generates a new one if not found.
func (t CollectionPath) IRI(i Item) IRI {
if IsNil(i) {
return IRIf("", t)
}
if IsObject(i) {
if it := t.Of(i); !IsNil(it) {
return it.GetLink()
}
}
return IRIf(i.GetLink(), t)
}
func (t CollectionPath) ofItemCollection(col ItemCollection) Item {
iriCol := make(ItemCollection, len(col))
for i, it := range col {
iriCol[i] = t.Of(it)
}
return iriCol
}
func (t CollectionPath) ofObject(ob *Object) Item {
var it Item
switch t {
case Likes:
it = ob.Likes
case Shares:
it = ob.Shares
case Replies:
it = ob.Replies
}
return it
}
func (t CollectionPath) ofActor(a *Actor) Item {
var it Item
switch t {
case Inbox:
it = a.Inbox
case Outbox:
it = a.Outbox
case Liked:
it = a.Liked
case Following:
it = a.Following
case Followers:
it = a.Followers
}
return it
}
func (t CollectionPath) ofIRI(iri IRI) Item {
if len(iri) == 0 {
return nil
}
return iri.AddPath(string(t))
}
func (t CollectionPath) ofItem(i Item) Item {
if IsNil(i) {
return nil
}
var it Item
if IsIRI(i) {
it = t.ofIRI(i.GetLink())
}
if IsItemCollection(i) {
_ = OnItemCollection(i, func(col *ItemCollection) error {
it = t.ofItemCollection(*col)
return nil
})
}
if OfActor.Contains(t) && ActorTypes.Contains(i.GetType()) {
_ = OnActor(i, func(a *Actor) error {
it = t.ofActor(a)
return nil
})
} else {
_ = OnObject(i, func(o *Object) error {
it = t.ofObject(o)
return nil
})
}
return it
}
// Of returns the property of the i [Item] that corresponds to the t [CollectionPath] type.
// If it's missing, it returns nil.
func (t CollectionPath) Of(i Item) Item {
return t.ofItem(i)
}
// OfActor returns the base IRI of received i, if i represents an IRI matching CollectionPath type t
func (t CollectionPath) OfActor(i IRI) (IRI, error) {
maybeActor, maybeCol := filepath.Split(i.String())
if strings.EqualFold(maybeCol, string(t)) {
maybeActor = strings.TrimRight(maybeActor, "/")
return IRI(maybeActor), nil
}
return EmptyIRI, errors.Newf("IRI does not represent a valid %s CollectionPath", t)
}
// Split returns the base IRI of received i, if i represents an IRI matching CollectionPath type t
func Split(i IRI) (IRI, CollectionPath) {
return ActivityPubCollections.Split(i)
}
func getValidActivityCollection(t CollectionPath) CollectionPath {
if validActivityCollection.Contains(t) {
return t
}
return Unknown
}
// ValidActivityCollection shows if the current ActivityPub end-point type is a valid one for handling Activities
func ValidActivityCollection(typ CollectionPath) bool {
return getValidActivityCollection(typ) != Unknown
}
var validObjectCollection = []CollectionPath{
Following,
Followers,
Liked,
}
func getValidObjectCollection(typ CollectionPath) CollectionPath {
for _, t := range validObjectCollection {
if strings.EqualFold(string(typ), string(t)) {
return t
}
}
return Unknown
}
// ValidActivityCollection shows if the current ActivityPub end-point type is a valid one for handling Objects
func ValidObjectCollection(typ CollectionPath) bool {
return getValidObjectCollection(typ) != Unknown
}
func getValidCollection(typ CollectionPath) CollectionPath {
if typ := getValidActivityCollection(typ); typ != Unknown {
return typ
}
if typ := getValidObjectCollection(typ); typ != Unknown {
return typ
}
return Unknown
}
func ValidCollection(typ CollectionPath) bool {
return getValidCollection(typ) != Unknown
}
func ValidCollectionIRI(i IRI) bool {
_, t := Split(i)
return getValidCollection(t) != Unknown
}
// AddTo adds CollectionPath type IRI on the corresponding property of the i Item
func (t CollectionPath) AddTo(i Item) (IRI, bool) {
if IsNil(i) || !i.IsObject() {
return NilIRI, false
}
status := false
var iri IRI
if OfActor.Contains(t) {
OnActor(i, func(a *Actor) error {
if status = t == Inbox && IsNil(a.Inbox); status {
a.Inbox = IRIf(a.GetLink(), t)
iri = a.Inbox.GetLink()
} else if status = t == Outbox && IsNil(a.Outbox); status {
a.Outbox = IRIf(a.GetLink(), t)
iri = a.Outbox.GetLink()
} else if status = t == Liked && IsNil(a.Liked); status {
a.Liked = IRIf(a.GetLink(), t)
iri = a.Liked.GetLink()
} else if status = t == Following && IsNil(a.Following); status {
a.Following = IRIf(a.GetLink(), t)
iri = a.Following.GetLink()
} else if status = t == Followers && IsNil(a.Followers); status {
a.Followers = IRIf(a.GetLink(), t)
iri = a.Followers.GetLink()
}
return nil
})
} else if OfObject.Contains(t) {
OnObject(i, func(o *Object) error {
if status = t == Likes && IsNil(o.Likes); status {
o.Likes = IRIf(o.GetLink(), t)
iri = o.Likes.GetLink()
} else if status = t == Shares && IsNil(o.Shares); status {
o.Shares = IRIf(o.GetLink(), t)
iri = o.Shares.GetLink()
} else if status = t == Replies && IsNil(o.Replies); status {
o.Replies = IRIf(o.GetLink(), t)
iri = o.Replies.GetLink()
}
return nil
})
} else {
iri = IRIf(i.GetLink(), t)
}
return iri, status
}