mirror of
https://github.com/pocket-id/pocket-id.git
synced 2025-12-17 09:13:28 +03:00
fix: ensure file descriptors are closed + other bugs (#413)
This commit is contained in:
committed by
GitHub
parent
980780e48b
commit
2f7646105e
@@ -250,6 +250,9 @@ func (uc *UserController) getUserProfilePictureHandler(c *gin.Context) {
|
|||||||
_ = c.Error(err)
|
_ = c.Error(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if picture != nil {
|
||||||
|
defer picture.Close()
|
||||||
|
}
|
||||||
|
|
||||||
c.Header("Cache-Control", "public, max-age=300")
|
c.Header("Cache-Control", "public, max-age=300")
|
||||||
|
|
||||||
|
|||||||
@@ -38,9 +38,9 @@ func (j *FileCleanupJobs) clearUnusedDefaultProfilePictures() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Create a map to track which initials are in use
|
// Create a map to track which initials are in use
|
||||||
initialsInUse := make(map[string]bool)
|
initialsInUse := make(map[string]struct{})
|
||||||
for _, user := range users {
|
for _, user := range users {
|
||||||
initialsInUse[user.Initials()] = true
|
initialsInUse[user.Initials()] = struct{}{}
|
||||||
}
|
}
|
||||||
|
|
||||||
defaultPicturesDir := common.EnvConfig.UploadPath + "/profile-pictures/defaults"
|
defaultPicturesDir := common.EnvConfig.UploadPath + "/profile-pictures/defaults"
|
||||||
@@ -63,7 +63,7 @@ func (j *FileCleanupJobs) clearUnusedDefaultProfilePictures() error {
|
|||||||
initials := strings.TrimSuffix(filename, ".png")
|
initials := strings.TrimSuffix(filename, ".png")
|
||||||
|
|
||||||
// If these initials aren't used by any user, delete the file
|
// If these initials aren't used by any user, delete the file
|
||||||
if !initialsInUse[initials] {
|
if _, ok := initialsInUse[initials]; !ok {
|
||||||
filePath := filepath.Join(defaultPicturesDir, filename)
|
filePath := filepath.Join(defaultPicturesDir, filename)
|
||||||
if err := os.Remove(filePath); err != nil {
|
if err := os.Remove(filePath); err != nil {
|
||||||
log.Printf("Failed to delete unused default profile picture %s: %v", filePath, err)
|
log.Printf("Failed to delete unused default profile picture %s: %v", filePath, err)
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import (
|
|||||||
"github.com/go-webauthn/webauthn/protocol"
|
"github.com/go-webauthn/webauthn/protocol"
|
||||||
"github.com/go-webauthn/webauthn/webauthn"
|
"github.com/go-webauthn/webauthn/webauthn"
|
||||||
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
datatype "github.com/pocket-id/pocket-id/backend/internal/model/types"
|
||||||
|
"github.com/pocket-id/pocket-id/backend/internal/utils"
|
||||||
)
|
)
|
||||||
|
|
||||||
type User struct {
|
type User struct {
|
||||||
@@ -66,14 +67,9 @@ func (u User) WebAuthnCredentialDescriptors() (descriptors []protocol.Credential
|
|||||||
func (u User) FullName() string { return u.FirstName + " " + u.LastName }
|
func (u User) FullName() string { return u.FirstName + " " + u.LastName }
|
||||||
|
|
||||||
func (u User) Initials() string {
|
func (u User) Initials() string {
|
||||||
initials := ""
|
return strings.ToUpper(
|
||||||
if len(u.FirstName) > 0 {
|
utils.GetFirstCharacter(u.FirstName) + utils.GetFirstCharacter(u.LastName),
|
||||||
initials += string(u.FirstName[0])
|
)
|
||||||
}
|
|
||||||
if len(u.LastName) > 0 {
|
|
||||||
initials += string(u.LastName[0])
|
|
||||||
}
|
|
||||||
return strings.ToUpper(initials)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type OneTimeAccessToken struct {
|
type OneTimeAccessToken struct {
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ func (s *UserService) GetUser(userID string) (model.User, error) {
|
|||||||
return user, err
|
return user, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) GetProfilePicture(userID string) (io.Reader, int64, error) {
|
func (s *UserService) GetProfilePicture(userID string) (io.ReadCloser, int64, error) {
|
||||||
// Validate the user ID to prevent directory traversal
|
// Validate the user ID to prevent directory traversal
|
||||||
if err := uuid.Validate(userID); err != nil {
|
if err := uuid.Validate(userID); err != nil {
|
||||||
return nil, 0, &common.InvalidUUIDError{}
|
return nil, 0, &common.InvalidUUIDError{}
|
||||||
@@ -99,7 +99,7 @@ func (s *UserService) GetProfilePicture(userID string) (io.Reader, int64, error)
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Save the default picture for future use (in a goroutine to avoid blocking)
|
// Save the default picture for future use (in a goroutine to avoid blocking)
|
||||||
defaultPictureCopy := bytes.NewBuffer(defaultPicture.Bytes())
|
defaultPictureBytes := defaultPicture.Bytes()
|
||||||
go func() {
|
go func() {
|
||||||
// Ensure the directory exists
|
// Ensure the directory exists
|
||||||
err = os.MkdirAll(defaultProfilePicturesDir, os.ModePerm)
|
err = os.MkdirAll(defaultProfilePicturesDir, os.ModePerm)
|
||||||
@@ -107,12 +107,12 @@ func (s *UserService) GetProfilePicture(userID string) (io.Reader, int64, error)
|
|||||||
log.Printf("Failed to create directory for default profile picture: %v", err)
|
log.Printf("Failed to create directory for default profile picture: %v", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if err := utils.SaveFileStream(defaultPictureCopy, defaultPicturePath); err != nil {
|
if err := utils.SaveFileStream(bytes.NewReader(defaultPictureBytes), defaultPicturePath); err != nil {
|
||||||
log.Printf("Failed to cache default profile picture for initials %s: %v", user.Initials(), err)
|
log.Printf("Failed to cache default profile picture for initials %s: %v", user.Initials(), err)
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
return defaultPicture, int64(defaultPicture.Len()), nil
|
return io.NopCloser(bytes.NewReader(defaultPictureBytes)), int64(defaultPicture.Len()), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *UserService) GetUserGroups(userID string) ([]model.UserGroup, error) {
|
func (s *UserService) GetUserGroups(userID string) ([]model.UserGroup, error) {
|
||||||
|
|||||||
@@ -102,3 +102,16 @@ func CamelCaseToScreamingSnakeCase(s string) string {
|
|||||||
// Convert to uppercase
|
// Convert to uppercase
|
||||||
return strings.ToUpper(snake)
|
return strings.ToUpper(snake)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// GetFirstCharacter returns the first non-whitespace character of the string, correctly handling Unicode
|
||||||
|
func GetFirstCharacter(str string) string {
|
||||||
|
for _, c := range str {
|
||||||
|
if unicode.IsSpace(c) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return string(c)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Empty string case
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|||||||
@@ -103,3 +103,28 @@ func TestCamelCaseToSnakeCase(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestGetFirstCharacter(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
input string
|
||||||
|
expected string
|
||||||
|
}{
|
||||||
|
{"empty string", "", ""},
|
||||||
|
{"single character", "a", "a"},
|
||||||
|
{"multiple characters", "hello", "h"},
|
||||||
|
{"unicode character", "étoile", "é"},
|
||||||
|
{"special character", "!test", "!"},
|
||||||
|
{"number as first character", "123abc", "1"},
|
||||||
|
{"whitespace as first character", " hello", "h"},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
result := GetFirstCharacter(tt.input)
|
||||||
|
if result != tt.expected {
|
||||||
|
t.Errorf("GetFirstCharacter(%q) = %q, want %q", tt.input, result, tt.expected)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user