summaryrefslogtreecommitdiff
path: root/vendor/github.com/superseriousbusiness/activity/pub/util.go
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/github.com/superseriousbusiness/activity/pub/util.go')
-rw-r--r--vendor/github.com/superseriousbusiness/activity/pub/util.go1006
1 files changed, 1006 insertions, 0 deletions
diff --git a/vendor/github.com/superseriousbusiness/activity/pub/util.go b/vendor/github.com/superseriousbusiness/activity/pub/util.go
new file mode 100644
index 000000000..d8937bba2
--- /dev/null
+++ b/vendor/github.com/superseriousbusiness/activity/pub/util.go
@@ -0,0 +1,1006 @@
+package pub
+
+import (
+ "bytes"
+ "context"
+ "crypto/sha256"
+ "encoding/base64"
+ "encoding/json"
+ "errors"
+ "fmt"
+ "net/http"
+ "net/url"
+ "strings"
+ "time"
+
+ "github.com/superseriousbusiness/activity/streams"
+ "github.com/superseriousbusiness/activity/streams/vocab"
+)
+
+var (
+ // ErrObjectRequired indicates the activity needs its object property
+ // set. Can be returned by DelegateActor's PostInbox or PostOutbox so a
+ // Bad Request response is set.
+ ErrObjectRequired = errors.New("object property required on the provided activity")
+ // ErrTargetRequired indicates the activity needs its target property
+ // set. Can be returned by DelegateActor's PostInbox or PostOutbox so a
+ // Bad Request response is set.
+ ErrTargetRequired = errors.New("target property required on the provided activity")
+)
+
+// activityStreamsMediaTypes contains all of the accepted ActivityStreams media
+// types. Generated at init time.
+var activityStreamsMediaTypes []string
+
+func init() {
+ activityStreamsMediaTypes = []string{
+ "application/activity+json",
+ }
+ jsonLdType := "application/ld+json"
+ for _, semi := range []string{";", " ;", " ; ", "; "} {
+ for _, profile := range []string{
+ "profile=https://www.w3.org/ns/activitystreams",
+ "profile=\"https://www.w3.org/ns/activitystreams\"",
+ } {
+ activityStreamsMediaTypes = append(
+ activityStreamsMediaTypes,
+ fmt.Sprintf("%s%s%s", jsonLdType, semi, profile))
+ }
+ }
+}
+
+// headerIsActivityPubMediaType returns true if the header string contains one
+// of the accepted ActivityStreams media types.
+//
+// Note we don't try to build a comprehensive parser and instead accept a
+// tolerable amount of whitespace since the HTTP specification is ambiguous
+// about the format and significance of whitespace.
+func headerIsActivityPubMediaType(header string) bool {
+ for _, mediaType := range activityStreamsMediaTypes {
+ if strings.Contains(header, mediaType) {
+ return true
+ }
+ }
+ return false
+}
+
+const (
+ // The Content-Type header.
+ contentTypeHeader = "Content-Type"
+ // The Accept header.
+ acceptHeader = "Accept"
+)
+
+// isActivityPubPost returns true if the request is a POST request that has the
+// ActivityStreams content type header
+func isActivityPubPost(r *http.Request) bool {
+ return r.Method == "POST" && headerIsActivityPubMediaType(r.Header.Get(contentTypeHeader))
+}
+
+// isActivityPubGet returns true if the request is a GET request that has the
+// ActivityStreams content type header
+func isActivityPubGet(r *http.Request) bool {
+ return r.Method == "GET" && headerIsActivityPubMediaType(r.Header.Get(acceptHeader))
+}
+
+// dedupeOrderedItems deduplicates the 'orderedItems' within an ordered
+// collection type. Deduplication happens by the 'id' property.
+func dedupeOrderedItems(oc orderedItemser) error {
+ oi := oc.GetActivityStreamsOrderedItems()
+ if oi == nil {
+ return nil
+ }
+ seen := make(map[string]bool, oi.Len())
+ for i := 0; i < oi.Len(); {
+ var id *url.URL
+
+ iter := oi.At(i)
+ asType := iter.GetType()
+ if asType != nil {
+ var err error
+ id, err = GetId(asType)
+ if err != nil {
+ return err
+ }
+ } else if iter.IsIRI() {
+ id = iter.GetIRI()
+ } else {
+ return fmt.Errorf("element %d in OrderedCollection does not have an ID nor is an IRI", i)
+ }
+ if seen[id.String()] {
+ oi.Remove(i)
+ } else {
+ seen[id.String()] = true
+ i++
+ }
+ }
+ return nil
+}
+
+const (
+ // The Location header
+ locationHeader = "Location"
+ // Contains the ActivityStreams Content-Type value.
+ contentTypeHeaderValue = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\""
+ // The Date header.
+ dateHeader = "Date"
+ // The Digest header.
+ digestHeader = "Digest"
+ // The delimiter used in the Digest header.
+ digestDelimiter = "="
+ // SHA-256 string for the Digest header.
+ sha256Digest = "SHA-256"
+)
+
+// addResponseHeaders sets headers needed in the HTTP response, such but not
+// limited to the Content-Type, Date, and Digest headers.
+func addResponseHeaders(h http.Header, c Clock, responseContent []byte) {
+ h.Set(contentTypeHeader, contentTypeHeaderValue)
+ // RFC 7231 ยง7.1.1.2
+ h.Set(dateHeader, c.Now().UTC().Format("Mon, 02 Jan 2006 15:04:05")+" GMT")
+ // RFC 3230 and RFC 5843
+ var b bytes.Buffer
+ b.WriteString(sha256Digest)
+ b.WriteString(digestDelimiter)
+ hashed := sha256.Sum256(responseContent)
+ b.WriteString(base64.StdEncoding.EncodeToString(hashed[:]))
+ h.Set(digestHeader, b.String())
+}
+
+// IdProperty is a property that can readily have its id obtained
+type IdProperty interface {
+ // GetIRI returns the IRI of this property. When IsIRI returns false,
+ // GetIRI will return an arbitrary value.
+ GetIRI() *url.URL
+ // GetType returns the value in this property as a Type. Returns nil if
+ // the value is not an ActivityStreams type, such as an IRI or another
+ // value.
+ GetType() vocab.Type
+ // IsIRI returns true if this property is an IRI.
+ IsIRI() bool
+}
+
+// ToId returns an IdProperty's id.
+func ToId(i IdProperty) (*url.URL, error) {
+ if i.GetType() != nil {
+ return GetId(i.GetType())
+ } else if i.IsIRI() {
+ return i.GetIRI(), nil
+ }
+ return nil, fmt.Errorf("cannot determine id of activitystreams property")
+}
+
+// GetId will attempt to find the 'id' property or, if it happens to be a
+// Link or derived from Link type, the 'href' property instead.
+//
+// Returns an error if the id is not set and either the 'href' property is not
+// valid on this type, or it is also not set.
+func GetId(t vocab.Type) (*url.URL, error) {
+ if id := t.GetJSONLDId(); id != nil {
+ return id.Get(), nil
+ } else if h, ok := t.(hrefer); ok {
+ if href := h.GetActivityStreamsHref(); href != nil {
+ return href.Get(), nil
+ }
+ }
+ return nil, fmt.Errorf("cannot determine id of activitystreams value")
+}
+
+// getInboxForwardingValues obtains the 'inReplyTo', 'object', 'target', and
+// 'tag' values on an ActivityStreams value.
+func getInboxForwardingValues(o vocab.Type) (t []vocab.Type, iri []*url.URL) {
+ // 'inReplyTo'
+ if i, ok := o.(inReplyToer); ok {
+ if irt := i.GetActivityStreamsInReplyTo(); irt != nil {
+ for iter := irt.Begin(); iter != irt.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'tag'
+ if i, ok := o.(tagger); ok {
+ if tag := i.GetActivityStreamsTag(); tag != nil {
+ for iter := tag.Begin(); iter != tag.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'object'
+ if i, ok := o.(objecter); ok {
+ if obj := i.GetActivityStreamsObject(); obj != nil {
+ for iter := obj.Begin(); iter != obj.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ // 'target'
+ if i, ok := o.(targeter); ok {
+ if tar := i.GetActivityStreamsTarget(); tar != nil {
+ for iter := tar.Begin(); iter != tar.End(); iter = iter.Next() {
+ if tv := iter.GetType(); tv != nil {
+ t = append(t, tv)
+ } else {
+ iri = append(iri, iter.GetIRI())
+ }
+ }
+ }
+ }
+ return
+}
+
+// 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 wrapInCreate(ctx context.Context, o vocab.Type, actor *url.URL) (c vocab.ActivityStreamsCreate, err error) {
+ c = streams.NewActivityStreamsCreate()
+ // Object property
+ oProp := streams.NewActivityStreamsObjectProperty()
+ oProp.AppendType(o)
+ c.SetActivityStreamsObject(oProp)
+ // Actor Property
+ actorProp := streams.NewActivityStreamsActorProperty()
+ actorProp.AppendIRI(actor)
+ c.SetActivityStreamsActor(actorProp)
+ // Published Property
+ if v, ok := o.(publisheder); ok {
+ c.SetActivityStreamsPublished(v.GetActivityStreamsPublished())
+ }
+ // Copying over properties.
+ if v, ok := o.(toer); ok {
+ if to := v.GetActivityStreamsTo(); to != nil {
+ activityTo := streams.NewActivityStreamsToProperty()
+ for iter := to.Begin(); iter != to.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityTo.AppendIRI(id)
+ }
+ c.SetActivityStreamsTo(activityTo)
+ }
+ }
+ if v, ok := o.(btoer); ok {
+ if bto := v.GetActivityStreamsBto(); bto != nil {
+ activityBto := streams.NewActivityStreamsBtoProperty()
+ for iter := bto.Begin(); iter != bto.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityBto.AppendIRI(id)
+ }
+ c.SetActivityStreamsBto(activityBto)
+ }
+ }
+ if v, ok := o.(ccer); ok {
+ if cc := v.GetActivityStreamsCc(); cc != nil {
+ activityCc := streams.NewActivityStreamsCcProperty()
+ for iter := cc.Begin(); iter != cc.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityCc.AppendIRI(id)
+ }
+ c.SetActivityStreamsCc(activityCc)
+ }
+ }
+ if v, ok := o.(bccer); ok {
+ if bcc := v.GetActivityStreamsBcc(); bcc != nil {
+ activityBcc := streams.NewActivityStreamsBccProperty()
+ for iter := bcc.Begin(); iter != bcc.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityBcc.AppendIRI(id)
+ }
+ c.SetActivityStreamsBcc(activityBcc)
+ }
+ }
+ if v, ok := o.(audiencer); ok {
+ if aud := v.GetActivityStreamsAudience(); aud != nil {
+ activityAudience := streams.NewActivityStreamsAudienceProperty()
+ for iter := aud.Begin(); iter != aud.End(); iter = iter.Next() {
+ var id *url.URL
+ id, err = ToId(iter)
+ if err != nil {
+ return
+ }
+ activityAudience.AppendIRI(id)
+ }
+ c.SetActivityStreamsAudience(activityAudience)
+ }
+ }
+ return
+}
+
+// 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
+}
+
+const (
+ // PublicActivityPubIRI is the IRI that indicates an Activity is meant
+ // to be visible for general public consumption.
+ PublicActivityPubIRI = "https://www.w3.org/ns/activitystreams#Public"
+ publicJsonLD = "Public"
+ publicJsonLDAS = "as:Public"
+)
+
+// IsPublic determines if an IRI string is the Public collection as defined in
+// the spec, including JSON-LD compliant collections.
+func IsPublic(s string) bool {
+ return s == PublicActivityPubIRI || s == publicJsonLD || s == publicJsonLDAS
+}
+
+// getInboxes extracts the 'inbox' IRIs from actor types.
+func getInboxes(t []vocab.Type) (u []*url.URL, err error) {
+ for _, elem := range t {
+ var iri *url.URL
+ iri, err = getInbox(elem)
+ if err != nil {
+ return
+ }
+ u = append(u, iri)
+ }
+ return
+}
+
+// getInbox extracts the 'inbox' IRI from an actor type.
+func getInbox(t vocab.Type) (u *url.URL, err error) {
+ ib, ok := t.(inboxer)
+ if !ok {
+ err = fmt.Errorf("actor type %T has no inbox", t)
+ return
+ }
+ inbox := ib.GetActivityStreamsInbox()
+ return ToId(inbox)
+}
+
+// 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[string]bool, len(ignored))
+ for _, elem := range ignored {
+ ignoredMap[elem.String()] = true
+ }
+ outMap := make(map[string]bool, len(recipients))
+ for _, k := range recipients {
+ kStr := k.String()
+ if !ignoredMap[kStr] && !outMap[kStr] {
+ out = append(out, k)
+ outMap[kStr] = true
+ }
+ }
+ return
+}
+
+// removeOne removes any occurrences of entry from a slice of entries.
+func removeOne(entries []*url.URL, entry *url.URL) (out []*url.URL) {
+ for _, e := range entries {
+ if e.String() != entry.String() {
+ out = append(out, e)
+ }
+ }
+ return out
+}
+
+// stripHiddenRecipients removes "bto" and "bcc" from the activity.
+//
+// Note that this requirement of the specification is under "Section 6: Client
+// to Server Interactions", the Social API, and not the Federative API.
+func stripHiddenRecipients(activity Activity) {
+ activity.SetActivityStreamsBto(nil)
+ activity.SetActivityStreamsBcc(nil)
+ op := activity.GetActivityStreamsObject()
+ if op != nil {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ if v, ok := iter.GetType().(btoer); ok {
+ v.SetActivityStreamsBto(nil)
+ }
+ if v, ok := iter.GetType().(bccer); ok {
+ v.SetActivityStreamsBcc(nil)
+ }
+ }
+ }
+}
+
+// mustHaveActivityOriginMatchObjects ensures that the Host in the activity id
+// IRI matches all of the Hosts in the object id IRIs.
+func mustHaveActivityOriginMatchObjects(a Activity) error {
+ originIRI, err := GetId(a)
+ if err != nil {
+ return err
+ }
+ originHost := originIRI.Host
+ op := a.GetActivityStreamsObject()
+ if op == nil || op.Len() == 0 {
+ return nil
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ iri, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if originHost != iri.Host {
+ return fmt.Errorf("object %q: not in activity origin", iri)
+ }
+ }
+ return nil
+}
+
+// normalizeRecipients 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.
+func normalizeRecipients(a vocab.ActivityStreamsCreate) error {
+ // Phase 0: Acquire all recipients on the activity.
+ //
+ // Obtain the actorTo map
+ actorToMap := make(map[string]*url.URL)
+ actorTo := a.GetActivityStreamsTo()
+ if actorTo == nil {
+ actorTo = streams.NewActivityStreamsToProperty()
+ a.SetActivityStreamsTo(actorTo)
+ }
+ for iter := actorTo.Begin(); iter != actorTo.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorToMap[id.String()] = id
+ }
+ // Obtain the actorBto map
+ actorBtoMap := make(map[string]*url.URL)
+ actorBto := a.GetActivityStreamsBto()
+ if actorBto == nil {
+ actorBto = streams.NewActivityStreamsBtoProperty()
+ a.SetActivityStreamsBto(actorBto)
+ }
+ for iter := actorBto.Begin(); iter != actorBto.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorBtoMap[id.String()] = id
+ }
+ // Obtain the actorCc map
+ actorCcMap := make(map[string]*url.URL)
+ actorCc := a.GetActivityStreamsCc()
+ if actorCc == nil {
+ actorCc = streams.NewActivityStreamsCcProperty()
+ a.SetActivityStreamsCc(actorCc)
+ }
+ for iter := actorCc.Begin(); iter != actorCc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorCcMap[id.String()] = id
+ }
+ // Obtain the actorBcc map
+ actorBccMap := make(map[string]*url.URL)
+ actorBcc := a.GetActivityStreamsBcc()
+ if actorBcc == nil {
+ actorBcc = streams.NewActivityStreamsBccProperty()
+ a.SetActivityStreamsBcc(actorBcc)
+ }
+ for iter := actorBcc.Begin(); iter != actorBcc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorBccMap[id.String()] = id
+ }
+ // Obtain the actorAudience map
+ actorAudienceMap := make(map[string]*url.URL)
+ actorAudience := a.GetActivityStreamsAudience()
+ if actorAudience == nil {
+ actorAudience = streams.NewActivityStreamsAudienceProperty()
+ a.SetActivityStreamsAudience(actorAudience)
+ }
+ for iter := actorAudience.Begin(); iter != actorAudience.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ actorAudienceMap[id.String()] = id
+ }
+ // Obtain the objects maps for each recipient type.
+ o := a.GetActivityStreamsObject()
+ objsTo := make([]map[string]*url.URL, o.Len())
+ objsBto := make([]map[string]*url.URL, o.Len())
+ objsCc := make([]map[string]*url.URL, o.Len())
+ objsBcc := make([]map[string]*url.URL, o.Len())
+ objsAudience := make([]map[string]*url.URL, o.Len())
+ for i := 0; i < o.Len(); i++ {
+ iter := o.At(i)
+ // Phase 1: Acquire all existing recipients on the object.
+ //
+ // Object to
+ objsTo[i] = make(map[string]*url.URL)
+ var oTo vocab.ActivityStreamsToProperty
+ if tr, ok := iter.GetType().(toer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'to' property", i)
+ } else {
+ oTo = tr.GetActivityStreamsTo()
+ if oTo == nil {
+ oTo = streams.NewActivityStreamsToProperty()
+ tr.SetActivityStreamsTo(oTo)
+ }
+ }
+ for iter := oTo.Begin(); iter != oTo.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsTo[i][id.String()] = id
+ }
+ // Object bto
+ objsBto[i] = make(map[string]*url.URL)
+ var oBto vocab.ActivityStreamsBtoProperty
+ if tr, ok := iter.GetType().(btoer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'bto' property", i)
+ } else {
+ oBto = tr.GetActivityStreamsBto()
+ if oBto == nil {
+ oBto = streams.NewActivityStreamsBtoProperty()
+ tr.SetActivityStreamsBto(oBto)
+ }
+ }
+ for iter := oBto.Begin(); iter != oBto.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsBto[i][id.String()] = id
+ }
+ // Object cc
+ objsCc[i] = make(map[string]*url.URL)
+ var oCc vocab.ActivityStreamsCcProperty
+ if tr, ok := iter.GetType().(ccer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'cc' property", i)
+ } else {
+ oCc = tr.GetActivityStreamsCc()
+ if oCc == nil {
+ oCc = streams.NewActivityStreamsCcProperty()
+ tr.SetActivityStreamsCc(oCc)
+ }
+ }
+ for iter := oCc.Begin(); iter != oCc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsCc[i][id.String()] = id
+ }
+ // Object bcc
+ objsBcc[i] = make(map[string]*url.URL)
+ var oBcc vocab.ActivityStreamsBccProperty
+ if tr, ok := iter.GetType().(bccer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'bcc' property", i)
+ } else {
+ oBcc = tr.GetActivityStreamsBcc()
+ if oBcc == nil {
+ oBcc = streams.NewActivityStreamsBccProperty()
+ tr.SetActivityStreamsBcc(oBcc)
+ }
+ }
+ for iter := oBcc.Begin(); iter != oBcc.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsBcc[i][id.String()] = id
+ }
+ // Object audience
+ objsAudience[i] = make(map[string]*url.URL)
+ var oAudience vocab.ActivityStreamsAudienceProperty
+ if tr, ok := iter.GetType().(audiencer); !ok {
+ return fmt.Errorf("the Create object at %d has no 'audience' property", i)
+ } else {
+ oAudience = tr.GetActivityStreamsAudience()
+ if oAudience == nil {
+ oAudience = streams.NewActivityStreamsAudienceProperty()
+ tr.SetActivityStreamsAudience(oAudience)
+ }
+ }
+ for iter := oAudience.Begin(); iter != oAudience.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ objsAudience[i][id.String()] = id
+ }
+ // Phase 2: Apply missing recipients to the object from the
+ // activity.
+ //
+ // Activity to -> Object to
+ for k, v := range actorToMap {
+ if _, ok := objsTo[i][k]; !ok {
+ oTo.AppendIRI(v)
+ }
+ }
+ // Activity bto -> Object bto
+ for k, v := range actorBtoMap {
+ if _, ok := objsBto[i][k]; !ok {
+ oBto.AppendIRI(v)
+ }
+ }
+ // Activity cc -> Object cc
+ for k, v := range actorCcMap {
+ if _, ok := objsCc[i][k]; !ok {
+ oCc.AppendIRI(v)
+ }
+ }
+ // Activity bcc -> Object bcc
+ for k, v := range actorBccMap {
+ if _, ok := objsBcc[i][k]; !ok {
+ oBcc.AppendIRI(v)
+ }
+ }
+ // Activity audience -> Object audience
+ for k, v := range actorAudienceMap {
+ if _, ok := objsAudience[i][k]; !ok {
+ oAudience.AppendIRI(v)
+ }
+ }
+ }
+ // Phase 3: Apply missing recipients to the activity from the objects.
+ //
+ // Object to -> Activity to
+ for i := 0; i < len(objsTo); i++ {
+ for k, v := range objsTo[i] {
+ if _, ok := actorToMap[k]; !ok {
+ actorTo.AppendIRI(v)
+ }
+ }
+ }
+ // Object bto -> Activity bto
+ for i := 0; i < len(objsBto); i++ {
+ for k, v := range objsBto[i] {
+ if _, ok := actorBtoMap[k]; !ok {
+ actorBto.AppendIRI(v)
+ }
+ }
+ }
+ // Object cc -> Activity cc
+ for i := 0; i < len(objsCc); i++ {
+ for k, v := range objsCc[i] {
+ if _, ok := actorCcMap[k]; !ok {
+ actorCc.AppendIRI(v)
+ }
+ }
+ }
+ // Object bcc -> Activity bcc
+ for i := 0; i < len(objsBcc); i++ {
+ for k, v := range objsBcc[i] {
+ if _, ok := actorBccMap[k]; !ok {
+ actorBcc.AppendIRI(v)
+ }
+ }
+ }
+ // Object audience -> Activity audience
+ for i := 0; i < len(objsAudience); i++ {
+ for k, v := range objsAudience[i] {
+ if _, ok := actorAudienceMap[k]; !ok {
+ actorAudience.AppendIRI(v)
+ }
+ }
+ }
+ return nil
+}
+
+// toTombstone creates a Tombstone object for the given ActivityStreams value.
+func toTombstone(obj vocab.Type, id *url.URL, now time.Time) vocab.ActivityStreamsTombstone {
+ tomb := streams.NewActivityStreamsTombstone()
+ // id property
+ idProp := streams.NewJSONLDIdProperty()
+ idProp.Set(id)
+ tomb.SetJSONLDId(idProp)
+ // formerType property
+ former := streams.NewActivityStreamsFormerTypeProperty()
+ tomb.SetActivityStreamsFormerType(former)
+ // Populate Former Type
+ former.AppendXMLSchemaString(obj.GetTypeName())
+ // Copy over the published property if it existed
+ if pubber, ok := obj.(publisheder); ok {
+ if pub := pubber.GetActivityStreamsPublished(); pub != nil {
+ tomb.SetActivityStreamsPublished(pub)
+ }
+ }
+ // Copy over the updated property if it existed
+ if upder, ok := obj.(updateder); ok {
+ if upd := upder.GetActivityStreamsUpdated(); upd != nil {
+ tomb.SetActivityStreamsUpdated(upd)
+ }
+ }
+ // Set deleted time to now.
+ deleted := streams.NewActivityStreamsDeletedProperty()
+ deleted.Set(now)
+ tomb.SetActivityStreamsDeleted(deleted)
+ return tomb
+}
+
+// mustHaveActivityActorsMatchObjectActors ensures that the actors on types in
+// the 'object' property are all listed in the 'actor' property.
+func mustHaveActivityActorsMatchObjectActors(c context.Context,
+ actors vocab.ActivityStreamsActorProperty,
+ op vocab.ActivityStreamsObjectProperty,
+ newTransport func(c context.Context, actorBoxIRI *url.URL, gofedAgent string) (t Transport, err error),
+ boxIRI *url.URL) error {
+ activityActorMap := make(map[string]bool, actors.Len())
+ for iter := actors.Begin(); iter != actors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ activityActorMap[id.String()] = true
+ }
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ iri, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ // Attempt to dereference the IRI, regardless whether it is a
+ // type or IRI
+ tport, err := newTransport(c, boxIRI, goFedUserAgent())
+ if err != nil {
+ return err
+ }
+ b, err := tport.Dereference(c, iri)
+ if err != nil {
+ return err
+ }
+ var m map[string]interface{}
+ if err = json.Unmarshal(b, &m); err != nil {
+ return err
+ }
+ t, err := streams.ToType(c, m)
+ if err != nil {
+ return err
+ }
+ ac, ok := t.(actorer)
+ if !ok {
+ return fmt.Errorf("cannot verify actors: object value has no 'actor' property")
+ }
+ objActors := ac.GetActivityStreamsActor()
+ for iter := objActors.Begin(); iter != objActors.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ if !activityActorMap[id.String()] {
+ return fmt.Errorf("activity does not have all actors from its object's actors")
+ }
+ }
+ }
+ return nil
+}
+
+// add implements the logic of adding object ids to a target Collection or
+// OrderedCollection. This logic is shared by both the C2S and S2S protocols.
+func add(c context.Context,
+ op vocab.ActivityStreamsObjectProperty,
+ target vocab.ActivityStreamsTargetProperty,
+ db Database) error {
+ opIds := make([]*url.URL, 0, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ opIds = append(opIds, id)
+ }
+ targetIds := make([]*url.URL, 0, op.Len())
+ for iter := target.Begin(); iter != target.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ targetIds = append(targetIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(t *url.URL) error {
+ if err := db.Lock(c, t); err != nil {
+ return err
+ }
+ defer db.Unlock(c, t)
+ if owns, err := db.Owns(c, t); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ tp, err := db.Get(c, t)
+ if err != nil {
+ return err
+ }
+ if streams.IsOrExtendsActivityStreamsOrderedCollection(tp) {
+ oi, ok := tp.(orderedItemser)
+ if !ok {
+ return fmt.Errorf("type extending from OrderedCollection cannot convert to orderedItemser interface")
+ }
+ oiProp := oi.GetActivityStreamsOrderedItems()
+ if oiProp == nil {
+ oiProp = streams.NewActivityStreamsOrderedItemsProperty()
+ oi.SetActivityStreamsOrderedItems(oiProp)
+ }
+ for _, objId := range opIds {
+ oiProp.AppendIRI(objId)
+ }
+ } else if streams.IsOrExtendsActivityStreamsCollection(tp) {
+ i, ok := tp.(itemser)
+ if !ok {
+ return fmt.Errorf("type extending from Collection cannot convert to itemser interface")
+ }
+ iProp := i.GetActivityStreamsItems()
+ if iProp == nil {
+ iProp = streams.NewActivityStreamsItemsProperty()
+ i.SetActivityStreamsItems(iProp)
+ }
+ for _, objId := range opIds {
+ iProp.AppendIRI(objId)
+ }
+ } else {
+ return fmt.Errorf("target in Add is neither a Collection nor an OrderedCollection")
+ }
+ err = db.Update(c, tp)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ for _, t := range targetIds {
+ if err := loopFn(t); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// remove implements the logic of removing object ids to a target Collection or
+// OrderedCollection. This logic is shared by both the C2S and S2S protocols.
+func remove(c context.Context,
+ op vocab.ActivityStreamsObjectProperty,
+ target vocab.ActivityStreamsTargetProperty,
+ db Database) error {
+ opIds := make(map[string]bool, op.Len())
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ opIds[id.String()] = true
+ }
+ targetIds := make([]*url.URL, 0, op.Len())
+ for iter := target.Begin(); iter != target.End(); iter = iter.Next() {
+ id, err := ToId(iter)
+ if err != nil {
+ return err
+ }
+ targetIds = append(targetIds, id)
+ }
+ // Create anonymous loop function to be able to properly scope the defer
+ // for the database lock at each iteration.
+ loopFn := func(t *url.URL) error {
+ if err := db.Lock(c, t); err != nil {
+ return err
+ }
+ defer db.Unlock(c, t)
+ if owns, err := db.Owns(c, t); err != nil {
+ return err
+ } else if !owns {
+ return nil
+ }
+ tp, err := db.Get(c, t)
+ if err != nil {
+ return err
+ }
+ if streams.IsOrExtendsActivityStreamsOrderedCollection(tp) {
+ oi, ok := tp.(orderedItemser)
+ if !ok {
+ return fmt.Errorf("type extending from OrderedCollection cannot convert to orderedItemser interface")
+ }
+ oiProp := oi.GetActivityStreamsOrderedItems()
+ if oiProp != nil {
+ for i := 0; i < oiProp.Len(); /*Conditional*/ {
+ id, err := ToId(oiProp.At(i))
+ if err != nil {
+ return err
+ }
+ if opIds[id.String()] {
+ oiProp.Remove(i)
+ } else {
+ i++
+ }
+ }
+ }
+ } else if streams.IsOrExtendsActivityStreamsCollection(tp) {
+ i, ok := tp.(itemser)
+ if !ok {
+ return fmt.Errorf("type extending from Collection cannot convert to itemser interface")
+ }
+ iProp := i.GetActivityStreamsItems()
+ if iProp != nil {
+ for i := 0; i < iProp.Len(); /*Conditional*/ {
+ id, err := ToId(iProp.At(i))
+ if err != nil {
+ return err
+ }
+ if opIds[id.String()] {
+ iProp.Remove(i)
+ } else {
+ i++
+ }
+ }
+ }
+ } else {
+ return fmt.Errorf("target in Remove is neither a Collection nor an OrderedCollection")
+ }
+ err = db.Update(c, tp)
+ if err != nil {
+ return err
+ }
+ return nil
+ }
+ for _, t := range targetIds {
+ if err := loopFn(t); err != nil {
+ return err
+ }
+ }
+ return nil
+}
+
+// clearSensitiveFields removes the 'bto' and 'bcc' entries on the given value
+// and recursively on every 'object' property value.
+func clearSensitiveFields(obj vocab.Type) {
+ if t, ok := obj.(btoer); ok {
+ t.SetActivityStreamsBto(nil)
+ }
+ if t, ok := obj.(bccer); ok {
+ t.SetActivityStreamsBcc(nil)
+ }
+ if t, ok := obj.(objecter); ok {
+ op := t.GetActivityStreamsObject()
+ if op != nil {
+ for iter := op.Begin(); iter != op.End(); iter = iter.Next() {
+ clearSensitiveFields(iter.GetType())
+ }
+ }
+ }
+}
+
+// requestId forms an ActivityPub id based on the HTTP request. Always assumes
+// that the id is HTTPS.
+func requestId(r *http.Request, scheme string) *url.URL {
+ id := r.URL
+ id.Host = r.Host
+ id.Scheme = scheme
+ return id
+}