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";