github.com/mattermosttest/mattermost-server/v5@v5.0.0-20200917143240-9dfa12e121f9/app/emoji.go (about) 1 // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. 2 // See LICENSE.txt for license information. 3 4 package app 5 6 import ( 7 "bytes" 8 "errors" 9 "fmt" 10 "image" 11 "image/color/palette" 12 "image/draw" 13 "image/gif" 14 _ "image/jpeg" 15 "image/png" 16 "io" 17 "mime/multipart" 18 "net/http" 19 "path" 20 21 "github.com/disintegration/imaging" 22 "github.com/mattermost/mattermost-server/v5/mlog" 23 "github.com/mattermost/mattermost-server/v5/model" 24 "github.com/mattermost/mattermost-server/v5/store" 25 "github.com/mattermost/mattermost-server/v5/utils" 26 ) 27 28 const ( 29 MaxEmojiFileSize = 1 << 20 // 1 MB 30 MaxEmojiWidth = 128 31 MaxEmojiHeight = 128 32 MaxEmojiOriginalWidth = 1028 33 MaxEmojiOriginalHeight = 1028 34 ) 35 36 func (a *App) CreateEmoji(sessionUserId string, emoji *model.Emoji, multiPartImageData *multipart.Form) (*model.Emoji, *model.AppError) { 37 if !*a.Config().ServiceSettings.EnableCustomEmoji { 38 return nil, model.NewAppError("UploadEmojiImage", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 39 } 40 41 if len(*a.Config().FileSettings.DriverName) == 0 { 42 return nil, model.NewAppError("GetEmoji", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) 43 } 44 45 // wipe the emoji id so that existing emojis can't get overwritten 46 emoji.Id = "" 47 48 // do our best to validate the emoji before committing anything to the DB so that we don't have to clean up 49 // orphaned files left over when validation fails later on 50 emoji.PreSave() 51 if err := emoji.IsValid(); err != nil { 52 return nil, err 53 } 54 55 if emoji.CreatorId != sessionUserId { 56 return nil, model.NewAppError("createEmoji", "api.emoji.create.other_user.app_error", nil, "", http.StatusForbidden) 57 } 58 59 if existingEmoji, err := a.Srv().Store.Emoji().GetByName(emoji.Name, true); err == nil && existingEmoji != nil { 60 return nil, model.NewAppError("createEmoji", "api.emoji.create.duplicate.app_error", nil, "", http.StatusBadRequest) 61 } 62 63 imageData := multiPartImageData.File["image"] 64 if len(imageData) == 0 { 65 err := model.NewAppError("Context", "api.context.invalid_body_param.app_error", map[string]interface{}{"Name": "createEmoji"}, "", http.StatusBadRequest) 66 return nil, err 67 } 68 69 if err := a.UploadEmojiImage(emoji.Id, imageData[0]); err != nil { 70 return nil, err 71 } 72 73 emoji, err := a.Srv().Store.Emoji().Save(emoji) 74 if err != nil { 75 return nil, model.NewAppError("CreateEmoji", "app.emoji.create.internal_error", nil, err.Error(), http.StatusInternalServerError) 76 } 77 78 message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil) 79 message.Add("emoji", emoji.ToJson()) 80 a.Publish(message) 81 return emoji, nil 82 } 83 84 func (a *App) GetEmojiList(page, perPage int, sort string) ([]*model.Emoji, *model.AppError) { 85 list, err := a.Srv().Store.Emoji().GetList(page*perPage, perPage, sort) 86 if err != nil { 87 return nil, model.NewAppError("GetEmojiList", "app.emoji.get_list.internal_error", nil, err.Error(), http.StatusInternalServerError) 88 } 89 90 return list, nil 91 } 92 93 func (a *App) UploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError { 94 if !*a.Config().ServiceSettings.EnableCustomEmoji { 95 return model.NewAppError("UploadEmojiImage", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 96 } 97 98 if len(*a.Config().FileSettings.DriverName) == 0 { 99 return model.NewAppError("UploadEmojiImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) 100 } 101 102 file, err := imageData.Open() 103 if err != nil { 104 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.open.app_error", nil, "", http.StatusBadRequest) 105 } 106 defer file.Close() 107 108 buf := bytes.NewBuffer(nil) 109 io.Copy(buf, file) 110 111 // make sure the file is an image and is within the required dimensions 112 config, _, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())) 113 if err != nil { 114 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.image.app_error", nil, "", http.StatusBadRequest) 115 } 116 117 if config.Width > MaxEmojiOriginalWidth || config.Height > MaxEmojiOriginalHeight { 118 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.too_large.app_error", map[string]interface{}{ 119 "MaxWidth": MaxEmojiOriginalWidth, 120 "MaxHeight": MaxEmojiOriginalHeight, 121 }, "", http.StatusBadRequest) 122 } 123 124 if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight { 125 data := buf.Bytes() 126 newbuf := bytes.NewBuffer(nil) 127 info, err := model.GetInfoForBytes(imageData.Filename, data) 128 if err != nil { 129 return err 130 } 131 132 if info.MimeType == "image/gif" { 133 gif_data, err := gif.DecodeAll(bytes.NewReader(data)) 134 if err != nil { 135 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_decode_error", nil, "", http.StatusBadRequest) 136 } 137 138 resized_gif := resizeEmojiGif(gif_data) 139 if err := gif.EncodeAll(newbuf, resized_gif); err != nil { 140 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "", http.StatusBadRequest) 141 } 142 143 if _, err := a.WriteFile(newbuf, getEmojiImagePath(id)); err != nil { 144 return err 145 } 146 } else { 147 img, _, err := image.Decode(bytes.NewReader(data)) 148 if err != nil { 149 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.decode_error", nil, "", http.StatusBadRequest) 150 } 151 152 resized_image := resizeEmoji(img, config.Width, config.Height) 153 if err := png.Encode(newbuf, resized_image); err != nil { 154 return model.NewAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "", http.StatusBadRequest) 155 } 156 if _, err := a.WriteFile(newbuf, getEmojiImagePath(id)); err != nil { 157 return err 158 } 159 } 160 } 161 162 _, appErr := a.WriteFile(buf, getEmojiImagePath(id)) 163 return appErr 164 } 165 166 func (a *App) DeleteEmoji(emoji *model.Emoji) *model.AppError { 167 if err := a.Srv().Store.Emoji().Delete(emoji, model.GetMillis()); err != nil { 168 var nfErr *store.ErrNotFound 169 switch { 170 case errors.As(err, &nfErr): 171 return model.NewAppError("DeleteEmoji", "app.emoji.delete.no_results", nil, "id="+emoji.Id+", err="+err.Error(), http.StatusNotFound) 172 default: 173 return model.NewAppError("DeleteEmoji", "app.emoji.delete.app_error", nil, "id="+emoji.Id+", err="+err.Error(), http.StatusInternalServerError) 174 } 175 } 176 177 a.deleteEmojiImage(emoji.Id) 178 a.deleteReactionsForEmoji(emoji.Name) 179 return nil 180 } 181 182 func (a *App) GetEmoji(emojiId string) (*model.Emoji, *model.AppError) { 183 if !*a.Config().ServiceSettings.EnableCustomEmoji { 184 return nil, model.NewAppError("GetEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 185 } 186 187 if len(*a.Config().FileSettings.DriverName) == 0 { 188 return nil, model.NewAppError("GetEmoji", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) 189 } 190 191 emoji, err := a.Srv().Store.Emoji().Get(emojiId, false) 192 if err != nil { 193 var nfErr *store.ErrNotFound 194 switch { 195 case errors.As(err, &nfErr): 196 return emoji, model.NewAppError("GetEmoji", "app.emoji.get.no_result", nil, err.Error(), http.StatusNotFound) 197 default: 198 return emoji, model.NewAppError("GetEmoji", "app.emoji.get.app_error", nil, err.Error(), http.StatusInternalServerError) 199 } 200 } 201 202 return emoji, nil 203 } 204 205 func (a *App) GetEmojiByName(emojiName string) (*model.Emoji, *model.AppError) { 206 if !*a.Config().ServiceSettings.EnableCustomEmoji { 207 return nil, model.NewAppError("GetEmojiByName", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 208 } 209 210 if len(*a.Config().FileSettings.DriverName) == 0 { 211 return nil, model.NewAppError("GetEmojiByName", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented) 212 } 213 214 emoji, err := a.Srv().Store.Emoji().GetByName(emojiName, true) 215 if err != nil { 216 var nfErr *store.ErrNotFound 217 switch { 218 case errors.As(err, &nfErr): 219 return emoji, model.NewAppError("GetEmojiByName", "app.emoji.get_by_name.no_result", nil, err.Error(), http.StatusNotFound) 220 default: 221 return emoji, model.NewAppError("GetEmojiByName", "app.emoji.get_by_name.app_error", nil, err.Error(), http.StatusInternalServerError) 222 } 223 } 224 225 return emoji, nil 226 } 227 228 func (a *App) GetMultipleEmojiByName(names []string) ([]*model.Emoji, *model.AppError) { 229 if !*a.Config().ServiceSettings.EnableCustomEmoji { 230 return nil, model.NewAppError("GetMultipleEmojiByName", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 231 } 232 233 emoji, err := a.Srv().Store.Emoji().GetMultipleByName(names) 234 if err != nil { 235 return nil, model.NewAppError("GetMultipleEmojiByName", "app.emoji.get_by_name.app_error", nil, fmt.Sprintf("names=%v, %v", names, err.Error()), http.StatusInternalServerError) 236 } 237 238 return emoji, nil 239 } 240 241 func (a *App) GetEmojiImage(emojiId string) ([]byte, string, *model.AppError) { 242 _, storeErr := a.Srv().Store.Emoji().Get(emojiId, true) 243 if storeErr != nil { 244 var nfErr *store.ErrNotFound 245 switch { 246 case errors.As(storeErr, &nfErr): 247 return nil, "", model.NewAppError("GetEmojiImage", "app.emoji.get.no_result", nil, storeErr.Error(), http.StatusNotFound) 248 default: 249 return nil, "", model.NewAppError("GetEmojiImage", "app.emoji.get.app_error", nil, storeErr.Error(), http.StatusInternalServerError) 250 } 251 } 252 253 img, appErr := a.ReadFile(getEmojiImagePath(emojiId)) 254 if appErr != nil { 255 return nil, "", model.NewAppError("getEmojiImage", "api.emoji.get_image.read.app_error", nil, appErr.Error(), http.StatusNotFound) 256 } 257 258 _, imageType, err := image.DecodeConfig(bytes.NewReader(img)) 259 if err != nil { 260 return nil, "", model.NewAppError("getEmojiImage", "api.emoji.get_image.decode.app_error", nil, err.Error(), http.StatusInternalServerError) 261 } 262 263 return img, imageType, nil 264 } 265 266 func (a *App) SearchEmoji(name string, prefixOnly bool, limit int) ([]*model.Emoji, *model.AppError) { 267 if !*a.Config().ServiceSettings.EnableCustomEmoji { 268 return nil, model.NewAppError("SearchEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented) 269 } 270 271 list, err := a.Srv().Store.Emoji().Search(name, prefixOnly, limit) 272 if err != nil { 273 return nil, model.NewAppError("SearchEmoji", "app.emoji.get_by_name.app_error", nil, "name="+name+", "+err.Error(), http.StatusInternalServerError) 274 } 275 276 return list, nil 277 } 278 279 // GetEmojiStaticUrl returns a relative static URL for system default emojis, 280 // and the API route for custom ones. Errors if not found or if custom and deleted. 281 func (a *App) GetEmojiStaticUrl(emojiName string) (string, *model.AppError) { 282 subPath, _ := utils.GetSubpathFromConfig(a.Config()) 283 284 if id, found := model.GetSystemEmojiId(emojiName); found { 285 return path.Join(subPath, "/static/emoji", id+".png"), nil 286 } 287 288 if emoji, err := a.Srv().Store.Emoji().GetByName(emojiName, true); err == nil { 289 return path.Join(subPath, "/api/v4/emoji", emoji.Id, "image"), nil 290 } else { 291 var nfErr *store.ErrNotFound 292 switch { 293 case errors.As(err, &nfErr): 294 return "", model.NewAppError("GetEmojiStaticUrl", "app.emoji.get_by_name.no_result", nil, err.Error(), http.StatusNotFound) 295 default: 296 return "", model.NewAppError("GetEmojiStaticUrl", "app.emoji.get_by_name.app_error", nil, err.Error(), http.StatusInternalServerError) 297 } 298 } 299 } 300 301 func resizeEmojiGif(gifImg *gif.GIF) *gif.GIF { 302 // Create a new RGBA image to hold the incremental frames. 303 firstFrame := gifImg.Image[0].Bounds() 304 b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy()) 305 img := image.NewRGBA(b) 306 307 resizedImage := image.Image(nil) 308 // Resize each frame. 309 for index, frame := range gifImg.Image { 310 bounds := frame.Bounds() 311 draw.Draw(img, bounds, frame, bounds.Min, draw.Over) 312 resizedImage = resizeEmoji(img, firstFrame.Dx(), firstFrame.Dy()) 313 gifImg.Image[index] = imageToPaletted(resizedImage) 314 } 315 // Set new gif width and height 316 gifImg.Config.Width = resizedImage.Bounds().Dx() 317 gifImg.Config.Height = resizedImage.Bounds().Dy() 318 return gifImg 319 } 320 321 func getEmojiImagePath(id string) string { 322 return "emoji/" + id + "/image" 323 } 324 325 func resizeEmoji(img image.Image, width int, height int) image.Image { 326 emojiWidth := float64(width) 327 emojiHeight := float64(height) 328 329 if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth { 330 return img 331 } 332 return imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos) 333 } 334 335 func imageToPaletted(img image.Image) *image.Paletted { 336 b := img.Bounds() 337 pm := image.NewPaletted(b, palette.Plan9) 338 draw.FloydSteinberg.Draw(pm, b, img, image.Point{}) 339 return pm 340 } 341 342 func (a *App) deleteEmojiImage(id string) { 343 if err := a.MoveFile(getEmojiImagePath(id), "emoji/"+id+"/image_deleted"); err != nil { 344 mlog.Error("Failed to rename image when deleting emoji", mlog.String("emoji_id", id)) 345 } 346 } 347 348 func (a *App) deleteReactionsForEmoji(emojiName string) { 349 if err := a.Srv().Store.Reaction().DeleteAllWithEmojiName(emojiName); err != nil { 350 mlog.Warn("Unable to delete reactions when deleting emoji", mlog.String("emoji_name", emojiName), mlog.Err(err)) 351 } 352 }