diff --git a/src/utils/proxy.js b/src/utils/proxy.js index 0e8702afbef..e25c8c05042 100644 --- a/src/utils/proxy.js +++ b/src/utils/proxy.js @@ -146,7 +146,7 @@ async function serveRedirect(req, res, proxy, match, options) { req.hostname}` ) - const staticFile = await getStatic(reqUrl.pathname, options.publicFolder) + const staticFile = await getStatic(decodeURIComponent(reqUrl.pathname), options.publicFolder) if (staticFile) req.url = staticFile + reqUrl.search if (match.force404) { res.writeHead(404) diff --git a/tests/command.dev.test.js b/tests/command.dev.test.js index c19eb51c943..1d2b252bc02 100644 --- a/tests/command.dev.test.js +++ b/tests/command.dev.test.js @@ -861,4 +861,40 @@ testMatrix.forEach(({ args }) => { }) }) }) + + test(testName('should not shadow an existing file that has unsafe URL characters', args), async t => { + await withSiteBuilder('site-with-same-name-for-file-and-folder', async builder => { + builder + .withContentFile({ + path: 'public/index.html', + content: 'index', + }) + .withContentFile({ + path: 'public/files/file with spaces.html', + content: 'file with spaces', + }) + .withContentFile({ + path: 'public/files/[file_with_brackets].html', + content: 'file with brackets', + }) + .withNetlifyToml({ + config: { + build: { publish: 'public' }, + redirects: [{ from: '/*', to: '/index.html', status: 200 }], + }, + }) + + await builder.buildAsync() + + await withDevServer({ cwd: builder.directory, args }, async server => { + const [spaces, brackets] = await Promise.all([ + fetch(`${server.url}/files/file with spaces`).then(r => r.text()), + fetch(`${server.url}/files/[file_with_brackets]`).then(r => r.text()), + ]) + + t.is(spaces, 'file with spaces') + t.is(brackets, 'file with brackets') + }) + }) + }) })