From add1df8dc0f4a169ee5d453271074950a76361b0 Mon Sep 17 00:00:00 2001 From: Andy Bitz Date: Tue, 15 Oct 2024 14:16:13 +0200 Subject: [PATCH] Encode URI components before rendering --- src/directory.jst | 2 +- src/index.js | 16 ++++++---- test/fixtures/special#char/in#my#path.txt | 1 + test/integration.test.js | 36 +++++++++++++++++++++++ 4 files changed, 48 insertions(+), 7 deletions(-) create mode 100644 test/fixtures/special#char/in#my#path.txt diff --git a/src/directory.jst b/src/directory.jst index 844207b..032d59a 100644 --- a/src/directory.jst +++ b/src/directory.jst @@ -150,7 +150,7 @@ diff --git a/src/index.js b/src/index.js index 34c2501..4a91105 100644 --- a/src/index.js +++ b/src/index.js @@ -359,6 +359,10 @@ const renderDirectory = async (current, acceptsJSON, handlers, methods, config, } details.relative = path.join(relativePath, details.base); + details.href = details.relative + .split('/') + .map(p => encodeURIComponent(p)) + .join('/'); if (stats.isDirectory()) { details.base += slashSuffix; @@ -393,14 +397,15 @@ const renderDirectory = async (current, acceptsJSON, handlers, methods, config, const toRoot = path.relative(current, absolutePath); const directory = path.join(path.basename(current), toRoot, slashSuffix); - const pathParts = directory.split(path.sep).filter(Boolean); + const pathParts = directory.split(path.sep) + .map(p => encodeURIComponent(p)) + .filter(Boolean); // Sort to list directories first, then sort alphabetically files = files.sort((a, b) => { const aIsDir = a.type === 'directory'; const bIsDir = b.type === 'directory'; - /* istanbul ignore next */ if (aIsDir && !bIsDir) { return -1; } @@ -409,12 +414,10 @@ const renderDirectory = async (current, acceptsJSON, handlers, methods, config, return 1; } - /* istanbul ignore next */ if (a.base < b.base) { return -1; } - /* istanbul ignore next */ return 0; }).filter(Boolean); @@ -428,6 +431,7 @@ const renderDirectory = async (current, acceptsJSON, handlers, methods, config, base: '..', relative, title: relative, + href: relative, ext: '' }); } @@ -448,8 +452,8 @@ const renderDirectory = async (current, acceptsJSON, handlers, methods, config, parents.shift(); subPaths.push({ - name: pathParts[index] + (isLast ? slashSuffix : '/'), - url: index === 0 ? '' : parents.join('/') + slashSuffix + name: decodeURIComponent(pathParts[index]) + (isLast ? slashSuffix : '/'), + url: index === 0 ? '' : parents.map(p => encodeURIComponent(p)).join('/') + slashSuffix }); } diff --git a/test/fixtures/special#char/in#my#path.txt b/test/fixtures/special#char/in#my#path.txt new file mode 100644 index 0000000..53d0671 --- /dev/null +++ b/test/fixtures/special#char/in#my#path.txt @@ -0,0 +1 @@ +If you could show the cabbage that I planted with my own hands to your emperor, he definitely wouldn't dare suggest that I replace the peace and happiness of this place with the storms of a never-satisfied greed. diff --git a/test/integration.test.js b/test/integration.test.js index d0e5d67..19cf976 100644 --- a/test/integration.test.js +++ b/test/integration.test.js @@ -1361,3 +1361,39 @@ test('etag header is set', async () => { '"ba114dbc69e41e180362234807f093c3c4628f90"' ); }); + +test('escape paths with special chars', async () => { + const dirName = 'special#char'; + const fileName = 'in#my#path.txt'; + + const sub = path.join(fixturesFull, dirName); + // const contents = await getDirectoryContents(sub, true); + const url = await getUrl(); + + console.log('url', url); + await new Promise((res) => setTimeout(res, 20_000)); + + { + const text = await fetch(`${url}/`).then(resp => resp.text()); + expect(text).toContain(`${encodeURIComponent(dirName)}`); + } + + { + const text = await fetch(`${url}/${encodeURIComponent(dirName)}/`).then(resp => resp.text()); + expect(text).toContain(`${encodeURIComponent(dirName)}`); + expect(text).toContain(`${encodeURIComponent(fileName)}`); + } + + { + const text = await fetch(`${url}/${encodeURIComponent(dirName)}/${encodeURIComponent(fileName)}`).then(resp => resp.text()); + expect(text).toContain('cabbage'); + } + + // console.log('text', text); + + // const type = response.headers.get('content-type'); + // expect(type).toBe('text/html; charset=utf-8'); + + // expect(contents.every(item => text.includes(item))).toBe(true); + expect(true).toBe(true); +});