diff --git a/context.go b/context.go index 9a7429f39..8e58f6c9a 100644 --- a/context.go +++ b/context.go @@ -718,7 +718,14 @@ func (c *Context) Inline(file, name string) error { var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"") func (c *Context) contentDisposition(file, name, dispositionType string) error { - c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name))) + // RFC 6266 / 5987: keep a quoted filename= for legacy clients and add + // filename* so non-ASCII and reserved characters survive download prompts. + c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf( + `%s; filename="%s"; filename*=UTF-8''%s`, + dispositionType, + quoteEscaper.Replace(name), + url.PathEscape(name), + )) return c.File(file) } diff --git a/context_test.go b/context_test.go index 9b820c6e3..de7f2722b 100644 --- a/context_test.go +++ b/context_test.go @@ -431,12 +431,17 @@ func TestContextAttachment(t *testing.T) { { name: "ok", whenName: "walle.png", - expectHeader: `attachment; filename="walle.png"`, + expectHeader: `attachment; filename="walle.png"; filename*=UTF-8''walle.png`, }, { name: "ok, escape quotes in malicious filename", whenName: `malicious.sh"; \"; dummy=.txt`, - expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"`, + expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"; filename*=UTF-8''malicious.sh%22%3B%20%5C%22%3B%20dummy=.txt`, + }, + { + name: "ok, non-ASCII filename uses filename*", + whenName: "报告.pdf", + expectHeader: `attachment; filename="报告.pdf"; filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf`, }, } for _, tc := range testCases { @@ -466,12 +471,12 @@ func TestContextInline(t *testing.T) { { name: "ok", whenName: "walle.png", - expectHeader: `inline; filename="walle.png"`, + expectHeader: `inline; filename="walle.png"; filename*=UTF-8''walle.png`, }, { name: "ok, escape quotes in malicious filename", whenName: `malicious.sh"; \"; dummy=.txt`, - expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"`, + expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"; filename*=UTF-8''malicious.sh%22%3B%20%5C%22%3B%20dummy=.txt`, }, } for _, tc := range testCases {