diff --git a/packages/docusaurus/src/commands/serve/__tests__/requestHandler.test.ts b/packages/docusaurus/src/commands/serve/__tests__/requestHandler.test.ts index b441b39eaebf..57edd8940c1b 100644 --- a/packages/docusaurus/src/commands/serve/__tests__/requestHandler.test.ts +++ b/packages/docusaurus/src/commands/serve/__tests__/requestHandler.test.ts @@ -165,7 +165,7 @@ describe('createServeRequestHandler', () => { /café 200 café.html //file 301 /file → 200 file.html /%E0%A4%A 400 Bad Request - /%00 500 Internal Server Error + /%00 400 Bad Request /..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -226,7 +226,7 @@ describe('createServeRequestHandler', () => { /café 301 /caf%C3%A9/ → 200 café.html //file 301 /file → 301 /file/ → 200 file.html /%E0%A4%A 301 /%E0%A4%A/ → 400 Bad Request - /%00 301 /%00/ → 500 Internal Server Error + /%00 400 Bad Request /..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -287,7 +287,7 @@ describe('createServeRequestHandler', () => { /café 200 café.html //file 301 /file → 200 file.html /%E0%A4%A 400 Bad Request - /%00 500 Internal Server Error + /%00 400 Bad Request /..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -350,7 +350,7 @@ describe('createServeRequestHandler', () => { /base/café 200 café.html /base//file 301 /base/file → 200 file.html /base/%E0%A4%A 400 Bad Request - /base/%00 500 Internal Server Error + /base/%00 400 Bad Request /base/..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -411,7 +411,7 @@ describe('createServeRequestHandler', () => { /base/café 301 /base/caf%C3%A9/ → 200 café.html /base//file 301 /base/file → 301 /base/file/ → 200 file.html /base/%E0%A4%A 301 /base/%E0%A4%A/ → 400 Bad Request - /base/%00 301 /base/%00/ → 500 Internal Server Error + /base/%00 400 Bad Request /base/..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -472,7 +472,7 @@ describe('createServeRequestHandler', () => { /base/café 200 café.html /base//file 301 /base/file → 200 file.html /base/%E0%A4%A 400 Bad Request - /base/%00 500 Internal Server Error + /base/%00 400 Bad Request /base/..%2Fdocusaurus.config.js 400 Bad Request" `); }); @@ -803,14 +803,23 @@ describe('createServeRequestHandler', () => { ); }); - // TODO fix: answer 400, like the malformed percent-encodings - it('answers 500 to null bytes', async () => { + // Like the malformed percent-encodings + it('answers 400 to null bytes', async () => { await expect( serveRequest( {baseUrl: '/base/', trailingSlash: undefined}, '/base/%00', ), - ).resolves.toBe('500 Internal Server Error'); + ).resolves.toBe('400 Bad Request'); + }); + + it('keeps null bytes in the query string', async () => { + await expect( + serveRequest( + {baseUrl: '/base/', trailingSlash: undefined}, + '/base/file?query=%00', + ), + ).resolves.toBe('200 file.html'); }); // Like other ".." segments going above the root diff --git a/packages/docusaurus/src/commands/serve/requestHandler.ts b/packages/docusaurus/src/commands/serve/requestHandler.ts index f40df5088ab1..b2f6729a4ea2 100644 --- a/packages/docusaurus/src/commands/serve/requestHandler.ts +++ b/packages/docusaurus/src/commands/serve/requestHandler.ts @@ -45,6 +45,14 @@ export function createServeRequestHandler({ return; } + // serve-handler answers 500 to null bytes, its file system calls throw + // Answer 400 instead, like serve-handler does to malformed encodings + if (hasNullByte(req.url)) { + res.writeHead(400); + res.end('Bad Request'); + return; + } + const redirectLocation = await getRedirectLocation(req.url, { outDir, baseUrl, @@ -180,6 +188,12 @@ function parseFakeRootURLPath( return {pathname: pathname.slice(fakeRoot.length), search}; } +// Like serve-handler, only the pathname is decoded, not the query string +function hasNullByte(url: string): boolean { + const pathname = url.split(/[?#]/)[0]!; + return pathname.includes('%00'); +} + // Pages with a dot in their name look like assets, like "docs/2.x" async function isPage(outDir: string, urlPath: string): Promise { const filePath = getFilePath(outDir, urlPath);