From ce7bc65561e9b05cc03f9b6c2cbaf125dcddd4bf Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Mon, 21 May 2018 08:26:04 +0100 Subject: [PATCH 1/9] added name to package json --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index 02430330b..bc914b33d 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "Falco Nogatz", "Gergely Nemeth", "Guillaume Chauvet", + "Ifiok Idiang", "Isaac Schlueter", "Jacob Quatier", "James O'Cull", From 1ac60082a35dc9f69295eb36d2d47eaaed9c1caf Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Mon, 21 May 2018 08:28:02 +0100 Subject: [PATCH 2/9] updated parser to check for extended content type formats for JSON --- lib/plugins/bodyParser.js | 25 +++++++++++++++++-------- lib/plugins/jsonBodyParser.js | 9 +++++++-- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/plugins/bodyParser.js b/lib/plugins/bodyParser.js index 1d77ca978..c52f26721 100644 --- a/lib/plugins/bodyParser.js +++ b/lib/plugins/bodyParser.js @@ -160,25 +160,34 @@ function bodyParser(options) { } var parser; - var type = req.contentType().toLowerCase(); + //var type = req.contentType().toLowerCase(); + var type = req + .contentType() + .toLowerCase() + .split(';')[0]; - switch (type) { - case 'application/json': + var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); + + switch (true) { + case jsonPatternMatcher.test(type): + parser = parseJson[0]; + break; + case type === 'application/json': parser = parseJson[0]; break; - case 'application/x-www-form-urlencoded': + case type === 'application/x-www-form-urlencoded': parser = parseForm[0]; break; - case 'multipart/form-data': + case type === 'multipart/form-data': parser = parseMultipart; break; - case 'text/tsv': + case type === 'text/tsv': parser = parseFieldedText; break; - case 'text/tab-separated-values': + case type === 'text/tab-separated-values': parser = parseFieldedText; break; - case 'text/csv': + case type === 'text/csv': parser = parseFieldedText; break; diff --git a/lib/plugins/jsonBodyParser.js b/lib/plugins/jsonBodyParser.js index 174b5eba4..001963d5a 100644 --- a/lib/plugins/jsonBodyParser.js +++ b/lib/plugins/jsonBodyParser.js @@ -28,8 +28,13 @@ function jsonBodyParser(options) { // save original body on req.rawBody and req._body req.rawBody = req._body = req.body; - if (req.getContentType() !== 'application/json' || !req.body) { - return next(); + var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); + var contentType = req.getContentType().split(';')[0]; + + if (contentType !== 'application/json' || !req.body) { + if (!jsonPatternMatcher.test(contentType)) { + return next(); + } } var params; From 18ed8bd3eb4961c061882552200b9176d71223b1 Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Mon, 21 May 2018 08:43:21 +0100 Subject: [PATCH 3/9] added tests to validate change in accepting extended headers --- test/plugins/jsonBodyParser.test.js | 944 ++++++++++++++++++++++++++++ 1 file changed, 944 insertions(+) diff --git a/test/plugins/jsonBodyParser.test.js b/test/plugins/jsonBodyParser.test.js index c0ce2b7ca..189d8531b 100644 --- a/test/plugins/jsonBodyParser.test.js +++ b/test/plugins/jsonBodyParser.test.js @@ -490,3 +490,947 @@ describe('JSON body parser', function() { client.end(); }); }); + +describe('JSON body parser with content type application/hal+json', function() { + beforeEach(function(done) { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server') + }); + + SERVER.listen(0, '127.0.0.1', function() { + PORT = SERVER.address().port; + CLIENT = restifyClients.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + STRING_CLIENT = restifyClients.createStringClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false, + contentType: 'application/hal+json', + accept: 'application/json' + }); + + done(); + }); + }); + + afterEach(function(done) { + CLIENT.close(); + STRING_CLIENT.close(); + SERVER.close(done); + }); + + it('should parse null JSON body', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body, null); + res.send(); + next(); + }); + + STRING_CLIENT.post('/body/foo?name=markc', 'null', function( + err, + _, + res + ) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should parse empty JSON body', function(done) { + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.deepEqual(req.body, {}); + res.send(); + next(); + }); + + CLIENT.post('/body/foo', null, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should parse req.body and req.params independently', function(done) { + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body.id, 'bar'); + assert.equal(req.body.name, 'alex'); + assert.notDeepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should fail to map array req.body onto req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + // this handler should never be reached + res.send(); + next(); + }); + + CLIENT.post('/body/foo', [1, 2, 3], function(err, _, res) { + assert.ok(err); + assert.equal(err.name, 'InternalServerError'); + assert.equal(res.statusCode, 500); + done(); + }); + }); + + // TODO: router param mapping runs later + it('should map req.body onto req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.params.name, 'alex'); + assert.notDeepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should take req.body and stomp on req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true, + overrideParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'bar'); + assert.equal(req.params.name, 'alex'); + assert.deepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should parse JSON body with reviver', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + reviver: function reviver(key, value) { + if (key === '') { + return value; + } + return value + value; + } + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body.apple, 'redred'); + assert.equal(req.body.orange, 'orangeorange'); + assert.equal(req.body.banana, 'yellowyellow'); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + apple: 'red', + orange: 'orange', + banana: 'yellow' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('restify-GH-318 get request with body (default)', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true + }) + ); + + SERVER.get('/getWithoutBody', function(req, res, next) { + assert.notEqual(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = + 'GET /getWithoutBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + + var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { + client.write(request); + }); + client.once('data', function(data) { + client.end(); + }); + client.once('end', function() { + done(); + }); + }); + + // eslint-disable-next-line + it('restify-GH-318 get request with body (requestBodyOnGet=true)', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true, + requestBodyOnGet: true + }) + ); + + SERVER.get('/getWithBody', function(req, res, next) { + assert.equal(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = + 'GET /getWithBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + + var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { + client.write(request); + }); + + client.once('data', function(data) { + client.end(); + }); + + client.once('end', function() { + done(); + }); + }); + + it('restify-GH-111 JSON Parser not right for arrays', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true + }) + ); + + SERVER.post('/gh111', function(req, res, next) { + assert.ok(Array.isArray(req.params)); + assert.equal(req.params[0], 'foo'); + assert.equal(req.params[1], 'bar'); + res.send(); + next(); + }); + + var obj = ['foo', 'bar']; + CLIENT.post('/gh111', obj, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-279 more JSON Arrays', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/gh279', function respond(req, res, next) { + assert.ok(Array.isArray(req.params)); + assert.equal(req.params[0].id, '123654'); + assert.ok(req.params[0].name, 'mimi'); + assert.ok(req.params[1].id, '987654'); + assert.ok(req.params[1].name, 'pijama'); + res.send(200); + next(); + }); + + var obj = [ + { + id: '123654', + name: 'mimi' + }, + { + id: '987654', + name: 'pijama' + } + ]; + CLIENT.post('/gh279', obj, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-774 utf8 corruption in body parser', function(done) { + var slen = 100000; + SERVER.use(restify.plugins.bodyParser()); + SERVER.post('/utf8', function(req, res, next) { + assert.notOk(/\ufffd/.test(req.body.text)); + assert.equal(req.body.text.length, slen); + res.send({ len: req.body.text.length }); + next(); + }); + + // create a long string of unicode characters + var tx = ''; + + for (var i = 0; i < slen; ++i) { + tx += '\u2661'; + } + + CLIENT.post('/utf8', { text: tx }, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-149 limit request body size', function(done) { + SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); + + SERVER.post('/', function(req, res, next) { + res.send(200, { length: req.body.length }); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 413); + res.once('end', done); + res.resume(); + }); + client.write(new Array(1028).join('x')); + client.end(); + }); + + it('restify-GH-149 limit request body size (json)', function(done) { + SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); + + SERVER.post('/', function(req, res, next) { + res.send(200, { length: req.body.length }); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/json', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 413); + res.once('end', done); + res.resume(); + }); + client.write('{"a":[' + new Array(512).join('1,') + '0]}'); + client.end(); + }); + + it('plugins-GH-6: should expose rawBody', function(done) { + var payload = { + id: 'bar', + name: 'alex' + }; + + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.rawBody, JSON.stringify(payload)); + assert.equal(req.body.id, 'bar'); + assert.equal(req.body.name, 'alex'); + res.send(); + next(); + }); + + CLIENT.post('/body/foo', payload, done); + }); + + it('should not throw uncaught "too few args to sprintf"', function(done) { + // https://github.com/restify/node-restify/issues/1411 + SERVER.use(restify.plugins.bodyParser()); + + SERVER.post('/', function(req, res, next) { + res.send(); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/json' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 400); + res.once('end', done); + res.resume(); + }); + client.write('{"malformedJsonWithPercentSign":30%}'); + client.end(); + }); +}); + +describe('JSON body parser with extended content type', function() { + beforeEach(function(done) { + SERVER = restify.createServer({ + dtrace: helper.dtrace, + log: helper.getLog('server') + }); + + SERVER.listen(0, '127.0.0.1', function() { + PORT = SERVER.address().port; + CLIENT = restifyClients.createJsonClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false + }); + STRING_CLIENT = restifyClients.createStringClient({ + url: 'http://127.0.0.1:' + PORT, + dtrace: helper.dtrace, + retry: false, + agent: false, + contentType: 'application/json; charset=utf-8', + accept: 'application/json' + }); + + done(); + }); + }); + + afterEach(function(done) { + CLIENT.close(); + STRING_CLIENT.close(); + SERVER.close(done); + }); + + it('should parse null JSON body', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body, null); + res.send(); + next(); + }); + + STRING_CLIENT.post('/body/foo?name=markc', 'null', function( + err, + _, + res + ) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should parse empty JSON body', function(done) { + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.deepEqual(req.body, {}); + res.send(); + next(); + }); + + CLIENT.post('/body/foo', null, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('should parse req.body and req.params independently', function(done) { + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body.id, 'bar'); + assert.equal(req.body.name, 'alex'); + assert.notDeepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should fail to map array req.body onto req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + // this handler should never be reached + res.send(); + next(); + }); + + CLIENT.post('/body/foo', [1, 2, 3], function(err, _, res) { + assert.ok(err); + assert.equal(err.name, 'InternalServerError'); + assert.equal(res.statusCode, 500); + done(); + }); + }); + + // TODO: router param mapping runs later + it('should map req.body onto req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.params.name, 'alex'); + assert.notDeepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should take req.body and stomp on req.params', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true, + overrideParams: true + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'bar'); + assert.equal(req.params.name, 'alex'); + assert.deepEqual(req.body, req.params); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + id: 'bar', + name: 'alex' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('should parse JSON body with reviver', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + reviver: function reviver(key, value) { + if (key === '') { + return value; + } + return value + value; + } + }) + ); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.params.id, 'foo'); + assert.equal(req.body.apple, 'redred'); + assert.equal(req.body.orange, 'orangeorange'); + assert.equal(req.body.banana, 'yellowyellow'); + res.send(); + next(); + }); + + CLIENT.post( + '/body/foo', + { + apple: 'red', + orange: 'orange', + banana: 'yellow' + }, + function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + } + ); + }); + + it('restify-GH-318 get request with body (default)', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true + }) + ); + + SERVER.get('/getWithoutBody', function(req, res, next) { + assert.notEqual(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = + 'GET /getWithoutBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + + var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { + client.write(request); + }); + client.once('data', function(data) { + client.end(); + }); + client.once('end', function() { + done(); + }); + }); + + // eslint-disable-next-line + it('restify-GH-318 get request with body (requestBodyOnGet=true)', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true, + requestBodyOnGet: true + }) + ); + + SERVER.get('/getWithBody', function(req, res, next) { + assert.equal(req.params.foo, 'bar'); + res.send(); + next(); + }); + + var request = + 'GET /getWithBody HTTP/1.1\r\n' + + 'Content-Type: application/json\r\n' + + 'Content-Length: 13\r\n' + + '\r\n' + + '{"foo":"bar"}'; + + var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { + client.write(request); + }); + + client.once('data', function(data) { + client.end(); + }); + + client.once('end', function() { + done(); + }); + }); + + it('restify-GH-111 JSON Parser not right for arrays', function(done) { + SERVER.use( + restify.plugins.bodyParser({ + mapParams: true + }) + ); + + SERVER.post('/gh111', function(req, res, next) { + assert.ok(Array.isArray(req.params)); + assert.equal(req.params[0], 'foo'); + assert.equal(req.params[1], 'bar'); + res.send(); + next(); + }); + + var obj = ['foo', 'bar']; + CLIENT.post('/gh111', obj, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-279 more JSON Arrays', function(done) { + SERVER.use( + restify.plugins.jsonBodyParser({ + mapParams: true + }) + ); + + SERVER.post('/gh279', function respond(req, res, next) { + assert.ok(Array.isArray(req.params)); + assert.equal(req.params[0].id, '123654'); + assert.ok(req.params[0].name, 'mimi'); + assert.ok(req.params[1].id, '987654'); + assert.ok(req.params[1].name, 'pijama'); + res.send(200); + next(); + }); + + var obj = [ + { + id: '123654', + name: 'mimi' + }, + { + id: '987654', + name: 'pijama' + } + ]; + CLIENT.post('/gh279', obj, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-774 utf8 corruption in body parser', function(done) { + var slen = 100000; + SERVER.use(restify.plugins.bodyParser()); + SERVER.post('/utf8', function(req, res, next) { + assert.notOk(/\ufffd/.test(req.body.text)); + assert.equal(req.body.text.length, slen); + res.send({ len: req.body.text.length }); + next(); + }); + + // create a long string of unicode characters + var tx = ''; + + for (var i = 0; i < slen; ++i) { + tx += '\u2661'; + } + + CLIENT.post('/utf8', { text: tx }, function(err, _, res) { + assert.ifError(err); + assert.equal(res.statusCode, 200); + done(); + }); + }); + + it('restify-GH-149 limit request body size', function(done) { + SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); + + SERVER.post('/', function(req, res, next) { + res.send(200, { length: req.body.length }); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/x-www-form-urlencoded', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 413); + res.once('end', done); + res.resume(); + }); + client.write(new Array(1028).join('x')); + client.end(); + }); + + it('restify-GH-149 limit request body size (json)', function(done) { + SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); + + SERVER.post('/', function(req, res, next) { + res.send(200, { length: req.body.length }); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/json', + 'transfer-encoding': 'chunked' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 413); + res.once('end', done); + res.resume(); + }); + client.write('{"a":[' + new Array(512).join('1,') + '0]}'); + client.end(); + }); + + it('plugins-GH-6: should expose rawBody', function(done) { + var payload = { + id: 'bar', + name: 'alex' + }; + + SERVER.use(restify.plugins.jsonBodyParser()); + + SERVER.post('/body/:id', function(req, res, next) { + assert.equal(req.rawBody, JSON.stringify(payload)); + assert.equal(req.body.id, 'bar'); + assert.equal(req.body.name, 'alex'); + res.send(); + next(); + }); + + CLIENT.post('/body/foo', payload, done); + }); + + it('should not throw uncaught "too few args to sprintf"', function(done) { + // https://github.com/restify/node-restify/issues/1411 + SERVER.use(restify.plugins.bodyParser()); + + SERVER.post('/', function(req, res, next) { + res.send(); + next(); + }); + + var opts = { + hostname: '127.0.0.1', + port: PORT, + path: '/', + method: 'POST', + agent: false, + headers: { + accept: 'application/json', + 'content-type': 'application/json' + } + }; + var client = http.request(opts, function(res) { + assert.equal(res.statusCode, 400); + res.once('end', done); + res.resume(); + }); + client.write('{"malformedJsonWithPercentSign":30%}'); + client.end(); + }); +}); From 6ede98ec442148bf119974bd034ea6ba8ed42713 Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Thu, 24 May 2018 08:54:31 +0100 Subject: [PATCH 4/9] updated bodyParser to treat *+json as application/json --- lib/plugins/bodyParser.js | 27 ++++++++++++--------------- lib/plugins/jsonBodyParser.js | 9 ++------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/lib/plugins/bodyParser.js b/lib/plugins/bodyParser.js index c52f26721..e42331fed 100644 --- a/lib/plugins/bodyParser.js +++ b/lib/plugins/bodyParser.js @@ -160,34 +160,31 @@ function bodyParser(options) { } var parser; - //var type = req.contentType().toLowerCase(); - var type = req - .contentType() - .toLowerCase() - .split(';')[0]; + var type = req.contentType().toLowerCase(); var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); + // map any +json to application/json + if (jsonPatternMatcher.test(type)) { + type = 'application/json'; + } - switch (true) { - case jsonPatternMatcher.test(type): - parser = parseJson[0]; - break; - case type === 'application/json': + switch (type) { + case 'application/json': parser = parseJson[0]; break; - case type === 'application/x-www-form-urlencoded': + case 'application/x-www-form-urlencoded': parser = parseForm[0]; break; - case type === 'multipart/form-data': + case 'multipart/form-data': parser = parseMultipart; break; - case type === 'text/tsv': + case 'text/tsv': parser = parseFieldedText; break; - case type === 'text/tab-separated-values': + case 'text/tab-separated-values': parser = parseFieldedText; break; - case type === 'text/csv': + case 'text/csv': parser = parseFieldedText; break; diff --git a/lib/plugins/jsonBodyParser.js b/lib/plugins/jsonBodyParser.js index 001963d5a..174b5eba4 100644 --- a/lib/plugins/jsonBodyParser.js +++ b/lib/plugins/jsonBodyParser.js @@ -28,13 +28,8 @@ function jsonBodyParser(options) { // save original body on req.rawBody and req._body req.rawBody = req._body = req.body; - var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); - var contentType = req.getContentType().split(';')[0]; - - if (contentType !== 'application/json' || !req.body) { - if (!jsonPatternMatcher.test(contentType)) { - return next(); - } + if (req.getContentType() !== 'application/json' || !req.body) { + return next(); } var params; From ef5a15eb9b8912ae9542df8ebf83e5dad57fe39e Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Thu, 24 May 2018 11:18:43 +0100 Subject: [PATCH 5/9] added test case to validate that */*+json content type is handled --- test/plugins/jsonBodyParser.test.js | 919 +--------------------------- 1 file changed, 2 insertions(+), 917 deletions(-) diff --git a/test/plugins/jsonBodyParser.test.js b/test/plugins/jsonBodyParser.test.js index 189d8531b..0a1f75498 100644 --- a/test/plugins/jsonBodyParser.test.js +++ b/test/plugins/jsonBodyParser.test.js @@ -489,875 +489,8 @@ describe('JSON body parser', function() { client.write('{"malformedJsonWithPercentSign":30%}'); client.end(); }); -}); - -describe('JSON body parser with content type application/hal+json', function() { - beforeEach(function(done) { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server') - }); - - SERVER.listen(0, '127.0.0.1', function() { - PORT = SERVER.address().port; - CLIENT = restifyClients.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - STRING_CLIENT = restifyClients.createStringClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false, - contentType: 'application/hal+json', - accept: 'application/json' - }); - - done(); - }); - }); - - afterEach(function(done) { - CLIENT.close(); - STRING_CLIENT.close(); - SERVER.close(done); - }); - - it('should parse null JSON body', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body, null); - res.send(); - next(); - }); - - STRING_CLIENT.post('/body/foo?name=markc', 'null', function( - err, - _, - res - ) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('should parse empty JSON body', function(done) { - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.deepEqual(req.body, {}); - res.send(); - next(); - }); - - CLIENT.post('/body/foo', null, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('should parse req.body and req.params independently', function(done) { - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body.id, 'bar'); - assert.equal(req.body.name, 'alex'); - assert.notDeepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should fail to map array req.body onto req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - // this handler should never be reached - res.send(); - next(); - }); - - CLIENT.post('/body/foo', [1, 2, 3], function(err, _, res) { - assert.ok(err); - assert.equal(err.name, 'InternalServerError'); - assert.equal(res.statusCode, 500); - done(); - }); - }); - - // TODO: router param mapping runs later - it('should map req.body onto req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.params.name, 'alex'); - assert.notDeepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should take req.body and stomp on req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true, - overrideParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'bar'); - assert.equal(req.params.name, 'alex'); - assert.deepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should parse JSON body with reviver', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - reviver: function reviver(key, value) { - if (key === '') { - return value; - } - return value + value; - } - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body.apple, 'redred'); - assert.equal(req.body.orange, 'orangeorange'); - assert.equal(req.body.banana, 'yellowyellow'); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - apple: 'red', - orange: 'orange', - banana: 'yellow' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('restify-GH-318 get request with body (default)', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true - }) - ); - - SERVER.get('/getWithoutBody', function(req, res, next) { - assert.notEqual(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = - 'GET /getWithoutBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - - var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { - client.write(request); - }); - client.once('data', function(data) { - client.end(); - }); - client.once('end', function() { - done(); - }); - }); - - // eslint-disable-next-line - it('restify-GH-318 get request with body (requestBodyOnGet=true)', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true, - requestBodyOnGet: true - }) - ); - - SERVER.get('/getWithBody', function(req, res, next) { - assert.equal(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = - 'GET /getWithBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - - var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { - client.write(request); - }); - - client.once('data', function(data) { - client.end(); - }); - - client.once('end', function() { - done(); - }); - }); - - it('restify-GH-111 JSON Parser not right for arrays', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true - }) - ); - - SERVER.post('/gh111', function(req, res, next) { - assert.ok(Array.isArray(req.params)); - assert.equal(req.params[0], 'foo'); - assert.equal(req.params[1], 'bar'); - res.send(); - next(); - }); - - var obj = ['foo', 'bar']; - CLIENT.post('/gh111', obj, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-279 more JSON Arrays', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/gh279', function respond(req, res, next) { - assert.ok(Array.isArray(req.params)); - assert.equal(req.params[0].id, '123654'); - assert.ok(req.params[0].name, 'mimi'); - assert.ok(req.params[1].id, '987654'); - assert.ok(req.params[1].name, 'pijama'); - res.send(200); - next(); - }); - - var obj = [ - { - id: '123654', - name: 'mimi' - }, - { - id: '987654', - name: 'pijama' - } - ]; - CLIENT.post('/gh279', obj, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-774 utf8 corruption in body parser', function(done) { - var slen = 100000; - SERVER.use(restify.plugins.bodyParser()); - SERVER.post('/utf8', function(req, res, next) { - assert.notOk(/\ufffd/.test(req.body.text)); - assert.equal(req.body.text.length, slen); - res.send({ len: req.body.text.length }); - next(); - }); - - // create a long string of unicode characters - var tx = ''; - - for (var i = 0; i < slen; ++i) { - tx += '\u2661'; - } - - CLIENT.post('/utf8', { text: tx }, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-149 limit request body size', function(done) { - SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); - - SERVER.post('/', function(req, res, next) { - res.send(200, { length: req.body.length }); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - accept: 'application/json', - 'content-type': 'application/x-www-form-urlencoded', - 'transfer-encoding': 'chunked' - } - }; - var client = http.request(opts, function(res) { - assert.equal(res.statusCode, 413); - res.once('end', done); - res.resume(); - }); - client.write(new Array(1028).join('x')); - client.end(); - }); - - it('restify-GH-149 limit request body size (json)', function(done) { - SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); - - SERVER.post('/', function(req, res, next) { - res.send(200, { length: req.body.length }); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - accept: 'application/json', - 'content-type': 'application/json', - 'transfer-encoding': 'chunked' - } - }; - var client = http.request(opts, function(res) { - assert.equal(res.statusCode, 413); - res.once('end', done); - res.resume(); - }); - client.write('{"a":[' + new Array(512).join('1,') + '0]}'); - client.end(); - }); - - it('plugins-GH-6: should expose rawBody', function(done) { - var payload = { - id: 'bar', - name: 'alex' - }; - - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.rawBody, JSON.stringify(payload)); - assert.equal(req.body.id, 'bar'); - assert.equal(req.body.name, 'alex'); - res.send(); - next(); - }); - - CLIENT.post('/body/foo', payload, done); - }); - - it('should not throw uncaught "too few args to sprintf"', function(done) { - // https://github.com/restify/node-restify/issues/1411 - SERVER.use(restify.plugins.bodyParser()); - - SERVER.post('/', function(req, res, next) { - res.send(); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - accept: 'application/json', - 'content-type': 'application/json' - } - }; - var client = http.request(opts, function(res) { - assert.equal(res.statusCode, 400); - res.once('end', done); - res.resume(); - }); - client.write('{"malformedJsonWithPercentSign":30%}'); - client.end(); - }); -}); - -describe('JSON body parser with extended content type', function() { - beforeEach(function(done) { - SERVER = restify.createServer({ - dtrace: helper.dtrace, - log: helper.getLog('server') - }); - - SERVER.listen(0, '127.0.0.1', function() { - PORT = SERVER.address().port; - CLIENT = restifyClients.createJsonClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false - }); - STRING_CLIENT = restifyClients.createStringClient({ - url: 'http://127.0.0.1:' + PORT, - dtrace: helper.dtrace, - retry: false, - agent: false, - contentType: 'application/json; charset=utf-8', - accept: 'application/json' - }); - - done(); - }); - }); - - afterEach(function(done) { - CLIENT.close(); - STRING_CLIENT.close(); - SERVER.close(done); - }); - - it('should parse null JSON body', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body, null); - res.send(); - next(); - }); - - STRING_CLIENT.post('/body/foo?name=markc', 'null', function( - err, - _, - res - ) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('should parse empty JSON body', function(done) { - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.deepEqual(req.body, {}); - res.send(); - next(); - }); - - CLIENT.post('/body/foo', null, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('should parse req.body and req.params independently', function(done) { - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body.id, 'bar'); - assert.equal(req.body.name, 'alex'); - assert.notDeepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should fail to map array req.body onto req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - // this handler should never be reached - res.send(); - next(); - }); - - CLIENT.post('/body/foo', [1, 2, 3], function(err, _, res) { - assert.ok(err); - assert.equal(err.name, 'InternalServerError'); - assert.equal(res.statusCode, 500); - done(); - }); - }); - - // TODO: router param mapping runs later - it('should map req.body onto req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.params.name, 'alex'); - assert.notDeepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should take req.body and stomp on req.params', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true, - overrideParams: true - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'bar'); - assert.equal(req.params.name, 'alex'); - assert.deepEqual(req.body, req.params); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - id: 'bar', - name: 'alex' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('should parse JSON body with reviver', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - reviver: function reviver(key, value) { - if (key === '') { - return value; - } - return value + value; - } - }) - ); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.params.id, 'foo'); - assert.equal(req.body.apple, 'redred'); - assert.equal(req.body.orange, 'orangeorange'); - assert.equal(req.body.banana, 'yellowyellow'); - res.send(); - next(); - }); - - CLIENT.post( - '/body/foo', - { - apple: 'red', - orange: 'orange', - banana: 'yellow' - }, - function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - } - ); - }); - - it('restify-GH-318 get request with body (default)', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true - }) - ); - - SERVER.get('/getWithoutBody', function(req, res, next) { - assert.notEqual(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = - 'GET /getWithoutBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - - var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { - client.write(request); - }); - client.once('data', function(data) { - client.end(); - }); - client.once('end', function() { - done(); - }); - }); - - // eslint-disable-next-line - it('restify-GH-318 get request with body (requestBodyOnGet=true)', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true, - requestBodyOnGet: true - }) - ); - - SERVER.get('/getWithBody', function(req, res, next) { - assert.equal(req.params.foo, 'bar'); - res.send(); - next(); - }); - - var request = - 'GET /getWithBody HTTP/1.1\r\n' + - 'Content-Type: application/json\r\n' + - 'Content-Length: 13\r\n' + - '\r\n' + - '{"foo":"bar"}'; - - var client = net.connect({ host: '127.0.0.1', port: PORT }, function() { - client.write(request); - }); - - client.once('data', function(data) { - client.end(); - }); - - client.once('end', function() { - done(); - }); - }); - - it('restify-GH-111 JSON Parser not right for arrays', function(done) { - SERVER.use( - restify.plugins.bodyParser({ - mapParams: true - }) - ); - - SERVER.post('/gh111', function(req, res, next) { - assert.ok(Array.isArray(req.params)); - assert.equal(req.params[0], 'foo'); - assert.equal(req.params[1], 'bar'); - res.send(); - next(); - }); - - var obj = ['foo', 'bar']; - CLIENT.post('/gh111', obj, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-279 more JSON Arrays', function(done) { - SERVER.use( - restify.plugins.jsonBodyParser({ - mapParams: true - }) - ); - - SERVER.post('/gh279', function respond(req, res, next) { - assert.ok(Array.isArray(req.params)); - assert.equal(req.params[0].id, '123654'); - assert.ok(req.params[0].name, 'mimi'); - assert.ok(req.params[1].id, '987654'); - assert.ok(req.params[1].name, 'pijama'); - res.send(200); - next(); - }); - - var obj = [ - { - id: '123654', - name: 'mimi' - }, - { - id: '987654', - name: 'pijama' - } - ]; - CLIENT.post('/gh279', obj, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-774 utf8 corruption in body parser', function(done) { - var slen = 100000; - SERVER.use(restify.plugins.bodyParser()); - SERVER.post('/utf8', function(req, res, next) { - assert.notOk(/\ufffd/.test(req.body.text)); - assert.equal(req.body.text.length, slen); - res.send({ len: req.body.text.length }); - next(); - }); - - // create a long string of unicode characters - var tx = ''; - - for (var i = 0; i < slen; ++i) { - tx += '\u2661'; - } - - CLIENT.post('/utf8', { text: tx }, function(err, _, res) { - assert.ifError(err); - assert.equal(res.statusCode, 200); - done(); - }); - }); - - it('restify-GH-149 limit request body size', function(done) { - SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); - - SERVER.post('/', function(req, res, next) { - res.send(200, { length: req.body.length }); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - accept: 'application/json', - 'content-type': 'application/x-www-form-urlencoded', - 'transfer-encoding': 'chunked' - } - }; - var client = http.request(opts, function(res) { - assert.equal(res.statusCode, 413); - res.once('end', done); - res.resume(); - }); - client.write(new Array(1028).join('x')); - client.end(); - }); - it('restify-GH-149 limit request body size (json)', function(done) { + it('should handle */*+json to application/json', function(done) { SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); SERVER.post('/', function(req, res, next) { @@ -1373,7 +506,7 @@ describe('JSON body parser with extended content type', function() { agent: false, headers: { accept: 'application/json', - 'content-type': 'application/json', + 'content-type': 'application/hal+json', 'transfer-encoding': 'chunked' } }; @@ -1385,52 +518,4 @@ describe('JSON body parser with extended content type', function() { client.write('{"a":[' + new Array(512).join('1,') + '0]}'); client.end(); }); - - it('plugins-GH-6: should expose rawBody', function(done) { - var payload = { - id: 'bar', - name: 'alex' - }; - - SERVER.use(restify.plugins.jsonBodyParser()); - - SERVER.post('/body/:id', function(req, res, next) { - assert.equal(req.rawBody, JSON.stringify(payload)); - assert.equal(req.body.id, 'bar'); - assert.equal(req.body.name, 'alex'); - res.send(); - next(); - }); - - CLIENT.post('/body/foo', payload, done); - }); - - it('should not throw uncaught "too few args to sprintf"', function(done) { - // https://github.com/restify/node-restify/issues/1411 - SERVER.use(restify.plugins.bodyParser()); - - SERVER.post('/', function(req, res, next) { - res.send(); - next(); - }); - - var opts = { - hostname: '127.0.0.1', - port: PORT, - path: '/', - method: 'POST', - agent: false, - headers: { - accept: 'application/json', - 'content-type': 'application/json' - } - }; - var client = http.request(opts, function(res) { - assert.equal(res.statusCode, 400); - res.once('end', done); - res.resume(); - }); - client.write('{"malformedJsonWithPercentSign":30%}'); - client.end(); - }); }); From c302a1215524fa426b1d1593ac3da8531498a44d Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Thu, 24 May 2018 11:33:54 +0100 Subject: [PATCH 6/9] updated test name --- test/plugins/jsonBodyParser.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/plugins/jsonBodyParser.test.js b/test/plugins/jsonBodyParser.test.js index 0a1f75498..431b477f3 100644 --- a/test/plugins/jsonBodyParser.test.js +++ b/test/plugins/jsonBodyParser.test.js @@ -490,7 +490,7 @@ describe('JSON body parser', function() { client.end(); }); - it('should handle */*+json to application/json', function(done) { + it('should handle application/*+json as application/json', function(done) { SERVER.use(restify.plugins.bodyParser({ maxBodySize: 1024 })); SERVER.post('/', function(req, res, next) { From f3517f26c1ae609a35edec1a52540e6a7436a9e9 Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Sun, 27 May 2018 10:35:54 +0100 Subject: [PATCH 7/9] added regext content type check for extend types to jsonbodyparser as it can used independently --- lib/plugins/jsonBodyParser.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/lib/plugins/jsonBodyParser.js b/lib/plugins/jsonBodyParser.js index 174b5eba4..97b20eaed 100644 --- a/lib/plugins/jsonBodyParser.js +++ b/lib/plugins/jsonBodyParser.js @@ -28,8 +28,13 @@ function jsonBodyParser(options) { // save original body on req.rawBody and req._body req.rawBody = req._body = req.body; - if (req.getContentType() !== 'application/json' || !req.body) { - return next(); + var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); + var contentType = req.getContentType(); + + if (contentType !== 'application/json' || !req.body) { + if (!jsonPatternMatcher.test(contentType)) { + return next(); + } } var params; From e5d6fe03aebb58a96086cae410c401fab8f8b760 Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Sat, 2 Jun 2018 11:43:41 +0100 Subject: [PATCH 8/9] added utility function for validating extended json content types --- .../utils/validateExtendedJsonContentType.js | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 lib/plugins/utils/validateExtendedJsonContentType.js diff --git a/lib/plugins/utils/validateExtendedJsonContentType.js b/lib/plugins/utils/validateExtendedJsonContentType.js new file mode 100644 index 000000000..6f4193291 --- /dev/null +++ b/lib/plugins/utils/validateExtendedJsonContentType.js @@ -0,0 +1,22 @@ +// Copyright 2018 Ifiok Idiang. All rights reserved. + +'use strict'; + +/** + * Return the stripped content type string if it is a valid JSON extended type + * + * @public + * @function validateExtendedJsonContentType + * @param {String} contentType - the content type string to validate + * @returns {Boolean} validity flag + */ +function validateExtendedJsonContentType(contentType) { + var type = contentType.toLowerCase(); + // map any +json to application/json + var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); + return jsonPatternMatcher.test(type); +} + +///--- Exports + +module.exports = validateExtendedJsonContentType; From 2c9f9c73f83ee65928b6be9b6c6ad3a4b5092fe2 Mon Sep 17 00:00:00 2001 From: Ifiok Idiang Date: Sat, 2 Jun 2018 11:46:09 +0100 Subject: [PATCH 9/9] extracted json content type checker to avoid duplicate implementations --- lib/plugins/bodyParser.js | 7 ++----- lib/plugins/jsonBodyParser.js | 4 ++-- ...alidateExtendedJsonContentType.js => isExtendedJson.js} | 0 3 files changed, 4 insertions(+), 7 deletions(-) rename lib/plugins/utils/{validateExtendedJsonContentType.js => isExtendedJson.js} (100%) diff --git a/lib/plugins/bodyParser.js b/lib/plugins/bodyParser.js index e42331fed..753929ce0 100644 --- a/lib/plugins/bodyParser.js +++ b/lib/plugins/bodyParser.js @@ -10,6 +10,7 @@ var jsonParser = require('./jsonBodyParser'); var formParser = require('./formBodyParser'); var multipartParser = require('./multipartBodyParser'); var fieldedTextParser = require('./fieldedTextBodyParser.js'); +var isExtendedJsonContentType = require('../plugins/utils/isExtendedJson'); ///--- Globals @@ -162,11 +163,7 @@ function bodyParser(options) { var parser; var type = req.contentType().toLowerCase(); - var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); - // map any +json to application/json - if (jsonPatternMatcher.test(type)) { - type = 'application/json'; - } + if (isExtendedJsonContentType(type)) type = 'application/json'; switch (type) { case 'application/json': diff --git a/lib/plugins/jsonBodyParser.js b/lib/plugins/jsonBodyParser.js index 97b20eaed..9450f139a 100644 --- a/lib/plugins/jsonBodyParser.js +++ b/lib/plugins/jsonBodyParser.js @@ -6,6 +6,7 @@ var assert = require('assert-plus'); var errors = require('restify-errors'); var bodyReader = require('./bodyReader'); +var isExtendedJsonContentType = require('../plugins/utils/isExtendedJson'); ///--- API @@ -28,11 +29,10 @@ function jsonBodyParser(options) { // save original body on req.rawBody and req._body req.rawBody = req._body = req.body; - var jsonPatternMatcher = new RegExp('^application/[a-zA-Z.]+\\+json'); var contentType = req.getContentType(); if (contentType !== 'application/json' || !req.body) { - if (!jsonPatternMatcher.test(contentType)) { + if (!isExtendedJsonContentType(contentType)) { return next(); } } diff --git a/lib/plugins/utils/validateExtendedJsonContentType.js b/lib/plugins/utils/isExtendedJson.js similarity index 100% rename from lib/plugins/utils/validateExtendedJsonContentType.js rename to lib/plugins/utils/isExtendedJson.js