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); +});