2018-01-26 08:19:21 +09:00
package pub
import (
2018-02-05 02:50:32 +09:00
"bytes"
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-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
}
// dereference makes an HTTP GET request to an IRI in order to obtain the
// ActivityStream representation.
func dereference ( c * http . Client , u url . URL , agent string ) ( [ ] byte , error ) {
// 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-02-05 02:50:32 +09:00
func postToOutbox ( c * http . Client , b [ ] byte , to url . URL , agent string ) error {
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-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-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-01-29 04:51:12 +09:00
targets := getInboxes ( receiverActors )
// 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
}
ignore := getInboxes ( senderActors )
// 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-02-13 05:19:35 +09:00
func ( c * federator ) resolveInboxes ( r [ ] url . URL , depth int , max int ) ( [ ] actorObject , error ) {
2018-01-26 08:19:21 +09:00
if depth >= max {
return nil , nil
}
2018-02-13 05:19:35 +09:00
a := make ( [ ] actorObject , 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-02-13 05:19:35 +09:00
var actor actorObject
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-02-13 05:19:35 +09:00
func getInboxes ( a [ ] actorObject ) [ ] url . URL {
2018-01-29 04:51:12 +09:00
var u [ ] url . URL
for _ , actor := range a {
if actor . HasInbox ( ) {
u = append ( u , actor . GetInbox ( ) )
}
}
return u
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 ) )
for k , _ := range outMap {
if ! ignoredMap [ k ] {
out = append ( out , k )
}
}
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.
func doForCollectionPage ( h * http . Client , agent string , cb func ( c vocab . CollectionPageType ) error , c vocab . CollectionPageType ) error {
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.
func doForOrderedCollectionPage ( h * http . Client , agent string , cb func ( c vocab . OrderedCollectionPageType ) error , c vocab . OrderedCollectionPageType ) error {
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-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
}