From 4e79cc2a100971acf45c8d53d9ffef364081ca47 Mon Sep 17 00:00:00 2001 From: Manuel Spigolon Date: Tue, 25 Oct 2022 20:10:10 +0200 Subject: [PATCH] fix: route body limit (#36) * fix: route body limit * test: improve --- plugin.js | 10 ++- test/plugin.test.js | 6 ++ test/route-limit.test.js | 144 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 158 insertions(+), 2 deletions(-) create mode 100644 test/route-limit.test.js diff --git a/plugin.js b/plugin.js index 54196e6..ce2510a 100644 --- a/plugin.js +++ b/plugin.js @@ -56,16 +56,22 @@ function rawBody (fastify, opts, next) { next() function preparsingRawBody (request, reply, payload, done) { + const applyLimit = request.context._parserOptions.limit ?? fastify.initialConfig.bodyLimit + getRawBody(runFirst ? request.raw : payload, { length: null, // avoid content lenght check: fastify will do it - limit: fastify.initialConfig.bodyLimit, // limit to avoid memory leak or DoS + limit: applyLimit, // limit to avoid memory leak or DoS encoding }, function (err, string) { if (err) { /** * the error is managed by fastify server * so the request object will not have any - * `body` parsed + * `body` parsed. + * + * The preparsingRawBody decorates the request + * meanwhile the `payload` is processed by + * the fastify server. */ return } diff --git a/test/plugin.test.js b/test/plugin.test.js index b815ec3..766deeb 100644 --- a/test/plugin.test.js +++ b/test/plugin.test.js @@ -415,6 +415,12 @@ t.test('body limit', async t => { payload }) t.equal(res.statusCode, 413) + t.same(res.json(), { + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' + }) }) t.test('empty body', async t => { diff --git a/test/route-limit.test.js b/test/route-limit.test.js new file mode 100644 index 0000000..66b97f0 --- /dev/null +++ b/test/route-limit.test.js @@ -0,0 +1,144 @@ +'use strict' + +const t = require('tap') +const Fastify = require('fastify') +const rawBody = require('../plugin') + +const payloadMini = { h: 1 } +const payloadSmall = { hello: '1' } +const payloadBig = { hello: '1'.repeat(100) } + +const limitError = Object.freeze({ + statusCode: 413, + code: 'FST_ERR_CTP_BODY_TOO_LARGE', + error: 'Payload Too Large', + message: 'Request body is too large' +}) + +async function buildApp ({ + serverLimit, + routeLimit +}) { + const app = Fastify({ bodyLimit: serverLimit }) + + await app.register(rawBody, { + field: 'rawBody', + global: false, + runFirst: false + }) + + app.post('/route-limit', { + config: { rawBody: true }, + bodyLimit: routeLimit, + handler (req) { return req.rawBody } + }) + + app.post('/server-limit', { + config: { rawBody: true }, + handler (req) { return req.rawBody } + }) + + return app +} + +t.test('body limit per route (route-limit > server-limit)', async t => { + const app = await buildApp({ + serverLimit: 10, + routeLimit: 100 + }) + + await t.test('must succeed if body is smaller than route limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/route-limit', + payload: payloadSmall + }) + + t.equal(res.statusCode, 200) + t.equal(res.payload, JSON.stringify(payloadSmall)) + }) + + await t.test('must reject if body is bigger than route limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/route-limit', + payload: payloadBig + }) + + t.equal(res.statusCode, 413) + t.same(res.json(), limitError) + }) + + await t.test('must succeed if body is smaller then server limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/server-limit', + payload: payloadMini + }) + + t.equal(res.statusCode, 200) + t.equal(res.payload, JSON.stringify(payloadMini)) + }) + + await t.test('must reject if body is bigger then the server limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/server-limit', + payload: payloadSmall + }) + + t.equal(res.statusCode, 413) + t.same(res.json(), limitError) + }) +}) + +t.test('body limit per route (route-limit < server-limit)', async t => { + const app = await buildApp({ + serverLimit: 100, + routeLimit: 10 + }) + + await t.test('must succeed if body is smaller than route limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/route-limit', + payload: payloadMini + }) + + t.equal(res.statusCode, 200) + t.equal(res.payload, JSON.stringify(payloadMini)) + }) + + await t.test('must reject if body is bigger than route limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/route-limit', + payload: payloadSmall + }) + + t.equal(res.statusCode, 413) + t.same(res.json(), limitError) + }) + + await t.test('must succeed if body is smaller then server limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/server-limit', + payload: payloadSmall + }) + + t.equal(res.statusCode, 200) + t.equal(res.payload, JSON.stringify(payloadSmall)) + }) + + await t.test('must reject if body is bigger then the server limit', async t => { + const res = await app.inject({ + method: 'POST', + url: '/server-limit', + payload: payloadBig + }) + + t.equal(res.statusCode, 413) + t.same(res.json(), limitError) + }) +})