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
This commit is contained in:
Jakub Zych
2026-10-28 21:10:48 +01:00
parent 61d10436c7
commit ba8432cfb8
8 changed files with 205 additions and 8 deletions

View File

@@ -106,9 +106,13 @@ type Limits struct {
// is then streamed into bucket at BlobKey of a server-generated disk name (22
// random lowercase hex characters, a dot and the extension DiskExtension
// derives from the sniffed type and the client extension, so a PNG named
// x.html is stored as .png; file_name keeps the client's name); a body longer
// than Limits.MaxBytes aborts the write, deletes the key and returns
// ErrTooLarge. Finally it inserts the row with empty attachment columns,
// x.html is stored as .png; file_name keeps the client's name). The blob's
// Content-Type is the stored content type; a type IsInlineContentType does
// not accept also gets Content-Disposition: attachment, so a public bucket or
// CDN that sends the stored headers makes a browser download the file instead
// of rendering it. The row's content_type holds the same type either way. A
// body longer than Limits.MaxBytes aborts the write, deletes the key and
// returns ErrTooLarge. Finally it inserts the row with empty attachment columns,
// is_public from Upload.Public, the byte size and the content type, and sets
// sort_order to the new id as WinterCMS's Sortable trait does. When the row
// cannot be written the blob is deleted again.
@@ -201,7 +205,7 @@ func Store(ctx context.Context, db *gorm.DB, bucket *blob.Bucket, in Upload, lim
func writeBlob(ctx context.Context, bucket *blob.Bucket, key string, r io.Reader, contentType string, limit int64) (int64, error) {
writeCtx, cancel := context.WithCancel(ctx)
defer cancel()
w, err := bucket.NewWriter(writeCtx, key, &blob.WriterOptions{ContentType: contentType})
w, err := bucket.NewWriter(writeCtx, key, uploadWriterOptions(contentType))
if err != nil {
return 0, fmt.Errorf("attach: blob writer: %w", err)
}