Files
summercms/modules/lagoon/attach/inline_test.go
Jakub Zych ba8432cfb8 fix(lagoon/attach): store non-inline uploads with Content-Disposition attachment (quick-261028-rqe)
- attach.IsInlineContentType: the shared inline raster list (JPEG, PNG,
  GIF, WebP, AVIF), parameters and case ignored, false for empty or
  unparsable values
- writeBlob stores Content-Disposition: attachment for every other type,
  so a public bucket or CDN makes the browser download HTML, SVG, PDF or
  plain text; content_type and thumbnails are unchanged
- driver tests on memblob and fileblob, Postgres-backed Store assertion,
  lagoon README and the storage and attachments pages
2026-10-28 21:10:48 +01:00

117 lines
3.2 KiB
Go

package attach
import (
"strings"
"testing"
"gocloud.dev/blob"
"gocloud.dev/blob/fileblob"
"gocloud.dev/blob/memblob"
)
func TestIsInlineContentType(t *testing.T) {
cases := []struct {
in string
want bool
}{
{"image/jpeg", true},
{"image/png", true},
{"image/gif", true},
{"image/webp", true},
{"image/avif", true},
{"IMAGE/WEBP; charset=binary", true},
{"text/html; charset=utf-8", false},
{"image/svg+xml", false},
{"application/pdf", false},
{"text/plain", false},
{"application/octet-stream", false},
{"application/zip", false},
{"image/bmp", false},
{"image/x-icon", false},
{"video/mp4", false},
{"audio/mpeg", false},
{"", false},
{"not a media type;;", false},
}
for _, tc := range cases {
if got := IsInlineContentType(tc.in); got != tc.want {
t.Errorf("IsInlineContentType(%q) = %v, want %v", tc.in, got, tc.want)
}
}
}
// TestWriteBlobDisposition is the driver half of the public-bucket fix: a
// public bucket serves the headers Store stores with the object, so the
// attachment disposition must survive the write on the real drivers. The
// metadata-skip fileblob bucket shows that a driver which does not keep
// attributes still accepts the option without an error.
func TestWriteBlobDisposition(t *testing.T) {
cases := []struct {
name, ct, disposition string
}{
{"html", "text/html", "attachment"},
{"text", "text/plain", "attachment"},
{"pdf", "application/pdf", "attachment"},
{"svg", "image/svg+xml", "attachment"},
{"zip", "application/zip", "attachment"},
{"png", "image/png", ""},
{"jpeg", "image/jpeg", ""},
{"avif", "image/avif", ""},
}
sidecar, err := fileblob.OpenBucket(t.TempDir(), nil)
if err != nil {
t.Fatal(err)
}
noMeta, err := fileblob.OpenBucket(t.TempDir(), &fileblob.Options{Metadata: fileblob.MetadataDontWrite})
if err != nil {
t.Fatal(err)
}
buckets := []struct {
name string
bucket *blob.Bucket
keepsAttrs bool
}{
{"memblob", memblob.OpenBucket(nil), true},
{"fileblob sidecar", sidecar, true},
{"fileblob metadata skipped", noMeta, false},
}
const body = "<html><script>alert(1)</script></html>"
for _, b := range buckets {
bucket := b.bucket
t.Cleanup(func() { _ = bucket.Close() })
t.Run(b.name, func(t *testing.T) {
ctx := t.Context()
for _, tc := range cases {
key := "abc/def/012/" + tc.name + "-file.bin"
n, err := writeBlob(ctx, bucket, key, strings.NewReader(body), tc.ct, 0)
if err != nil {
t.Fatalf("%s: writeBlob: %v", tc.name, err)
}
if n != int64(len(body)) {
t.Fatalf("%s: wrote %d bytes, want %d", tc.name, n, len(body))
}
got, err := bucket.ReadAll(ctx, key)
if err != nil {
t.Fatalf("%s: ReadAll: %v", tc.name, err)
}
if string(got) != body {
t.Fatalf("%s: body = %q, want %q", tc.name, got, body)
}
if !b.keepsAttrs {
continue
}
attrs, err := bucket.Attributes(ctx, key)
if err != nil {
t.Fatalf("%s: Attributes: %v", tc.name, err)
}
if attrs.ContentType != tc.ct {
t.Errorf("%s: ContentType = %q, want %q", tc.name, attrs.ContentType, tc.ct)
}
if attrs.ContentDisposition != tc.disposition {
t.Errorf("%s: ContentDisposition = %q, want %q", tc.name, attrs.ContentDisposition, tc.disposition)
}
}
})
}
}