Skip to content

Commit

Permalink
implement POST /emoji for apiV4 (#5868)
Browse files Browse the repository at this point in the history
  • Loading branch information
cpanato authored and jwilander committed Apr 6, 2017
1 parent 1a09b2d commit 48d9099
Show file tree
Hide file tree
Showing 10 changed files with 528 additions and 143 deletions.
72 changes: 5 additions & 67 deletions api/emoji.go
Expand Up @@ -8,10 +8,6 @@ import (
"image"
"image/draw"
"image/gif"
_ "image/jpeg"
"image/png"
"io"
"mime/multipart"
"net/http"
"strings"

Expand All @@ -25,12 +21,6 @@ import (
"image/color/palette"
)

const (
MaxEmojiFileSize = 1000 * 1024 // 1 MB
MaxEmojiWidth = 128
MaxEmojiHeight = 128
)

func InitEmoji() {
l4g.Debug(utils.T("api.emoji.init.debug"))

Expand Down Expand Up @@ -76,13 +66,13 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
return
}

if r.ContentLength > MaxEmojiFileSize {
if r.ContentLength > app.MaxEmojiFileSize {
c.Err = model.NewLocAppError("createEmoji", "api.emoji.create.too_large.app_error", nil, "")
c.Err.StatusCode = http.StatusRequestEntityTooLarge
return
}

if err := r.ParseMultipartForm(MaxEmojiFileSize); err != nil {
if err := r.ParseMultipartForm(app.MaxEmojiFileSize); err != nil {
c.Err = model.NewLocAppError("createEmoji", "api.emoji.create.parse.app_error", nil, err.Error())
c.Err.StatusCode = http.StatusBadRequest
return
Expand Down Expand Up @@ -124,7 +114,7 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
if imageData := m.File["image"]; len(imageData) == 0 {
c.SetInvalidParam("createEmoji", "image")
return
} else if err := uploadEmojiImage(emoji.Id, imageData[0]); err != nil {
} else if err := app.UploadEmojiImage(emoji.Id, imageData[0]); err != nil {
c.Err = err
return
}
Expand All @@ -137,58 +127,6 @@ func createEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
}
}

func uploadEmojiImage(id string, imageData *multipart.FileHeader) *model.AppError {
file, err := imageData.Open()
if err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.open.app_error", nil, "")
}
defer file.Close()

buf := bytes.NewBuffer(nil)
io.Copy(buf, file)

// make sure the file is an image and is within the required dimensions
if config, _, err := image.DecodeConfig(bytes.NewReader(buf.Bytes())); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.image.app_error", nil, err.Error())
} else if config.Width > MaxEmojiWidth || config.Height > MaxEmojiHeight {
data := buf.Bytes()
newbuf := bytes.NewBuffer(nil)
if info, err := model.GetInfoForBytes(imageData.Filename, data); err != nil {
return err
} else if info.MimeType == "image/gif" {
if gif_data, err := gif.DecodeAll(bytes.NewReader(data)); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_decode_error", nil, "")
} else {
resized_gif := resizeEmojiGif(gif_data)
if err := gif.EncodeAll(newbuf, resized_gif); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.gif_encode_error", nil, "")
}
if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
} else {
if img, _, err := image.Decode(bytes.NewReader(data)); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.decode_error", nil, "")
} else {
resized_image := resizeEmoji(img, config.Width, config.Height)
if err := png.Encode(newbuf, resized_image); err != nil {
return model.NewLocAppError("uploadEmojiImage", "api.emoji.upload.large_image.encode_error", nil, "")
}
if err := app.WriteFile(newbuf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}
}
} else {
if err := app.WriteFile(buf.Bytes(), getEmojiImagePath(id)); err != nil {
return err
}
}

return nil
}

func deleteEmoji(c *Context, w http.ResponseWriter, r *http.Request) {
if !*utils.Cfg.ServiceSettings.EnableCustomEmoji {
c.Err = model.NewLocAppError("deleteEmoji", "api.emoji.disabled.app_error", nil, "")
Expand Down Expand Up @@ -302,10 +240,10 @@ func resizeEmoji(img image.Image, width int, height int) image.Image {
emojiHeight := float64(height)

var emoji image.Image
if emojiHeight <= MaxEmojiHeight && emojiWidth <= MaxEmojiWidth {
if emojiHeight <= app.MaxEmojiHeight && emojiWidth <= app.MaxEmojiWidth {
emoji = img
} else {
emoji = imaging.Fit(img, MaxEmojiWidth, MaxEmojiHeight, imaging.Lanczos)
emoji = imaging.Fit(img, app.MaxEmojiWidth, app.MaxEmojiHeight, imaging.Lanczos)
}
return emoji
}
Expand Down
101 changes: 25 additions & 76 deletions api/emoji_test.go
Expand Up @@ -6,10 +6,7 @@ package api
import (
"bytes"
"image"
"image/color"
"image/gif"
"image/jpeg"
"image/png"
"testing"
"time"

Expand Down Expand Up @@ -113,14 +110,14 @@ func TestCreateEmoji(t *testing.T) {
}

// try to create an emoji when they're disabled
if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil {
if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil {
t.Fatal("shouldn't be able to create an emoji when they're disabled")
}

*utils.Cfg.ServiceSettings.EnableCustomEmoji = true

// try to create a valid gif emoji when they're enabled
if emojiResult, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err != nil {
if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err != nil {
t.Fatal(err)
} else {
emoji = emojiResult
Expand All @@ -131,7 +128,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: emoji.Name,
}
if _, err := Client.CreateEmoji(emoji2, createTestGif(t, 10, 10), "image.gif"); err == nil {
if _, err := Client.CreateEmoji(emoji2, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil {
t.Fatal("shouldn't be able to create an emoji with a duplicate name")
}

Expand All @@ -142,7 +139,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if emojiResult, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 10, 10, 10), "image.gif"); err != nil {
if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif"); err != nil {
t.Fatal(err)
} else {
emoji = emojiResult
Expand All @@ -154,7 +151,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if emojiResult, err := Client.CreateEmoji(emoji, createTestJpeg(t, 10, 10), "image.jpeg"); err != nil {
if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestJpeg(t, 10, 10), "image.jpeg"); err != nil {
t.Fatal(err)
} else {
emoji = emojiResult
Expand All @@ -166,7 +163,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if emojiResult, err := Client.CreateEmoji(emoji, createTestPng(t, 10, 10), "image.png"); err != nil {
if emojiResult, err := Client.CreateEmoji(emoji, utils.CreateTestPng(t, 10, 10), "image.png"); err != nil {
t.Fatal(err)
} else {
emoji = emojiResult
Expand All @@ -178,7 +175,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if _, err := Client.CreateEmoji(emoji, createTestGif(t, 1000, 10), "image.gif"); err != nil {
if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 1000, 10), "image.gif"); err != nil {
t.Fatal("should be able to create an emoji that's too wide by resizing it")
}

Expand All @@ -187,7 +184,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 1000), "image.gif"); err != nil {
if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 1000), "image.gif"); err != nil {
t.Fatal("should be able to create an emoji that's too tall by resizing it")
}

Expand All @@ -196,7 +193,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
if _, err := Client.CreateEmoji(emoji, createTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil {
if _, err := Client.CreateEmoji(emoji, utils.CreateTestAnimatedGif(t, 100, 100, 10000), "image.gif"); err == nil {
t.Fatal("shouldn't be able to create an emoji that's too large")
}

Expand All @@ -214,7 +211,7 @@ func TestCreateEmoji(t *testing.T) {
CreatorId: th.BasicUser2.Id,
Name: model.NewId(),
}
if _, err := Client.CreateEmoji(emoji, createTestGif(t, 10, 10), "image.gif"); err == nil {
if _, err := Client.CreateEmoji(emoji, utils.CreateTestGif(t, 10, 10), "image.gif"); err == nil {
t.Fatal("shouldn't be able to create an emoji as another user")
}
}
Expand All @@ -232,7 +229,7 @@ func TestDeleteEmoji(t *testing.T) {
emoji1 := createTestEmoji(t, &model.Emoji{
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}, createTestGif(t, 10, 10))
}, utils.CreateTestGif(t, 10, 10))

if _, err := Client.DeleteEmoji(emoji1.Id); err == nil {
t.Fatal("shouldn't have been able to delete an emoji when they're disabled")
Expand All @@ -253,7 +250,7 @@ func TestDeleteEmoji(t *testing.T) {
emoji2 := createTestEmoji(t, &model.Emoji{
CreatorId: th.BasicUser2.Id,
Name: model.NewId(),
}, createTestGif(t, 10, 10))
}, utils.CreateTestGif(t, 10, 10))

if _, err := Client.DeleteEmoji(emoji2.Id); err == nil {
t.Fatal("shouldn't be able to delete another user's emoji")
Expand All @@ -266,54 +263,6 @@ func TestDeleteEmoji(t *testing.T) {
}
}

func createTestGif(t *testing.T, width int, height int) []byte {
var buffer bytes.Buffer

if err := gif.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height)), nil); err != nil {
t.Fatalf("failed to create gif: %v", err.Error())
}

return buffer.Bytes()
}

func createTestAnimatedGif(t *testing.T, width int, height int, frames int) []byte {
var buffer bytes.Buffer

img := gif.GIF{
Image: make([]*image.Paletted, frames, frames),
Delay: make([]int, frames, frames),
}
for i := 0; i < frames; i++ {
img.Image[i] = image.NewPaletted(image.Rect(0, 0, width, height), color.Palette{color.Black})
img.Delay[i] = 0
}
if err := gif.EncodeAll(&buffer, &img); err != nil {
t.Fatalf("failed to create animated gif: %v", err.Error())
}

return buffer.Bytes()
}

func createTestJpeg(t *testing.T, width int, height int) []byte {
var buffer bytes.Buffer

if err := jpeg.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height)), nil); err != nil {
t.Fatalf("failed to create jpeg: %v", err.Error())
}

return buffer.Bytes()
}

func createTestPng(t *testing.T, width int, height int) []byte {
var buffer bytes.Buffer

if err := png.Encode(&buffer, image.NewRGBA(image.Rect(0, 0, width, height))); err != nil {
t.Fatalf("failed to create png: %v", err.Error())
}

return buffer.Bytes()
}

func createTestEmoji(t *testing.T, emoji *model.Emoji, imageData []byte) *model.Emoji {
emoji = store.Must(app.Srv.Store.Emoji().Save(emoji)).(*model.Emoji)

Expand Down Expand Up @@ -342,7 +291,7 @@ func TestGetEmojiImage(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
emoji1 = Client.MustGeneric(Client.CreateEmoji(emoji1, createTestGif(t, 10, 10), "image.gif")).(*model.Emoji)
emoji1 = Client.MustGeneric(Client.CreateEmoji(emoji1, utils.CreateTestGif(t, 10, 10), "image.gif")).(*model.Emoji)
defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji1.Id)) }()

*utils.Cfg.ServiceSettings.EnableCustomEmoji = false
Expand All @@ -367,7 +316,7 @@ func TestGetEmojiImage(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
emoji2 = Client.MustGeneric(Client.CreateEmoji(emoji2, createTestAnimatedGif(t, 10, 10, 10), "image.gif")).(*model.Emoji)
emoji2 = Client.MustGeneric(Client.CreateEmoji(emoji2, utils.CreateTestAnimatedGif(t, 10, 10, 10), "image.gif")).(*model.Emoji)
defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji2.Id)) }()

if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji2.Id), "", ""); err != nil {
Expand All @@ -384,7 +333,7 @@ func TestGetEmojiImage(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
emoji3 = Client.MustGeneric(Client.CreateEmoji(emoji3, createTestJpeg(t, 10, 10), "image.jpeg")).(*model.Emoji)
emoji3 = Client.MustGeneric(Client.CreateEmoji(emoji3, utils.CreateTestJpeg(t, 10, 10), "image.jpeg")).(*model.Emoji)
defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji3.Id)) }()

if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji3.Id), "", ""); err != nil {
Expand All @@ -401,7 +350,7 @@ func TestGetEmojiImage(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
emoji4 = Client.MustGeneric(Client.CreateEmoji(emoji4, createTestPng(t, 10, 10), "image.png")).(*model.Emoji)
emoji4 = Client.MustGeneric(Client.CreateEmoji(emoji4, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji)
defer func() { Client.MustGeneric(Client.DeleteEmoji(emoji4.Id)) }()

if resp, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji4.Id), "", ""); err != nil {
Expand All @@ -418,7 +367,7 @@ func TestGetEmojiImage(t *testing.T) {
CreatorId: th.BasicUser.Id,
Name: model.NewId(),
}
emoji5 = Client.MustGeneric(Client.CreateEmoji(emoji5, createTestPng(t, 10, 10), "image.png")).(*model.Emoji)
emoji5 = Client.MustGeneric(Client.CreateEmoji(emoji5, utils.CreateTestPng(t, 10, 10), "image.png")).(*model.Emoji)
Client.MustGeneric(Client.DeleteEmoji(emoji5.Id))

if _, err := Client.DoApiGet(Client.GetCustomEmojiImageUrl(emoji5.Id), "", ""); err == nil {
Expand All @@ -428,45 +377,45 @@ func TestGetEmojiImage(t *testing.T) {

func TestResizeEmoji(t *testing.T) {
// try to resize a jpeg image within MaxEmojiWidth and MaxEmojiHeight
small_img_data := createTestJpeg(t, MaxEmojiWidth, MaxEmojiHeight)
small_img_data := utils.CreateTestJpeg(t, app.MaxEmojiWidth, app.MaxEmojiHeight)
if small_img, _, err := image.Decode(bytes.NewReader(small_img_data)); err != nil {
t.Fatal("failed to decode jpeg bytes to image.Image")
} else {
resized_img := resizeEmoji(small_img, small_img.Bounds().Dx(), small_img.Bounds().Dy())
if resized_img.Bounds().Dx() > MaxEmojiWidth || resized_img.Bounds().Dy() > MaxEmojiHeight {
if resized_img.Bounds().Dx() > app.MaxEmojiWidth || resized_img.Bounds().Dy() > app.MaxEmojiHeight {
t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
}
if resized_img != small_img {
t.Fatal("should've returned small_img itself")
}
}
// try to resize a jpeg image
jpeg_data := createTestJpeg(t, 256, 256)
jpeg_data := utils.CreateTestJpeg(t, 256, 256)
if jpeg_img, _, err := image.Decode(bytes.NewReader(jpeg_data)); err != nil {
t.Fatal("failed to decode jpeg bytes to image.Image")
} else {
resized_jpeg := resizeEmoji(jpeg_img, jpeg_img.Bounds().Dx(), jpeg_img.Bounds().Dy())
if resized_jpeg.Bounds().Dx() > MaxEmojiWidth || resized_jpeg.Bounds().Dy() > MaxEmojiHeight {
if resized_jpeg.Bounds().Dx() > app.MaxEmojiWidth || resized_jpeg.Bounds().Dy() > app.MaxEmojiHeight {
t.Fatal("resized jpeg width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
}
}
// try to resize a png image
png_data := createTestJpeg(t, 256, 256)
png_data := utils.CreateTestJpeg(t, 256, 256)
if png_img, _, err := image.Decode(bytes.NewReader(png_data)); err != nil {
t.Fatal("failed to decode png bytes to image.Image")
} else {
resized_png := resizeEmoji(png_img, png_img.Bounds().Dx(), png_img.Bounds().Dy())
if resized_png.Bounds().Dx() > MaxEmojiWidth || resized_png.Bounds().Dy() > MaxEmojiHeight {
if resized_png.Bounds().Dx() > app.MaxEmojiWidth || resized_png.Bounds().Dy() > app.MaxEmojiHeight {
t.Fatal("resized png width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
}
}
// try to resize an animated gif
gif_data := createTestAnimatedGif(t, 256, 256, 10)
gif_data := utils.CreateTestAnimatedGif(t, 256, 256, 10)
if gif_img, err := gif.DecodeAll(bytes.NewReader(gif_data)); err != nil {
t.Fatal("failed to decode gif bytes to gif.GIF")
} else {
resized_gif := resizeEmojiGif(gif_img)
if resized_gif.Config.Width > MaxEmojiWidth || resized_gif.Config.Height > MaxEmojiHeight {
if resized_gif.Config.Width > app.MaxEmojiWidth || resized_gif.Config.Height > app.MaxEmojiHeight {
t.Fatal("resized gif width and height should not be greater than MaxEmojiWidth or MaxEmojiHeight")
}
if len(resized_gif.Image) != len(gif_img.Image) {
Expand Down

0 comments on commit 48d9099

Please sign in to comment.