gtsocial-umbx

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs | README | LICENSE

normalize.go (7568B)


      1 // GoToSocial
      2 // Copyright (C) GoToSocial Authors admin@gotosocial.org
      3 // SPDX-License-Identifier: AGPL-3.0-or-later
      4 //
      5 // This program is free software: you can redistribute it and/or modify
      6 // it under the terms of the GNU Affero General Public License as published by
      7 // the Free Software Foundation, either version 3 of the License, or
      8 // (at your option) any later version.
      9 //
     10 // This program is distributed in the hope that it will be useful,
     11 // but WITHOUT ANY WARRANTY; without even the implied warranty of
     12 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     13 // GNU Affero General Public License for more details.
     14 //
     15 // You should have received a copy of the GNU Affero General Public License
     16 // along with this program.  If not, see <http://www.gnu.org/licenses/>.
     17 
     18 package ap
     19 
     20 import (
     21 	"github.com/superseriousbusiness/activity/pub"
     22 	"github.com/superseriousbusiness/activity/streams"
     23 )
     24 
     25 /*
     26 	NORMALIZE INCOMING
     27 	The below functions should be called to normalize the content
     28 	of messages *COMING INTO* GoToSocial via the federation API,
     29 	either as the result of delivery from a remote instance to this
     30 	instance, or as a result of this instance doing an http call to
     31 	another instance to dereference something.
     32 */
     33 
     34 // NormalizeIncomingActivityObject normalizes the 'object'.'content' field of the given Activity.
     35 //
     36 // The rawActivity map should the freshly deserialized json representation of the Activity.
     37 //
     38 // This function is a noop if the type passed in is anything except a Create or Update with a Statusable or Accountable as its Object.
     39 func NormalizeIncomingActivityObject(activity pub.Activity, rawJSON map[string]interface{}) {
     40 	if typeName := activity.GetTypeName(); typeName != ActivityCreate && typeName != ActivityUpdate {
     41 		// Only interested in Create or Update right now.
     42 		return
     43 	}
     44 
     45 	withObject, ok := activity.(WithObject)
     46 	if !ok {
     47 		// Create was not a WithObject.
     48 		return
     49 	}
     50 
     51 	createObject := withObject.GetActivityStreamsObject()
     52 	if createObject == nil {
     53 		// No object set.
     54 		return
     55 	}
     56 
     57 	if createObject.Len() != 1 {
     58 		// Not interested in Object arrays.
     59 		return
     60 	}
     61 
     62 	// We now know length is 1 so get the first
     63 	// item from the iter. We need this to be
     64 	// a Statusable or Accountable if we're to continue.
     65 	i := createObject.At(0)
     66 	if i == nil {
     67 		// This is awkward.
     68 		return
     69 	}
     70 
     71 	t := i.GetType()
     72 	if t == nil {
     73 		// This is also awkward.
     74 		return
     75 	}
     76 
     77 	switch t.GetTypeName() {
     78 	case ObjectArticle, ObjectDocument, ObjectImage, ObjectVideo, ObjectNote, ObjectPage, ObjectEvent, ObjectPlace, ObjectProfile:
     79 		statusable, ok := t.(Statusable)
     80 		if !ok {
     81 			// Object is not Statusable;
     82 			// we're not interested.
     83 			return
     84 		}
     85 
     86 		rawObject, ok := rawJSON["object"]
     87 		if !ok {
     88 			// No object in raw map.
     89 			return
     90 		}
     91 
     92 		rawStatusableJSON, ok := rawObject.(map[string]interface{})
     93 		if !ok {
     94 			// Object wasn't a json object.
     95 			return
     96 		}
     97 
     98 		// Normalize everything we can on the statusable.
     99 		NormalizeIncomingContent(statusable, rawStatusableJSON)
    100 		NormalizeIncomingAttachments(statusable, rawStatusableJSON)
    101 		NormalizeIncomingSummary(statusable, rawStatusableJSON)
    102 		NormalizeIncomingName(statusable, rawStatusableJSON)
    103 	case ActorApplication, ActorGroup, ActorOrganization, ActorPerson, ActorService:
    104 		accountable, ok := t.(Accountable)
    105 		if !ok {
    106 			// Object is not Accountable;
    107 			// we're not interested.
    108 			return
    109 		}
    110 
    111 		rawObject, ok := rawJSON["object"]
    112 		if !ok {
    113 			// No object in raw map.
    114 			return
    115 		}
    116 
    117 		rawAccountableJSON, ok := rawObject.(map[string]interface{})
    118 		if !ok {
    119 			// Object wasn't a json object.
    120 			return
    121 		}
    122 
    123 		// Normalize everything we can on the accountable.
    124 		NormalizeIncomingSummary(accountable, rawAccountableJSON)
    125 	}
    126 }
    127 
    128 // NormalizeIncomingContent replaces the Content of the given item
    129 // with the raw 'content' value from the raw json object map.
    130 //
    131 // noop if there was no content in the json object map or the
    132 // content was not a plain string.
    133 func NormalizeIncomingContent(item WithSetContent, rawJSON map[string]interface{}) {
    134 	rawContent, ok := rawJSON["content"]
    135 	if !ok {
    136 		// No content in rawJSON.
    137 		// TODO: In future we might also
    138 		// look for "contentMap" property.
    139 		return
    140 	}
    141 
    142 	content, ok := rawContent.(string)
    143 	if !ok {
    144 		// Not interested in content arrays.
    145 		return
    146 	}
    147 
    148 	// Set normalized content property from the raw string;
    149 	// this replaces any existing content property on the item.
    150 	contentProp := streams.NewActivityStreamsContentProperty()
    151 	contentProp.AppendXMLSchemaString(content)
    152 	item.SetActivityStreamsContent(contentProp)
    153 }
    154 
    155 // NormalizeIncomingAttachments normalizes all attachments (if any) of the given
    156 // item, replacing the 'name' (aka content warning) field of each attachment
    157 // with the raw 'name' value from the raw json object map.
    158 //
    159 // noop if there are no attachments; noop if attachment is not a format
    160 // we can understand.
    161 func NormalizeIncomingAttachments(item WithAttachment, rawJSON map[string]interface{}) {
    162 	rawAttachments, ok := rawJSON["attachment"]
    163 	if !ok {
    164 		// No attachments in rawJSON.
    165 		return
    166 	}
    167 
    168 	// Convert to slice if not already,
    169 	// so we can iterate through it.
    170 	var attachments []interface{}
    171 	if attachments, ok = rawAttachments.([]interface{}); !ok {
    172 		attachments = []interface{}{rawAttachments}
    173 	}
    174 
    175 	attachmentProperty := item.GetActivityStreamsAttachment()
    176 	if attachmentProperty == nil {
    177 		// Nothing to do here.
    178 		return
    179 	}
    180 
    181 	if l := attachmentProperty.Len(); l == 0 || l != len(attachments) {
    182 		// Mismatch between item and
    183 		// JSON, can't normalize.
    184 		return
    185 	}
    186 
    187 	// Keep an index of where we are in the iter;
    188 	// we need this so we can modify the correct
    189 	// attachment, in case of multiples.
    190 	i := -1
    191 
    192 	for iter := attachmentProperty.Begin(); iter != attachmentProperty.End(); iter = iter.Next() {
    193 		i++
    194 
    195 		t := iter.GetType()
    196 		if t == nil {
    197 			continue
    198 		}
    199 
    200 		attachmentable, ok := t.(Attachmentable)
    201 		if !ok {
    202 			continue
    203 		}
    204 
    205 		rawAttachment, ok := attachments[i].(map[string]interface{})
    206 		if !ok {
    207 			continue
    208 		}
    209 
    210 		NormalizeIncomingName(attachmentable, rawAttachment)
    211 	}
    212 }
    213 
    214 // NormalizeIncomingSummary replaces the Summary of the given item
    215 // with the raw 'summary' value from the raw json object map.
    216 //
    217 // noop if there was no summary in the json object map or the
    218 // summary was not a plain string.
    219 func NormalizeIncomingSummary(item WithSetSummary, rawJSON map[string]interface{}) {
    220 	rawSummary, ok := rawJSON["summary"]
    221 	if !ok {
    222 		// No summary in rawJSON.
    223 		return
    224 	}
    225 
    226 	summary, ok := rawSummary.(string)
    227 	if !ok {
    228 		// Not interested in non-string summary.
    229 		return
    230 	}
    231 
    232 	// Set normalized summary property from the raw string; this
    233 	// will replace any existing summary property on the item.
    234 	summaryProp := streams.NewActivityStreamsSummaryProperty()
    235 	summaryProp.AppendXMLSchemaString(summary)
    236 	item.SetActivityStreamsSummary(summaryProp)
    237 }
    238 
    239 // NormalizeIncomingName replaces the Name of the given item
    240 // with the raw 'name' value from the raw json object map.
    241 //
    242 // noop if there was no name in the json object map or the
    243 // name was not a plain string.
    244 func NormalizeIncomingName(item WithSetName, rawJSON map[string]interface{}) {
    245 	rawName, ok := rawJSON["name"]
    246 	if !ok {
    247 		// No name in rawJSON.
    248 		return
    249 	}
    250 
    251 	name, ok := rawName.(string)
    252 	if !ok {
    253 		// Not interested in non-string name.
    254 		return
    255 	}
    256 
    257 	// Set normalized name property from the raw string; this
    258 	// will replace any existing name property on the item.
    259 	nameProp := streams.NewActivityStreamsNameProperty()
    260 	nameProp.AppendXMLSchemaString(name)
    261 	item.SetActivityStreamsName(nameProp)
    262 }