github.com/gigforks/mattermost-server@v4.9.1-0.20180619094218-800d97fa55d0+incompatible/api/emoji.go (about)

     1  // Copyright (c) 2016-present Mattermost, Inc. All Rights Reserved.
     2  // See License.txt for license information.
     3  
     4  package api
     5  
     6  import (
     7  	"image"
     8  	"image/draw"
     9  	"image/gif"
    10  	"net/http"
    11  	"strings"
    12  
    13  	"image/color/palette"
    14  
    15  	"github.com/disintegration/imaging"
    16  	"github.com/gorilla/mux"
    17  	"github.com/mattermost/mattermost-server/app"
    18  	"github.com/mattermost/mattermost-server/model"
    19  )
    20  
    21  func (api *API) InitEmoji() {
    22  	api.BaseRoutes.Emoji.Handle("/list", api.ApiUserRequired(getEmoji)).Methods("GET")
    23  	api.BaseRoutes.Emoji.Handle("/create", api.ApiUserRequired(createEmoji)).Methods("POST")
    24  	api.BaseRoutes.Emoji.Handle("/delete", api.ApiUserRequired(deleteEmoji)).Methods("POST")
    25  	api.BaseRoutes.Emoji.Handle("/{id:[A-Za-z0-9_]+}", api.ApiUserRequiredTrustRequester(getEmojiImage)).Methods("GET")
    26  }
    27  
    28  func getEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
    29  	if !*c.App.Config().ServiceSettings.EnableCustomEmoji {
    30  		c.Err = model.NewAppError("getEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
    31  		return
    32  	}
    33  
    34  	listEmoji, err := c.App.GetEmojiList(0, 100000, "")
    35  	if err != nil {
    36  		c.Err = err
    37  		return
    38  	} else {
    39  		w.Write([]byte(model.EmojiListToJson(listEmoji)))
    40  	}
    41  }
    42  
    43  func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
    44  	if !*c.App.Config().ServiceSettings.EnableCustomEmoji {
    45  		c.Err = model.NewAppError("createEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
    46  		return
    47  	}
    48  
    49  	if emojiInterface := c.App.Emoji; emojiInterface != nil &&
    50  		!emojiInterface.CanUserCreateEmoji(c.Session.Roles, c.Session.TeamMembers) {
    51  		c.Err = model.NewAppError("createEmoji", "api.emoji.create.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized)
    52  		return
    53  	}
    54  
    55  	if len(*c.App.Config().FileSettings.DriverName) == 0 {
    56  		c.Err = model.NewAppError("createEmoji", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented)
    57  		return
    58  	}
    59  
    60  	if r.ContentLength > app.MaxEmojiFileSize {
    61  		c.Err = model.NewAppError("createEmoji", "api.emoji.create.too_large.app_error", nil, "", http.StatusRequestEntityTooLarge)
    62  		return
    63  	}
    64  
    65  	if err := r.ParseMultipartForm(app.MaxEmojiFileSize); err != nil {
    66  		c.Err = model.NewAppError("createEmoji", "api.emoji.create.parse.app_error", nil, err.Error(), http.StatusBadRequest)
    67  		return
    68  	}
    69  
    70  	m := r.MultipartForm
    71  	props := m.Value
    72  
    73  	emoji := model.EmojiFromJson(strings.NewReader(props["emoji"][0]))
    74  	if emoji == nil {
    75  		c.SetInvalidParam("createEmoji", "emoji")
    76  		return
    77  	}
    78  
    79  	// wipe the emoji id so that existing emojis can't get overwritten
    80  	emoji.Id = ""
    81  
    82  	// do our best to validate the emoji before committing anything to the DB so that we don't have to clean up
    83  	// orphaned files left over when validation fails later on
    84  	emoji.PreSave()
    85  	if err := emoji.IsValid(); err != nil {
    86  		c.Err = err
    87  		c.Err.StatusCode = http.StatusBadRequest
    88  		return
    89  	}
    90  
    91  	if emoji.CreatorId != c.Session.UserId {
    92  		c.Err = model.NewAppError("createEmoji", "api.emoji.create.other_user.app_error", nil, "", http.StatusUnauthorized)
    93  		return
    94  	}
    95  
    96  	if result := <-c.App.Srv.Store.Emoji().GetByName(emoji.Name); result.Err == nil && result.Data != nil {
    97  		c.Err = model.NewAppError("createEmoji", "api.emoji.create.duplicate.app_error", nil, "", http.StatusBadRequest)
    98  		return
    99  	}
   100  
   101  	if imageData := m.File["image"]; len(imageData) == 0 {
   102  		c.SetInvalidParam("createEmoji", "image")
   103  		return
   104  	} else if err := c.App.UploadEmojiImage(emoji.Id, imageData[0]); err != nil {
   105  		c.Err = err
   106  		return
   107  	}
   108  
   109  	if result := <-c.App.Srv.Store.Emoji().Save(emoji); result.Err != nil {
   110  		c.Err = result.Err
   111  		return
   112  	} else {
   113  		message := model.NewWebSocketEvent(model.WEBSOCKET_EVENT_EMOJI_ADDED, "", "", "", nil)
   114  		message.Add("emoji", result.Data.(*model.Emoji).ToJson())
   115  
   116  		c.App.Publish(message)
   117  		w.Write([]byte(result.Data.(*model.Emoji).ToJson()))
   118  	}
   119  }
   120  
   121  func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
   122  	if !*c.App.Config().ServiceSettings.EnableCustomEmoji {
   123  		c.Err = model.NewAppError("deleteEmoji", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
   124  		return
   125  	}
   126  
   127  	if len(*c.App.Config().FileSettings.DriverName) == 0 {
   128  		c.Err = model.NewAppError("deleteImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented)
   129  		return
   130  	}
   131  
   132  	props := model.MapFromJson(r.Body)
   133  
   134  	id := props["id"]
   135  	if len(id) == 0 {
   136  		c.SetInvalidParam("deleteEmoji", "id")
   137  		return
   138  	}
   139  
   140  	emoji, err := c.App.GetEmoji(id)
   141  	if err != nil {
   142  		c.Err = err
   143  		return
   144  	}
   145  
   146  	if c.Session.UserId != emoji.CreatorId && !c.App.SessionHasPermissionTo(c.Session, model.PERMISSION_MANAGE_SYSTEM) {
   147  		c.Err = model.NewAppError("deleteEmoji", "api.emoji.delete.permissions.app_error", nil, "user_id="+c.Session.UserId, http.StatusUnauthorized)
   148  		return
   149  	}
   150  
   151  	err = c.App.DeleteEmoji(emoji)
   152  	if err != nil {
   153  		c.Err = err
   154  		return
   155  	} else {
   156  		ReturnStatusOK(w)
   157  	}
   158  }
   159  
   160  func getEmojiImage(c *Context, w http.ResponseWriter, r *http.Request) {
   161  	if !*c.App.Config().ServiceSettings.EnableCustomEmoji {
   162  		c.Err = model.NewAppError("getEmojiImage", "api.emoji.disabled.app_error", nil, "", http.StatusNotImplemented)
   163  		return
   164  	}
   165  
   166  	if len(*c.App.Config().FileSettings.DriverName) == 0 {
   167  		c.Err = model.NewAppError("getEmojiImage", "api.emoji.storage.app_error", nil, "", http.StatusNotImplemented)
   168  		return
   169  	}
   170  
   171  	params := mux.Vars(r)
   172  
   173  	id := params["id"]
   174  	if len(id) == 0 {
   175  		c.SetInvalidParam("getEmojiImage", "id")
   176  		return
   177  	}
   178  
   179  	image, imageType, err := c.App.GetEmojiImage(id)
   180  	if err != nil {
   181  		c.Err = err
   182  		return
   183  	}
   184  
   185  	w.Header().Set("Content-Type", "image/"+imageType)
   186  	w.Header().Set("Cache-Control", "max-age=2592000, public")
   187  	w.Write(image)
   188  }
   189  
   190  func resizeEmoji(img image.Image, width int, height int) image.Image {
   191  	emojiWidth := float64(width)
   192  	emojiHeight := float64(height)
   193  
   194  	var emoji image.Image
   195  	if emojiHeight <= app.MaxEmojiHeight && emojiWidth <= app.MaxEmojiWidth {
   196  		emoji = img
   197  	} else {
   198  		emoji = imaging.Fit(img, app.MaxEmojiWidth, app.MaxEmojiHeight, imaging.Lanczos)
   199  	}
   200  	return emoji
   201  }
   202  
   203  func resizeEmojiGif(gifImg *gif.GIF) *gif.GIF {
   204  	// Create a new RGBA image to hold the incremental frames.
   205  	firstFrame := gifImg.Image[0].Bounds()
   206  	b := image.Rect(0, 0, firstFrame.Dx(), firstFrame.Dy())
   207  	img := image.NewRGBA(b)
   208  
   209  	resizedImage := image.Image(nil)
   210  	// Resize each frame.
   211  	for index, frame := range gifImg.Image {
   212  		bounds := frame.Bounds()
   213  		draw.Draw(img, bounds, frame, bounds.Min, draw.Over)
   214  		resizedImage = resizeEmoji(img, firstFrame.Dx(), firstFrame.Dy())
   215  		gifImg.Image[index] = imageToPaletted(resizedImage)
   216  	}
   217  	// Set new gif width and height
   218  	gifImg.Config.Width = resizedImage.Bounds().Dx()
   219  	gifImg.Config.Height = resizedImage.Bounds().Dy()
   220  	return gifImg
   221  }
   222  
   223  func imageToPaletted(img image.Image) *image.Paletted {
   224  	b := img.Bounds()
   225  	pm := image.NewPaletted(b, palette.Plan9)
   226  	draw.FloydSteinberg.Draw(pm, b, img, image.ZP)
   227  	return pm
   228  }