diff --git a/lib/plugins/bodyReader.js b/lib/plugins/bodyReader.js index a71cfb0be..8d602e2fe 100644 --- a/lib/plugins/bodyReader.js +++ b/lib/plugins/bodyReader.js @@ -15,6 +15,8 @@ var BadDigestError = errors.BadDigestError; var RequestEntityTooLargeError = errors.RequestEntityTooLargeError; var PayloadTooLargeError = errors.PayloadTooLargeError; var UnsupportedMediaTypeError = errors.UnsupportedMediaTypeError; +var BadRequestError = errors.BadRequestError; +var InternalServerError = errors.InternalServerError; var MD5_MSG = "Content-MD5 '%s' didn't match '%s'"; @@ -96,12 +98,16 @@ function bodyReader(options) { var md5; var unsupportedContentEncoding; + var gzError; if ((md5 = req.headers['content-md5'])) { hash = crypto.createHash('md5'); } function done() { + var err; + var msg; + bodyWriter.end(); if (unsupportedContentEncoding) { @@ -119,8 +125,7 @@ function bodyReader(options) { } if (maxBodySize && bytesReceived > maxBodySize) { - var msg = 'Request body size exceeds ' + maxBodySize; - var err; + msg = 'Request body size exceeds ' + maxBodySize; // Between Node 0.12 and 4 http status code messages changed // RequestEntityTooLarge was changed to PayloadTooLarge @@ -135,6 +140,21 @@ function bodyReader(options) { return; } + if (gzError) { + if ( + gzError.errno === zlib.Z_DATA_ERROR || + gzError.errno === zlib.Z_STREAM_ERROR + ) { + msg = 'Request body is not a valid gzip'; + err = new BadRequestError(msg); + } else { + msg = 'Failed to gunzip request body'; + err = new InternalServerError(msg); + } + next(err); + return; + } + if (!req.body.length) { next(); return; @@ -155,6 +175,10 @@ function bodyReader(options) { gz = zlib.createGunzip(); gz.on('data', bodyWriter.write); gz.once('end', done); + gz.once('error', function onGzError(err) { + gzError = err; + done(); + }); req.once('end', gz.end.bind(gz)); } else { unsupportedContentEncoding = req.headers['content-encoding']; diff --git a/test/plugins/bodyReader.test.js b/test/plugins/bodyReader.test.js index b799fef90..5a131c322 100644 --- a/test/plugins/bodyReader.test.js +++ b/test/plugins/bodyReader.test.js @@ -3,6 +3,7 @@ // core requires var http = require('http'); +var zlib = require('zlib'); // external requires var assert = require('chai').assert; @@ -65,6 +66,78 @@ describe('body reader', function() { ); }); + it('should return 400 if body is not a valid gzip', function(done) { + SERVER.use(restify.plugins.bodyParser()); + + CLIENT = restifyClients.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + retry: false, + headers: { + 'content-encoding': 'gzip' + } + }); + + SERVER.post('/compressed', function(req, res, next) { + res.send(); + next(); + }); + + CLIENT.post( + '/compressed', + { + apple: 'red' + }, + function(err, _, res) { + assert.isOk(err, 'should fail'); + assert.equal(res.statusCode, 400); + done(); + } + ); + }); + + describe('when maxBodySize given should', function() { + var body = { apple: 'red' }; + var bodyTooLarge = { apple: 'red', lemon: 'yellow' }; + + var compressedBodySize = zlib.gzipSync(JSON.stringify(body)) + .byteLength; + + beforeEach(function() { + SERVER.use( + restify.plugins.bodyParser({ + maxBodySize: compressedBodySize + }) + ); + + CLIENT = restifyClients.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + retry: false, + gzip: {} + }); + + SERVER.post('/compressed', function(req, res, next) { + res.send(); + next(); + }); + }); + + it('return 200 when body size under the limit', function(done) { + CLIENT.post('/compressed', body, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('return 413 when body size over the limit', function(done) { + CLIENT.post('/compressed', bodyTooLarge, function(err, _, res) { + assert.isOk(err, 'should fail'); + assert.equal(res.statusCode, 413); + done(); + }); + }); + }); + it('should not accept unsupported content encoding', function(done) { SERVER.use(restify.plugins.bodyParser());