From 633ea479d7a007bf24ff97f3c7fc0a1d397284bc Mon Sep 17 00:00:00 2001 From: Aaron Liu Date: Thu, 12 Jun 2025 11:48:41 +0800 Subject: [PATCH] =?UTF-8?q?fix(es):=20redact=20non-ASCII=20file=20name=20i?= =?UTF-8?q?n=20`filename`=20segment=20=EF=BC=88#2473=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../manager/entitysource/entitysource.go | 33 +++++++++++++++++-- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/pkg/filemanager/manager/entitysource/entitysource.go b/pkg/filemanager/manager/entitysource/entitysource.go index c38f9f9..f664c19 100644 --- a/pkg/filemanager/manager/entitysource/entitysource.go +++ b/pkg/filemanager/manager/entitysource/entitysource.go @@ -166,6 +166,21 @@ type ( } ) +// toASCIISafeFilename converts a filename to ASCII-safe version by replacing +// non-ASCII characters and special characters with underscores. +// This is used for the fallback filename parameter in Content-Disposition header. +func toASCIISafeFilename(filename string) string { + asciiFilename := "" + for _, r := range filename { + if r <= 127 && r >= 32 && r != '"' && r != '\\' { + asciiFilename += string(r) + } else { + asciiFilename += "_" + } + } + return asciiFilename +} + // NewEntitySource creates a new EntitySource. func NewEntitySource( e fs.Entity, @@ -249,9 +264,21 @@ func (f *entitySource) Serve(w http.ResponseWriter, r *http.Request, opts ...Ent w.Header().Set("Etag", "\""+hashid.EncodeEntityID(f.hasher, f.e.ID())+"\"") if f.o.IsDownload { - encodedFilename := url.PathEscape(f.o.DisplayName) - w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", - f.o.DisplayName, encodedFilename)) + // Properly handle non-ASCII characters in filename according to RFC 6266 + displayName := f.o.DisplayName + asciiFilename := toASCIISafeFilename(displayName) + + // RFC 6266 compliant filename* encoding + encodedFilename := url.QueryEscape(displayName) + + if displayName == asciiFilename { + // Filename contains only ASCII characters, use simple form + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"", displayName)) + } else { + // Filename contains non-ASCII characters, use both parameters + w.Header().Set("Content-Disposition", fmt.Sprintf("attachment; filename=\"%s\"; filename*=UTF-8''%s", + asciiFilename, encodedFilename)) + } } done, rangeReq := checkPreconditions(w, r, etag)