fix(attachment): detect heic/heif mime type from filename extension (#6246)

Signed-off-by: Abdel <hktitof@gmail.com>
This commit is contained in:
Abdellatif Anaflous 2026-08-30 02:09:53 +01:00 committed by GitHub
parent 0e9e94e0e8
commit d5849bdf52
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 83 additions and 5 deletions

View file

@ -0,0 +1,54 @@
package v1
import (
"testing"
"github.com/stretchr/testify/require"
)
func TestDetectAttachmentMimeType(t *testing.T) {
tests := []struct {
name string
filename string
content []byte
want string
}{
{
name: "heic image via extension fallback",
filename: "photo.heic",
want: "image/heic",
},
{
name: "heif image via extension fallback",
filename: "photo.heif",
want: "image/heif",
},
{
name: "uppercase HEIC extension",
filename: "PHOTO.HEIC",
want: "image/heic",
},
{
name: "regular image by builtin extension",
filename: "image.png",
want: "image/png",
},
{
name: "unknown extension falls back to content sniffing",
filename: "photo.xyz",
content: []byte{0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a},
want: "image/png",
},
{
name: "no extension and no sniffable content",
filename: "attachment",
want: "text/plain; charset=utf-8",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := detectAttachmentMimeType(tt.filename, tt.content)
require.Equal(t, tt.want, got)
})
}
}

View file

@ -8,6 +8,7 @@ import (
"mime"
"net/http"
"path/filepath"
"strings"
"time"
"google.golang.org/grpc/codes"
@ -43,6 +44,33 @@ var exifCapableImageTypes = map[string]bool{
"image/heif": true,
}
// extensionMimeTypeFallbacks maps image extensions that Go's builtin MIME
// table does not cover. HEIC/HEIF files are the common case: browsers report
// an empty MIME type for them, Go's builtin table omits the extension, and
// http.DetectContentType cannot sniff the ISO BMFF container, so without
// this fallback these uploads are stored as "application/octet-stream" on
// minimal runtimes that ship no system MIME database (e.g. the Alpine image).
var extensionMimeTypeFallbacks = map[string]string{
".heic": "image/heic",
".heif": "image/heif",
}
// detectAttachmentMimeType resolves the MIME type for an uploaded file that
// arrived without a client-supplied type. It prefers the filename extension
// (including the curated fallback above, which keeps the result identical on
// machines with and without a system MIME database), then sniffs the content
// as a last resort.
func detectAttachmentMimeType(filename string, content []byte) string {
ext := strings.ToLower(filepath.Ext(filename))
if mimeType, ok := extensionMimeTypeFallbacks[ext]; ok {
return mimeType
}
if mimeType := mime.TypeByExtension(ext); mimeType != "" {
return mimeType
}
return http.DetectContentType(content)
}
func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.CreateAttachmentRequest) (*v1pb.Attachment, error) {
user, err := s.fetchCurrentUser(ctx)
if err != nil {
@ -64,11 +92,7 @@ func (s *APIV1Service) CreateAttachment(ctx context.Context, request *v1pb.Creat
}
normalizedMimeType := request.Attachment.Type
if normalizedMimeType == "" {
ext := filepath.Ext(request.Attachment.Filename)
mimeType := mime.TypeByExtension(ext)
if mimeType == "" {
mimeType = http.DetectContentType(request.Attachment.Content)
}
mimeType := detectAttachmentMimeType(request.Attachment.Filename, request.Attachment.Content)
if normalizedType, ok := normalizeMimeType(mimeType); ok {
normalizedMimeType = normalizedType
}