From 40767e5ce7b2581d462d8f74a1d9433496dd553e Mon Sep 17 00:00:00 2001 From: Sukka Date: Thu, 1 Sep 2022 07:08:56 +0800 Subject: [PATCH] fix(#40066): preserve error status code from serveStatic (#40128) The PR fixes #40066. Next.js' `serveStatic` method is powered by the [send](https://github.com/pillarjs/send) module, which could throw errors under specific circumstances. Currently, Next.js only preserves the 412 Error from send, hence issue #40066 (where 416 Error is not preserved). ## Bug - [x] Related issues linked using `fixes #number` - [x] Integration tests added - [ ] Errors have helpful link attached, see `contributing.md` --- packages/next/server/next-server.ts | 42 ++++++++++++++++++- .../file-serving/test/index.test.js | 10 +++++ 2 files changed, 50 insertions(+), 2 deletions(-) diff --git a/packages/next/server/next-server.ts b/packages/next/server/next-server.ts index dca746ea6b301e6..48f19bf2e47401c 100644 --- a/packages/next/server/next-server.ts +++ b/packages/next/server/next-server.ts @@ -153,6 +153,41 @@ function getMiddlewareMatcher( return matcher } +/** + * Hardcoded every possible error status code that could be thrown by "serveStatic" method + * This is done by searching "this.error" inside "send" module's source code: + * https://github.com/pillarjs/send/blob/master/index.js + * https://github.com/pillarjs/send/blob/develop/index.js + */ +const POSSIBLE_ERROR_CODE_FROM_SERVE_STATIC = new Set([ + // send module will throw 500 when header is already sent or fs.stat error happens + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L392 + // Note: we will use Next.js built-in 500 page to handle 500 errors + // 500, + + // send module will throw 404 when file is missing + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L421 + // Note: we will use Next.js built-in 404 page to handle 404 errors + // 404, + + // send module will throw 403 when redirecting to a directory without enabling directory listing + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L484 + // Note: Next.js throws a different error (without status code) for directory listing + // 403, + + // send module will throw 400 when fails to normalize the path + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L520 + 400, + + // send module will throw 412 with conditional GET request + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L632 + 412, + + // send module will throw 416 when range is not satisfiable + // https://github.com/pillarjs/send/blob/53f0ab476145670a9bdd3dc722ab2fdc8d358fc6/index.js#L669 + 416, +]) + function getEdgeMatcher( info: MiddlewareManifest['functions'][string] ): RouteMatch { @@ -1362,8 +1397,11 @@ export default class NextNodeServer extends BaseServer { const err = error as Error & { code?: string; statusCode?: number } if (err.code === 'ENOENT' || err.statusCode === 404) { this.render404(req, res, parsedUrl) - } else if (err.statusCode === 412) { - res.statusCode = 412 + } else if ( + typeof err.statusCode === 'number' && + POSSIBLE_ERROR_CODE_FROM_SERVE_STATIC.has(err.statusCode) + ) { + res.statusCode = err.statusCode return this.renderError(err, req, res, path) } else { throw err diff --git a/test/integration/file-serving/test/index.test.js b/test/integration/file-serving/test/index.test.js index cb886b6fd2387a1..7b09bb29cc63df0 100644 --- a/test/integration/file-serving/test/index.test.js +++ b/test/integration/file-serving/test/index.test.js @@ -68,6 +68,16 @@ const runTests = () => { expect(res.headers.get('content-type')).toBe('image/avif') }) + it('should serve correct error code', async () => { + // vercel-icon-dark.avif is downloaded from https://vercel.com/design and transformed to avif on avif.io + const res = await fetchViaHTTP(appPort, '/vercel-icon-dark.avif', '', { + headers: { + Range: 'bytes=1000000000-', + }, + }) + expect(res.status).toBe(416) // 416 Range Not Satisfiable + }) + // checks against traversal requests from // https://github.com/swisskyrepo/PayloadsAllTheThings/blob/master/Directory%20Traversal/Intruder/traversals-8-deep-exotic-encoding.txt