From 2adcc56581420e28e5887af91578aa50ade0522a Mon Sep 17 00:00:00 2001 From: Cory Slep Date: Sun, 22 Apr 2018 14:02:45 +0200 Subject: [PATCH] Add PostOutbox Create unit tests. Fix normalizing the recipents between the object and the activity. Also allow the handlers to adjust what gets placed in the outbox, since the client-to-server API allows the server to modify the object before being delivered. --- pub/fed.go | 34 +++--- pub/fed_test.go | 202 +++++++++++++++++++++++++------- pub/internal.go | 301 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 484 insertions(+), 53 deletions(-) diff --git a/pub/fed.go b/pub/fed.go index 120b7ac..637e15e 100644 --- a/pub/fed.go +++ b/pub/fed.go @@ -271,11 +271,11 @@ func (f *federator) PostOutbox(c context.Context, w http.ResponseWriter, r *http if m, err = typer.Serialize(); err != nil { return true, err } - if err := f.addToOutbox(c, r, m); err != nil { + deliverable := false + if err = f.getPostOutboxResolver(c, &deliverable, &m).Deserialize(m); err != nil { return true, err } - deliverable := false - if err = f.getPostOutboxResolver(c, &deliverable).Deserialize(m); err != nil { + if err := f.addToOutbox(c, r, m); err != nil { return true, err } if f.EnableServer && deliverable { @@ -334,9 +334,9 @@ func (f *federator) addToOutbox(c context.Context, r *http.Request, m map[string return f.App.Set(c, outbox) } -func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool) *streams.Resolver { +func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool, toAddToOutbox *map[string]interface{}) *streams.Resolver { return &streams.Resolver{ - CreateCallback: f.handleClientCreate(c, deliverable), + CreateCallback: f.handleClientCreate(c, deliverable, toAddToOutbox), UpdateCallback: f.handleClientUpdate(c, deliverable), DeleteCallback: f.handleClientDelete(c, deliverable), FollowCallback: f.handleClientFollow(c, deliverable), @@ -351,7 +351,7 @@ func (f *federator) getPostOutboxResolver(c context.Context, deliverable *bool) } } -func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) func(s *streams.Create) error { +func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool, toAddToOutbox *map[string]interface{}) func(s *streams.Create) error { return func(s *streams.Create) error { *deliverable = true if s.LenObject() == 0 { @@ -379,12 +379,11 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f } var obj []vocab.ObjectType for i := 0; i < c.ObjectLen(); i++ { - if c.IsObject(i) { - obj = append(obj, c.GetObject(i)) - } else if c.IsObjectIRI(i) { + if !c.IsObject(i) { // TODO: Fetch IRIs as well return fmt.Errorf("unsupported: Create Activity with 'object' that is only an IRI") } + obj = append(obj, c.GetObject(i)) } objectAttributedToIds := make([]map[string]interface{}, len(obj)) for i := range objectAttributedToIds { @@ -419,10 +418,6 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f } } } - // As such, a server SHOULD copy any recipients of the Create activity to its - // object upon initial distribution, and likewise with copying recipients from - // the object to the wrapping Create activity. - // Again, presumably if it does not already exist. for _, attributedToMap := range objectAttributedToIds { for k, v := range attributedToMap { if _, ok := createActorIds[k]; !ok { @@ -436,6 +431,12 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f } } } + // As such, a server SHOULD copy any recipients of the Create activity to its + // object upon initial distribution, and likewise with copying recipients from + // the object to the wrapping Create activity. + if err := f.sameRecipients(c); err != nil { + return err + } // Create requires the client application to persist the 'object' that // was created. for _, o := range obj { @@ -443,6 +444,13 @@ func (f *federator) handleClientCreate(ctx context.Context, deliverable *bool) f return err } } + // Persist the above changes in the outbox + var err error + *toAddToOutbox = make(map[string]interface{}) + *toAddToOutbox, err = c.Serialize() + if err != nil { + return err + } return f.ClientCallbacker.Create(ctx, s) } } diff --git a/pub/fed_test.go b/pub/fed_test.go index 6ee5099..5895a00 100644 --- a/pub/fed_test.go +++ b/pub/fed_test.go @@ -34,34 +34,36 @@ const ( ) var ( - iri *url.URL - noteIRI *url.URL - noteActivityIRI *url.URL - updateActivityIRI *url.URL - testNewIRI *url.URL - sallyIRI *url.URL - sallyIRIInbox *url.URL - sallyActor *vocab.Person - sallyActorJSON []byte - samIRI *url.URL - samIRIInbox *url.URL - samIRIFollowers *url.URL - samActor *vocab.Person - samActorJSON []byte - testNote *vocab.Note - testSingleOrderedCollection *vocab.OrderedCollection - testCreateNote *vocab.Create - testUpdateNote *vocab.Update - testDeleteNote *vocab.Delete - testTombstoneNote *vocab.Tombstone - testFollow *vocab.Follow - testAcceptNote *vocab.Accept - testAcceptFollow *vocab.Accept - testRejectFollow *vocab.Reject - testAddNote *vocab.Add - testRemoveNote *vocab.Remove - testLikeNote *vocab.Like - testUndoLike *vocab.Undo + iri *url.URL + noteIRI *url.URL + noteActivityIRI *url.URL + updateActivityIRI *url.URL + testNewIRI *url.URL + sallyIRI *url.URL + sallyIRIInbox *url.URL + sallyActor *vocab.Person + sallyActorJSON []byte + samIRI *url.URL + samIRIInbox *url.URL + samIRIFollowers *url.URL + samActor *vocab.Person + samActorJSON []byte + testNote *vocab.Note + testSingleOrderedCollection *vocab.OrderedCollection + testCreateNote *vocab.Create + testUpdateNote *vocab.Update + testDeleteNote *vocab.Delete + testTombstoneNote *vocab.Tombstone + testFollow *vocab.Follow + testAcceptNote *vocab.Accept + testAcceptFollow *vocab.Accept + testRejectFollow *vocab.Reject + testAddNote *vocab.Add + testRemoveNote *vocab.Remove + testLikeNote *vocab.Like + testUndoLike *vocab.Undo + testClientExpectedNote *vocab.Note + testClientExpectedCreateNote *vocab.Create ) func init() { @@ -207,6 +209,19 @@ func init() { testUndoLike.AddActorObject(sallyActor) testUndoLike.AddObject(testLikeNote) testUndoLike.AddToObject(samActor) + + testClientExpectedNote = &vocab.Note{} + testClientExpectedNote.SetId(*noteIRI) + testClientExpectedNote.AddNameString(noteName) + testClientExpectedNote.AddContentString("This is a simple note") + testClientExpectedNote.AddAttributedToObject(sallyActor) + testClientExpectedNote.AddToObject(samActor) + testClientExpectedCreateNote = &vocab.Create{} + testClientExpectedCreateNote.SetId(*testNewIRI) + testClientExpectedCreateNote.AddSummaryString("Sally created a note") + testClientExpectedCreateNote.AddActorObject(sallyActor) + testClientExpectedCreateNote.AddObject(testClientExpectedNote) + testClientExpectedCreateNote.AddToObject(samActor) } func Must(l *time.Location, e error) *time.Location { @@ -752,9 +767,9 @@ func TestSocialPubber_PostOutbox(t *testing.T) { app.set = func(c context.Context, o PubObject) error { gotSet++ if gotSet == 1 { - gotSetOutbox = o - } else if gotSet == 2 { gotSetCreateObject = o + } else if gotSet == 2 { + gotSetOutbox = o } return nil } @@ -1038,9 +1053,9 @@ func TestPubber_PostOutbox(t *testing.T) { app.set = func(c context.Context, o PubObject) error { gotSet++ if gotSet == 1 { - gotSetOutbox = o - } else if gotSet == 2 { gotSetCreateObject = o + } else if gotSet == 2 { + gotSetOutbox = o } return nil } @@ -2892,23 +2907,130 @@ func TestPostOutbox_RequiresTarget(t *testing.T) { } func TestPostOutbox_Create_CopyToAttributedTo(t *testing.T) { - // TODO: Implement -} - -func TestPostOutbox_Create_CopyRecipientsToObject(t *testing.T) { - // TODO: Implement + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) + var gotCallbackObject *streams.Create + socialCb.create = func(c context.Context, s *streams.Create) error { + gotCallbackObject = s + return nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if e := PubObjectEquals(gotCallbackObject.Raw(), testClientExpectedCreateNote); e != nil { + t.Fatal(e) + } } func TestPostOutbox_Create_SetCreatedObject(t *testing.T) { - // TODO: Implement + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) + socialCb.create = func(c context.Context, s *streams.Create) error { + return nil + } + gotSet := 0 + var gotSetOutbox PubObject + var gotSetCreate PubObject + app.set = func(c context.Context, o PubObject) error { + gotSet++ + if gotSet == 1 { + gotSetCreate = o + } else { + gotSetOutbox = o + } + return nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + expectedOutbox := &vocab.OrderedCollection{} + expectedOutbox.AddType("OrderedCollection") + expectedOutbox.AddOrderedItemsObject(testClientExpectedCreateNote) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotSet != 2 { + t.Fatalf("expected %d, got %d", 2, gotSet) + } else if err := PubObjectEquals(gotSetCreate, testClientExpectedNote); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } else if err := PubObjectEquals(gotSetOutbox, expectedOutbox); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } } func TestPostOutbox_Create_CallsCallback(t *testing.T) { - // TODO: Implement + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) + gotCallback := 0 + var gotCallbackObject *streams.Create + socialCb.create = func(c context.Context, s *streams.Create) error { + gotCallback++ + gotCallbackObject = s + return nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if gotCallback != 1 { + t.Fatalf("expected %d, got %d", 1, gotCallback) + } else if err := PubObjectEquals(gotCallbackObject.Raw(), testClientExpectedCreateNote); err != nil { + t.Fatalf("unexpected callback object: %s", err) + } } func TestPostOutbox_Create_IsDelivered(t *testing.T) { - // TODO: Implement + app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p := NewPubberTest(t) + PreparePostOutboxTest(t, app, socialApp, fedApp, socialCb, fedCb, d, httpClient, p) + resp := httptest.NewRecorder() + req := ActivityPubRequest(httptest.NewRequest("POST", testOutboxURI, bytes.NewBuffer(MustSerialize(testCreateNote)))) + socialCb.create = func(c context.Context, s *streams.Create) error { + return nil + } + gotHttpDo := 0 + var httpDeliveryRequest *http.Request + httpClient.do = func(req *http.Request) (*http.Response, error) { + gotHttpDo++ + if gotHttpDo == 1 { + actorResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer(samActorJSON)), + } + return actorResp, nil + } else if gotHttpDo == 2 { + actorResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer(sallyActorJSON)), + } + return actorResp, nil + } else if gotHttpDo == 3 { + httpDeliveryRequest = req + okResp := &http.Response{ + StatusCode: http.StatusOK, + Body: ioutil.NopCloser(bytes.NewBuffer([]byte{})), + } + return okResp, nil + } + return nil, nil + } + handled, err := p.PostOutbox(context.Background(), resp, req) + if err != nil { + t.Fatal(err) + } else if !handled { + t.Fatalf("expected handled, got !handled") + } else if httpDeliveryRequest.Method != "POST" { + t.Fatalf("expected %s, got %s", "POST", httpDeliveryRequest.Method) + } else if s := httpDeliveryRequest.URL.String(); s != samIRIInboxString { + t.Fatalf("expected %s, got %s", samIRIInboxString, s) + } } func TestPostOutbox_Update_OverwriteUpdatedFields(t *testing.T) { diff --git a/pub/internal.go b/pub/internal.go index 71a424e..dfa6958 100644 --- a/pub/internal.go +++ b/pub/internal.go @@ -173,6 +173,307 @@ func (f *federator) wrapInCreate(o vocab.ObjectType, actor url.URL) *vocab.Creat return c } +// Ensures the activity and object have the same 'to', 'bto', 'cc', 'bcc', and +// 'audience' properties. Copy the Activity's recipients to objects, and the +// objects to the activity, but does NOT copy objects' recipients to each other. +// +// If there is any disagreement between the activity and an object, we default +// to a no-op. +func (f *federator) sameRecipients(a vocab.ActivityType) error { + // First, map recipients for each object and the activity. + to := make([]map[string]interface{}, a.ObjectLen()) + for i := 0; i < a.ObjectLen(); i++ { + to[i] = make(map[string]interface{}) + if !a.IsObject(i) { + return fmt.Errorf("sameRecipients does not support 'to' object IRIs on Activities") + } + o := a.GetObject(i) + for j := 0; j < o.ToLen(); j++ { + if o.IsToObject(j) { + id := o.GetToObject(j).GetId() + to[i][(&id).String()] = o.GetToObject(j) + } else if o.IsToLink(j) { + id := o.GetToLink(j).GetHref() + to[i][(&id).String()] = o.GetToLink(j) + } else if o.IsToIRI(j) { + id := o.GetToIRI(j) + to[i][(&id).String()] = id + } + } + } + toActivity := make(map[string]interface{}) + for i := 0; i < a.ToLen(); i++ { + if a.IsToObject(i) { + id := a.GetToObject(i).GetId() + toActivity[(&id).String()] = a.GetToObject(i) + } else if a.IsToLink(i) { + id := a.GetToLink(i).GetHref() + toActivity[(&id).String()] = a.GetToLink(i) + } else if a.IsToIRI(i) { + id := a.GetToIRI(i) + toActivity[(&id).String()] = id + } + } + bto := make([]map[string]interface{}, a.ObjectLen()) + for i := 0; i < a.ObjectLen(); i++ { + bto[i] = make(map[string]interface{}) + if !a.IsObject(i) { + return fmt.Errorf("sameRecipients does not support 'bto' object IRIs on Activities") + } + o := a.GetObject(i) + for j := 0; j < o.BtoLen(); j++ { + if o.IsBtoObject(j) { + id := o.GetBtoObject(j).GetId() + bto[i][(&id).String()] = o.GetBtoObject(j) + } else if o.IsBtoLink(j) { + id := o.GetBtoLink(j).GetHref() + bto[i][(&id).String()] = o.GetBtoLink(j) + } else if o.IsBtoIRI(j) { + id := o.GetBtoIRI(j) + bto[i][(&id).String()] = id + } + } + } + btoActivity := make(map[string]interface{}) + for i := 0; i < a.BtoLen(); i++ { + if a.IsBtoObject(i) { + id := a.GetBtoObject(i).GetId() + btoActivity[(&id).String()] = a.GetBtoObject(i) + } else if a.IsBtoLink(i) { + id := a.GetBtoLink(i).GetHref() + btoActivity[(&id).String()] = a.GetBtoLink(i) + } else if a.IsBtoIRI(i) { + id := a.GetBtoIRI(i) + btoActivity[(&id).String()] = id + } + } + cc := make([]map[string]interface{}, a.ObjectLen()) + for i := 0; i < a.ObjectLen(); i++ { + cc[i] = make(map[string]interface{}) + if !a.IsObject(i) { + return fmt.Errorf("sameRecipients does not support 'cc' object IRIs on Activities") + } + o := a.GetObject(i) + for j := 0; j < o.CcLen(); j++ { + if o.IsCcObject(j) { + id := o.GetCcObject(j).GetId() + cc[i][(&id).String()] = o.GetCcObject(j) + } else if o.IsCcLink(j) { + id := o.GetCcLink(j).GetHref() + cc[i][(&id).String()] = o.GetCcLink(j) + } else if o.IsCcIRI(j) { + id := o.GetCcIRI(j) + cc[i][(&id).String()] = id + } + } + } + ccActivity := make(map[string]interface{}) + for i := 0; i < a.CcLen(); i++ { + if a.IsCcObject(i) { + id := a.GetCcObject(i).GetId() + ccActivity[(&id).String()] = a.GetCcObject(i) + } else if a.IsCcLink(i) { + id := a.GetCcLink(i).GetHref() + ccActivity[(&id).String()] = a.GetCcLink(i) + } else if a.IsCcIRI(i) { + id := a.GetCcIRI(i) + ccActivity[(&id).String()] = id + } + } + bcc := make([]map[string]interface{}, a.ObjectLen()) + for i := 0; i < a.ObjectLen(); i++ { + bcc[i] = make(map[string]interface{}) + if !a.IsObject(i) { + return fmt.Errorf("sameRecipients does not support 'bcc' object IRIs on Activities") + } + o := a.GetObject(i) + for j := 0; j < o.BccLen(); j++ { + if o.IsBccObject(j) { + id := o.GetBccObject(j).GetId() + bcc[i][(&id).String()] = o.GetBccObject(j) + } else if o.IsBccLink(j) { + id := o.GetBccLink(j).GetHref() + bcc[i][(&id).String()] = o.GetBccLink(j) + } else if o.IsBccIRI(j) { + id := o.GetBccIRI(j) + bcc[i][(&id).String()] = id + } + } + } + bccActivity := make(map[string]interface{}) + for i := 0; i < a.BccLen(); i++ { + if a.IsBccObject(i) { + id := a.GetBccObject(i).GetId() + bccActivity[(&id).String()] = a.GetBccObject(i) + } else if a.IsBccLink(i) { + id := a.GetBccLink(i).GetHref() + bccActivity[(&id).String()] = a.GetBccLink(i) + } else if a.IsBccIRI(i) { + id := a.GetBccIRI(i) + bccActivity[(&id).String()] = id + } + } + audience := make([]map[string]interface{}, a.ObjectLen()) + for i := 0; i < a.ObjectLen(); i++ { + audience[i] = make(map[string]interface{}) + if !a.IsObject(i) { + return fmt.Errorf("sameRecipients does not support 'audience' object IRIs on Activities") + } + o := a.GetObject(i) + for j := 0; j < o.AudienceLen(); j++ { + if o.IsAudienceObject(j) { + id := o.GetAudienceObject(j).GetId() + audience[i][(&id).String()] = o.GetAudienceObject(j) + } else if o.IsAudienceLink(j) { + id := o.GetAudienceLink(j).GetHref() + audience[i][(&id).String()] = o.GetAudienceLink(j) + } else if o.IsAudienceIRI(j) { + id := o.GetAudienceIRI(j) + audience[i][(&id).String()] = id + } + } + } + audienceActivity := make(map[string]interface{}) + for i := 0; i < a.AudienceLen(); i++ { + if a.IsAudienceObject(i) { + id := a.GetAudienceObject(i).GetId() + audienceActivity[(&id).String()] = a.GetAudienceObject(i) + } else if a.IsAudienceLink(i) { + id := a.GetAudienceLink(i).GetHref() + audienceActivity[(&id).String()] = a.GetAudienceLink(i) + } else if a.IsAudienceIRI(i) { + id := a.GetAudienceIRI(i) + audienceActivity[(&id).String()] = id + } + } + // Next, add activity recipients to all objects if not already present + for k, v := range toActivity { + for i := 0; i < a.ObjectLen(); i++ { + if _, ok := to[i][k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.GetObject(i).AddToObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.GetObject(i).AddToLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.GetObject(i).AddToIRI(vIRI) + } + } + } + } + for k, v := range btoActivity { + for i := 0; i < a.ObjectLen(); i++ { + if _, ok := bto[i][k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.GetObject(i).AddBtoObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.GetObject(i).AddBtoLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.GetObject(i).AddBtoIRI(vIRI) + } + } + } + } + for k, v := range ccActivity { + for i := 0; i < a.ObjectLen(); i++ { + if _, ok := cc[i][k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.GetObject(i).AddCcObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.GetObject(i).AddCcLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.GetObject(i).AddCcIRI(vIRI) + } + } + } + } + for k, v := range bccActivity { + for i := 0; i < a.ObjectLen(); i++ { + if _, ok := bcc[i][k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.GetObject(i).AddBccObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.GetObject(i).AddBccLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.GetObject(i).AddBccIRI(vIRI) + } + } + } + } + for k, v := range audienceActivity { + for i := 0; i < a.ObjectLen(); i++ { + if _, ok := audience[i][k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.GetObject(i).AddAudienceObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.GetObject(i).AddAudienceLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.GetObject(i).AddAudienceIRI(vIRI) + } + } + } + } + // Finally, add all the objects' recipients to the activity if not + // already present. + for i := 0; i < a.ObjectLen(); i++ { + for k, v := range to[i] { + if _, ok := toActivity[k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.AddToObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.AddToLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.AddToIRI(vIRI) + } + } + } + for k, v := range bto[i] { + if _, ok := btoActivity[k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.AddBtoObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.AddBtoLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.AddBtoIRI(vIRI) + } + } + } + for k, v := range cc[i] { + if _, ok := ccActivity[k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.AddCcObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.AddCcLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.AddCcIRI(vIRI) + } + } + } + for k, v := range bcc[i] { + if _, ok := bccActivity[k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.AddBccObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.AddBccLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.AddBccIRI(vIRI) + } + } + } + for k, v := range audience[i] { + if _, ok := audienceActivity[k]; !ok { + if vObj, ok := v.(vocab.ObjectType); ok { + a.AddAudienceObject(vObj) + } else if vLink, ok := v.(vocab.LinkType); ok { + a.AddAudienceLink(vLink) + } else if vIRI, ok := v.(url.URL); ok { + a.AddAudienceIRI(vIRI) + } + } + } + } + return nil +} + // TODO: (Section 7) HTTP caching mechanisms [RFC7234] SHOULD be respected when appropriate, both when receiving responses from other servers as well as sending responses to other servers. // deliver will complete the peer-to-peer sending of a federated message to