From e89603497fcd3e37b3f64dfd2d16da8f03455601 Mon Sep 17 00:00:00 2001 From: Gani Georgiev Date: Tue, 9 Dec 2025 17:09:32 +0200 Subject: [PATCH] store the correct image/png as attrs content type when generating a thumb fallback --- CHANGELOG.md | 5 ++++ tools/filesystem/file_test.go | 12 ++++----- tools/filesystem/filesystem.go | 35 ++++++++++++++++++-------- tools/filesystem/filesystem_test.go | 39 ++++++++++++++++++++++++----- 4 files changed, 68 insertions(+), 23 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ec345732..214a56fa 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,8 @@ +## v0.34.3 (WIP) + +- Store the correct `image/png` as attrs content type when generating a thumb fallback _(e.g. for `webp`)_. + + ## v0.34.2 - Bumped JS SDK to v0.26.5 to fix Safari AbortError detection introduced with the previous release ([#7369](https://github.com/pocketbase/pocketbase/issues/7369)). diff --git a/tools/filesystem/file_test.go b/tools/filesystem/file_test.go index 43ad31c1..e3bb00fb 100644 --- a/tools/filesystem/file_test.go +++ b/tools/filesystem/file_test.go @@ -52,8 +52,8 @@ func TestNewFileFromPath(t *testing.T) { } // existing file - originalName := "image_! noext" - normalizedNamePattern := regexp.QuoteMeta("image_noext_") + `\w{10}` + regexp.QuoteMeta(".png") + originalName := "image_!@ special" + normalizedNamePattern := regexp.QuoteMeta("image_special_") + `\w{10}` + regexp.QuoteMeta(".png") f, err := filesystem.NewFileFromPath(filepath.Join(testDir, originalName)) if err != nil { t.Fatalf("Expected nil error, got %v", err) @@ -83,8 +83,8 @@ func TestNewFileFromBytes(t *testing.T) { t.Fatal("Expected error, got nil") } - originalName := "image_! noext" - normalizedNamePattern := regexp.QuoteMeta("image_noext_") + `\w{10}` + regexp.QuoteMeta(".txt") + originalName := "image_!@ special" + normalizedNamePattern := regexp.QuoteMeta("image_special_") + `\w{10}` + regexp.QuoteMeta(".txt") f, err := filesystem.NewFileFromBytes([]byte("text\n"), originalName) if err != nil { t.Fatal(err) @@ -175,8 +175,8 @@ func TestNewFileFromURLTimeout(t *testing.T) { // valid response { - originalName := "image_! noext" - normalizedNamePattern := regexp.QuoteMeta("image_noext_") + `\w{10}` + regexp.QuoteMeta(".txt") + originalName := "image_!@ special" + normalizedNamePattern := regexp.QuoteMeta("image_special_") + `\w{10}` + regexp.QuoteMeta(".txt") f, err := filesystem.NewFileFromURL(context.Background(), srv.URL+"/"+originalName) if err != nil { diff --git a/tools/filesystem/filesystem.go b/tools/filesystem/filesystem.go index d1aac2e7..2f11584f 100644 --- a/tools/filesystem/filesystem.go +++ b/tools/filesystem/filesystem.go @@ -536,25 +536,38 @@ func (s *System) CreateThumb(originalKey string, thumbKey, thumbSize string) err } } + originalContentType := r.ContentType() + opts := &blob.WriterOptions{ - ContentType: r.ContentType(), + ContentType: originalContentType, } - // open a thumb storage writer (aka. prepare for upload) - w, writerErr := s.bucket.NewWriter(s.ctx, thumbKey, opts) - if writerErr != nil { - return writerErr - } + var format imaging.Format - // try to detect the thumb format based on the original file name - // (fallbacks to png on error) - format, err := imaging.FormatFromFilename(thumbKey) - if err != nil { + switch originalContentType { + case "image/jpeg": + format = imaging.JPEG + case "image/gif": + format = imaging.GIF + case "image/tiff": + format = imaging.TIFF + case "image/bmp": + format = imaging.BMP + default: + // fallback to PNG (this includes webp!) + opts.ContentType = "image/png" format = imaging.PNG } + // open a thumb storage writer (aka. prepare for upload) + w, err := s.bucket.NewWriter(s.ctx, thumbKey, opts) + if err != nil { + return err + } + // thumb encode (aka. upload) - if err := imaging.Encode(w, thumbImg, format); err != nil { + err = imaging.Encode(w, thumbImg, format) + if err != nil { w.Close() return err } diff --git a/tools/filesystem/filesystem_test.go b/tools/filesystem/filesystem_test.go index 257f101e..182f187c 100644 --- a/tools/filesystem/filesystem_test.go +++ b/tools/filesystem/filesystem_test.go @@ -709,7 +709,8 @@ func TestFileSystemList(t *testing.T) { "image.jpg", "image.svg", "image.webp", - "image_! noext", + "image_!@ special", + "image_noext", "style.css", "main.js", "main.mjs", @@ -861,6 +862,8 @@ func TestFileSystemCreateThumb(t *testing.T) { {"image.jpg", "thumb.jpg", "100x100", "image/jpeg"}, // webp (should produce png) {"image.webp", "thumb.webp", "100x100", "image/png"}, + // without extension (should extract the mimetype from its stored ContentType) + {"image_noext", "image_noext.jpeg", "100x100", "image/jpeg"}, } for _, s := range scenarios { @@ -884,13 +887,20 @@ func TestFileSystemCreateThumb(t *testing.T) { } defer f.Close() + attrsMimeType := f.ContentType() + mt, err := mimetype.DetectReader(f) if err != nil { t.Fatalf("Failed to detect thumb %s mimetype (%v)", s.thumb, err) } + fileMimeType := mt.String() - if mtStr := mt.String(); mtStr != s.expectedMimeType { - t.Fatalf("Expected thumb %s MimeType %q, got %q", s.thumb, s.expectedMimeType, mtStr) + if fileMimeType != s.expectedMimeType { + t.Fatalf("Expected thumb file %s MimeType %q, got %q", s.thumb, s.expectedMimeType, fileMimeType) + } + + if attrsMimeType != s.expectedMimeType { + t.Fatalf("Expected thumb attrs %s MimeType %q, got %q", s.thumb, s.expectedMimeType, attrsMimeType) } }) } @@ -975,16 +985,33 @@ func createTestDir(t *testing.T) string { } } - // no extension and invalid characters + // invalid/special characters { - file, err := os.OpenFile(filepath.Join(dir, "image_! noext"), os.O_WRONLY|os.O_CREATE, 0644) + file, err := os.OpenFile(filepath.Join(dir, "image_!@ special"), os.O_WRONLY|os.O_CREATE, 0644) if err != nil { t.Fatal(err) } - _ = png.Encode(file, image.Rect(0, 0, 1, 1)) // tiny 1x1 png + imgRect := image.Rect(0, 0, 1, 1) // tiny 1x1 png + _ = png.Encode(file, imgRect) file.Close() } + // no extension + { + fullPath := filepath.Join(dir, "image_noext") + file, err := os.OpenFile(fullPath, os.O_WRONLY|os.O_CREATE, 0644) + if err != nil { + t.Fatal(err) + } + imgRect := image.Rect(0, 0, 1, 1) // tiny 1x1 jpg + _ = jpeg.Encode(file, imgRect, nil) + file.Close() + err = os.WriteFile(fullPath+".attrs", []byte(`{"user.cache_control":"","user.content_disposition":"","user.content_encoding":"","user.content_language":"","user.content_type":"image/jpeg","user.metadata":null}`), 0644) + if err != nil { + t.Fatal(err) + } + } + // css { file, err := os.OpenFile(filepath.Join(dir, "style.css"), os.O_WRONLY|os.O_CREATE, 0644)