From bb74120e3684159ecb27763cbd5408951ce92439 Mon Sep 17 00:00:00 2001 From: viettranx Date: Wed, 25 Mar 2026 18:26:04 +0700 Subject: [PATCH] feat(files): add download=true query param for Content-Disposition attachment header --- internal/http/files.go | 6 ++++++ ui/web/src/lib/file-helpers.ts | 5 +++++ 2 files changed, 11 insertions(+) diff --git a/internal/http/files.go b/internal/http/files.go index ab13ca30..951a2fbe 100644 --- a/internal/http/files.go +++ b/internal/http/files.go @@ -1,6 +1,7 @@ package http import ( + "fmt" "log/slog" "mime" "net/http" @@ -132,6 +133,11 @@ func (h *FilesHandler) handleServe(w http.ResponseWriter, r *http.Request) { w.Header().Set("Content-Type", ct) } + // Trigger browser download with original filename when ?download=true + if r.URL.Query().Get("download") == "true" { + w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(absPath))) + } + http.ServeFile(w, r, absPath) } diff --git a/ui/web/src/lib/file-helpers.ts b/ui/web/src/lib/file-helpers.ts index b99ea69d..6fcf5ff3 100644 --- a/ui/web/src/lib/file-helpers.ts +++ b/ui/web/src/lib/file-helpers.ts @@ -165,6 +165,11 @@ export function toFileUrl(path: string): string { return `/v1/files/${basename}`; } +/** Append ?download=true to a URL for Content-Disposition: attachment. */ +export function toDownloadUrl(url: string): string { + return url + (url.includes("?") ? "&" : "?") + "download=true"; +} + /** Determine MediaItem kind from MIME type */ export function mediaKindFromMime(mime: string): "image" | "video" | "audio" | "document" | "code" { if (mime.startsWith("image/")) return "image";