2018-01-26 08:19:21 +09:00
package pub
import (
2018-02-05 02:50:32 +09:00
"bytes"
2018-02-20 08:09:29 +09:00
"context"
2018-01-26 08:19:21 +09:00
"encoding/json"
"fmt"
"github.com/go-fed/activity/streams"
"github.com/go-fed/activity/vocab"
"io/ioutil"
"net/http"
"net/url"
"strings"
"time"
)
const (
2018-02-01 06:43:13 +09:00
postContentTypeHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
responseContentTypeHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
getAcceptHeader = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
contentTypeHeader = "Content-Type"
acceptHeader = "Accept"
publicActivityPub = "https://www.w3.org/ns/activitystreams#Public"
publicJsonLD = "Public"
publicJsonLDAS = "as:Public"
2018-04-09 02:17:08 +09:00
jsonLDContext = "@context"
activityPubContext = "https://www.w3.org/ns/activitystreams"
2018-01-26 08:19:21 +09:00
)
var alternatives = [ ] string { "application/activity+json" }
func trimAll ( s [ ] string ) [ ] string {
var r [ ] string
for _ , e := range s {
r = append ( r , strings . Trim ( e , " " ) )
}
return r
}
func headerEqualsOneOf ( header string , acceptable [ ] string ) bool {
sanitizedHeader := strings . Join ( trimAll ( strings . Split ( header , ";" ) ) , ";" )
for _ , v := range acceptable {
// Remove any number of whitespace after ;'s
sanitizedV := strings . Join ( trimAll ( strings . Split ( v , ";" ) ) , ";" )
if sanitizedHeader == sanitizedV {
return true
}
}
return false
}
func isActivityPubPost ( r * http . Request ) bool {
return r . Method == "POST" && headerEqualsOneOf ( r . Header . Get ( contentTypeHeader ) , [ ] string { postContentTypeHeader , contentTypeHeader } )
}
func isActivityPubGet ( r * http . Request ) bool {
return r . Method == "GET" && headerEqualsOneOf ( r . Header . Get ( acceptHeader ) , [ ] string { getAcceptHeader , contentTypeHeader } )
}
// isPublic determines if a target is the Public collection as defined in the
// spec, including JSON-LD compliant collections.
func isPublic ( s string ) bool {
return s == publicActivityPub || s == publicJsonLD || s == publicJsonLDAS
}
2018-04-09 02:17:08 +09:00
func addJSONLDContext ( m map [ string ] interface { } ) {
m [ jsonLDContext ] = activityPubContext
}
2018-01-26 08:19:21 +09:00
// dereference makes an HTTP GET request to an IRI in order to obtain the
// ActivityStream representation.
2018-04-04 07:23:55 +09:00
func dereference ( c HttpClient , u url . URL , agent string ) ( [ ] byte , error ) {
2018-01-26 08:19:21 +09:00
// TODO: (section 7.1) The server MUST dereference the collection (with the user's credentials)
req , err := http . NewRequest ( "GET" , u . String ( ) , nil )
if err != nil {
return nil , err
}
req . Header . Add ( acceptHeader , getAcceptHeader )
req . Header . Add ( "Accept-Charset" , "utf-8" )
req . Header . Add ( "Date" , time . Now ( ) . UTC ( ) . Format ( "Mon, 02 Jan 2006 15:04:05" ) + " GMT" )
req . Header . Add ( "User-Agent" , fmt . Sprintf ( "%s (go-fed ActivityPub)" , agent ) )
resp , err := c . Do ( req )
if err != nil {
return nil , err
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
return nil , fmt . Errorf ( "Request to %s failed (%d): %s" , u . String ( ) , resp . StatusCode , resp . Status )
}
return ioutil . ReadAll ( resp . Body )
}
2018-02-05 01:42:22 +09:00
// postToOutbox will attempt to send a POST request to the given URL with the
// body set to the provided bytes.
2018-04-04 07:23:55 +09:00
func postToOutbox ( c HttpClient , b [ ] byte , to url . URL , agent string ) error {
2018-02-05 02:50:32 +09:00
byteCopy := make ( [ ] byte , 0 , len ( b ) )
copy ( b , byteCopy )
buf := bytes . NewBuffer ( byteCopy )
req , err := http . NewRequest ( "POST" , to . String ( ) , buf )
if err != nil {
return err
}
req . Header . Add ( contentTypeHeader , postContentTypeHeader )
req . Header . Add ( "Accept-Charset" , "utf-8" )
req . Header . Add ( "Date" , time . Now ( ) . UTC ( ) . Format ( "Mon, 02 Jan 2006 15:04:05" ) + " GMT" )
req . Header . Add ( "User-Agent" , fmt . Sprintf ( "%s (go-fed ActivityPub)" , agent ) )
resp , err := c . Do ( req )
if err != nil {
return err
}
defer resp . Body . Close ( )
if resp . StatusCode != http . StatusOK {
return fmt . Errorf ( "Request to %s failed (%d): %s" , to . String ( ) , resp . StatusCode , resp . Status )
}
2018-02-05 01:42:22 +09:00
return nil
}
2018-02-05 02:32:01 +09:00
// wrapInCreate will automatically wrap the provided object in a Create
// activity. This will copy over the 'to', 'bto', 'cc', 'bcc', and 'audience'
// properties. It will also copy over the published time if present.
func ( f * federator ) wrapInCreate ( o vocab . ObjectType , actor url . URL ) * vocab . Create {
c := & vocab . Create { }
c . AddObject ( o )
c . AddActorIRI ( actor )
if o . IsPublished ( ) {
c . SetPublished ( o . GetPublished ( ) )
}
for i := 0 ; i < o . ToLen ( ) ; i ++ {
if o . IsToObject ( i ) {
c . AddToObject ( o . GetToObject ( i ) )
} else if o . IsToLink ( i ) {
c . AddToLink ( o . GetToLink ( i ) )
} else if o . IsToIRI ( i ) {
c . AddToIRI ( o . GetToIRI ( i ) )
}
}
for i := 0 ; i < o . BtoLen ( ) ; i ++ {
if o . IsBtoObject ( i ) {
c . AddBtoObject ( o . GetBtoObject ( i ) )
} else if o . IsBtoLink ( i ) {
c . AddBtoLink ( o . GetBtoLink ( i ) )
} else if o . IsBtoIRI ( i ) {
c . AddBtoIRI ( o . GetBtoIRI ( i ) )
}
}
for i := 0 ; i < o . CcLen ( ) ; i ++ {
if o . IsCcObject ( i ) {
c . AddCcObject ( o . GetCcObject ( i ) )
} else if o . IsCcLink ( i ) {
c . AddCcLink ( o . GetCcLink ( i ) )
} else if o . IsCcIRI ( i ) {
c . AddCcIRI ( o . GetCcIRI ( i ) )
}
}
for i := 0 ; i < o . BccLen ( ) ; i ++ {
if o . IsBccObject ( i ) {
c . AddBccObject ( o . GetBccObject ( i ) )
} else if o . IsBccLink ( i ) {
c . AddBccLink ( o . GetBccLink ( i ) )
} else if o . IsBccIRI ( i ) {
c . AddBccIRI ( o . GetBccIRI ( i ) )
}
}
for i := 0 ; i < o . AudienceLen ( ) ; i ++ {
if o . IsAudienceObject ( i ) {
c . AddAudienceObject ( o . GetAudienceObject ( i ) )
} else if o . IsAudienceLink ( i ) {
c . AddAudienceLink ( o . GetAudienceLink ( i ) )
} else if o . IsAudienceIRI ( i ) {
c . AddAudienceIRI ( o . GetAudienceIRI ( i ) )
}
}
return c
}
2018-04-22 21:02:45 +09:00
// 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
}
2018-01-26 08:19:21 +09:00
// 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.
2018-02-20 08:09:29 +09:00
// deliver will complete the peer-to-peer sending of a federated message to
// another server.
func ( f * federator ) deliver ( obj vocab . ActivityType ) error {
recipients , err := f . prepare ( obj )
if err != nil {
return err
}
2018-05-10 19:49:37 +09:00
return f . deliverToRecipients ( obj , recipients )
}
// deliverToRecipients will take a prepared Activity and send it to specific
// recipients without examining the activity.
func ( f * federator ) deliverToRecipients ( obj vocab . ActivityType , recipients [ ] url . URL ) error {
2018-02-20 08:09:29 +09:00
m , err := obj . Serialize ( )
if err != nil {
return err
}
2018-04-09 02:17:08 +09:00
addJSONLDContext ( m )
2018-02-20 08:09:29 +09:00
b , err := json . Marshal ( m )
if err != nil {
return err
}
for _ , to := range recipients {
2018-04-05 03:32:55 +09:00
f . deliverer . Do ( b , to , func ( b [ ] byte , u url . URL ) error {
2018-02-20 08:09:29 +09:00
return postToOutbox ( f . Client , b , u , f . Agent )
} )
}
return nil
}
2018-02-13 05:19:35 +09:00
// prepare takes a deliverableObject and returns a list of the proper recipient
// target URIs. Additionally, the deliverableObject will have any hidden
2018-01-26 08:19:21 +09:00
// hidden recipients ("bto" and "bcc") stripped from it.
2018-02-13 05:19:35 +09:00
func ( c * federator ) prepare ( o deliverableObject ) ( [ ] url . URL , error ) {
2018-01-29 04:51:12 +09:00
// Get inboxes of recipients
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
r = append ( r , getToIRIs ( o ) ... )
r = append ( r , getBToIRIs ( o ) ... )
r = append ( r , getCcIRIs ( o ) ... )
r = append ( r , getBccIRIs ( o ) ... )
r = append ( r , getAudienceIRIs ( o ) ... )
2018-02-01 06:43:13 +09:00
// TODO: Support delivery to shared inbox
// 1. When an object is being delivered to the originating actor's
// followers, a server MAY reduce the number of receiving actors
// delivered to by identifying all followers which share the same
// sharedInbox who would otherwise be individual recipients and instead
// deliver objects to said sharedInbox.
// 2. If an object is addressed to the Public special collection, a
// server MAY deliver that object to all known sharedInbox endpoints on
// the network.
r = filterURLs ( r , isPublic )
2018-02-05 01:42:22 +09:00
receiverActors , err := c . resolveInboxes ( r , 0 , c . MaxDeliveryDepth )
2018-01-26 08:19:21 +09:00
if err != nil {
return nil , err
}
2018-04-11 07:07:51 +09:00
targets , err := getInboxes ( receiverActors )
if err != nil {
return nil , err
}
2018-01-29 04:51:12 +09:00
// Get inboxes of sender(s)
2018-02-05 01:42:22 +09:00
senderActors , err := c . resolveInboxes ( getActorsAttributedToURI ( o ) , 0 , c . MaxDeliveryDepth )
2018-01-29 04:51:12 +09:00
if err != nil {
return nil , err
}
2018-04-11 07:07:51 +09:00
ignore , err := getInboxes ( senderActors )
if err != nil {
return nil , err
}
2018-01-29 04:51:12 +09:00
// Post-processing
2018-01-26 08:19:21 +09:00
r = dedupeIRIs ( targets , ignore )
stripHiddenRecipients ( o )
return r , nil
}
// resolveInboxes takes a list of Actor id URIs and returns them as concrete
2018-02-13 05:19:35 +09:00
// instances of actorObject. It applies recursively when it encounters a target
2018-01-26 08:19:21 +09:00
// that is a Collection or OrderedCollection.
2018-04-04 07:23:55 +09:00
func ( c * federator ) resolveInboxes ( r [ ] url . URL , depth int , max int ) ( [ ] actor , error ) {
2018-01-26 08:19:21 +09:00
if depth >= max {
return nil , nil
}
2018-04-04 07:23:55 +09:00
a := make ( [ ] actor , 0 , len ( r ) )
2018-01-26 08:19:21 +09:00
for _ , u := range r {
// Do not retry here -- if a dereference fails, then fail the
// entire delivery.
resp , err := dereference ( c . Client , u , c . Agent )
if err != nil {
return nil , err
}
var m map [ string ] interface { }
if err = json . Unmarshal ( resp , & m ) ; err != nil {
return nil , err
}
2018-04-04 07:23:55 +09:00
var actor actor
2018-01-26 08:19:21 +09:00
var co * streams . Collection
var oc * streams . OrderedCollection
var cp * streams . CollectionPage
var ocp * streams . OrderedCollectionPage
// Set AT MOST one of: co, oc, cp, ocp
// If none of these are set, attempt to use actor
if err = toActorCollectionResolver ( & actor , & co , & oc , & cp , & ocp ) . Deserialize ( m ) ; err != nil {
return nil , err
}
// If a recipient is a Collection or OrderedCollection, then the
// server MUST dereference the collection. Note that this also
// applies to CollectionPage and OrderedCollectionPage.
var uris [ ] url . URL
if co != nil {
uris := getURIsInItemer ( co . Raw ( ) )
actors , err := c . resolveInboxes ( uris , depth + 1 , max )
if err != nil {
return nil , err
}
a = append ( a , actors ... )
} else if oc != nil {
uris := getURIsInOrderedItemer ( oc . Raw ( ) )
actors , err := c . resolveInboxes ( uris , depth + 1 , max )
if err != nil {
return nil , err
}
a = append ( a , actors ... )
} else if cp != nil {
cb := func ( c vocab . CollectionPageType ) error {
uris = getURIsInItemer ( c )
return nil
}
err := doForCollectionPage ( c . Client , c . Agent , cb , cp . Raw ( ) )
if err != nil {
return nil , err
}
actors , err := c . resolveInboxes ( uris , depth + 1 , max )
if err != nil {
return nil , err
}
a = append ( a , actors ... )
} else if ocp != nil {
cb := func ( c vocab . OrderedCollectionPageType ) error {
uris = getURIsInOrderedItemer ( c )
return nil
}
err := doForOrderedCollectionPage ( c . Client , c . Agent , cb , ocp . Raw ( ) )
if err != nil {
return nil , err
}
actors , err := c . resolveInboxes ( uris , depth + 1 , max )
if err != nil {
return nil , err
}
a = append ( a , actors ... )
} else if actor != nil {
a = append ( a , actor )
}
}
return a , nil
}
// getInboxes extracts the 'inbox' IRIs from actors.
2018-04-11 07:07:51 +09:00
func getInboxes ( a [ ] actor ) ( [ ] url . URL , error ) {
2018-01-29 04:51:12 +09:00
var u [ ] url . URL
for _ , actor := range a {
2018-04-11 07:07:51 +09:00
if actor . IsInboxAnyURI ( ) {
u = append ( u , actor . GetInboxAnyURI ( ) )
} else if actor . IsInboxOrderedCollection ( ) {
oc := actor . GetInboxOrderedCollection ( )
if ! oc . HasId ( ) {
return nil , fmt . Errorf ( "actor inbox OrderedCollection has no IRI" )
}
u = append ( u , oc . GetId ( ) )
2018-01-29 04:51:12 +09:00
}
}
2018-04-11 07:07:51 +09:00
return u , nil
2018-01-26 08:19:21 +09:00
}
2018-01-29 04:51:12 +09:00
// getActorAttributedToURI attempts to find the URIs for the "actor" and
// "attributedTo" originators on the object.
2018-02-13 05:19:35 +09:00
func getActorsAttributedToURI ( a actorObject ) [ ] url . URL {
2018-01-29 04:51:12 +09:00
var u [ ] url . URL
for i := 0 ; i < a . AttributedToLen ( ) ; i ++ {
if a . IsAttributedToObject ( i ) {
obj := a . GetAttributedToObject ( i )
if obj . HasId ( ) {
u = append ( u , obj . GetId ( ) )
}
} else if a . IsAttributedToLink ( i ) {
l := a . GetAttributedToLink ( i )
if l . HasHref ( ) {
u = append ( u , l . GetHref ( ) )
}
} else if a . IsAttributedToIRI ( i ) {
u = append ( u , a . GetAttributedToIRI ( i ) )
}
}
for i := 0 ; i < a . ActorLen ( ) ; i ++ {
if a . IsActorObject ( i ) {
obj := a . GetActorObject ( i )
if obj . HasId ( ) {
u = append ( u , obj . GetId ( ) )
}
} else if a . IsActorLink ( i ) {
l := a . GetActorLink ( i )
if l . HasHref ( ) {
u = append ( u , l . GetHref ( ) )
}
} else if a . IsActorIRI ( i ) {
u = append ( u , a . GetActorIRI ( i ) )
}
}
return u
2018-01-26 08:19:21 +09:00
}
2018-02-13 05:19:35 +09:00
// stripHiddenRecipients removes "bto" and "bcc" from the deliverableObject.
2018-01-26 08:19:21 +09:00
// Note that this requirement of the specification is under "Section 6: Client
// to Server Interactions", the Social API, and not the Federative API.
2018-02-13 05:19:35 +09:00
func stripHiddenRecipients ( o deliverableObject ) {
2018-01-26 08:19:21 +09:00
for o . BtoLen ( ) > 0 {
if o . IsBtoObject ( 0 ) {
o . RemoveBtoObject ( 0 )
} else if o . IsBtoLink ( 0 ) {
o . RemoveBtoLink ( 0 )
} else if o . IsBtoIRI ( 0 ) {
o . RemoveBtoIRI ( 0 )
}
}
for o . BccLen ( ) > 0 {
if o . IsBccObject ( 0 ) {
o . RemoveBccObject ( 0 )
} else if o . IsBccLink ( 0 ) {
o . RemoveBccLink ( 0 )
} else if o . IsBtoIRI ( 0 ) {
o . RemoveBccIRI ( 0 )
}
}
}
// dedupeIRIs will deduplicate final inbox IRIs. The ignore list is applied to
// the final list
func dedupeIRIs ( recipients , ignored [ ] url . URL ) ( out [ ] url . URL ) {
ignoredMap := make ( map [ url . URL ] bool , len ( ignored ) )
for _ , elem := range ignored {
ignoredMap [ elem ] = true
}
outMap := make ( map [ url . URL ] bool , len ( recipients ) )
2018-04-04 07:23:55 +09:00
for _ , k := range recipients {
if ! ignoredMap [ k ] && ! outMap [ k ] {
2018-01-26 08:19:21 +09:00
out = append ( out , k )
2018-04-04 07:23:55 +09:00
outMap [ k ] = true
2018-01-26 08:19:21 +09:00
}
}
return
}
2018-02-01 06:43:13 +09:00
// dedupeOrderedItems will deduplicate the 'orderedItems' within an ordered
// collection type. Deduplication happens by simply examining the 'id'.
func ( f * federator ) dedupeOrderedItems ( oc vocab . OrderedCollectionType ) ( vocab . OrderedCollectionType , error ) {
i := 0
seen := make ( map [ string ] bool , oc . OrderedItemsLen ( ) )
for i < oc . OrderedItemsLen ( ) {
var id string
var removeFn func ( int )
if oc . IsOrderedItemsObject ( i ) {
removeFn = oc . RemoveOrderedItemsObject
iri := oc . GetOrderedItemsObject ( i ) . GetId ( )
pIri := & iri
id = pIri . String ( )
} else if oc . IsOrderedItemsLink ( i ) {
removeFn = oc . RemoveOrderedItemsLink
iri := oc . GetOrderedItemsLink ( i ) . GetId ( )
pIri := & iri
id = pIri . String ( )
} else if oc . IsOrderedItemsIRI ( i ) {
removeFn = oc . RemoveOrderedItemsIRI
b , err := dereference ( f . Client , oc . GetOrderedItemsIRI ( i ) , f . Agent )
var m map [ string ] interface { }
if err := json . Unmarshal ( b , & m ) ; err != nil {
return oc , err
}
var iri url . URL
var hasIri bool
if err = toIdResolver ( & hasIri , & iri ) . Deserialize ( m ) ; err != nil {
return oc , err
}
pIri := & iri
id = pIri . String ( )
}
if seen [ id ] {
removeFn ( i )
} else {
seen [ id ] = true
i ++
}
}
return oc , nil
}
// filterURLs removes urls whose strings match the provided filter
func filterURLs ( u [ ] url . URL , fn func ( s string ) bool ) [ ] url . URL {
i := 0
for i < len ( u ) {
if fn ( u [ i ] . String ( ) ) {
u = append ( u [ : i ] , u [ i + 1 : ] ... )
} else {
i ++
}
}
return u
}
2018-02-13 05:19:35 +09:00
func getToIRIs ( o deliverableObject ) [ ] url . URL {
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
for i := 0 ; i < o . ToLen ( ) ; i ++ {
if o . IsToObject ( i ) {
obj := o . GetToObject ( i )
if obj . HasId ( ) {
r = append ( r , obj . GetId ( ) )
}
} else if o . IsToLink ( i ) {
l := o . GetToLink ( i )
if l . HasHref ( ) {
r = append ( r , l . GetHref ( ) )
}
} else if o . IsToIRI ( i ) {
r = append ( r , o . GetToIRI ( i ) )
}
}
return r
}
2018-02-13 05:19:35 +09:00
func getBToIRIs ( o deliverableObject ) [ ] url . URL {
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
for i := 0 ; i < o . BtoLen ( ) ; i ++ {
if o . IsBtoObject ( i ) {
obj := o . GetBtoObject ( i )
if obj . HasId ( ) {
r = append ( r , obj . GetId ( ) )
}
} else if o . IsBtoLink ( i ) {
l := o . GetBtoLink ( i )
if l . HasHref ( ) {
r = append ( r , l . GetHref ( ) )
}
} else if o . IsBtoIRI ( i ) {
r = append ( r , o . GetBtoIRI ( i ) )
}
}
return r
}
2018-02-13 05:19:35 +09:00
func getCcIRIs ( o deliverableObject ) [ ] url . URL {
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
for i := 0 ; i < o . CcLen ( ) ; i ++ {
if o . IsCcObject ( i ) {
obj := o . GetCcObject ( i )
if obj . HasId ( ) {
r = append ( r , obj . GetId ( ) )
}
} else if o . IsCcLink ( i ) {
l := o . GetCcLink ( i )
if l . HasHref ( ) {
r = append ( r , l . GetHref ( ) )
}
} else if o . IsCcIRI ( i ) {
r = append ( r , o . GetCcIRI ( i ) )
}
}
return r
}
2018-02-13 05:19:35 +09:00
func getBccIRIs ( o deliverableObject ) [ ] url . URL {
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
for i := 0 ; i < o . BccLen ( ) ; i ++ {
if o . IsBccObject ( i ) {
obj := o . GetBccObject ( i )
if obj . HasId ( ) {
r = append ( r , obj . GetId ( ) )
}
} else if o . IsBccLink ( i ) {
l := o . GetBccLink ( i )
if l . HasHref ( ) {
r = append ( r , l . GetHref ( ) )
}
} else if o . IsBccIRI ( i ) {
r = append ( r , o . GetBccIRI ( i ) )
}
}
return r
}
2018-02-13 05:19:35 +09:00
func getAudienceIRIs ( o deliverableObject ) [ ] url . URL {
2018-01-26 08:19:21 +09:00
var r [ ] url . URL
for i := 0 ; i < o . AudienceLen ( ) ; i ++ {
if o . IsAudienceObject ( i ) {
obj := o . GetAudienceObject ( i )
if obj . HasId ( ) {
r = append ( r , obj . GetId ( ) )
}
} else if o . IsAudienceLink ( i ) {
l := o . GetAudienceLink ( i )
if l . HasHref ( ) {
r = append ( r , l . GetHref ( ) )
}
} else if o . IsAudienceIRI ( i ) {
r = append ( r , o . GetAudienceIRI ( i ) )
}
}
return r
}
// doForCollectionPage applies a function over a collection and its subsequent
// pages recursively. It returns the first non-nil error it encounters.
2018-04-04 07:23:55 +09:00
func doForCollectionPage ( h HttpClient , agent string , cb func ( c vocab . CollectionPageType ) error , c vocab . CollectionPageType ) error {
2018-01-26 08:19:21 +09:00
err := cb ( c )
if err != nil {
return err
}
if c . IsNextCollectionPage ( ) {
// Handle this one weird trick that other peers HATE federating
// with.
return doForCollectionPage ( h , agent , cb , c . GetNextCollectionPage ( ) )
} else if c . IsNextLink ( ) {
l := c . GetNextLink ( )
if l . HasHref ( ) {
u := l . GetHref ( )
resp , err := dereference ( h , u , agent )
if err != nil {
return err
}
var m map [ string ] interface { }
err = json . Unmarshal ( resp , & m )
if err != nil {
return err
}
next , err := toCollectionPage ( m )
if err != nil {
return err
}
if next != nil {
return doForCollectionPage ( h , agent , cb , next . Raw ( ) )
}
}
} else if c . IsNextIRI ( ) {
u := c . GetNextIRI ( )
resp , err := dereference ( h , u , agent )
if err != nil {
return err
}
var m map [ string ] interface { }
err = json . Unmarshal ( resp , & m )
if err != nil {
return err
}
next , err := toCollectionPage ( m )
if err != nil {
return err
}
if next != nil {
return doForCollectionPage ( h , agent , cb , next . Raw ( ) )
}
}
return nil
}
// doForOrderedCollectionPage applies a function over a collection and its
// subsequent pages recursively. It returns the first non-nil error it
// encounters.
2018-04-04 07:23:55 +09:00
func doForOrderedCollectionPage ( h HttpClient , agent string , cb func ( c vocab . OrderedCollectionPageType ) error , c vocab . OrderedCollectionPageType ) error {
2018-01-26 08:19:21 +09:00
err := cb ( c )
if err != nil {
return err
}
if c . IsNextOrderedCollectionPage ( ) {
// Handle this one weird trick that other peers HATE federating
// with.
return doForOrderedCollectionPage ( h , agent , cb , c . GetNextOrderedCollectionPage ( ) )
} else if c . IsNextLink ( ) {
l := c . GetNextLink ( )
if l . HasHref ( ) {
u := l . GetHref ( )
resp , err := dereference ( h , u , agent )
if err != nil {
return err
}
var m map [ string ] interface { }
err = json . Unmarshal ( resp , & m )
if err != nil {
return err
}
next , err := toOrderedCollectionPage ( m )
if err != nil {
return err
}
if next != nil {
return doForOrderedCollectionPage ( h , agent , cb , next . Raw ( ) )
}
}
} else if c . IsNextIRI ( ) {
u := c . GetNextIRI ( )
resp , err := dereference ( h , u , agent )
if err != nil {
return err
}
var m map [ string ] interface { }
err = json . Unmarshal ( resp , & m )
if err != nil {
return err
}
next , err := toOrderedCollectionPage ( m )
if err != nil {
return err
}
if next != nil {
return doForOrderedCollectionPage ( h , agent , cb , next . Raw ( ) )
}
}
return nil
}
type itemer interface {
ItemsLen ( ) ( l int )
IsItemsObject ( index int ) ( ok bool )
GetItemsObject ( index int ) ( v vocab . ObjectType )
IsItemsLink ( index int ) ( ok bool )
GetItemsLink ( index int ) ( v vocab . LinkType )
IsItemsIRI ( index int ) ( ok bool )
GetItemsIRI ( index int ) ( v url . URL )
}
// getURIsInItemer will extract 'items' from the provided Collection or
// CollectionPage.
func getURIsInItemer ( i itemer ) [ ] url . URL {
2018-01-29 04:51:12 +09:00
var u [ ] url . URL
for j := 0 ; j < i . ItemsLen ( ) ; j ++ {
if i . IsItemsObject ( j ) {
obj := i . GetItemsObject ( j )
if obj . HasId ( ) {
u = append ( u , obj . GetId ( ) )
}
} else if i . IsItemsLink ( j ) {
l := i . GetItemsLink ( j )
if l . HasHref ( ) {
u = append ( u , l . GetHref ( ) )
}
} else if i . IsItemsIRI ( j ) {
u = append ( u , i . GetItemsIRI ( j ) )
}
}
return u
2018-01-26 08:19:21 +09:00
}
type orderedItemer interface {
OrderedItemsLen ( ) ( l int )
IsOrderedItemsObject ( index int ) ( ok bool )
GetOrderedItemsObject ( index int ) ( v vocab . ObjectType )
IsOrderedItemsLink ( index int ) ( ok bool )
GetOrderedItemsLink ( index int ) ( v vocab . LinkType )
IsOrderedItemsIRI ( index int ) ( ok bool )
GetOrderedItemsIRI ( index int ) ( v url . URL )
}
// getURIsInOrderedItemer will extract 'items' from the provided
// OrderedCollection or OrderedCollectionPage.
func getURIsInOrderedItemer ( i orderedItemer ) [ ] url . URL {
2018-01-29 04:51:12 +09:00
var u [ ] url . URL
for j := 0 ; j < i . OrderedItemsLen ( ) ; j ++ {
if i . IsOrderedItemsObject ( j ) {
obj := i . GetOrderedItemsObject ( j )
if obj . HasId ( ) {
u = append ( u , obj . GetId ( ) )
}
} else if i . IsOrderedItemsLink ( j ) {
l := i . GetOrderedItemsLink ( j )
if l . HasHref ( ) {
u = append ( u , l . GetHref ( ) )
}
} else if i . IsOrderedItemsIRI ( j ) {
u = append ( u , i . GetOrderedItemsIRI ( j ) )
}
}
return u
2018-01-26 08:19:21 +09:00
}
2018-02-05 01:42:22 +09:00
2018-02-16 07:38:58 +09:00
type activityWithObject interface {
ObjectLen ( ) ( l int )
IsObject ( index int ) ( ok bool )
GetObject ( index int ) ( v vocab . ObjectType )
IsObjectIRI ( index int ) ( ok bool )
GetObjectIRI ( index int ) ( v url . URL )
}
func getObjectIds ( a activityWithObject ) ( u [ ] url . URL , e error ) {
for i := 0 ; i < a . ObjectLen ( ) ; i ++ {
if a . IsObject ( i ) {
obj := a . GetObject ( i )
if ! obj . HasId ( ) {
e = fmt . Errorf ( "object has no id: %v" , obj )
return
}
u = append ( u , obj . GetId ( ) )
} else if a . IsObjectIRI ( i ) {
u = append ( u , a . GetObjectIRI ( i ) )
}
}
return
}
type activityWithTarget interface {
TargetLen ( ) ( l int )
IsTargetObject ( index int ) ( ok bool )
GetTargetObject ( index int ) ( v vocab . ObjectType )
IsTargetIRI ( index int ) ( ok bool )
GetTargetIRI ( index int ) ( v url . URL )
}
func getTargetIds ( a activityWithTarget ) ( u [ ] url . URL , e error ) {
for i := 0 ; i < a . TargetLen ( ) ; i ++ {
if a . IsTargetObject ( i ) {
obj := a . GetTargetObject ( i )
if ! obj . HasId ( ) {
e = fmt . Errorf ( "object has no id: %v" , obj )
return
}
u = append ( u , obj . GetId ( ) )
} else if a . IsTargetIRI ( i ) {
u = append ( u , a . GetTargetIRI ( i ) )
}
}
return
}
func removeCollectionItemWithId ( c vocab . CollectionType , iri url . URL ) {
for i := 0 ; i < c . ItemsLen ( ) ; i ++ {
if c . IsItemsObject ( i ) {
o := c . GetItemsObject ( i )
if ! o . HasId ( ) {
continue
}
if o . GetId ( ) == iri {
c . RemoveItemsObject ( i )
return
}
} else if c . IsItemsLink ( i ) {
l := c . GetItemsLink ( i )
if ! l . HasHref ( ) {
continue
}
if l . GetHref ( ) == iri {
c . RemoveItemsLink ( i )
return
}
} else if c . IsItemsIRI ( i ) {
if c . GetItemsIRI ( i ) == iri {
c . RemoveItemsIRI ( i )
return
}
}
}
}
func removeOrderedCollectionItemWithId ( c vocab . OrderedCollectionType , iri url . URL ) {
for i := 0 ; i < c . OrderedItemsLen ( ) ; i ++ {
if c . IsOrderedItemsObject ( i ) {
o := c . GetOrderedItemsObject ( i )
if ! o . HasId ( ) {
continue
}
if o . GetId ( ) == iri {
c . RemoveOrderedItemsObject ( i )
return
}
} else if c . IsOrderedItemsLink ( i ) {
l := c . GetOrderedItemsLink ( i )
if ! l . HasHref ( ) {
continue
}
if l . GetHref ( ) == iri {
c . RemoveOrderedItemsLink ( i )
return
}
} else if c . IsOrderedItemsIRI ( i ) {
if c . GetOrderedItemsIRI ( i ) == iri {
c . RemoveOrderedItemsIRI ( i )
return
}
}
}
}
// toTombstone creates a Tombstone for the given object.
func toTombstone ( obj vocab . ObjectType , id url . URL , now time . Time ) vocab . TombstoneType {
tomb := & vocab . Tombstone { }
tomb . SetId ( id )
for i := 0 ; i < obj . TypeLen ( ) ; i ++ {
if s , ok := obj . GetType ( i ) . ( string ) ; ok {
tomb . AddFormerTypeString ( s )
} else if fObj , ok := obj . GetType ( i ) . ( vocab . ObjectType ) ; ok {
tomb . AddFormerTypeObject ( fObj )
}
}
if obj . IsPublished ( ) {
tomb . SetPublished ( obj . GetPublished ( ) )
} else if obj . IsPublishedIRI ( ) {
tomb . SetPublishedIRI ( obj . GetPublishedIRI ( ) )
}
if obj . IsUpdated ( ) {
tomb . SetUpdated ( obj . GetUpdated ( ) )
} else if obj . IsUpdatedIRI ( ) {
tomb . SetUpdatedIRI ( obj . GetUpdatedIRI ( ) )
}
tomb . SetDeleted ( now )
return tomb
}
2018-04-11 07:07:51 +09:00
func ( f * federator ) addAllObjectsToActorCollection ( ctx context . Context , getter func ( actor vocab . ObjectType , lc * vocab . CollectionType , loc * vocab . OrderedCollectionType ) error , c vocab . ActivityType ) error {
2018-02-20 08:09:29 +09:00
for i := 0 ; i < c . ActorLen ( ) ; i ++ {
2018-04-11 07:07:51 +09:00
var iri url . URL
2018-02-20 08:09:29 +09:00
if c . IsActorObject ( i ) {
2018-04-11 07:07:51 +09:00
obj := c . GetActorObject ( i )
if ! obj . HasId ( ) {
return fmt . Errorf ( "actor does not have id" )
}
iri = obj . GetId ( )
2018-02-20 08:09:29 +09:00
} else if c . IsActorLink ( i ) {
l := c . GetActorLink ( i )
if ! l . HasHref ( ) {
return fmt . Errorf ( "actor Link href required" )
}
2018-04-11 07:07:51 +09:00
iri = l . GetHref ( )
2018-02-20 08:09:29 +09:00
} else if c . IsActorIRI ( i ) {
2018-04-11 07:07:51 +09:00
iri = c . GetActorIRI ( i )
}
if ! f . App . Owns ( ctx , iri ) {
continue
}
var actor vocab . ObjectType
pObj , err := f . App . Get ( ctx , iri )
if err != nil {
return err
}
ok := false
actor , ok = pObj . ( vocab . ObjectType )
if ! ok {
2018-05-12 21:44:07 +09:00
// TODO: Handle links, too
2018-04-11 07:07:51 +09:00
return fmt . Errorf ( "actor is not vocab.ObjectType" )
2018-02-20 08:09:29 +09:00
}
var lc vocab . CollectionType
var loc vocab . OrderedCollectionType
2018-04-11 07:07:51 +09:00
if err := getter ( actor , & lc , & loc ) ; err != nil {
return err
2018-02-20 08:09:29 +09:00
}
for i := 0 ; i < c . ObjectLen ( ) ; i ++ {
if c . IsObjectIRI ( i ) {
if lc != nil {
lc . AddItemsIRI ( c . GetObjectIRI ( i ) )
} else if loc != nil {
loc . AddOrderedItemsIRI ( c . GetObjectIRI ( i ) )
}
} else if c . IsObject ( i ) {
if lc != nil {
lc . AddItemsObject ( c . GetObject ( i ) )
} else if loc != nil {
loc . AddOrderedItemsObject ( c . GetObject ( i ) )
}
}
}
2018-05-12 21:44:07 +09:00
if err := f . App . Set ( ctx , actor ) ; err != nil {
return err
2018-02-20 08:09:29 +09:00
}
}
return nil
}
2018-04-11 07:07:51 +09:00
func ( f * federator ) addAllActorsToObjectCollection ( ctx context . Context , getter func ( object vocab . ObjectType , lc * vocab . CollectionType , loc * vocab . OrderedCollectionType ) error , c vocab . ActivityType ) error {
2018-02-20 08:09:29 +09:00
for i := 0 ; i < c . ObjectLen ( ) ; i ++ {
2018-04-11 07:07:51 +09:00
var iri url . URL
2018-02-20 08:09:29 +09:00
if c . IsObject ( i ) {
2018-04-11 07:07:51 +09:00
obj := c . GetObject ( i )
if ! obj . HasId ( ) {
return fmt . Errorf ( "object does not have id" )
2018-02-20 08:09:29 +09:00
}
2018-04-11 07:07:51 +09:00
iri = obj . GetId ( )
} else if c . IsObjectIRI ( i ) {
iri = c . GetObjectIRI ( i )
}
if ! f . App . Owns ( ctx , iri ) {
continue
}
var object vocab . ObjectType
pObj , err := f . App . Get ( ctx , iri )
if err != nil {
return err
}
ok := false
object , ok = pObj . ( vocab . ObjectType )
if ! ok {
2018-05-12 21:44:07 +09:00
// TODO: Handle links, too
2018-04-11 07:07:51 +09:00
return fmt . Errorf ( "object is not vocab.ObjectType" )
2018-02-20 08:09:29 +09:00
}
var lc vocab . CollectionType
var loc vocab . OrderedCollectionType
2018-04-11 07:07:51 +09:00
if err := getter ( object , & lc , & loc ) ; err != nil {
return err
2018-02-20 08:09:29 +09:00
}
for i := 0 ; i < c . ActorLen ( ) ; i ++ {
if c . IsActorIRI ( i ) {
if lc != nil {
lc . AddItemsIRI ( c . GetActorIRI ( i ) )
} else if loc != nil {
loc . AddOrderedItemsIRI ( c . GetActorIRI ( i ) )
}
} else if c . IsActorObject ( i ) {
if lc != nil {
lc . AddItemsObject ( c . GetActorObject ( i ) )
} else if loc != nil {
loc . AddOrderedItemsObject ( c . GetActorObject ( i ) )
}
} else if c . IsActorLink ( i ) {
if lc != nil {
lc . AddItemsLink ( c . GetActorLink ( i ) )
} else if loc != nil {
loc . AddOrderedItemsLink ( c . GetActorLink ( i ) )
}
}
}
2018-05-12 21:44:07 +09:00
if err := f . App . Set ( ctx , object ) ; err != nil {
return err
2018-02-20 08:09:29 +09:00
}
}
return nil
}
2018-04-29 22:56:38 +09:00
func ( f * federator ) addToOutbox ( c context . Context , r * http . Request , m map [ string ] interface { } ) error {
outbox , err := f . App . GetOutbox ( c , r )
if err != nil {
return err
}
activity , err := toAnyActivity ( m )
if err != nil {
return err
}
outbox . AddOrderedItemsObject ( activity )
return f . App . Set ( c , outbox )
}
func ( f * federator ) addToInbox ( c context . Context , r * http . Request , m map [ string ] interface { } ) error {
inbox , err := f . App . GetInbox ( c , r )
if err != nil {
return err
}
activity , err := toAnyActivity ( m )
if err != nil {
return err
}
inbox . AddOrderedItemsObject ( activity )
return f . App . Set ( c , inbox )
}
2018-05-10 19:49:37 +09:00
// Note: This is a mechanism for causing other victim servers to DDOS
// or forward spam on a malicious user's behalf. The trick is a simple
// one: Reply to a user, and CC a ton of 'follower' collections owned
// by the victim server. Bonus points for listing more 'follower'
// collections from other popular instances as well. Leveraging the
// Inbox Forwarding mechanism, a storm of messages will ensue.
//
// I don't want users of this library to be vulnerable to this kind of
// spam/DDOS storm. So here we allow the client application to filter
// out recipient collections.
func ( f * federator ) inboxForwarding ( c context . Context , m map [ string ] interface { } ) error {
a , err := toAnyActivity ( m )
if err != nil {
return err
}
// 1. Must be first time we have seen this Activity.
if ok , err := f . App . Has ( c , a . GetId ( ) ) ; err != nil {
return err
} else if ok {
return nil
}
// 2. The values of 'to', 'cc', or 'audience' are Collections owned by
// this server.
var r [ ] url . URL
r = append ( r , getToIRIs ( a ) ... )
r = append ( r , getCcIRIs ( a ) ... )
r = append ( r , getAudienceIRIs ( a ) ... )
var myIRIs [ ] url . URL
col := make ( map [ string ] vocab . CollectionType , 0 )
oCol := make ( map [ string ] vocab . OrderedCollectionType , 0 )
for _ , iri := range r {
if ok , err := f . App . Has ( c , iri ) ; err != nil {
return err
} else if ! ok {
continue
}
obj , err := f . App . Get ( c , iri )
if err != nil {
return err
}
if c , ok := obj . ( vocab . CollectionType ) ; ok {
col [ ( & iri ) . String ( ) ] = c
myIRIs = append ( myIRIs , iri )
} else if oc , ok := obj . ( vocab . OrderedCollectionType ) ; ok {
oCol [ ( & iri ) . String ( ) ] = oc
myIRIs = append ( myIRIs , iri )
}
}
if len ( myIRIs ) == 0 {
return nil
}
// 3. The values of 'inReplyTo', 'object', 'target', or 'tag' are owned
// by this server.
ownsValue := false
objs , l , iris := getInboxForwardingValues ( a )
for _ , obj := range objs {
if f . hasInboxForwardingValues ( c , 0 , f . MaxInboxForwardingDepth , obj ) {
ownsValue = true
break
}
}
if ! ownsValue && f . ownsAnyLinks ( c , l ) {
ownsValue = true
}
if ! ownsValue && f . ownsAnyIRIs ( c , iris ) {
ownsValue = true
}
if ! ownsValue {
return nil
}
// Do the inbox forwarding since the above conditions hold true. Support
// the behavior of letting the application filter out the resulting
// collections to be targeted.
toSend , err := f . FederateApp . FilterForwarding ( c , a , myIRIs )
if err != nil {
return err
}
recipients := make ( [ ] url . URL , 0 , len ( toSend ) )
for _ , iri := range toSend {
if c , ok := col [ ( & iri ) . String ( ) ] ; ok {
for i := 0 ; i < c . ItemsLen ( ) ; i ++ {
if c . IsItemsObject ( i ) {
obj := c . GetItemsObject ( i )
if obj . HasId ( ) {
recipients = append ( recipients , obj . GetId ( ) )
}
} else if c . IsItemsLink ( i ) {
l := c . GetItemsLink ( i )
if l . HasHref ( ) {
recipients = append ( recipients , l . GetHref ( ) )
}
} else if c . IsItemsIRI ( i ) {
recipients = append ( recipients , c . GetItemsIRI ( i ) )
}
}
} else if oc , ok := oCol [ ( & iri ) . String ( ) ] ; ok {
for i := 0 ; i < oc . OrderedItemsLen ( ) ; i ++ {
if oc . IsOrderedItemsObject ( i ) {
obj := oc . GetOrderedItemsObject ( i )
if obj . HasId ( ) {
recipients = append ( recipients , obj . GetId ( ) )
}
} else if oc . IsOrderedItemsLink ( i ) {
l := oc . GetItemsLink ( i )
if l . HasHref ( ) {
recipients = append ( recipients , l . GetHref ( ) )
}
} else if oc . IsOrderedItemsIRI ( i ) {
recipients = append ( recipients , oc . GetOrderedItemsIRI ( i ) )
}
}
}
}
return f . deliverToRecipients ( a , recipients )
}
// Given an 'inReplyTo', 'object', 'target', or 'tag' object, recursively
// examines those same values to determine if the app owns any, up to a maximum
// depth.
func ( f * federator ) hasInboxForwardingValues ( c context . Context , depth , maxDepth int , o vocab . ObjectType ) bool {
if depth == maxDepth {
return false
}
if f . App . Owns ( c , o . GetId ( ) ) {
return true
}
objs , l , iris := getInboxForwardingValues ( o )
for _ , obj := range objs {
if f . hasInboxForwardingValues ( c , depth + 1 , maxDepth , obj ) {
return true
}
}
if f . ownsAnyLinks ( c , l ) {
return true
}
return f . ownsAnyIRIs ( c , iris )
}
func ( f * federator ) ownsAnyIRIs ( c context . Context , iris [ ] url . URL ) bool {
for _ , iri := range iris {
if f . App . Owns ( c , iri ) {
return true
}
// TODO: Dereference the IRI
}
return false
}
func ( f * federator ) ownsAnyLinks ( c context . Context , links [ ] vocab . LinkType ) bool {
for _ , link := range links {
if ! link . HasHref ( ) {
continue
}
href := link . GetHref ( )
if f . App . Owns ( c , href ) {
return true
}
// TODO: Dereference the IRI
}
return false
}
func getInboxForwardingValues ( o vocab . ObjectType ) ( objs [ ] vocab . ObjectType , l [ ] vocab . LinkType , iri [ ] url . URL ) {
// 'inReplyTo'
for i := 0 ; i < o . InReplyToLen ( ) ; i ++ {
if o . IsInReplyToObject ( i ) {
objs = append ( objs , o . GetInReplyToObject ( i ) )
} else if o . IsInReplyToLink ( i ) {
l = append ( l , o . GetInReplyToLink ( i ) )
} else if o . IsInReplyToIRI ( i ) {
iri = append ( iri , o . GetInReplyToIRI ( i ) )
}
}
// 'tag'
for i := 0 ; i < o . TagLen ( ) ; i ++ {
if o . IsTagObject ( i ) {
objs = append ( objs , o . GetTagObject ( i ) )
} else if o . IsTagLink ( i ) {
l = append ( l , o . GetTagLink ( i ) )
} else if o . IsTagIRI ( i ) {
iri = append ( iri , o . GetTagIRI ( i ) )
}
}
if a , ok := o . ( vocab . ActivityType ) ; ok {
// 'object'
for i := 0 ; i < a . ObjectLen ( ) ; i ++ {
if a . IsObject ( i ) {
objs = append ( objs , a . GetObject ( i ) )
} else if a . IsObjectIRI ( i ) {
iri = append ( iri , a . GetObjectIRI ( i ) )
}
}
// 'target'
for i := 0 ; i < a . TargetLen ( ) ; i ++ {
if a . IsTargetObject ( i ) {
objs = append ( objs , a . GetTargetObject ( i ) )
} else if a . IsTargetLink ( i ) {
l = append ( l , a . GetTargetLink ( i ) )
} else if a . IsTargetIRI ( i ) {
iri = append ( iri , a . GetTargetIRI ( i ) )
}
}
}
return
}
2018-04-28 20:47:33 +09:00
// Fetches an "object" on a raw JSON map of an Activity with the matching 'id'
// field. If there is no object matching the IRI, or the object just is an IRI,
// or the object wth the matching id is not in the array of objects, then a nil
// map is returned.
func getRawObject ( m map [ string ] interface { } , id string ) map [ string ] interface { } {
for k , v := range m {
if k == "object" {
switch val := v . ( type ) {
case map [ string ] interface { } :
if r , ok := val [ "id" ] ; ok {
if rId , ok := r . ( string ) ; ok && rId == id {
return val
}
}
case [ ] interface { } :
for _ , elem := range val {
if elemVal , ok := elem . ( map [ string ] interface { } ) ; ok {
if r , ok := elemVal [ "id" ] ; ok {
if rId , ok := r . ( string ) ; ok && rId == id {
return elemVal
}
}
}
}
}
}
}
return nil
}
// recursivelyApplyDeletes takes input map 'm' and deletes entries in its maps
// where the 'hasNils' map has nils. If the interface{} of the maps are
// themselves map[string]interface{} types, then this function recurs.
func recursivelyApplyDeletes ( m , hasNils map [ string ] interface { } ) {
for k , v := range hasNils {
if _ , ok := m [ k ] ; v == nil && ok {
delete ( m , k )
} else if nilsSubMap , ok := v . ( map [ string ] interface { } ) ; ok {
if mSub , ok := m [ k ] ; ok {
if mSubMap , ok := mSub . ( map [ string ] interface { } ) ; ok {
recursivelyApplyDeletes ( mSubMap , nilsSubMap )
}
}
}
}
}
2018-02-05 01:42:22 +09:00
// TODO: Move this to vocab package.
var activityTypes = [ ] string { "Accept" , "Add" , "Announce" , "Arrive" , "Block" , "Create" , "Delete" , "Dislike" , "Flag" , "Follow" , "Ignore" , "Invite" , "Join" , "Leave" , "Like" , "Listen" , "Move" , "Offer" , "Question" , "Reject" , "Read" , "Remove" , "TentativeReject" , "TentativeAccept" , "Travel" , "Undo" , "Update" , "View" }
func isActivityType ( t Typer ) bool {
hasType := make ( map [ string ] bool , 1 )
for i := 0 ; i < t . TypeLen ( ) ; i ++ {
v := t . GetType ( i )
if s , ok := v . ( string ) ; ok {
hasType [ s ] = true
}
}
for _ , t := range activityTypes {
if hasType [ t ] {
return true
}
}
return false
}