From c78e7be0fa87a22473c56eee3e7678b6d0fe6473 Mon Sep 17 00:00:00 2001 From: cjihrig Date: Mon, 4 Apr 2016 14:48:32 -0400 Subject: [PATCH] add maxBytes setting to detect oversized payloads --- lib/index.js | 28 +++++++++++++++++++++++++--- package.json | 2 +- test/index.js | 43 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 69 insertions(+), 4 deletions(-) diff --git a/lib/index.js b/lib/index.js index 55088fa..8eb993b 100755 --- a/lib/index.js +++ b/lib/index.js @@ -51,11 +51,20 @@ internals.state = { }; -exports.Dispenser = internals.Dispenser = function (contentType) { +internals.defaults = { + maxBytes: Infinity +}; + + +exports.Dispenser = internals.Dispenser = function (options) { Stream.Writable.call(this); - this._boundary = contentType.boundary; + Hoek.assert(options !== null && typeof options === 'object', + 'options must be an object'); + const settings = Hoek.applyToDefaults(internals.defaults, options); + + this._boundary = settings.boundary; this._state = internals.state.preamble; this._held = ''; @@ -64,8 +73,10 @@ exports.Dispenser = internals.Dispenser = function (contentType) { this._name = ''; this._pendingHeader = ''; this._error = null; + this._bytes = 0; + this._maxBytes = settings.maxBytes; - this._parts = new Nigel.Stream(new Buffer('--' + contentType.boundary)); + this._parts = new Nigel.Stream(new Buffer('--' + settings.boundary)); this._lines = new Nigel.Stream(new Buffer('\r\n')); this._parts.on('needle', () => { @@ -102,6 +113,7 @@ exports.Dispenser = internals.Dispenser = function (contentType) { let finish = (err) => { if (piper) { + piper.removeListener('data', onReqData); piper.removeListener('error', finish); piper.removeListener('aborted', onReqAborted); } @@ -143,9 +155,19 @@ exports.Dispenser = internals.Dispenser = function (contentType) { finish(Boom.badRequest('Client request aborted')); }; + const onReqData = (data) => { + + this._bytes += Buffer.byteLength(data); + + if (this._bytes > this._maxBytes) { + finish(Boom.badRequest('Maximum size exceeded')); + } + }; + this.once('pipe', (req) => { piper = req; + req.on('data', onReqData); req.once('error', finish); req.once('aborted', onReqAborted); }); diff --git a/package.json b/package.json index 3525ba4..06aa9fc 100755 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "devDependencies": { "code": "2.x.x", "form-data": "0.2.x", - "lab": "9.x.x", + "lab": "10.x.x", "shot": "3.x.x", "wreck": "7.x.x" }, diff --git a/test/index.js b/test/index.js index a37ce0b..6656c94 100755 --- a/test/index.js +++ b/test/index.js @@ -44,6 +44,24 @@ describe('Dispenser', () => { req.pipe(dispenser); }; + it('throws on invalid options', (done) => { + + const fail = (options) => { + + expect(() => { + + return new Pez.Dispenser(options); + }).to.throw(Error, 'options must be an object'); + }; + + fail(); + fail(null); + fail('foo'); + fail(1); + fail(false); + done(); + }); + it('parses RFC1867 payload', (done) => { const payload = @@ -587,6 +605,31 @@ describe('Dispenser', () => { }); }); + it('errors if the payload size exceeds the byte limit', (done) => { + + const payload = + '--AaB03x\r\n' + + 'content-disposition: form-data; name="file"; filename="file1.txt"\r\n' + + 'Content-Type: text/plain\r\n' + + '\r\n' + + 'I am a plain text file\r\n' + + '--AaB03x--\r\n'; + + const req = new internals.Payload(payload, true); + req.headers = { 'content-type': 'multipart/form-data; boundary="AaB03x"' }; + + const dispenser = new Pez.Dispenser({ boundary: 'AaB03x', maxBytes: payload.length - 1 }); + + dispenser.once('error', (err) => { + + expect(err).to.exist(); + expect(err.message).to.equal('Maximum size exceeded'); + done(); + }); + + req.pipe(dispenser); + }); + it('parses a request with "=" in the boundary', (done) => { const payload =