diff --git a/backend/internal/service/ldap_service.go b/backend/internal/service/ldap_service.go index 51af4089..731c7e66 100644 --- a/backend/internal/service/ldap_service.go +++ b/backend/internal/service/ldap_service.go @@ -440,7 +440,7 @@ func (s *LdapService) SyncUsers(ctx context.Context, tx *gorm.DB, client *ldap.C } func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId string, pictureString string) error { - var reader io.Reader + var reader io.ReadSeeker _, err := url.ParseRequestURI(pictureString) if err == nil { @@ -460,7 +460,12 @@ func (s *LdapService) saveProfilePicture(parentCtx context.Context, userId strin } defer res.Body.Close() - reader = res.Body + data, err := io.ReadAll(res.Body) + if err != nil { + return fmt.Errorf("failed to read profile picture: %w", err) + } + + reader = bytes.NewReader(data) } else if decodedPhoto, err := base64.StdEncoding.DecodeString(pictureString); err == nil { // If the photo is a base64 encoded string, decode it reader = bytes.NewReader(decodedPhoto) diff --git a/backend/internal/service/user_service.go b/backend/internal/service/user_service.go index 3ba8ab78..6cef3176 100644 --- a/backend/internal/service/user_service.go +++ b/backend/internal/service/user_service.go @@ -159,7 +159,7 @@ func (s *UserService) GetUserGroups(ctx context.Context, userID string) ([]model return user.UserGroups, nil } -func (s *UserService) UpdateProfilePicture(ctx context.Context, userID string, file io.Reader) error { +func (s *UserService) UpdateProfilePicture(ctx context.Context, userID string, file io.ReadSeeker) error { // Validate the user ID to prevent directory traversal err := uuid.Validate(userID) if err != nil { diff --git a/backend/internal/utils/image/profile_picture.go b/backend/internal/utils/image/profile_picture.go index 2e28585d..21d78797 100644 --- a/backend/internal/utils/image/profile_picture.go +++ b/backend/internal/utils/image/profile_picture.go @@ -12,24 +12,36 @@ import ( "golang.org/x/image/font" "golang.org/x/image/font/opentype" "golang.org/x/image/math/fixed" + "golang.org/x/image/webp" "github.com/pocket-id/pocket-id/backend/resources" ) const profilePictureSize = 300 -// CreateProfilePicture resizes the profile picture to a square -func CreateProfilePicture(file io.Reader) (io.ReadSeeker, error) { +// CreateProfilePicture resizes the profile picture to a square and encodes it as PNG +func CreateProfilePicture(file io.ReadSeeker) (io.ReadSeeker, error) { + // Attempt standard formats first img, _, err := imageorient.Decode(file) if err != nil { - return nil, fmt.Errorf("failed to decode image: %w", err) + if _, seekErr := file.Seek(0, io.SeekStart); seekErr != nil { + return nil, fmt.Errorf("failed to seek file: %w", seekErr) + } + // Try WebP + webpImg, webpErr := webp.Decode(file) + if webpErr != nil { + return nil, fmt.Errorf("failed to decode image: %w", err) + } + + img = webpImg } + // Resize to square img = imaging.Fill(img, profilePictureSize, profilePictureSize, imaging.Center, imaging.Lanczos) + // Encode back to PNG var buf bytes.Buffer - err = imaging.Encode(&buf, img, imaging.PNG) - if err != nil { + if err := imaging.Encode(&buf, img, imaging.PNG); err != nil { return nil, fmt.Errorf("failed to encode image: %w", err) }