Fix EXIF profile picture orientation bug (#34275) (#35594)

* Fix EXIF profile picture orientation bug (#34275)

* Test AdustProfileImage with rotated PNG assets

This commit adds two test assets:
- quadrants-orientation-1.png
- quadrants-orientation-8.png

Both represent the exact same image: a 128x128 image with four
differently coloured 64x64 quadrants. Clockwise, starting from the
top-left: green, white, blue and red

  [G][W]
  [R][B]

quadrants-orientation-1.png has an EXIF rotation tag of 1, meaning that
its data is already correctly rotated. quadrants-orientation-8.png has
an EXIF rotation tag of 8, meaning that the data in the file is rotated
90° clockwise, and an inverse rotation needs to be applied to render it
correctly. Rendering the raw data would show the following:

  [R][G]
  [B][W]

That rotation is what we test in the new TestAdjustProfileImage
sub-test, which calls AdjustImage in both PNGs and make a byte-to-byte
comparison of the result, which is expected to be equal.

* Fix imports

---------

Co-authored-by: Alejandro García Montoro <alejandro.garciamontoro@gmail.com>
This commit is contained in:
Patel Parthkumar
2026-03-19 21:16:21 +05:30
committed by GitHub
parent ad03248cd3
commit 92533c44c1
4 changed files with 28 additions and 3 deletions
+6
View File
@@ -1026,6 +1026,12 @@ func (a *App) AdjustImage(rctx request.CTX, file io.ReadSeeker) (*bytes.Buffer,
return nil, model.NewAppError("SetProfileImage", "api.user.upload_profile_user.decode.app_error", nil, "", http.StatusBadRequest).Wrap(err)
}
// Decode() reads the file to EOF; seek back to beginning so GetImageOrientation
// can read the EXIF data to determine the correct orientation.
if _, seekErr := file.Seek(0, io.SeekStart); seekErr != nil {
rctx.Logger().Warn("Failed to seek image file for orientation check", mlog.Err(seekErr))
}
orientation, err := imaging.GetImageOrientation(file, format)
if err != nil {
rctx.Logger().Warn("Failed to get image orientation", mlog.Err(err))
+22 -3
View File
@@ -8,6 +8,8 @@ import (
"database/sql"
"encoding/json"
"errors"
_ "image/jpeg"
_ "image/png"
"net/http"
"os"
"path/filepath"
@@ -140,11 +142,28 @@ func TestAdjustProfileImage(t *testing.T) {
// default image should not require adjustment
user := th.BasicUser
image, appErr := th.App.GetDefaultProfileImage(user)
defaultImg, appErr := th.App.GetDefaultProfileImage(user)
require.Nil(t, appErr)
image2, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(image))
image2, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(defaultImg))
require.Nil(t, appErr)
assert.Equal(t, image, image2.Bytes())
assert.Equal(t, defaultImg, image2.Bytes())
t.Run("EXIF orientation is applied for rotated images", func(t *testing.T) {
// quadrants-orientation-8.png: 128×128 color quadrants with EXIF orientation 8.
// quadrants-orientation-1.png: same visual content already rotated, EXIF orientation 1.
rotated, err := testutils.ReadTestFile("exif_samples/quadrants-orientation-8.png")
require.NoError(t, err)
normal, err := testutils.ReadTestFile("exif_samples/quadrants-orientation-1.png")
require.NoError(t, err)
rotatedResult, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(rotated))
require.Nil(t, appErr)
normalResult, appErr := th.App.AdjustImage(th.Context, bytes.NewReader(normal))
require.Nil(t, appErr)
assert.Equal(t, rotatedResult.Bytes(), normalResult.Bytes(),
"EXIF-rotated image should produce the same profile picture as the normally-oriented one")
})
}
func TestUpdateUserToRestrictedDomain(t *testing.T) {
Binary file not shown.

After

Width:  |  Height:  |  Size: 414 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 413 B