From 4915f535d0a815698ce1af43491b13b6683ec527 Mon Sep 17 00:00:00 2001 From: kolaente Date: Sat, 21 Feb 2026 23:19:26 +0100 Subject: [PATCH] fix: add Content-Disposition attachment header to task attachment downloads Set Content-Disposition to "attachment" instead of "inline" so browsers trigger a file download with the correct filename. Also move shared response headers (Content-Type, Content-Length, Last-Modified) out of the S3-specific branch so they apply to both storage backends. --- pkg/routes/api/v1/task_attachment.go | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/pkg/routes/api/v1/task_attachment.go b/pkg/routes/api/v1/task_attachment.go index e23781503..8a832602e 100644 --- a/pkg/routes/api/v1/task_attachment.go +++ b/pkg/routes/api/v1/task_attachment.go @@ -204,21 +204,18 @@ func GetTaskAttachment(c *echo.Context) error { _ = s.Rollback() return err } + c.Response().Header().Set("Content-Disposition", "attachment; filename=\""+taskAttachment.File.Name+"\"") + c.Response().Header().Set("Content-Type", taskAttachment.File.Mime) + c.Response().Header().Set("Content-Length", strconv.FormatUint(taskAttachment.File.Size, 10)) + c.Response().Header().Set("Last-Modified", taskAttachment.File.Created.UTC().Format(http.TimeFormat)) + if config.FilesType.GetString() == "s3" { // s3 files cannot use http.ServeContent as it requires a Seekable file - // Set response headers - c.Response().Header().Set("Content-Type", taskAttachment.File.Mime) - c.Response().Header().Set("Content-Disposition", "inline; filename=\""+taskAttachment.File.Name+"\"") - c.Response().Header().Set("Content-Length", strconv.FormatUint(taskAttachment.File.Size, 10)) - c.Response().Header().Set("Last-Modified", taskAttachment.File.Created.UTC().Format(http.TimeFormat)) - - // Stream the file content directly to the response + // so we stream the file content directly to the response _, err = io.Copy(c.Response(), taskAttachment.File.File) - if err != nil { - return err - } - } else { - http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File) + return err } + + http.ServeContent(c.Response(), c.Request(), taskAttachment.File.Name, taskAttachment.File.Created, taskAttachment.File.File) return nil }