From 85eb065fdb787626b6fb7b659a704af7b534e4fd Mon Sep 17 00:00:00 2001 From: RaphaelFakhri <153192858+RaphaelFakhri@users.noreply.github.com> Date: Tue, 29 Sep 2026 17:42:27 +0300 Subject: [PATCH] fix: keep the Content-Disposition filename ASCII for non-ASCII file names --- src/index.js | 6 +++++- test/integration.test.js | 28 ++++++++++++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 4ab8997..384d054 100644 --- a/src/index.js +++ b/src/index.js @@ -219,7 +219,11 @@ const getHeaders = async (handlers, config, current, absolutePath, stats) => { // if that's not working, it will save the file. But to be clear: This // only happens if it cannot find a appropiate value. 'Content-Disposition': contentDisposition(base, { - type: 'inline' + type: 'inline', + // Node.js rejects non-ASCII header values on some responses, so + // the plain `filename` parameter must stay ASCII. The full name + // goes into `filename*`. + fallback: base.replace(/[^\x20-\x7e]/g, '?') }), 'Accept-Ranges': 'bytes' }; diff --git a/test/integration.test.js b/test/integration.test.js index 896a4ac..bdd3a28 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1,5 +1,6 @@ // Native const path = require('path'); +const os = require('os'); // Packages const listen = require('test-listen'); @@ -1442,3 +1443,30 @@ test('prefer directory index.html over .html sibling when both 1.3/ and 1.3.html expect(response.status).toBe(200); expect(text).toBe(content); }); + +test('serve a file whose name has non-ASCII characters', async () => { + const directory = await fs.mkdtemp(path.join(os.tmpdir(), 'serve-handler-')); + const names = ['bras\u00e3o.txt', 'DALL\u00b7E.png', '\u65e5\u672c\u8a9e.txt']; + + try { + await Promise.all(names.map(name => fs.writeFile(path.join(directory, name), 'content'))); + + const url = await getUrl({ + 'public': directory + }); + + for (const name of names) { + const response = await fetch(`${url}/${encodeURIComponent(name)}`, { + timeout: 2000 + }); + + expect(response.status).toBe(200); + expect(await response.text()).toBe('content'); + + const disposition = response.headers.get('content-disposition'); + expect(disposition).toContain(`filename*=UTF-8''${encodeURIComponent(name)}`); + } + } finally { + await fs.remove(directory); + } +});