Skip to content

Commit 5326dae

Browse files
committed
fix: emit RFC 5987 filename* in Attachment/Inline
Content-Disposition only set a quoted filename=, which breaks non-ASCII download names in many browsers. Keep filename= for legacy clients and add filename*=UTF-8'' with url.PathEscape.
1 parent ed8bbe4 commit 5326dae

2 files changed

Lines changed: 17 additions & 5 deletions

File tree

context.go

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,14 @@ func (c *Context) Inline(file, name string) error {
718718
var quoteEscaper = strings.NewReplacer("\\", "\\\\", `"`, "\\\"")
719719

720720
func (c *Context) contentDisposition(file, name, dispositionType string) error {
721-
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(`%s; filename="%s"`, dispositionType, quoteEscaper.Replace(name)))
721+
// RFC 6266 / 5987: keep a quoted filename= for legacy clients and add
722+
// filename* so non-ASCII and reserved characters survive download prompts.
723+
c.response.Header().Set(HeaderContentDisposition, fmt.Sprintf(
724+
`%s; filename="%s"; filename*=UTF-8''%s`,
725+
dispositionType,
726+
quoteEscaper.Replace(name),
727+
url.PathEscape(name),
728+
))
722729
return c.File(file)
723730
}
724731

context_test.go

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -431,12 +431,17 @@ func TestContextAttachment(t *testing.T) {
431431
{
432432
name: "ok",
433433
whenName: "walle.png",
434-
expectHeader: `attachment; filename="walle.png"`,
434+
expectHeader: `attachment; filename="walle.png"; filename*=UTF-8''walle.png`,
435435
},
436436
{
437437
name: "ok, escape quotes in malicious filename",
438438
whenName: `malicious.sh"; \"; dummy=.txt`,
439-
expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"`,
439+
expectHeader: `attachment; filename="malicious.sh\"; \\\"; dummy=.txt"; filename*=UTF-8''malicious.sh%22%3B%20%5C%22%3B%20dummy=.txt`,
440+
},
441+
{
442+
name: "ok, non-ASCII filename uses filename*",
443+
whenName: "报告.pdf",
444+
expectHeader: `attachment; filename="报告.pdf"; filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf`,
440445
},
441446
}
442447
for _, tc := range testCases {
@@ -466,12 +471,12 @@ func TestContextInline(t *testing.T) {
466471
{
467472
name: "ok",
468473
whenName: "walle.png",
469-
expectHeader: `inline; filename="walle.png"`,
474+
expectHeader: `inline; filename="walle.png"; filename*=UTF-8''walle.png`,
470475
},
471476
{
472477
name: "ok, escape quotes in malicious filename",
473478
whenName: `malicious.sh"; \"; dummy=.txt`,
474-
expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"`,
479+
expectHeader: `inline; filename="malicious.sh\"; \\\"; dummy=.txt"; filename*=UTF-8''malicious.sh%22%3B%20%5C%22%3B%20dummy=.txt`,
475480
},
476481
}
477482
for _, tc := range testCases {

0 commit comments

Comments
 (0)