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 }