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