diff --git a/.eslintrc.js b/.eslintrc.js index c86d680..d7eea0a 100644 --- a/.eslintrc.js +++ b/.eslintrc.js @@ -2,13 +2,15 @@ module.exports = { 'root': true , 'env': { 'browser': true , + 'node': true , 'es6': true , - 'node': true + 'es2022': true } , 'parserOptions': { - 'ecmaVersion': 2020 + 'ecmaVersion': 2022 } , 'extends': [ 'eslint:recommended' ] , + 'ignorePatterns': [ "*.min.js" ] , 'rules': { /* @@ -81,7 +83,7 @@ module.exports = { 'words': true , 'nonwords': true , 'overrides': { - '-': false , + //'-': false , } } ] , 'space-in-parens': [ 'error' , 'always' , { diff --git a/.npmignore b/.npmignore new file mode 100644 index 0000000..74bee0a --- /dev/null +++ b/.npmignore @@ -0,0 +1,57 @@ +# +++ .gitignore +# Specific # +############ + +*.local.* +*.local +*.log +*.html.gz +*.css.gz +*.js.gz +.spellcast +build +_build +_templates +_static + + +# gitignore / Node.gitignore # +############################## +lib-cov +lcov.info +*.seed +*.log +*.csv +*.dat +*.out +*.pid +*.gz + +pids +logs +results +build +.grunt +package-lock.json + +node_modules + + +# OS generated files # +###################### +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +Icon? +ehthumbs.db +Thumbs.db + + + +# --- .gitignore +test +log +sample +wfm.json diff --git a/CHANGELOG b/CHANGELOG index 0583923..8b517a0 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,4 +1,10 @@ +v0.7.5 +------ + +Fix prototype pollution in .extend() when the 'unflat' option is set + + v0.7.4 ------ diff --git a/browser/tree-kit.js b/browser/tree-kit.js index dfe3ecc..55dbaec 100644 --- a/browser/tree-kit.js +++ b/browser/tree-kit.js @@ -205,7 +205,7 @@ function toPathArray( path ) { if ( ! path ) { return EMPTY_PATH ; } if ( typeof path === 'string' ) { - return path[ path.length - 1 ] === '.' ? path.slice( 0 , -1 ).split( '.' ) : path.split( '.' ) ; + return path[ path.length - 1 ] === '.' ? path.slice( 0 , - 1 ).split( '.' ) : path.split( '.' ) ; } throw new TypeError( '[tree.dotPath]: the path argument should be a string or an array' ) ; @@ -332,7 +332,7 @@ dotPath.dec = ( object , path , value ) => { var pointer = pave( object , pathArray ) ; if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } - else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = -1 ; } + else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } return value ; } ; @@ -393,7 +393,7 @@ dotPath.delete = ( object , path ) => { if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } - var pointer = walk( object , pathArray , -1 ) ; + var pointer = walk( object , pathArray , - 1 ) ; if ( ! pointer || typeof pointer !== 'object' ) { return false ; } @@ -620,10 +620,7 @@ module.exports = extend ; function extendOne( runtime , options , target , source , mask ) { - var j , jmax , path , - sourceKeys , sourceKey , sourceValue , sourceValueIsObject , sourceValueProto , sourceDescriptor , - targetKey , targetPointer , targetValue , targetValueIsObject , - indexOfSource = -1 ; + var sourceKeys , sourceKey ; // Max depth check if ( options.maxDepth && runtime.depth > options.maxDepth ) { @@ -636,158 +633,193 @@ function extendOne( runtime , options , target , source , mask ) { runtime.references.targets.push( target ) ; } - if ( options.own ) { + // 'unflat' mode computing + if ( options.unflat && runtime.depth === 0 ) { + for ( sourceKey in source ) { + runtime.unflatKeys = sourceKey.split( options.unflat ) ; + runtime.unflatIndex = 0 ; + runtime.unflatFullKey = sourceKey ; + extendOneKV( runtime , options , target , source , runtime.unflatKeys[ runtime.unflatIndex ] , mask ) ; + } + + delete runtime.unflatKeys ; + delete runtime.unflatIndex ; + delete runtime.unflatFullKey ; + } + else if ( options.own ) { if ( options.nonEnum ) { sourceKeys = Object.getOwnPropertyNames( source ) ; } else { sourceKeys = Object.keys( source ) ; } + + for ( sourceKey of sourceKeys ) { + extendOneKV( runtime , options , target , source , sourceKey , mask ) ; + } } - else { sourceKeys = source ; } + else { + for ( sourceKey in source ) { + extendOneKV( runtime , options , target , source , sourceKey , mask ) ; + } + } +} - for ( sourceKey in sourceKeys ) { - if ( options.own ) { sourceKey = sourceKeys[ sourceKey ] ; } - // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything >< - if ( sourceKey === '__proto__' ) { continue ; } - // If descriptor is on, get it now - if ( options.descriptor ) { - sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ; - sourceValue = sourceDescriptor.value ; +function extendOneKV( runtime , options , target , source , sourceKey , mask ) { + // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything >< + if ( sourceKey === '__proto__' ) { return ; } + + let sourceValue , sourceDescriptor , sourceValueProto ; + + if ( runtime.unflatKeys ) { + if ( runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { + sourceValue = {} ; } else { - // We have to trigger an eventual getter only once - sourceValue = source[ sourceKey ] ; + sourceValue = source[ runtime.unflatFullKey ] ; } + } + else if ( options.descriptor ) { + // If descriptor is on, get it now + sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ; + sourceValue = sourceDescriptor.value ; + } + else { + // We have to trigger an eventual getter only once + sourceValue = source[ sourceKey ] ; + } - targetPointer = target ; - targetKey = runtime.prefix + sourceKey ; - - // Do not copy if property is a function and we don't want them - if ( options.nofunc && typeof sourceValue === 'function' ) { continue ; } - - // 'unflat' mode computing - if ( options.unflat && runtime.depth === 0 ) { - path = sourceKey.split( options.unflat ) ; - jmax = path.length - 1 ; + let targetKey = runtime.prefix + sourceKey ; + + // Do not copy if property is a function and we don't want them + if ( options.nofunc && typeof sourceValue === 'function' ) { return ; } + + // Again, trigger an eventual getter only once + let targetValue = target[ targetKey ] ; + let targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ; + let sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ; + + if ( + ( options.deep || runtime.unflatKeys ) + && sourceValue + && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) ) + && ( ! options.descriptor || ! sourceDescriptor.get ) + // not a condition we just cache sourceValueProto now... ok it's trashy >< + && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true ) + && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) ) + && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) ) + && ( ! options.preserve || targetValueIsObject ) + && ( ! mask || targetValueIsObject ) + ) { + let indexOfSource = options.circular ? runtime.references.sources.indexOf( sourceValue ) : - 1 ; + + if ( options.flat ) { + // No circular references reconnection when in 'flat' mode + if ( indexOfSource >= 0 ) { return ; } + + extendOne( + { + depth: runtime.depth + 1 , + prefix: runtime.prefix + sourceKey + options.flat , + references: runtime.references + } , + options , target , sourceValue , mask + ) ; + } + else { + if ( indexOfSource >= 0 ) { + // Circular references reconnection... + targetValue = runtime.references.targets[ indexOfSource ] ; + + if ( options.descriptor ) { + Object.defineProperty( target , targetKey , { + value: targetValue , + enumerable: sourceDescriptor.enumerable , + writable: sourceDescriptor.writable , + configurable: sourceDescriptor.configurable + } ) ; + } + else { + target[ targetKey ] = targetValue ; + } - if ( jmax ) { - for ( j = 0 ; j < jmax ; j ++ ) { - if ( ! targetPointer[ path[ j ] ] || - ( typeof targetPointer[ path[ j ] ] !== 'object' && - typeof targetPointer[ path[ j ] ] !== 'function' ) ) { - targetPointer[ path[ j ] ] = {} ; - } + return ; + } - targetPointer = targetPointer[ path[ j ] ] ; + if ( ! targetValueIsObject || ! Object.prototype.hasOwnProperty.call( target , targetKey ) ) { + if ( Array.isArray( sourceValue ) ) { targetValue = [] ; } + else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; } + else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; } + else { targetValue = {} ; } + + if ( options.descriptor ) { + Object.defineProperty( target , targetKey , { + value: targetValue , + enumerable: sourceDescriptor.enumerable , + writable: sourceDescriptor.writable , + configurable: sourceDescriptor.configurable + } ) ; + } + else { + target[ targetKey ] = targetValue ; } - - targetKey = runtime.prefix + path[ jmax ] ; } - } + else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) { + Object.setPrototypeOf( targetValue , sourceValueProto ) ; + } + else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) { + Object.setPrototypeOf( targetValue , sourceValue ) ; + } - // Again, trigger an eventual getter only once - targetValue = targetPointer[ targetKey ] ; - targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ; - sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ; - - - if ( options.deep // eslint-disable-line no-constant-condition - && sourceValue - && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) ) - && ( ! options.descriptor || ! sourceDescriptor.get ) - // not a condition we just cache sourceValueProto now... ok it's trashy >< - && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true ) - && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) ) - && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) ) - && ( ! options.preserve || targetValueIsObject ) - && ( ! mask || targetValueIsObject ) - ) { if ( options.circular ) { - indexOfSource = runtime.references.sources.indexOf( sourceValue ) ; + runtime.references.sources.push( sourceValue ) ; + runtime.references.targets.push( targetValue ) ; } - if ( options.flat ) { - // No circular references reconnection when in 'flat' mode - if ( indexOfSource >= 0 ) { continue ; } - - extendOne( - { depth: runtime.depth + 1 , prefix: runtime.prefix + sourceKey + options.flat , references: runtime.references } , - options , targetPointer , sourceValue , mask + if ( runtime.unflatKeys && runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { + // Finish unflatting this property + let nextSourceKey = runtime.unflatKeys[ runtime.unflatIndex + 1 ] ; + + extendOneKV( + { + depth: runtime.depth , + unflatKeys: runtime.unflatKeys , + unflatIndex: runtime.unflatIndex + 1 , + unflatFullKey: runtime.unflatFullKey , + unflatValue: runtime.unflatValue , + prefix: '' , + references: runtime.references + } , + options , targetValue , source , nextSourceKey , mask ) ; } else { - if ( indexOfSource >= 0 ) { - // Circular references reconnection... - targetValue = runtime.references.targets[ indexOfSource ] ; - - if ( options.descriptor ) { - Object.defineProperty( targetPointer , targetKey , { - value: targetValue , - enumerable: sourceDescriptor.enumerable , - writable: sourceDescriptor.writable , - configurable: sourceDescriptor.configurable - } ) ; - } - else { - targetPointer[ targetKey ] = targetValue ; - } - - continue ; - } - - if ( ! targetValueIsObject || ! Object.prototype.hasOwnProperty.call( targetPointer , targetKey ) ) { - if ( Array.isArray( sourceValue ) ) { targetValue = [] ; } - else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; } // jshint ignore:line - else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; } - else { targetValue = {} ; } - - if ( options.descriptor ) { - Object.defineProperty( targetPointer , targetKey , { - value: targetValue , - enumerable: sourceDescriptor.enumerable , - writable: sourceDescriptor.writable , - configurable: sourceDescriptor.configurable - } ) ; - } - else { - targetPointer[ targetKey ] = targetValue ; - } - } - else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) { - Object.setPrototypeOf( targetValue , sourceValueProto ) ; - } - else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) { - Object.setPrototypeOf( targetValue , sourceValue ) ; - } - - if ( options.circular ) { - runtime.references.sources.push( sourceValue ) ; - runtime.references.targets.push( targetValue ) ; - } - // Recursively extends sub-object extendOne( - { depth: runtime.depth + 1 , prefix: '' , references: runtime.references } , + { + depth: runtime.depth + 1 , + prefix: '' , + references: runtime.references + } , options , targetValue , sourceValue , mask ) ; } } - else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) { - // Do not create new value, and so do not delete source's properties that were not moved. - // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy) - continue ; - } - else if ( options.preserve && targetValue !== undefined ) { - // Do not overwrite, and so do not delete source's properties that were not moved - continue ; - } - else if ( ! options.inherit ) { - if ( options.descriptor ) { Object.defineProperty( targetPointer , targetKey , sourceDescriptor ) ; } - else { targetPointer[ targetKey ] = targetValue = sourceValue ; } - } - - // Delete owned property of the source object - if ( options.move ) { delete source[ sourceKey ] ; } } + else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) { + // Do not create new value, and so do not delete source's properties that were not moved. + // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy) + return ; + } + else if ( options.preserve && targetValue !== undefined ) { + // Do not overwrite, and so do not delete source's properties that were not moved + return ; + } + else if ( ! options.inherit ) { + if ( options.descriptor ) { Object.defineProperty( target , targetKey , sourceDescriptor ) ; } + else { target[ targetKey ] = targetValue = sourceValue ; } + } + + // Delete owned property of the source object + if ( options.move ) { delete source[ sourceKey ] ; } } @@ -1009,7 +1041,7 @@ treePath.op = function( type , object , path , value ) { return pointer[ key ] ; case 'dec' : if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } - else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = -1 ; } + else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } return pointer[ key ] ; case 'append' : if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } diff --git a/browser/tree-kit.min.js b/browser/tree-kit.min.js index 2646a2e..30f31f4 100644 --- a/browser/tree-kit.min.js +++ b/browser/tree-kit.min.js @@ -1 +1 @@ -(function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.treeKit=e()}})(function(){var e,t,r;return function(){function e(t,r,n){function i(f,u){if(!r[f]){if(!t[f]){var a="function"==typeof require&&require;if(!u&&a)return a(f,!0);if(o)return o(f,!0);var s=new Error("Cannot find module '"+f+"'");throw s.code="MODULE_NOT_FOUND",s}var c=r[f]={exports:{}};t[f][0].call(c.exports,function(e){var r=t[f][1][e];return i(r||e)},c,c.exports,e,t,r,n)}return r[f].exports}for(var o="function"==typeof require&&require,f=0;fnew Date(e))},{}],3:[function(e,t,r){"use strict";const n={};t.exports=n;const i=[];const o="This would cause prototype pollution";function f(e){if(Array.isArray(e)){return e}if(!e){return i}if(typeof e==="string"){return e[e.length-1]==="."?e.slice(0,-1).split("."):e.split(".")}throw new TypeError("[tree.dotPath]: the path argument should be a string or an array")}function u(e,t,r=0){var n,i,f,u=e;for(n=0,i=t.length+r;nu(e,f(t)));n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);u[i]=r;return r});n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(!(i in u)){u[i]=r}return r});n.inc=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(typeof u[i]==="number"){u[i]++}else if(!u[i]||typeof u[i]!=="object"){u[i]=1}return r});n.dec=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(typeof u[i]==="number"){u[i]--}else if(!u[i]||typeof u[i]!=="object"){u[i]=-1}return r});n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(!u[i]){u[i]=r}else if(Array.isArray(u[i])&&Array.isArray(r)){u[i]=u[i].concat(r)}return r});n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(!u[i]){u[i]=r}else if(Array.isArray(u[i])&&Array.isArray(r)){u[i]=r.concat(u[i])}return r});n.delete=((e,t)=>{var r=f(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(o)}var i=u(e,r,-1);if(!i||typeof i!=="object"){return false}return delete i[n]});n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(u[i]===undefined){u[i]=r}else if(Array.isArray(u[i])){u[i].push(r)}else{u[i]=[u[i],r]}return u[i]});n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(!u[i]){u[i]=[r]}else if(Array.isArray(u[i])){u[i].push(r)}return u[i]});n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=f(t),i=n[n.length-1];if(typeof i==="object"||i==="__proto__"){throw new Error(o)}var u=a(e,n);if(!u[i]){u[i]=[r]}else if(Array.isArray(u[i])){u[i].unshift(r)}return u[i]})},{}],4:[function(e,t,r){"use strict";function n(e,t,...r){var n,o,f=false,u=r.length;if(!u){return t}if(!e||typeof e!=="object"){e={}}var a={depth:0,prefix:""};if(e.deep){if(Array.isArray(e.deep)){e.deep=new Set(e.deep)}else if(!(e.deep instanceof Set)){e.deep=true}}if(e.immutables){if(Array.isArray(e.immutables)){e.immutables=new Set(e.immutables)}else if(!(e.immutables instanceof Set)){delete e.immutables}}if(!e.maxDepth&&e.deep&&!e.circular){e.maxDepth=100}if(e.deepFunc){e.deep=true}if(e.flat){e.deep=true;e.proto=false;e.inherit=false;e.unflat=false;if(typeof e.flat!=="string"){e.flat="."}}if(e.unflat){e.deep=false;e.proto=false;e.inherit=false;e.flat=false;if(typeof e.unflat!=="string"){e.unflat="."}}if(e.inherit){e.own=true;e.proto=false}else if(e.proto){e.own=true}if(!t||typeof t!=="object"&&typeof t!=="function"){f=true}if(!e.skipRoot&&(e.inherit||e.proto)){for(n=u-1;n>=0;n--){o=r[n];if(o&&(typeof o==="object"||typeof o==="function")){if(e.inherit){if(f){t=Object.create(o)}else{Object.setPrototypeOf(t,o)}}else if(e.proto){if(f){t=Object.create(Object.getPrototypeOf(o))}else{Object.setPrototypeOf(t,Object.getPrototypeOf(o))}}break}}}else if(f){t={}}a.references={sources:[],targets:[]};for(n=0;nt.maxDepth){throw new Error("[tree] extend(): max depth reached("+t.maxDepth+")")}if(t.circular){e.references.sources.push(n);e.references.targets.push(r)}if(t.own){if(t.nonEnum){s=Object.getOwnPropertyNames(n)}else{s=Object.keys(n)}}else{s=n}for(c in s){if(t.own){c=s[c]}if(c==="__proto__"){continue}if(t.descriptor){y=Object.getOwnPropertyDescriptor(n,c);p=y.value}else{p=n[c]}h=r;b=e.prefix+c;if(t.nofunc&&typeof p==="function"){continue}if(t.unflat&&e.depth===0){a=c.split(t.unflat);u=a.length-1;if(u){for(f=0;f=0){continue}i({depth:e.depth+1,prefix:e.prefix+c+t.flat,references:e.references},t,h,p,o)}else{if(w>=0){j=e.references.targets[w];if(t.descriptor){Object.defineProperty(h,b,{value:j,enumerable:y.enumerable,writable:y.writable,configurable:y.configurable})}else{h[b]=j}continue}if(!g||!Object.prototype.hasOwnProperty.call(h,b)){if(Array.isArray(p)){j=[]}else if(t.proto){j=Object.create(l)}else if(t.inherit){j=Object.create(p)}else{j={}}if(t.descriptor){Object.defineProperty(h,b,{value:j,enumerable:y.enumerable,writable:y.writable,configurable:y.configurable})}else{h[b]=j}}else if(t.proto&&Object.getPrototypeOf(j)!==l){Object.setPrototypeOf(j,l)}else if(t.inherit&&Object.getPrototypeOf(j)!==p){Object.setPrototypeOf(j,p)}if(t.circular){e.references.sources.push(p);e.references.targets.push(j)}i({depth:e.depth+1,prefix:"",references:e.references},t,j,p,o)}}else if(o&&(j===undefined||g||d)){continue}else if(t.preserve&&j!==undefined){continue}else if(!t.inherit){if(t.descriptor){Object.defineProperty(h,b,y)}else{h[b]=j=p}}if(t.move){delete n[c]}}}},{}],5:[function(e,t,r){"use strict";const n={};t.exports=n;const i="This would cause prototype pollution";n.op=function(e,t,r,n){var o,f,u,a,s,c=false,p=false,d,l=true;if(!t||typeof t!=="object"){return}if(typeof r==="string"){if(r){f=r.match(/([.#[\]]|[^.#[\]]+)/g)}else{f=[""]}if(f[0]==="."){f.unshift("")}if(f[f.length-1]==="."){f.push("")}}else if(Array.isArray(r)){f=r;p=true}else{throw new TypeError("[tree.path] ."+e+"(): the path argument should be a string or an array")}switch(e){case"get":case"delete":d=false;break;case"set":case"define":case"inc":case"dec":case"append":case"prepend":case"concat":case"insert":case"autoPush":d=true;break;default:throw new TypeError("[tree.path] .op(): wrong type of operation '"+e+"'")}a=t;u=f.length-1;for(o=0;o<=u;o++){if(p){if(s===undefined){s=f[o];if(typeof s==="object"||s==="__proto__"){throw new Error(i)}continue}if(typeof a[s]==="function"){throw new Error(i)}if(!a[s]||typeof a[s]!=="object"){if(!d){return undefined}a[s]={}}a=a[s];s=f[o];if(typeof s==="object"||s==="__proto__"){throw new Error(i)}continue}else if(f[o]==="."){c=false;if(s===undefined){if(!l){l=true;continue}s=""}if(typeof a[s]==="function"){throw new Error(i)}if(!a[s]||typeof a[s]!=="object"){if(!d){return undefined}a[s]={}}a=a[s];l=true;continue}else if(f[o]==="#"||f[o]==="["){c=true;l=false;if(s===undefined){if(!Array.isArray(a)){return undefined}continue}if(typeof a[s]==="function"){throw new Error(i)}if(!a[s]||!Array.isArray(a[s])){if(!d){return undefined}a[s]=[]}a=a[s];continue}else if(f[o]==="]"){l=false;continue}l=false;if(!c){s=f[o];if(typeof s==="object"||s==="__proto__"){throw new Error(i)}continue}switch(f[o]){case"length":s="length";break;case"first":s=0;break;case"last":s=a.length-1;if(s<0){s=0}break;case"next":if(!d){return undefined}s=a.length;break;case"insert":if(!d){return undefined}a.unshift(undefined);s=0;break;default:s=parseInt(f[o],10)}}switch(e){case"get":return a[s];case"delete":if(c&&typeof s==="number"){a.splice(s,1)}else{delete a[s]}return;case"set":a[s]=n;return a[s];case"define":if(!(s in a)){a[s]=n}return a[s];case"inc":if(typeof a[s]==="number"){a[s]++}else if(!a[s]||typeof a[s]!=="object"){a[s]=1}return a[s];case"dec":if(typeof a[s]==="number"){a[s]--}else if(!a[s]||typeof a[s]!=="object"){a[s]=-1}return a[s];case"append":if(!a[s]){a[s]=[n]}else if(Array.isArray(a[s])){a[s].push(n)}return a[s];case"prepend":if(!a[s]){a[s]=[n]}else if(Array.isArray(a[s])){a[s].unshift(n)}return a[s];case"concat":if(!a[s]){a[s]=n}else if(Array.isArray(a[s])&&Array.isArray(n)){a[s]=a[s].concat(n)}return a[s];case"insert":if(!a[s]){a[s]=n}else if(Array.isArray(a[s])&&Array.isArray(n)){a[s]=n.concat(a[s])}return a[s];case"autoPush":if(a[s]===undefined){a[s]=n}else if(Array.isArray(a[s])){a[s].push(n)}else{a[s]=[a[s],n]}return a[s]}};n.get=n.op.bind(undefined,"get");n.delete=n.op.bind(undefined,"delete");n.set=n.op.bind(undefined,"set");n.define=n.op.bind(undefined,"define");n.inc=n.op.bind(undefined,"inc");n.dec=n.op.bind(undefined,"dec");n.append=n.op.bind(undefined,"append");n.prepend=n.op.bind(undefined,"prepend");n.concat=n.op.bind(undefined,"concat");n.insert=n.op.bind(undefined,"insert");n.autoPush=n.op.bind(undefined,"autoPush");n.prototype={get:function(e){return n.get(this,e)},delete:function(e){return n.delete(this,e)},set:function(e,t){return n.set(this,e,t)},define:function(e,t){return n.define(this,e,t)},inc:function(e,t){return n.inc(this,e,t)},dec:function(e,t){return n.dec(this,e,t)},append:function(e,t){return n.append(this,e,t)},prepend:function(e,t){return n.prepend(this,e,t)},concat:function(e,t){return n.concat(this,e,t)},insert:function(e,t){return n.insert(this,e,t)},autoPush:function(e,t){return n.autoPush(this,e,t)}};n.upgrade=function(e){Object.defineProperties(e,{get:{value:n.op.bind(undefined,"get",e)},delete:{value:n.op.bind(undefined,"delete",e)},set:{value:n.op.bind(undefined,"set",e)},define:{value:n.op.bind(undefined,"define",e)},inc:{value:n.op.bind(undefined,"inc",e)},dec:{value:n.op.bind(undefined,"dec",e)},append:{value:n.op.bind(undefined,"append",e)},prepend:{value:n.op.bind(undefined,"prepend",e)},concat:{value:n.op.bind(undefined,"concat",e)},insert:{value:n.op.bind(undefined,"insert",e)},autoPush:{value:n.op.bind(undefined,"autoPush",e)}})}},{}]},{},[1])(1)}); \ No newline at end of file +(function(e){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=e()}else if(typeof define==="function"&&define.amd){define([],e)}else{var t;if(typeof window!=="undefined"){t=window}else if(typeof global!=="undefined"){t=global}else if(typeof self!=="undefined"){t=self}else{t=this}t.treeKit=e()}})(function(){var e,t,r;return function(){function e(t,r,n){function f(o,u){if(!r[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var s=new Error("Cannot find module '"+o+"'");throw s.code="MODULE_NOT_FOUND",s}var p=r[o]={exports:{}};t[o][0].call(p.exports,function(e){var r=t[o][1][e];return f(r||e)},p,p.exports,e,t,r,n)}return r[o].exports}for(var i="function"==typeof require&&require,o=0;onew Date(e))},{}],3:[function(e,t,r){"use strict";const n={};t.exports=n;const f=[];const i="This would cause prototype pollution";function o(e){if(Array.isArray(e)){return e}if(!e){return f}if(typeof e==="string"){return e[e.length-1]==="."?e.slice(0,-1).split("."):e.split(".")}throw new TypeError("[tree.dotPath]: the path argument should be a string or an array")}function u(e,t,r=0){var n,f,o,u=e;for(n=0,f=t.length+r;nu(e,o(t)));n.set=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);u[f]=r;return r});n.define=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(!(f in u)){u[f]=r}return r});n.inc=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(typeof u[f]==="number"){u[f]++}else if(!u[f]||typeof u[f]!=="object"){u[f]=1}return r});n.dec=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(typeof u[f]==="number"){u[f]--}else if(!u[f]||typeof u[f]!=="object"){u[f]=-1}return r});n.concat=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(!u[f]){u[f]=r}else if(Array.isArray(u[f])&&Array.isArray(r)){u[f]=u[f].concat(r)}return r});n.insert=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(!u[f]){u[f]=r}else if(Array.isArray(u[f])&&Array.isArray(r)){u[f]=r.concat(u[f])}return r});n.delete=((e,t)=>{var r=o(t),n=r[r.length-1];if(typeof n==="object"||n==="__proto__"){throw new Error(i)}var f=u(e,r,-1);if(!f||typeof f!=="object"){return false}return delete f[n]});n.autoPush=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(u[f]===undefined){u[f]=r}else if(Array.isArray(u[f])){u[f].push(r)}else{u[f]=[u[f],r]}return u[f]});n.append=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(!u[f]){u[f]=[r]}else if(Array.isArray(u[f])){u[f].push(r)}return u[f]});n.prepend=((e,t,r)=>{if(!e||typeof e!=="object"){return undefined}var n=o(t),f=n[n.length-1];if(typeof f==="object"||f==="__proto__"){throw new Error(i)}var u=a(e,n);if(!u[f]){u[f]=[r]}else if(Array.isArray(u[f])){u[f].unshift(r)}return u[f]})},{}],4:[function(e,t,r){"use strict";function n(e,t,...r){var n,i,o=false,u=r.length;if(!u){return t}if(!e||typeof e!=="object"){e={}}var a={depth:0,prefix:""};if(e.deep){if(Array.isArray(e.deep)){e.deep=new Set(e.deep)}else if(!(e.deep instanceof Set)){e.deep=true}}if(e.immutables){if(Array.isArray(e.immutables)){e.immutables=new Set(e.immutables)}else if(!(e.immutables instanceof Set)){delete e.immutables}}if(!e.maxDepth&&e.deep&&!e.circular){e.maxDepth=100}if(e.deepFunc){e.deep=true}if(e.flat){e.deep=true;e.proto=false;e.inherit=false;e.unflat=false;if(typeof e.flat!=="string"){e.flat="."}}if(e.unflat){e.deep=false;e.proto=false;e.inherit=false;e.flat=false;if(typeof e.unflat!=="string"){e.unflat="."}}if(e.inherit){e.own=true;e.proto=false}else if(e.proto){e.own=true}if(!t||typeof t!=="object"&&typeof t!=="function"){o=true}if(!e.skipRoot&&(e.inherit||e.proto)){for(n=u-1;n>=0;n--){i=r[n];if(i&&(typeof i==="object"||typeof i==="function")){if(e.inherit){if(o){t=Object.create(i)}else{Object.setPrototypeOf(t,i)}}else if(e.proto){if(o){t=Object.create(Object.getPrototypeOf(i))}else{Object.setPrototypeOf(t,Object.getPrototypeOf(i))}}break}}}else if(o){t={}}a.references={sources:[],targets:[]};for(n=0;nt.maxDepth){throw new Error("[tree] extend(): max depth reached("+t.maxDepth+")")}if(t.circular){e.references.sources.push(n);e.references.targets.push(r)}if(t.unflat&&e.depth===0){for(u in n){e.unflatKeys=u.split(t.unflat);e.unflatIndex=0;e.unflatFullKey=u;i(e,t,r,n,e.unflatKeys[e.unflatIndex],f)}delete e.unflatKeys;delete e.unflatIndex;delete e.unflatFullKey}else if(t.own){if(t.nonEnum){o=Object.getOwnPropertyNames(n)}else{o=Object.keys(n)}for(u of o){i(e,t,r,n,u,f)}}else{for(u in n){i(e,t,r,n,u,f)}}}function i(e,t,r,n,o,u){if(o==="__proto__"){return}let a,s,p;if(e.unflatKeys){if(e.unflatIndex=0){return}f({depth:e.depth+1,prefix:e.prefix+o+t.flat,references:e.references},t,r,a,u)}else{if(y>=0){l=e.references.targets[y];if(t.descriptor){Object.defineProperty(r,c,{value:l,enumerable:s.enumerable,writable:s.writable,configurable:s.configurable})}else{r[c]=l}return}if(!d||!Object.prototype.hasOwnProperty.call(r,c)){if(Array.isArray(a)){l=[]}else if(t.proto){l=Object.create(p)}else if(t.inherit){l=Object.create(a)}else{l={}}if(t.descriptor){Object.defineProperty(r,c,{value:l,enumerable:s.enumerable,writable:s.writable,configurable:s.configurable})}else{r[c]=l}}else if(t.proto&&Object.getPrototypeOf(l)!==p){Object.setPrototypeOf(l,p)}else if(t.inherit&&Object.getPrototypeOf(l)!==a){Object.setPrototypeOf(l,a)}if(t.circular){e.references.sources.push(a);e.references.targets.push(l)}if(e.unflatKeys&&e.unflatIndex { var pointer = pave( object , pathArray ) ; if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } - else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = -1 ; } + else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } return value ; } ; @@ -239,7 +239,7 @@ dotPath.delete = ( object , path ) => { if ( typeof key === 'object' || key === '__proto__' ) { throw new Error( PROTO_POLLUTION_MESSAGE ) ; } - var pointer = walk( object , pathArray , -1 ) ; + var pointer = walk( object , pathArray , - 1 ) ; if ( ! pointer || typeof pointer !== 'object' ) { return false ; } diff --git a/lib/extend.js b/lib/extend.js index 52c5151..f196c3e 100644 --- a/lib/extend.js +++ b/lib/extend.js @@ -150,10 +150,7 @@ module.exports = extend ; function extendOne( runtime , options , target , source , mask ) { - var j , jmax , path , - sourceKeys , sourceKey , sourceValue , sourceValueIsObject , sourceValueProto , sourceDescriptor , - targetKey , targetPointer , targetValue , targetValueIsObject , - indexOfSource = -1 ; + var sourceKeys , sourceKey ; // Max depth check if ( options.maxDepth && runtime.depth > options.maxDepth ) { @@ -166,157 +163,192 @@ function extendOne( runtime , options , target , source , mask ) { runtime.references.targets.push( target ) ; } - if ( options.own ) { + // 'unflat' mode computing + if ( options.unflat && runtime.depth === 0 ) { + for ( sourceKey in source ) { + runtime.unflatKeys = sourceKey.split( options.unflat ) ; + runtime.unflatIndex = 0 ; + runtime.unflatFullKey = sourceKey ; + extendOneKV( runtime , options , target , source , runtime.unflatKeys[ runtime.unflatIndex ] , mask ) ; + } + + delete runtime.unflatKeys ; + delete runtime.unflatIndex ; + delete runtime.unflatFullKey ; + } + else if ( options.own ) { if ( options.nonEnum ) { sourceKeys = Object.getOwnPropertyNames( source ) ; } else { sourceKeys = Object.keys( source ) ; } + + for ( sourceKey of sourceKeys ) { + extendOneKV( runtime , options , target , source , sourceKey , mask ) ; + } + } + else { + for ( sourceKey in source ) { + extendOneKV( runtime , options , target , source , sourceKey , mask ) ; + } } - else { sourceKeys = source ; } +} - for ( sourceKey in sourceKeys ) { - if ( options.own ) { sourceKey = sourceKeys[ sourceKey ] ; } - // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything >< - if ( sourceKey === '__proto__' ) { continue ; } - // If descriptor is on, get it now - if ( options.descriptor ) { - sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ; - sourceValue = sourceDescriptor.value ; +function extendOneKV( runtime , options , target , source , sourceKey , mask ) { + // OMG, this DEPRECATED __proto__ shit is still alive and can be used to hack anything >< + if ( sourceKey === '__proto__' ) { return ; } + + let sourceValue , sourceDescriptor , sourceValueProto ; + + if ( runtime.unflatKeys ) { + if ( runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { + sourceValue = {} ; } else { - // We have to trigger an eventual getter only once - sourceValue = source[ sourceKey ] ; + sourceValue = source[ runtime.unflatFullKey ] ; } + } + else if ( options.descriptor ) { + // If descriptor is on, get it now + sourceDescriptor = Object.getOwnPropertyDescriptor( source , sourceKey ) ; + sourceValue = sourceDescriptor.value ; + } + else { + // We have to trigger an eventual getter only once + sourceValue = source[ sourceKey ] ; + } - targetPointer = target ; - targetKey = runtime.prefix + sourceKey ; - - // Do not copy if property is a function and we don't want them - if ( options.nofunc && typeof sourceValue === 'function' ) { continue ; } - - // 'unflat' mode computing - if ( options.unflat && runtime.depth === 0 ) { - path = sourceKey.split( options.unflat ) ; - jmax = path.length - 1 ; + let targetKey = runtime.prefix + sourceKey ; + + // Do not copy if property is a function and we don't want them + if ( options.nofunc && typeof sourceValue === 'function' ) { return ; } + + // Again, trigger an eventual getter only once + let targetValue = target[ targetKey ] ; + let targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ; + let sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ; + + if ( + ( options.deep || runtime.unflatKeys ) + && sourceValue + && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) ) + && ( ! options.descriptor || ! sourceDescriptor.get ) + // not a condition we just cache sourceValueProto now... ok it's trashy >< + && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true ) + && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) ) + && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) ) + && ( ! options.preserve || targetValueIsObject ) + && ( ! mask || targetValueIsObject ) + ) { + let indexOfSource = options.circular ? runtime.references.sources.indexOf( sourceValue ) : - 1 ; + + if ( options.flat ) { + // No circular references reconnection when in 'flat' mode + if ( indexOfSource >= 0 ) { return ; } + + extendOne( + { + depth: runtime.depth + 1 , + prefix: runtime.prefix + sourceKey + options.flat , + references: runtime.references + } , + options , target , sourceValue , mask + ) ; + } + else { + if ( indexOfSource >= 0 ) { + // Circular references reconnection... + targetValue = runtime.references.targets[ indexOfSource ] ; + + if ( options.descriptor ) { + Object.defineProperty( target , targetKey , { + value: targetValue , + enumerable: sourceDescriptor.enumerable , + writable: sourceDescriptor.writable , + configurable: sourceDescriptor.configurable + } ) ; + } + else { + target[ targetKey ] = targetValue ; + } - if ( jmax ) { - for ( j = 0 ; j < jmax ; j ++ ) { - if ( ! targetPointer[ path[ j ] ] || - ( typeof targetPointer[ path[ j ] ] !== 'object' && - typeof targetPointer[ path[ j ] ] !== 'function' ) ) { - targetPointer[ path[ j ] ] = {} ; - } + return ; + } - targetPointer = targetPointer[ path[ j ] ] ; + if ( ! targetValueIsObject || ! Object.prototype.hasOwnProperty.call( target , targetKey ) ) { + if ( Array.isArray( sourceValue ) ) { targetValue = [] ; } + else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; } + else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; } + else { targetValue = {} ; } + + if ( options.descriptor ) { + Object.defineProperty( target , targetKey , { + value: targetValue , + enumerable: sourceDescriptor.enumerable , + writable: sourceDescriptor.writable , + configurable: sourceDescriptor.configurable + } ) ; + } + else { + target[ targetKey ] = targetValue ; } - - targetKey = runtime.prefix + path[ jmax ] ; } - } + else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) { + Object.setPrototypeOf( targetValue , sourceValueProto ) ; + } + else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) { + Object.setPrototypeOf( targetValue , sourceValue ) ; + } - // Again, trigger an eventual getter only once - targetValue = targetPointer[ targetKey ] ; - targetValueIsObject = targetValue && ( typeof targetValue === 'object' || typeof targetValue === 'function' ) ; - sourceValueIsObject = sourceValue && ( typeof sourceValue === 'object' || typeof sourceValue === 'function' ) ; - - - if ( options.deep // eslint-disable-line no-constant-condition - && sourceValue - && ( typeof sourceValue === 'object' || ( options.deepFunc && typeof sourceValue === 'function' ) ) - && ( ! options.descriptor || ! sourceDescriptor.get ) - // not a condition we just cache sourceValueProto now... ok it's trashy >< - && ( ( sourceValueProto = Object.getPrototypeOf( sourceValue ) ) || true ) - && ( ! ( options.deep instanceof Set ) || options.deep.has( sourceValueProto ) ) - && ( ! options.immutables || ! options.immutables.has( sourceValueProto ) ) - && ( ! options.preserve || targetValueIsObject ) - && ( ! mask || targetValueIsObject ) - ) { if ( options.circular ) { - indexOfSource = runtime.references.sources.indexOf( sourceValue ) ; + runtime.references.sources.push( sourceValue ) ; + runtime.references.targets.push( targetValue ) ; } - if ( options.flat ) { - // No circular references reconnection when in 'flat' mode - if ( indexOfSource >= 0 ) { continue ; } - - extendOne( - { depth: runtime.depth + 1 , prefix: runtime.prefix + sourceKey + options.flat , references: runtime.references } , - options , targetPointer , sourceValue , mask + if ( runtime.unflatKeys && runtime.unflatIndex < runtime.unflatKeys.length - 1 ) { + // Finish unflatting this property + let nextSourceKey = runtime.unflatKeys[ runtime.unflatIndex + 1 ] ; + + extendOneKV( + { + depth: runtime.depth , + unflatKeys: runtime.unflatKeys , + unflatIndex: runtime.unflatIndex + 1 , + unflatFullKey: runtime.unflatFullKey , + unflatValue: runtime.unflatValue , + prefix: '' , + references: runtime.references + } , + options , targetValue , source , nextSourceKey , mask ) ; } else { - if ( indexOfSource >= 0 ) { - // Circular references reconnection... - targetValue = runtime.references.targets[ indexOfSource ] ; - - if ( options.descriptor ) { - Object.defineProperty( targetPointer , targetKey , { - value: targetValue , - enumerable: sourceDescriptor.enumerable , - writable: sourceDescriptor.writable , - configurable: sourceDescriptor.configurable - } ) ; - } - else { - targetPointer[ targetKey ] = targetValue ; - } - - continue ; - } - - if ( ! targetValueIsObject || ! Object.prototype.hasOwnProperty.call( targetPointer , targetKey ) ) { - if ( Array.isArray( sourceValue ) ) { targetValue = [] ; } - else if ( options.proto ) { targetValue = Object.create( sourceValueProto ) ; } // jshint ignore:line - else if ( options.inherit ) { targetValue = Object.create( sourceValue ) ; } - else { targetValue = {} ; } - - if ( options.descriptor ) { - Object.defineProperty( targetPointer , targetKey , { - value: targetValue , - enumerable: sourceDescriptor.enumerable , - writable: sourceDescriptor.writable , - configurable: sourceDescriptor.configurable - } ) ; - } - else { - targetPointer[ targetKey ] = targetValue ; - } - } - else if ( options.proto && Object.getPrototypeOf( targetValue ) !== sourceValueProto ) { - Object.setPrototypeOf( targetValue , sourceValueProto ) ; - } - else if ( options.inherit && Object.getPrototypeOf( targetValue ) !== sourceValue ) { - Object.setPrototypeOf( targetValue , sourceValue ) ; - } - - if ( options.circular ) { - runtime.references.sources.push( sourceValue ) ; - runtime.references.targets.push( targetValue ) ; - } - // Recursively extends sub-object extendOne( - { depth: runtime.depth + 1 , prefix: '' , references: runtime.references } , + { + depth: runtime.depth + 1 , + prefix: '' , + references: runtime.references + } , options , targetValue , sourceValue , mask ) ; } } - else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) { - // Do not create new value, and so do not delete source's properties that were not moved. - // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy) - continue ; - } - else if ( options.preserve && targetValue !== undefined ) { - // Do not overwrite, and so do not delete source's properties that were not moved - continue ; - } - else if ( ! options.inherit ) { - if ( options.descriptor ) { Object.defineProperty( targetPointer , targetKey , sourceDescriptor ) ; } - else { targetPointer[ targetKey ] = targetValue = sourceValue ; } - } - - // Delete owned property of the source object - if ( options.move ) { delete source[ sourceKey ] ; } } + else if ( mask && ( targetValue === undefined || targetValueIsObject || sourceValueIsObject ) ) { + // Do not create new value, and so do not delete source's properties that were not moved. + // We also do not overwrite object with non-object, and we don't overwrite non-object with object (preserve hierarchy) + return ; + } + else if ( options.preserve && targetValue !== undefined ) { + // Do not overwrite, and so do not delete source's properties that were not moved + return ; + } + else if ( ! options.inherit ) { + if ( options.descriptor ) { Object.defineProperty( target , targetKey , sourceDescriptor ) ; } + else { target[ targetKey ] = targetValue = sourceValue ; } + } + + // Delete owned property of the source object + if ( options.move ) { delete source[ sourceKey ] ; } } diff --git a/lib/path.js b/lib/path.js index ad1591d..5d2717b 100644 --- a/lib/path.js +++ b/lib/path.js @@ -215,7 +215,7 @@ treePath.op = function( type , object , path , value ) { return pointer[ key ] ; case 'dec' : if ( typeof pointer[ key ] === 'number' ) { pointer[ key ] -- ; } - else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = -1 ; } + else if ( ! pointer[ key ] || typeof pointer[ key ] !== 'object' ) { pointer[ key ] = - 1 ; } return pointer[ key ] ; case 'append' : if ( ! pointer[ key ] ) { pointer[ key ] = [ value ] ; } diff --git a/package.json b/package.json index d9d958b..4d4f638 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "tree-kit", - "version": "0.7.4", + "version": "0.7.5", "description": "Tree utilities which provides a full-featured extend and object-cloning facility, and various tools to deal with nested object structures.", "main": "lib/tree.js", "directories": { diff --git a/test/dotPath-test.js b/test/dotPath-test.js index 4cabeaf..061b7ba 100644 --- a/test/dotPath-test.js +++ b/test/dotPath-test.js @@ -611,6 +611,7 @@ describe( "Tree's array dot-path on objects" , () => { describe( ".dotPath() security issues" , () => { it( "Prototype pollution using .__proto__" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , '__proto__.hack' , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ; expect( () => path.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; @@ -620,6 +621,7 @@ describe( ".dotPath() security issues" , () => { } ) ; it( "Prototype pollution using a path array: [['__proto__']]" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , [['__proto__'],'hack'] , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ; expect( () => path.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; @@ -629,6 +631,7 @@ describe( ".dotPath() security issues" , () => { } ) ; it( "Prototype pollution using .constructor" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , 'constructor.prototype' , 'hacked' ) ).to.throw() ; expect( () => path.set( {} , 'constructor.prototype.hack' , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ; diff --git a/test/extend-test.js b/test/extend-test.js index 7e1609e..2c22010 100644 --- a/test/extend-test.js +++ b/test/extend-test.js @@ -816,6 +816,40 @@ describe( "extend()" , () => { it( "with 'unflat' option" , () => { var e , o ; + e = {} ; + extend( { unflat: true } , e , { 'subtree.a': 'value' } ) ; + expect( e ).to.equal( { + subtree: { + a: 'value' , + } + } ) ; + extend( { unflat: true } , e , { 'subtree.b': 'value2' } ) ; + expect( e ).to.equal( { + subtree: { + a: 'value' , + b: 'value2' , + } + } ) ; + extend( { unflat: true } , e , { 'subtree.a': 'replaced' } ) ; + expect( e ).to.equal( { + subtree: { + a: 'replaced' , + b: 'value2' , + } + } ) ; + extend( { unflat: true } , e , { 'subtree2.subtree.c': 'value3' } ) ; + expect( e ).to.equal( { + subtree: { + a: 'replaced' , + b: 'value2' , + } , + subtree2: { + subtree: { + c: 'value3' , + } + } + } ) ; + o = { three: 3 , four: '4' , @@ -1012,6 +1046,39 @@ describe( "extend()" , () => { } ) ; it( "with 'skipRoot' option" ) ; + } ) ; + +describe( ".extend() security issues" , () => { + + it( "Prototype pollution using .__proto__" , () => { + delete Object.prototype.hack ; + var o = {} ; + extend( { deep: true } , o , { __proto__: { prototype: { hack: "hacked" } } } ) ; + expect( Object.prototype.hack ).to.be.undefined() ; + } ) ; + + it( "Prototype pollution using .constructor" , () => { + delete Object.prototype.hack ; + var o = {} ; + extend( { deep: true } , o , { constructor: { prototype: { hack: "hacked" } } } ) ; + expect( Object.prototype.hack ).to.be.undefined() ; + } ) ; + + it( "Prototype pollution using 'unflat' option and using .__proto__" , () => { + delete Object.prototype.hack ; + var o = {} ; + extend( { unflat: true } , o , { "__proto__.hack": "hacked" } ) ; + expect( Object.prototype.hack ).to.be.undefined() ; + } ) ; + + it( "Prototype pollution using 'unflat' option and using .constructor" , () => { + delete Object.prototype.hack ; + var o = {} ; + extend( { unflat: true } , o , { "constructor.prototype.hack": "hacked" } ) ; + expect( Object.prototype.hack ).to.be.undefined() ; + } ) ; +} ) ; + diff --git a/test/path-test.js b/test/path-test.js index 0842306..486ebff 100644 --- a/test/path-test.js +++ b/test/path-test.js @@ -940,6 +940,7 @@ describe( "Tree's array path on objects" , function() { describe( ".path() security issues" , () => { it( "Prototype pollution using .__proto__" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , '__proto__.hack' , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ; expect( () => path.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; @@ -949,6 +950,7 @@ describe( ".path() security issues" , () => { } ) ; it( "Prototype pollution using a path array: [['__proto__']]" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , [['__proto__'],'hack'] , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ; expect( () => path.set( {} , '__proto__' , 'hacked' ) ).to.throw() ; @@ -958,6 +960,7 @@ describe( ".path() security issues" , () => { } ) ; it( "Prototype pollution using .constructor" , () => { + delete Object.prototype.hack ; expect( () => path.set( {} , 'constructor.prototype' , 'hacked' ) ).to.throw() ; expect( () => path.set( {} , 'constructor.prototype.hack' , 'hacked' ) ).to.throw() ; expect( Object.prototype.hack ).to.be.undefined() ;