diff --git a/NEWS.md b/NEWS.md index a359def5..d2e7bf6c 100644 --- a/NEWS.md +++ b/NEWS.md @@ -7,6 +7,7 @@ New features: Bug Fixes: +- Validates tile coordinates (z/x/y) from request params to be a valid integer value. ## 6.1.0 diff --git a/lib/cartodb/controllers/layergroup/static.js b/lib/cartodb/controllers/layergroup/static.js index 00510780..1aa9cdeb 100644 --- a/lib/cartodb/controllers/layergroup/static.js +++ b/lib/cartodb/controllers/layergroup/static.js @@ -1,6 +1,7 @@ const cors = require('../../middleware/cors'); const user = require('../../middleware/user'); const layergroupToken = require('../../middleware/layergroup-token'); +const coordinates = require('../../middleware/coordinates'); const cleanUpQueryParams = require('../../middleware/clean-up-query-params'); const credentials = require('../../middleware/credentials'); const dbConnSetup = require('../../middleware/db-conn-setup'); @@ -43,6 +44,7 @@ module.exports = class StaticController { cors(), user(), layergroupToken(), + coordinates({ z: true, x: false, y: false }), credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), diff --git a/lib/cartodb/controllers/layergroup/tile.js b/lib/cartodb/controllers/layergroup/tile.js index 07727236..544c12b2 100644 --- a/lib/cartodb/controllers/layergroup/tile.js +++ b/lib/cartodb/controllers/layergroup/tile.js @@ -1,6 +1,7 @@ const cors = require('../../middleware/cors'); const user = require('../../middleware/user'); const layergroupToken = require('../../middleware/layergroup-token'); +const coordinates = require('../../middleware/coordinates'); const cleanUpQueryParams = require('../../middleware/clean-up-query-params'); const credentials = require('../../middleware/credentials'); const dbConnSetup = require('../../middleware/db-conn-setup'); @@ -51,6 +52,7 @@ module.exports = class TileController { cors(), user(), layergroupToken(), + coordinates(), credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), @@ -79,6 +81,7 @@ module.exports = class TileController { cors(), user(), layergroupToken(), + coordinates(), credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), @@ -108,6 +111,7 @@ module.exports = class TileController { cors(), user(), layergroupToken(), + coordinates(), credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 93e56bbe..c9656de0 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -1,6 +1,7 @@ const cors = require('../middleware/cors'); const user = require('../middleware/user'); const cleanUpQueryParams = require('../middleware/clean-up-query-params'); +const coordinates = require('../middleware/coordinates'); const credentials = require('../middleware/credentials'); const dbConnSetup = require('../middleware/db-conn-setup'); const authorize = require('../middleware/authorize'); @@ -56,6 +57,7 @@ NamedMapsController.prototype.register = function(app) { `${templateBasePath}/:template_id/:layer/:z/:x/:y.(:format)`, cors(), user(), + coordinates(), credentials(), authorize(this.authApi), dbConnSetup(this.pgConnection), diff --git a/lib/cartodb/middleware/coordinates.js b/lib/cartodb/middleware/coordinates.js new file mode 100644 index 00000000..d0840393 --- /dev/null +++ b/lib/cartodb/middleware/coordinates.js @@ -0,0 +1,41 @@ +const positiveIntegerNumberRegExp = /^\d+$/; +const integerNumberRegExp = /^-?\d+$/; +const invalidZoomMessage = function (zoom) { + return `Invalid zoom value (${zoom}). It should be an integer number greather than or equal to 0`; +}; +const invalidCoordXMessage = function (x) { + return `Invalid coodinate 'x' value (${x}). It should be an integer number`; +}; +const invalidCoordYMessage = function (y) { + return `Invalid coodinate 'y' value (${y}). It should be an integer number greather than or equal to 0`; +}; + +module.exports = function coordinates (validate = { z: true, x: true, y: true }) { + return function coordinatesMiddleware (req, res, next) { + const { z, x, y } = req.params; + + if (validate.z && !positiveIntegerNumberRegExp.test(z)) { + const err = new Error(invalidZoomMessage(z)); + err.http_status = 400; + + return next(err); + } + + // Negative values for x param are valid. The x param is wrapped + if (validate.x && !integerNumberRegExp.test(x)) { + const err = new Error(invalidCoordXMessage(x)); + err.http_status = 400; + + return next(err); + } + + if (validate.y && !positiveIntegerNumberRegExp.test(y)) { + const err = new Error(invalidCoordYMessage(y)); + err.http_status = 400; + + return next(err); + } + + next(); + }; +}; diff --git a/test/support/test-client.js b/test/support/test-client.js index ea3305fd..5e08394a 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -905,7 +905,7 @@ TestClient.prototype.getLayergroup = function (params, callback) { TestClient.prototype.getStaticCenter = function (params, callback) { var self = this; - let { layergroupid, z, lat, lng, width, height, format } = params; + let { layergroupid, zoom, lat, lng, width, height, format } = params; var url = `/api/v1/map/`; @@ -954,7 +954,7 @@ TestClient.prototype.getStaticCenter = function (params, callback) { self.keysToDelete['map_cfg|' + LayergroupToken.parse(layergroupId).token] = 0; self.keysToDelete['user:localhost:mapviews:global'] = 5; - url = `/api/v1/map/static/center/${layergroupId}/${z}/${lat}/${lng}/${width}/${height}.${format}`; + url = `/api/v1/map/static/center/${layergroupId}/${zoom}/${lat}/${lng}/${width}/${height}.${format}`; if (self.apiKey) { url += '?' + qs.stringify({api_key: self.apiKey}); diff --git a/test/unit/cartodb/middlewares/coordinates.test.js b/test/unit/cartodb/middlewares/coordinates.test.js new file mode 100644 index 00000000..11cd2626 --- /dev/null +++ b/test/unit/cartodb/middlewares/coordinates.test.js @@ -0,0 +1,258 @@ +const assert = require('assert'); +const coordinates = require('../../../../lib/cartodb/middleware/coordinates'); + +describe('coordinates middleware', function () { + it('should return error: invalid zoom paramenter (-1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '-1', + x: '0', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + 'Invalid zoom value (-1). It should be an integer number greather than or equal to 0' + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should return error: invalid zoom paramenter (1.1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1.1', + x: '0', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + 'Invalid zoom value (1.1). It should be an integer number greather than or equal to 0' + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should return error: invalid zoom paramenter (0.1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '0.1', + x: '0', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + 'Invalid zoom value (0.1). It should be an integer number greather than or equal to 0' + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + + it('should return error: invalid zoom paramenter (wadus)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: 'wadus', + x: '0', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + 'Invalid zoom value (wadus). It should be an integer number greather than or equal to 0' + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should NOT return error: \'zoom\' paramenter (1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '1', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should return error: invalid coordinate \'x\' paramenter (1.1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '1.1', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal(err.message, `Invalid coodinate 'x' value (1.1). It should be an integer number`); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should return error: invalid coordinate \'x\' paramenter (wadus)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: 'wadus', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal(err.message, `Invalid coodinate 'x' value (wadus). It should be an integer number`); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should NOT return error: \'x\' paramenter (-1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '-3', + y: '0' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should return error: invalid coordinate \'y\' paramenter (-1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '0', + y: '-1' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + `Invalid coodinate 'y' value (-1). It should be an integer number greather than or equal to 0` + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should return error: invalid coordinate \'y\' paramenter (1.1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '0', + y: '1.1' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + `Invalid coodinate 'y' value (1.1). It should be an integer number greather than or equal to 0` + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should return error: invalid coordinate \'y\' paramenter (wadus)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '0', + y: 'wadus' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + `Invalid coodinate 'y' value (wadus). It should be an integer number greather than or equal to 0` + ); + assert.equal(err.http_status, 400); + done(); + }); + }); + + it('should NOT return error: \'y\' paramenter (1)', function (done) { + const coords = coordinates(); + const req = { + params: { + z: '1', + x: '1', + y: '1' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.ifError(err); + done(); + }); + }); + + it('should validate zoom and should return error: invalid zoom paramenter (-1.1)', function (done) { + const coords = coordinates({ z: true, x: false, y: false }); + const req = { + params: { + z: '-1.1' + } + }; + const res = {}; + + coords(req, res, function (err) { + assert.equal( + err.message, + 'Invalid zoom value (-1.1). It should be an integer number greather than or equal to 0' + ); + assert.equal(err.http_status, 400); + done(); + }); + }); +});