diff --git a/lib/content-disposition.js b/lib/content-disposition.js index b8fcec99..68521a8a 100644 --- a/lib/content-disposition.js +++ b/lib/content-disposition.js @@ -1,5 +1,9 @@ /** Build a download disposition with an ASCII fallback and an RFC 5987 filename. */ export function getAttachmentContentDisposition(filename) { const fallback = filename.replace(/["\\\r\n]/g, "_").replace(/[^\x20-\x7e]/g, "_") || "download" - return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(filename)}` + const encodedFilename = encodeURIComponent(filename).replace( + /[!'()*]/g, + (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, + ) + return `attachment; filename="${fallback}"; filename*=UTF-8''${encodedFilename}` } diff --git a/lib/content-disposition.ts b/lib/content-disposition.ts index e12df8ff..7479036b 100644 --- a/lib/content-disposition.ts +++ b/lib/content-disposition.ts @@ -1,5 +1,9 @@ /** Build a download disposition with an ASCII fallback and an RFC 5987 filename. */ export function getAttachmentContentDisposition(filename: string): string { const fallback = filename.replace(/["\\\r\n]/g, "_").replace(/[^\x20-\x7e]/g, "_") || "download" - return `attachment; filename="${fallback}"; filename*=UTF-8''${encodeURIComponent(filename)}` + const encodedFilename = encodeURIComponent(filename).replace( + /[!'()*]/g, + (character) => `%${character.charCodeAt(0).toString(16).toUpperCase()}`, + ) + return `attachment; filename="${fallback}"; filename*=UTF-8''${encodedFilename}` } diff --git a/tests/lib/content-disposition.test.js b/tests/lib/content-disposition.test.js index 06b4b015..5c56af6e 100644 --- a/tests/lib/content-disposition.test.js +++ b/tests/lib/content-disposition.test.js @@ -15,3 +15,10 @@ test("getAttachmentContentDisposition encodes Unicode and unsafe fallback charac "attachment; filename=\"__ _final_.zip\"; filename*=UTF-8''%E6%8A%A5%E5%91%8A%20%22final%22.zip", ) }) + +test("getAttachmentContentDisposition percent-encodes RFC 8187 delimiters", () => { + assert.equal( + getAttachmentContentDisposition("报告(O'Reilly)*.zip"), + "attachment; filename=\"__(O'Reilly)*.zip\"; filename*=UTF-8''%E6%8A%A5%E5%91%8A%28O%27Reilly%29%2A.zip", + ) +})