From 519f66a51f519a33751e88fc66be6f0cc5a9a436 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 21 Feb 2026 23:19:15 +0100 Subject: [PATCH] fix: detect and store mime type when creating file attachments Use mimetype.DetectReader in files.Create to automatically detect and store the MIME type from file content. This fixes task attachment previews, S3 Content-Type headers, and UI display for newly uploaded files. --- pkg/files/files.go | 10 ++++++++- pkg/files/files_test.go | 33 ++++++++++++++++++++++++++++++ pkg/models/task_attachment_test.go | 1 + 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/pkg/files/files.go b/pkg/files/files.go index 8c67c6550..4c8460c8b 100644 --- a/pkg/files/files.go +++ b/pkg/files/files.go @@ -37,6 +37,7 @@ import ( "github.com/aws/aws-sdk-go-v2/aws" "github.com/aws/aws-sdk-go-v2/service/s3" "github.com/c2h5oh/datasize" + "github.com/gabriel-vasile/mimetype" "github.com/spf13/afero" "xorm.io/xorm" ) @@ -86,7 +87,14 @@ func (f *File) LoadFileMetaByID() (err error) { // Create creates a new file from an FileHeader func Create(f io.ReadSeeker, realname string, realsize uint64, a web.Auth) (file *File, err error) { - return CreateWithMime(f, realname, realsize, a, "") + mime, err := mimetype.DetectReader(f) + if err != nil { + return nil, fmt.Errorf("failed to detect mime type: %w", err) + } + if _, err := f.Seek(0, io.SeekStart); err != nil { + return nil, fmt.Errorf("failed to seek after mime detection: %w", err) + } + return CreateWithMime(f, realname, realsize, a, mime.String()) } // CreateWithMime creates a new file from an FileHeader and sets its mime type diff --git a/pkg/files/files_test.go b/pkg/files/files_test.go index 70377fd32..cdaad2f5d 100644 --- a/pkg/files/files_test.go +++ b/pkg/files/files_test.go @@ -18,6 +18,8 @@ package files import ( "bytes" + "image" + "image/png" "os" "testing" @@ -60,6 +62,37 @@ func TestCreate(t *testing.T) { }) } +func TestCreateDetectsMimeType(t *testing.T) { + initFixtures(t) + ta := &testauth{id: 1} + + // Minimal valid PNG (1x1 pixel) + pngData := createMinimalPNG(t) + + f, err := Create(bytes.NewReader(pngData), "test.png", uint64(len(pngData)), ta) + require.NoError(t, err) + assert.Equal(t, "image/png", f.Mime) +} + +func TestCreateDetectsMimeTypePlainText(t *testing.T) { + initFixtures(t) + ta := &testauth{id: 1} + + textData := []byte("hello world this is plain text") + + f, err := Create(bytes.NewReader(textData), "readme.txt", uint64(len(textData)), ta) + require.NoError(t, err) + assert.Equal(t, "text/plain; charset=utf-8", f.Mime) +} + +func createMinimalPNG(t *testing.T) []byte { + t.Helper() + img := image.NewRGBA(image.Rect(0, 0, 1, 1)) + buf := &bytes.Buffer{} + require.NoError(t, png.Encode(buf, img)) + return buf.Bytes() +} + func TestFile_Delete(t *testing.T) { t.Run("Normal", func(t *testing.T) { s := db.NewSession() diff --git a/pkg/models/task_attachment_test.go b/pkg/models/task_attachment_test.go index abdffd029..258c136d4 100644 --- a/pkg/models/task_attachment_test.go +++ b/pkg/models/task_attachment_test.go @@ -113,6 +113,7 @@ func TestTaskAttachment_NewAttachment(t *testing.T) { assert.Equal(t, testuser.ID, ta.File.CreatedByID) assert.Equal(t, "testfile", ta.File.Name) assert.Equal(t, uint64(100), ta.File.Size) + assert.NotEmpty(t, ta.File.Mime, "mime type should be detected and stored") // Extra test for max size test }