From ba8432cfb8229233a00fe2b604804ce288a5490b Mon Sep 17 00:00:00 2001 From: Jakub Zych Date: Wed, 28 Oct 2026 21:10:48 +0100 Subject: [PATCH] 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 --- docs/database/attachments.md | 2 + docs/services/storage.md | 6 +- modules/lagoon/README.md | 5 +- modules/lagoon/attach/inline.go | 44 ++++++++++ modules/lagoon/attach/inline_test.go | 116 +++++++++++++++++++++++++++ modules/lagoon/attach/store.go | 12 ++- modules/lagoon/attach/store_test.go | 20 ++++- modules/lagoon/attach/thumb_test.go | 8 ++ 8 files changed, 205 insertions(+), 8 deletions(-) create mode 100644 modules/lagoon/attach/inline.go create mode 100644 modules/lagoon/attach/inline_test.go diff --git a/docs/database/attachments.md b/docs/database/attachments.md index 82f006b6..dee0e174 100644 --- a/docs/database/attachments.md +++ b/docs/database/attachments.md @@ -97,6 +97,8 @@ The client file name never reaches a blob key. The disk name is 22 random hexade The disk name's extension follows the content, not the client's name, because a static web server in front of the uploads directory picks the Content-Type from the extension. A public bucket, or a CDN that pulls from it, sends the type stored with the object instead; see the [deploy note](../services/storage.md#deploy-note) for both cases. `attach.DiskExtension` derives the extension from the sniffed type and the client's extension. For a specific type such as PNG, JPEG, PDF, MP3 or WebM, the client's extension is kept when it names that type, else the type's usual extension is used: a PNG uploaded as `x.html` (with `html` allowed) is stored as `.png`, and its URL ends in `.png`. For a generic sniff (plain text, HTML, XML, a zip container or unknown bytes) the client's extension is kept, so `report.docx` stays `.docx` and an HTML body uploaded as `note.txt` stays `.txt`, unless the extension is one a browser renders or runs (html, svg, xml, js, css, php and the like), which becomes `.txt` for text and `.bin` otherwise. `file_name` always keeps the client's name, so downloads still carry it. +`attach.Store` writes every file whose type `attach.IsInlineContentType` does not report as a raster image (JPEG, PNG, GIF, WebP, AVIF) with `Content-Disposition: attachment`, so a browser that opens it from a public bucket downloads it instead of rendering it. ``, `