diff --git a/index.js b/index.js index 639b1bd..301996c 100644 --- a/index.js +++ b/index.js @@ -8,18 +8,21 @@ * @api public */ -exports.set = function (obj, path, val) { +exports.set = function(obj, path, val) { var segs = path.split('.'); var attr = segs.pop(); var src = obj; for (var i = 0; i < segs.length; i++) { var seg = segs[i]; + if (!isSafe(obj, seg)) return src; obj[seg] = obj[seg] || {}; obj = obj[seg]; } - obj[attr] = val; + if (isSafe(obj, attr)) { + obj[attr] = val; + } return src; }; @@ -33,7 +36,7 @@ exports.set = function (obj, path, val) { * @api public */ -exports.get = function (obj, path) { +exports.get = function(obj, path) { var segs = path.split('.'); var attr = segs.pop(); @@ -55,19 +58,42 @@ exports.get = function (obj, path) { * @api public */ -exports.delete = function (obj, path) { +exports.delete = function(obj, path) { var segs = path.split('.'); var attr = segs.pop(); for (var i = 0; i < segs.length; i++) { var seg = segs[i]; if (!obj[seg]) return; + if (!isSafe(obj, seg)) return; obj = obj[seg]; } + if (!isSafe(obj, attr)) return; + if (Array.isArray(obj)) { - obj.splice(path, 1); + obj.splice(attr, 1); } else { delete obj[attr]; } }; + +function isSafe(obj, prop) { + if (isObject(obj)) { + return obj[prop] === undefined || hasOwnProperty(obj, prop); + } + + if (Array.isArray(obj)) { + return !isNaN(parseInt(prop, 10)); + } + + return false; +} + +function hasOwnProperty(obj, prop) { + return Object.prototype.hasOwnProperty.call(obj, prop); +} + +function isObject(obj) { + return Object.prototype.toString.call(obj) === '[object Object]'; +} diff --git a/package.json b/package.json index 5821ec0..2ea87e2 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "publishConfig": { "access": "public" }, - "version": "1.0.2", + "version": "1.0.3", "description": "Get and set object properties with dot notation", "main": "index.js", "scripts": { diff --git a/test/index.js b/test/index.js index 1f5e4f5..f11b7ba 100644 --- a/test/index.js +++ b/test/index.js @@ -1,15 +1,15 @@ var assert = require('assert'); var dot = require('..'); -var tests = module.exports = { - 'test set': function () { +var tests = (module.exports = { + 'test set': function() { var obj = {}; var ret = dot.set(obj, 'cool.aid', 'rocks'); assert(obj.cool.aid === 'rocks'); assert(obj === ret); }, - 'test get': function () { + 'test get': function() { var obj = {}; obj.cool = {}; obj.cool.aid = 'rocks'; @@ -17,17 +17,44 @@ var tests = module.exports = { assert(value === 'rocks'); }, - 'test delete': function () { + 'test delete': function() { var obj = {}; obj.cool = {}; obj.cool.aid = 'rocks'; obj.cool.hello = ['world']; dot.delete(obj, 'cool.aid'); dot.delete(obj, 'cool.hello.0'); - assert(!obj.cool.hasOwnProperty('aid')) + assert(!obj.cool.hasOwnProperty('aid')); assert(obj.cool.hello.length == 0); + }, + + 'test prototype pollution': function() { + var obj = {}; + obj.cool = {}; + obj.cool.aid = 'rocks'; + obj.cool.hello = ['world']; + dot.set(obj, '__proto__', 'test'); + dot.set(obj, '__proto__.toString', 'test'); + dot.set(obj, 'toString', 'test'); + dot.set(obj, 'cool.hello.__proto__', 'test'); + dot.set(obj, 'cool.hello.__proto__.toString', 'test'); + dot.set(obj, 'cool.hello.toString', 'test'); + assert(obj.__proto__ === {}.__proto__); + assert(obj.toString === Object.prototype.toString); + assert(obj.cool.hello.__proto__ === [].__proto__); + assert(obj.cool.hello.toString === Array.prototype.toString); + dot.delete(obj, '__proto__.toString', 'test'); + dot.delete(obj, '__proto__', 'test'); + dot.delete(obj, 'toString', 'test'); + dot.delete(obj, 'cool.hello.__proto__.toString', 'test'); + dot.delete(obj, 'cool.hello.__proto__', 'test'); + dot.delete(obj, 'cool.hello.toString', 'test'); + assert(obj.__proto__ === {}.__proto__); + assert(obj.toString === Object.prototype.toString); + assert(obj.cool.hello.__proto__ === [].__proto__); + assert(obj.cool.hello.toString === Array.prototype.toString); } -} +}); for (var t in tests) { tests[t]();