From 1e8f91d59fac343bce0af33d4d8f561a94cf82a0 Mon Sep 17 00:00:00 2001 From: Nigro Simone Date: Wed, 7 Oct 2026 10:39:18 +0200 Subject: [PATCH] fix: answer 413 for a body over the limit, as body-parser and finalhandler do --- src/middlewares.js | 15 ++++- src/router.js | 6 +- tests/tests/middlewares/body-limit-413.js | 70 +++++++++++++++++++++++ 3 files changed, 88 insertions(+), 3 deletions(-) create mode 100644 tests/tests/middlewares/body-limit-413.js diff --git a/src/middlewares.js b/src/middlewares.js index 255098e8..34bbb66e 100644 --- a/src/middlewares.js +++ b/src/middlewares.js @@ -141,6 +141,17 @@ function createInflate(contentEncoding) { } } +// the error body-parser gives for a body over the limit, so the error handler answers 413 and not 500 +function entityTooLarge(fields) { + const err = new Error('request entity too large'); + err.name = 'PayloadTooLargeError'; + err.expose = true; + err.statusCode = 413; + err.status = 413; + err.type = 'entity.too.large'; + return Object.assign(err, fields); +} + function createBodyParser(defaultType, beforeReturn) { return function(options) { if(typeof options !== 'object') { @@ -206,7 +217,7 @@ function createBodyParser(defaultType, beforeReturn) { // skip reading too large body if(length && +length > options.limit) { - return next(new Error('Request entity too large')); + return next(entityTooLarge({ expected: +length, length: +length, limit: options.limit })); } @@ -257,7 +268,7 @@ function createBodyParser(defaultType, beforeReturn) { if(totalSize > options.limit) { finished = true; abs.length = 0; - return next(new Error('Request entity too large')); + return next(entityTooLarge({ limit: options.limit, received: totalSize })); } } diff --git a/src/router.js b/src/router.js index 97e2502c..034425c0 100644 --- a/src/router.js +++ b/src/router.js @@ -381,7 +381,11 @@ module.exports = class Router extends EventEmitter { }); } console.error(err); - if(response.statusCode === 200) { + // as finalhandler: an error carrying an HTTP status answers with it + const errorStatus = [err?.status, err?.statusCode].find(s => typeof s === 'number' && s >= 400 && s < 600); + if(errorStatus !== undefined) { + response.statusCode = errorStatus; + } else if(response.statusCode === 200) { response.statusCode = 500; } this._sendErrorPage(request, response, err, true); diff --git a/tests/tests/middlewares/body-limit-413.js b/tests/tests/middlewares/body-limit-413.js new file mode 100644 index 00000000..906aa60f --- /dev/null +++ b/tests/tests/middlewares/body-limit-413.js @@ -0,0 +1,70 @@ +// must answer 413 for a body over the limit, with the error body-parser gives + +const express = require("express"); + +const app = express(); + +app.use(express.json({ limit: '20b' })); + +app.post('/abc', (req, res) => { + res.json({ ok: true }); +}); + +app.post('/handled', (req, res) => { + res.json({ ok: true }); +}); + +// the error as the application sees it, minus the stack +app.use('/handled', (err, req, res, next) => { + res.status(err.status || 500).json({ + name: err.name, + message: err.message, + status: err.status, + statusCode: err.statusCode, + expose: err.expose, + type: err.type, + limit: err.limit, + length: err.length, + expected: err.expected + }); +}); + +app.listen(13333, async () => { + console.log('Server is running on port 13333'); + + const big = JSON.stringify({ a: 'b'.repeat(100) }); + + // over the limit by content-length, through the default error handler + let response = await fetch('http://localhost:13333/abc', { + method: 'POST', + body: big, + headers: { 'Content-Type': 'application/json' } + }); + console.log(response.status); + + // the same, to an error handler that reads the error + response = await fetch('http://localhost:13333/handled', { + method: 'POST', + body: big, + headers: { 'Content-Type': 'application/json' } + }); + console.log(response.status, await response.text()); + + // a chunked body carries no content-length, so the limit is caught while reading + const body = new ReadableStream({ + start(controller) { + controller.enqueue(new TextEncoder().encode('{"aaaaaaaaaa":')); + controller.enqueue(new TextEncoder().encode('"bbbbbbbbbbbbbbbbbbbb"}')); + controller.close(); + } + }); + response = await fetch('http://localhost:13333/abc', { + method: 'POST', + body, + duplex: 'half', + headers: { 'Content-Type': 'application/json' } + }); + console.log(response.status); + + process.exit(0); +});