CVE-2022-24279: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: The package madlib-object-utils before 0.1.8 are vulnerable to Prototype Pollution via the setValue method, as it allows an attacker to merge object prototypes into it. *Note:* This vulnerability derives from an incomplete fix of [CVE-2020-7701](https://security.snyk.io/vuln/SNYK-JS-MADLIBOBJECTUTILS-598676)
CWE description: The product receives input from an upstream component that specifies attributes that are to be initialized or updated in an object, but it does not properly control modifications of attributes of the object prototype.
CVSS v3 base score: 7.5
Published: 2022-04-15T20:15Z
Repository: https://github.com/Qwerios/madlib-object-utils
Language: Other
Files changed: .editorconfig, .gitignore, .jshintrc, .travis.yml, GruntFile.coffee, LICENSE, README.md, lib/utils.js, package.json, src/utils.coffee, test/dotted-key.coffee, test/manipulate.coffee, test/prototype-pollution.coffee
Commit date: 2022-03-07 10:27:07 +0100
Commit message: fix(set-value): prototype pollution

Diff: diff --git a/.editorconfig b/.editorconfig
new file mode 100644
index 0000000..c308ed0
--- /dev/null
+++ b/.editorconfig
@@ -0,0 +1,13 @@
+# http://editorconfig.org
+root = true
+
+[*]
+indent_style = space
+indent_size = 4
+end_of_line = lf
+charset = utf-8
+trim_trailing_whitespace = true
+insert_final_newline = true
+
+[*.md]
+trim_trailing_whitespace = false
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..5171c54
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,2 @@
+node_modules
+npm-debug.log
\ No newline at end of file
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..63ad1e9
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,26 @@
+{
+    "evil":         true
+,   "regexdash":    true
+,   "browser":      true
+,   "wsh":          true
+,   "trailing":     true
+,   "sub":          true
+,   "curly":        true
+,   "laxcomma":     true
+,   "laxbreak":     true
+,   "undef":        true
+,   "jquery":       true
+,   "globals":
+    {
+        "console":      false
+    ,   "Ti":           false
+    ,   "Titanium":     false
+    ,   "Alloy":        false
+    ,   "L":            false
+    ,   "exports":      false
+    ,   "define":       false
+    ,   "require":      false
+    ,   "module":       false
+    ,   "__dirname":    false
+    }
+}
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..b49c03a
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+node_js:
+  - "0.10"
+before_script :
+    - npm install -g grunt-cli
\ No newline at end of file
diff --git a/GruntFile.coffee b/GruntFile.coffee
new file mode 100644
index 0000000..eb119bd
--- /dev/null
+++ b/GruntFile.coffee
@@ -0,0 +1,38 @@
+module.exports = (grunt) ->
+
+    # Project configuration
+    #
+    grunt.initConfig
+        coffee:
+            default:
+                files: [
+                    expand: true         # Enable dynamic expansion.
+                    cwd: 'src/'          # Src matches are relative to this path.
+                    src: ['**/*.coffee'] # Actual pattern(s) to match.
+                    dest: 'lib/'         # Destination path prefix.
+                    ext: '.js'           # Dest filepaths will have this extension.
+                ]
+
+        watch:
+            src:
+                files: ['src/**/*.coffee']
+                tasks: ['coffee']
+
+        mochaTest:
+            test:
+                options:
+                    reporter: 'spec'
+                    require:  'coffee-script'
+                src: [ 'test/**/*.js', "test/**/*.coffee" ]
+
+
+    # These plugins provide the necessary tasks
+    #
+    grunt.loadNpmTasks 'grunt-contrib-coffee'
+    grunt.loadNpmTasks 'grunt-contrib-watch'
+    grunt.loadNpmTasks 'grunt-mocha-test'
+
+    # Default tasks
+    #
+    grunt.registerTask 'default', [ 'coffee' ]
+    grunt.registerTask 'test',    [ 'coffee', 'mochaTest' ]
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..8f7d288
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,22 @@
+Copyright (c) 2013, Marviq
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+   list of conditions and the following disclaimer.
+2. Redistributions in binary form must reproduce the above copyright notice,
+   this list of conditions and the following disclaimer in the documentation
+   and/or other materials provided with the distribution.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
+ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..86710ee
--- /dev/null
+++ b/README.md
@@ -0,0 +1,66 @@
+# madlib-object-utils
+[![Build Status](https://travis-ci.org/Qwerios/madlib-object-utils.svg?branch=master)](https://travis-ci.org/Qwerios/madlib-object-utils)  [![NPM version](https://badge.fury.io/js/madlib-object-utils.png)](http://badge.fury.io/js/madlib-object-utils) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/)
+
+[![Npm Downloads](https://nodei.co/npm/madlib-object-utils.png?downloads=true&stars=true)](https://nodei.co/npm/madlib-object-utils.png?downloads=true&stars=true)
+
+A small set of utility functions for working with objects
+
+
+## acknowledgments
+The Marviq Application Development library (aka madlib) was developed by me when I was working at Marviq. They were cool enough to let me publish it using my personal github account instead of the company account. We decided to open source it for our mutual benefit and to ensure future updates should I decide to leave the company.
+
+
+## philosophy
+JavaScript is the language of the web. Wouldn't it be nice if we could stop having to rewrite (most) of our code for all those web connected platforms running on JavaScript? That is what madLib hopes to achieve. The focus of madLib is to have the same old boring stuff ready made for multiple platforms. Write your core application logic once using modules and never worry about the basics stuff again. Basics including XHR, XML, JSON, host mappings, settings, storage, etcetera. The idea is to use the tried and proven frameworks where available and use madlib based modules as the missing link.
+
+Currently madLib is focused on supporting the following platforms:
+
+* Web browsers (IE6+, Chrome, Firefox, Opera)
+* Appcelerator/Titanium
+* PhoneGap
+* NodeJS
+
+
+## installation
+```bash
+$ npm install madlib-object-utils --save
+```
+
+## usage
+```javascript
+var objectUtils = require( "madlib-object-utils" )
+
+var myObject     =
+{
+    books:
+    {
+        book: [
+            "Example book 1",
+            "Example book 2"
+        ]
+    }
+}
+
+// Retrieve a value from an object
+// Will return 'Example book 1'
+// 3rd parameter will be return if the path can't be found
+//
+book1Name = objectUtils.getValue( "books.book.0", myObject, "unknown" )
+
+// Set a value on an object
+// Will update 'Example book 2' to 'Example book 2a'
+//
+objectUtils.setValue( "books.book.1", myObject, "Example book 2a" )
+
+// Retrieve a value from an object and creates is if it doesn't exist
+//
+book3 = objectUtils.getAndCreate( "books.book.2", myObject, "Example book 3" )
+
+// Check if something is an array
+//
+books = objectUtils.getValue( "books.book", myObject )
+if ( objectUtils.isArray( books ) )
+{
+    books.push( "Example book 4" )
+}
+```
\ No newline at end of file
diff --git a/lib/utils.js b/lib/utils.js
new file mode 100644
index 0000000..da95e25
--- /dev/null
+++ b/lib/utils.js
@@ -0,0 +1,146 @@
+(function() {
+  (function(factory) {
+    if (typeof exports === "object") {
+      return module.exports = factory();
+    } else if (typeof define === "function" && define.amd) {
+      return define([], factory);
+    }
+  })(function() {
+    var getAndCreate, getValue, isArray, isObject, objectUtils, setValue;
+    isObject = function(value) {
+      var type;
+      type = typeof value;
+      return value !== null && (type === 'object' || type === 'function');
+    };
+    isArray = function(object) {
+      if (Array.isArray != null) {
+        return Array.isArray(object);
+      } else {
+        return Object.prototype.toString.call(object) === "[object Array]";
+      }
+    };
+    getValue = function(path, object, valueIfMissing) {
+      var aPath, key, value;
+      if (valueIfMissing == null) {
+        valueIfMissing = void 0;
+      }
+      if (object == null) {
+        return valueIfMissing;
+      }
+      aPath = ("" + path).split(".");
+      value = object;
+      key = aPath.shift();
+      if (aPath.length === 0) {
+        value = value[key.replace("%2E", ".")];
+        if (value == null) {
+          value = valueIfMissing;
+        }
+      } else {
+        while (value && key) {
+          value = value[key.replace("%2E", ".")];
+          if (value == null) {
+            value = valueIfMissing;
+          }
+          key = aPath.shift();
+        }
+        value = 0 === aPath.length ? value : valueIfMissing;
+      }
+      return value;
+    };
+    getAndCreate = function(path, object, defaultValue) {
+      var aPath, key, value;
+      if (object == null) {
+        return;
+      }
+      if (!isObject(object)) {
+        return;
+      }
+      aPath = ("" + path).split(".");
+      value = object;
+      key = aPath.shift();
+      while (key) {
+        key = key.replace("%2E", ".");
+        if (value[key] == null) {
+          value[key] = {};
+        }
+        if (!value.hasOwnProperty(key)) {
+          return;
+        }
+        if (aPath.length === 0) {
+          if (defaultValue != null) {
+            value[key] = defaultValue;
+          }
+        }
+        value = value[key];
+        key = aPath.shift();
+      }
+      return value;
+    };
+    setValue = function(path, object, value) {
+      getAndCreate(path, object, value);
+      return object;
+    };
+
+    /**
+     *   A small set of utility functions for working with objects
+     *
+     *   @author     mdoeswijk
+     *   @module     objectUtils
+     *   @version    0.1
+     */
+    return objectUtils = {
+
+      /**
+       *   Checks if the provided parameter is an array
+       *
+       *   @function isArray
+       *   @param {Mixed}  object  The object to check
+       *
+       *   @return {Boolean} Returns true if the provided object is an array
+       *
+       */
+      isArray: isArray,
+
+      /**
+       *   Retrieves a value from the target object using the provided path
+       *
+       *   @function getValue
+       *   @param {String} path                The path to check on the object
+       *   @param {Object} object              The object to retrieve the value from
+       *   @param {Mixed}  [valueIfMissing]    Optional default value to return if the path isn't found
+       *
+       *   @return {Mixed} Returns the found value
+       *
+       */
+      getValue: getValue,
+
+      /**
+       *   Retrieves a value from the target object using the provided path
+       *   Creates the entire path if missing
+       *
+       *   @function getAndCreate
+       *   @param {String} path                The path to check on the object
+       *   @param {Object} object              The object to retrieve the value from
+       *   @param {Mixed}  [valueIfMissing]    Optional default value to set return if the path isn't found
+       *
+       *   @return {Mixed} Returns the found and/or created value
+       *
+       */
+      getAndCreate: getAndCreate,
+
+      /**
+       *   Sets a value on the target object using the provided path
+       *
+       *   @function setValue
+       *   @param {String} path                The path to check on the object
+       *   @param {Object} object              The object to retrieve the value from
+       *   @param {Mixed}  value               The value to set on the object at the provided path
+       *
+       *   @return {Object} Returns the updated object
+       *
+       */
+      setValue: setValue
+    };
+  });
+
+}).call(this);
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..ab005cb
--- /dev/null
+++ b/package.json
@@ -0,0 +1,33 @@
+{
+  "name": "madlib-object-utils",
+  "description": "A small set of utility functions for working with objects",
+  "main": "lib/utils.js",
+  "version": "0.1.7",
+  "author": {
+    "name": "Mark Doeswijk",
+    "email": "mdoeswijk@gmail.com",
+    "url": "https://github.com/Qwerios"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/Qwerios/madlib-object-utils.git"
+  },
+  "scripts": {
+    "test": "command -v grunt >/dev/null 2>&1 && grunt test || { echo >&2 'Grunt is not installed'; }",
+    "prepublish": "command -v grunt >/dev/null 2>&1 && grunt || { echo >&2 'Grunt is not installed'; }"
+  },
+  "devDependencies": {
+    "grunt-contrib-watch": "~0.6.1",
+    "grunt-contrib-coffee": "~0.10.1",
+    "grunt": "~0.4.5",
+    "coffee-script": "~1.7.1",
+    "chai": "~1.9.1",
+    "grunt-mocha-test": "~0.11.0"
+  },
+  "dependencies": {},
+  "licenses": [
+    {
+      "type": "BSD"
+    }
+  ]
+}
diff --git a/src/utils.coffee b/src/utils.coffee
new file mode 100644
index 0000000..80ce28f
--- /dev/null
+++ b/src/utils.coffee
@@ -0,0 +1,143 @@
+( ( factory ) ->
+    if typeof exports is "object"
+        module.exports = factory()
+    else if typeof define is "function" and define.amd
+        define( [], factory )
+
+)( () ->
+    isObject = ( value ) ->
+        type = typeof value
+        return value != null and ( type is 'object' or type is 'function' )
+
+    isArray = ( object ) ->
+        # This is lifted from underscore.js
+        # Reason is that it was the only reason to add underscore to some
+        # modules so this saves us more then a few kilobytes
+        #
+        if Array.isArray?
+            Array.isArray( object )
+        else
+            Object.prototype.toString.call( object ) == "[object Array]"
+
+    # Convenience function to retrieve a value from an object as if it was a
+    # selector-like path
+    #
+    # Example: child.attributes.0.value
+    #
+    getValue = ( path, object, valueIfMissing = undefined ) ->
+        if not object? then return valueIfMissing
+
+        aPath = "#{path}".split( "." )
+        value = object
+        key   = aPath.shift()
+
+        if aPath.length is 0
+            # This is only a 1 deep check
+            #
+            value = value[ key.replace( "%2E", "." ) ]
+            value = valueIfMissing if not value?
+
+        else
+            while value and key
+                value = value[ key.replace( "%2E", "." ) ]
+                value = valueIfMissing if not value?
+                key   = aPath.shift()
+
+            value = if 0 is aPath.length then value else valueIfMissing
+
+        return value
+
+    getAndCreate = ( path, object, defaultValue ) ->
+        if not object? then return
+        if not isObject( object ) then return
+
+        aPath = "#{path}".split( "." )
+        value = object
+        key   = aPath.shift()
+
+        while key
+            key = key.replace( "%2E", "." )
+
+            # Create non existing path element
+            #
+            if not value[ key ]?
+                value[ key ] = {}
+
+            if not value.hasOwnProperty(key)
+                return
+
+            if aPath.length is 0
+                # Assign the default value to the newly created key if supplied
+                #
+                value[ key ] = defaultValue if defaultValue?
+
+            # Process the next path element
+            #
+            value = value[ key ]
+            key   = aPath.shift()
+
+        return value
+
+    setValue = ( path, object, value ) ->
+        getAndCreate( path, object, value )
+        return object
+
+    ###*
+    #   A small set of utility functions for working with objects
+    #
+    #   @author     mdoeswijk
+    #   @module     objectUtils
+    #   @version    0.1
+    ###
+    objectUtils =
+        ###*
+        #   Checks if the provided parameter is an array
+        #
+        #   @function isArray
+        #   @param {Mixed}  object  The object to check
+        #
+        #   @return {Boolean} Returns true if the provided object is an array
+        #
+        ###
+        isArray:        isArray
+
+        ###*
+        #   Retrieves a value from the target object using the provided path
+        #
+        #   @function getValue
+        #   @param {String} path                The path to check on the object
+        #   @param {Object} object              The object to retrieve the value from
+        #   @param {Mixed}  [valueIfMissing]    Optional default value to return if the path isn't found
+        #
+        #   @return {Mixed} Returns the found value
+        #
+        ###
+        getValue:       getValue
+
+        ###*
+        #   Retrieves a value from the target object using the provided path
+        #   Creates the entire path if missing
+        #
+        #   @function getAndCreate
+        #   @param {String} path                The path to check on the object
+        #   @param {Object} object              The object to retrieve the value from
+        #   @param {Mixed}  [valueIfMissing]    Optional default value to set return if the path isn't found
+        #
+        #   @return {Mixed} Returns the found and/or created value
+        #
+        ###
+        getAndCreate:   getAndCreate
+
+        ###*
+        #   Sets a value on the target object using the provided path
+        #
+        #   @function setValue
+        #   @param {String} path                The path to check on the object
+        #   @param {Object} object              The object to retrieve the value from
+        #   @param {Mixed}  value               The value to set on the object at the provided path
+        #
+        #   @return {Object} Returns the updated object
+        #
+        ###
+        setValue:       setValue
+ )
diff --git a/test/dotted-key.coffee b/test/dotted-key.coffee
new file mode 100644
index 0000000..646b2fc
--- /dev/null
+++ b/test/dotted-key.coffee
@@ -0,0 +1,59 @@
+chai        = require "chai"
+objectUtils = require "../lib/utils.js"
+
+describe( "Object value retrieval", () ->
+    describe( "#getValue()", () ->
+        it( "Should get the value", () ->
+            testObject =  {
+                foo:
+                    bar: true
+            }
+
+            chai.expect( objectUtils.getValue( "foo.bar", testObject ) ).to.eql( true )
+        )
+    )
+
+    describe( "#getValue()", () ->
+        it( "Should get the value", () ->
+            testObjectWithDottedKey =  {
+                "foo.cheese":
+                    bar: true
+            }
+
+            chai.expect( objectUtils.getValue( "foo%2Echeese.bar", testObjectWithDottedKey ) ).to.eql( true )
+        )
+    )
+
+    describe( "#createValue()", () ->
+        it( "Should create the value", () ->
+            testCreate = {}
+
+            objectUtils.getAndCreate( "foo%2Echeese.bar", testCreate )
+
+            chai.expect( testCreate[ "foo.cheese" ].bar ).to.eql( {} )
+        )
+    )
+
+
+    describe( "#setValue()", () ->
+        it( "Should set the value", () ->
+            testSet =
+                "foo.cheese":
+                    bar: "yes"
+
+            objectUtils.setValue( "foo%2Echeese.bar", testSet, 1 )
+
+            chai.expect( testSet[ "foo.cheese" ].bar ).to.eql( 1 )
+        )
+    )
+
+    describe( "#setAndCreateValue()", () ->
+        it( "Should create and set the value", () ->
+            testCreateSet = {}
+
+            objectUtils.setValue( "foo%2Echeese.bar", testCreateSet, 1 )
+
+            chai.expect( testCreateSet[ "foo.cheese" ].bar ).to.eql( 1 )
+        )
+    )
+)
\ No newline at end of file
diff --git a/test/manipulate.coffee b/test/manipulate.coffee
new file mode 100644
index 0000000..fedf1e7
--- /dev/null
+++ b/test/manipulate.coffee
@@ -0,0 +1,37 @@
+chai        = require "chai"
+objectUtils = require "../lib/utils.js"
+
+describe( "Object manipulations", () ->
+    describe( "#createValue()", () ->
+        it( "Should create the value", () ->
+            testCreate = {}
+
+            objectUtils.getAndCreate( "foo.bar", testCreate )
+
+            chai.expect( testCreate.foo.bar ).to.eql( {} )
+        )
+    )
+
+
+    describe( "#setValue()", () ->
+        it( "Should set the value", () ->
+            testSet =
+                foo:
+                    bar: "yes"
+
+            objectUtils.setValue( "foo.bar", testSet, 1 )
+
+            chai.expect( testSet.foo.bar ).to.eql( 1 )
+        )
+    )
+
+    describe( "#setAndCreateValue()", () ->
+        it( "Should create and set the value", () ->
+            testCreateSet = {}
+
+            objectUtils.setValue( "foo.bar", testCreateSet, 1 )
+
+            chai.expect( testCreateSet.foo.bar ).to.eql( 1 )
+        )
+    )
+)
\ No newline at end of file
diff --git a/test/prototype-pollution.coffee b/test/prototype-pollution.coffee
new file mode 100644
index 0000000..7c797e1
--- /dev/null
+++ b/test/prototype-pollution.coffee
@@ -0,0 +1,14 @@
+chai        = require "chai"
+objectUtils = require "../lib/utils.js"
+
+describe( "Prototype pollution", () ->
+    describe( "#setValue()", () ->
+        it( "Should not pollute value", () ->
+            objectUtils.setValue( '__proto__.polluted', {}, true )
+            chai.expect( global.polluted ).to.eql( undefined )
+
+            objectUtils.setValue('this.constructor.prototype.polluted', {}, 'yes');
+            chai.expect( {}.polluted ).to.eql( undefined )
+        )
+    )
+)

Fixed code: # http://editorconfig.org
root = true

[*]
indent_style = space
indent_size = 4
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

[*.md]
trim_trailing_whitespace = false
node_modules
npm-debug.log
{
    "evil":         true
,   "regexdash":    true
,   "browser":      true
,   "wsh":          true
,   "trailing":     true
,   "sub":          true
,   "curly":        true
,   "laxcomma":     true
,   "laxbreak":     true
,   "undef":        true
,   "jquery":       true
,   "globals":
    {
        "console":      false
    ,   "Ti":           false
    ,   "Titanium":     false
    ,   "Alloy":        false
    ,   "L":            false
    ,   "exports":      false
    ,   "define":       false
    ,   "require":      false
    ,   "module":       false
    ,   "__dirname":    false
    }
}
language: node_js
node_js:
  - "0.10"
before_script :
    - npm install -g grunt-cli
module.exports = (grunt) ->

    # Project configuration
    #
    grunt.initConfig
        coffee:
            default:
                files: [
                    expand: true         # Enable dynamic expansion.
                    cwd: 'src/'          # Src matches are relative to this path.
                    src: ['**/*.coffee'] # Actual pattern(s) to match.
                    dest: 'lib/'         # Destination path prefix.
                    ext: '.js'           # Dest filepaths will have this extension.
                ]

        watch:
            src:
                files: ['src/**/*.coffee']
                tasks: ['coffee']

        mochaTest:
            test:
                options:
                    reporter: 'spec'
                    require:  'coffee-script'
                src: [ 'test/**/*.js', "test/**/*.coffee" ]


    # These plugins provide the necessary tasks
    #
    grunt.loadNpmTasks 'grunt-contrib-coffee'
    grunt.loadNpmTasks 'grunt-contrib-watch'
    grunt.loadNpmTasks 'grunt-mocha-test'

    # Default tasks
    #
    grunt.registerTask 'default', [ 'coffee' ]
    grunt.registerTask 'test',    [ 'coffee', 'mochaTest' ]
Copyright (c) 2013, Marviq
All rights reserved.

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
# madlib-object-utils
[![Build Status](https://travis-ci.org/Qwerios/madlib-object-utils.svg?branch=master)](https://travis-ci.org/Qwerios/madlib-object-utils)  [![NPM version](https://badge.fury.io/js/madlib-object-utils.png)](http://badge.fury.io/js/madlib-object-utils) [![Built with Grunt](https://cdn.gruntjs.com/builtwith.png)](http://gruntjs.com/)

[![Npm Downloads](https://nodei.co/npm/madlib-object-utils.png?downloads=true&stars=true)](https://nodei.co/npm/madlib-object-utils.png?downloads=true&stars=true)

A small set of utility functions for working with objects


## acknowledgments
The Marviq Application Development library (aka madlib) was developed by me when I was working at Marviq. They were cool enough to let me publish it using my personal github account instead of the company account. We decided to open source it for our mutual benefit and to ensure future updates should I decide to leave the company.


## philosophy
JavaScript is the language of the web. Wouldn't it be nice if we could stop having to rewrite (most) of our code for all those web connected platforms running on JavaScript? That is what madLib hopes to achieve. The focus of madLib is to have the same old boring stuff ready made for multiple platforms. Write your core application logic once using modules and never worry about the basics stuff again. Basics including XHR, XML, JSON, host mappings, settings, storage, etcetera. The idea is to use the tried and proven frameworks where available and use madlib based modules as the missing link.

Currently madLib is focused on supporting the following platforms:

* Web browsers (IE6+, Chrome, Firefox, Opera)
* Appcelerator/Titanium
* PhoneGap
* NodeJS


## installation
```bash
$ npm install madlib-object-utils --save
```

## usage
```javascript
var objectUtils = require( "madlib-object-utils" )

var myObject     =
{
    books:
    {
        book: [
            "Example book 1",
            "Example book 2"
        ]
    }
}

// Retrieve a value from an object
// Will return 'Example book 1'
// 3rd parameter will be return if the path can't be found
//
book1Name = objectUtils.getValue( "books.book.0", myObject, "unknown" )

// Set a value on an object
// Will update 'Example book 2' to 'Example book 2a'
//
objectUtils.setValue( "books.book.1", myObject, "Example book 2a" )

// Retrieve a value from an object and creates is if it doesn't exist
//
book3 = objectUtils.getAndCreate( "books.book.2", myObject, "Example book 3" )

// Check if something is an array
//
books = objectUtils.getValue( "books.book", myObject )
if ( objectUtils.isArray( books ) )
{
    books.push( "Example book 4" )
}
```
(function() {
  (function(factory) {
    if (typeof exports === "object") {
      return module.exports = factory();
    } else if (typeof define === "function" && define.amd) {
      return define([], factory);
    }
  })(function() {
    var getAndCreate, getValue, isArray, isObject, objectUtils, setValue;
    isObject = function(value) {
      var type;
      type = typeof value;
      return value !== null && (type === 'object' || type === 'function');
    };
    isArray = function(object) {
      if (Array.isArray != null) {
        return Array.isArray(object);
      } else {
        return Object.prototype.toString.call(object) === "[object Array]";
      }
    };
    getValue = function(path, object, valueIfMissing) {
      var aPath, key, value;
      if (valueIfMissing == null) {
        valueIfMissing = void 0;
      }
      if (object == null) {
        return valueIfMissing;
      }
      aPath = ("" + path).split(".");
      value = object;
      key = aPath.shift();
      if (aPath.length === 0) {
        value = value[key.replace("%2E", ".")];
        if (value == null) {
          value = valueIfMissing;
        }
      } else {
        while (value && key) {
          value = value[key.replace("%2E", ".")];
          if (value == null) {
            value = valueIfMissing;
          }
          key = aPath.shift();
        }
        value = 0 === aPath.length ? value : valueIfMissing;
      }
      return value;
    };
    getAndCreate = function(path, object, defaultValue) {
      var aPath, key, value;
      if (object == null) {
        return;
      }
      if (!isObject(object)) {
        return;
      }
      aPath = ("" + path).split(".");
      value = object;
      key = aPath.shift();
      while (key) {
        key = key.replace("%2E", ".");
        if (value[key] == null) {
          value[key] = {};
        }
        if (!value.hasOwnProperty(key)) {
          return;
        }
        if (aPath.length === 0) {
          if (defaultValue != null) {
            value[key] = defaultValue;
          }
        }
        value = value[key];
        key = aPath.shift();
      }
      return value;
    };
    setValue = function(path, object, value) {
      getAndCreate(path, object, value);
      return object;
    };

    /**
     *   A small set of utility functions for working with objects
     *
     *   @author     mdoeswijk
     *   @module     objectUtils
     *   @version    0.1
     */
    return objectUtils = {

      /**
       *   Checks if the provided parameter is an array
       *
       *   @function isArray
       *   @param {Mixed}  object  The object to check
       *
       *   @return {Boolean} Returns true if the provided object is an array
       *
       */
      isArray: isArray,

      /**
       *   Retrieves a value from the target object using the provided path
       *
       *   @function getValue
       *   @param {String} path                The path to check on the object
       *   @param {Object} object              The object to retrieve the value from
       *   @param {Mixed}  [valueIfMissing]    Optional default value to return if the path isn't found
       *
       *   @return {Mixed} Returns the found value
       *
       */
      getValue: getValue,

      /**
       *   Retrieves a value from the target object using the provided path
       *   Creates the entire path if missing
       *
       *   @function getAndCreate
       *   @param {String} path                The path to check on the object
       *   @param {Object} object              The object to retrieve the value from
       *   @param {Mixed}  [valueIfMissing]    Optional default value to set return if the path isn't found
       *
       *   @return {Mixed} Returns the found and/or created value
       *
       */
      getAndCreate: getAndCreate,

      /**
       *   Sets a value on the target object using the provided path
       *
       *   @function setValue
       *   @param {String} path                The path to check on the object
       *   @param {Object} object              The object to retrieve the value from
       *   @param {Mixed}  value               The value to set on the object at the provided path
       *
       *   @return {Object} Returns the updated object
       *
       */
      setValue: setValue
    };
  });

}).call(this);
{
  "name": "madlib-object-utils",
  "description": "A small set of utility functions for working with objects",
  "main": "lib/utils.js",
  "version": "0.1.7",
  "author": {
    "name": "Mark Doeswijk",
    "email": "mdoeswijk@gmail.com",
    "url": "https://github.com/Qwerios"
  },
  "repository": {
    "type": "git",
    "url": "https://github.com/Qwerios/madlib-object-utils.git"
  },
  "scripts": {
    "test": "command -v grunt >/dev/null 2>&1 && grunt test || { echo >&2 'Grunt is not installed'; }",
    "prepublish": "command -v grunt >/dev/null 2>&1 && grunt || { echo >&2 'Grunt is not installed'; }"
  },
  "devDependencies": {
    "grunt-contrib-watch": "~0.6.1",
    "grunt-contrib-coffee": "~0.10.1",
    "grunt": "~0.4.5",
    "coffee-script": "~1.7.1",
    "chai": "~1.9.1",
    "grunt-mocha-test": "~0.11.0"
  },
  "dependencies": {},
  "licenses": [
    {
      "type": "BSD"
    }
  ]
}
( ( factory ) ->
    if typeof exports is "object"
        module.exports = factory()
    else if typeof define is "function" and define.amd
        define( [], factory )

)( () ->
    isObject = ( value ) ->
        type = typeof value
        return value != null and ( type is 'object' or type is 'function' )

    isArray = ( object ) ->
        # This is lifted from underscore.js
        # Reason is that it was the only reason to add underscore to some
        # modules so this saves us more then a few kilobytes
        #
        if Array.isArray?
            Array.isArray( object )
        else
            Object.prototype.toString.call( object ) == "[object Array]"

    # Convenience function to retrieve a value from an object as if it was a
    # selector-like path
    #
    # Example: child.attributes.0.value
    #
    getValue = ( path, object, valueIfMissing = undefined ) ->
        if not object? then return valueIfMissing

        aPath = "#{path}".split( "." )
        value = object
        key   = aPath.shift()

        if aPath.length is 0
            # This is only a 1 deep check
            #
            value = value[ key.replace( "%2E", "." ) ]
            value = valueIfMissing if not value?

        else
            while value and key
                value = value[ key.replace( "%2E", "." ) ]
                value = valueIfMissing if not value?
                key   = aPath.shift()

            value = if 0 is aPath.length then value else valueIfMissing

        return value

    getAndCreate = ( path, object, defaultValue ) ->
        if not object? then return
        if not isObject( object ) then return

        aPath = "#{path}".split( "." )
        value = object
        key   = aPath.shift()

        while key
            key = key.replace( "%2E", "." )

            # Create non existing path element
            #
            if not value[ key ]?
                value[ key ] = {}

            if not value.hasOwnProperty(key)
                return

            if aPath.length is 0
                # Assign the default value to the newly created key if supplied
                #
                value[ key ] = defaultValue if defaultValue?

            # Process the next path element
            #
            value = value[ key ]
            key   = aPath.shift()

        return value

    setValue = ( path, object, value ) ->
        getAndCreate( path, object, value )
        return object

    ###*
    #   A small set of utility functions for working with objects
    #
    #   @author     mdoeswijk
    #   @module     objectUtils
    #   @version    0.1
    ###
    objectUtils =
        ###*
        #   Checks if the provided parameter is an array
        #
        #   @function isArray
        #   @param {Mixed}  object  The object to check
        #
        #   @return {Boolean} Returns true if the provided object is an array
        #
        ###
        isArray:        isArray

        ###*
        #   Retrieves a value from the target object using the provided path
        #
        #   @function getValue
        #   @param {String} path                The path to check on the object
        #   @param {Object} object              The object to retrieve the value from
        #   @param {Mixed}  [valueIfMissing]    Optional default value to return if the path isn't found
        #
        #   @return {Mixed} Returns the found value
        #
        ###
        getValue:       getValue

        ###*
        #   Retrieves a value from the target object using the provided path
        #   Creates the entire path if missing
        #
        #   @function getAndCreate
        #   @param {String} path                The path to check on the object
        #   @param {Object} object              The object to retrieve the value from
        #   @param {Mixed}  [valueIfMissing]    Optional default value to set return if the path isn't found
        #
        #   @return {Mixed} Returns the found and/or created value
        #
        ###
        getAndCreate:   getAndCreate

        ###*
        #   Sets a value on the target object using the provided path
        #
        #   @function setValue
        #   @param {String} path                The path to check on the object
        #   @param {Object} object              The object to retrieve the value from
        #   @param {Mixed}  value               The value to set on the object at the provided path
        #
        #   @return {Object} Returns the updated object
        #
        ###
        setValue:       setValue
 )
chai        = require "chai"
objectUtils = require "../lib/utils.js"

describe( "Object value retrieval", () ->
    describe( "#getValue()", () ->
        it( "Should get the value", () ->
            testObject =  {
                foo:
                    bar: true
            }

            chai.expect( objectUtils.getValue( "foo.bar", testObject ) ).to.eql( true )
        )
    )

    describe( "#getValue()", () ->
        it( "Should get the value", () ->
            testObjectWithDottedKey =  {
                "foo.cheese":
                    bar: true
            }

            chai.expect( objectUtils.getValue( "foo%2Echeese.bar", testObjectWithDottedKey ) ).to.eql( true )
        )
    )

    describe( "#createValue()", () ->
        it( "Should create the value", () ->
            testCreate = {}

            objectUtils.getAndCreate( "foo%2Echeese.bar", testCreate )

            chai.expect( testCreate[ "foo.cheese" ].bar ).to.eql( {} )
        )
    )


    describe( "#setValue()", () ->
        it( "Should set the value", () ->
            testSet =
                "foo.cheese":
                    bar: "yes"

            objectUtils.setValue( "foo%2Echeese.bar", testSet, 1 )

            chai.expect( testSet[ "foo.cheese" ].bar ).to.eql( 1 )
        )
    )

    describe( "#setAndCreateValue()", () ->
        it( "Should create and set the value", () ->
            testCreateSet = {}

            objectUtils.setValue( "foo%2Echeese.bar", testCreateSet, 1 )

            chai.expect( testCreateSet[ "foo.cheese" ].bar ).to.eql( 1 )
        )
    )
)
chai        = require "chai"
objectUtils = require "../lib/utils.js"

describe( "Object manipulations", () ->
    describe( "#createValue()", () ->
        it( "Should create the value", () ->
            testCreate = {}

            objectUtils.getAndCreate( "foo.bar", testCreate )

            chai.expect( testCreate.foo.bar ).to.eql( {} )
        )
    )


    describe( "#setValue()", () ->
        it( "Should set the value", () ->
            testSet =
                foo:
                    bar: "yes"

            objectUtils.setValue( "foo.bar", testSet, 1 )

            chai.expect( testSet.foo.bar ).to.eql( 1 )
        )
    )

    describe( "#setAndCreateValue()", () ->
        it( "Should create and set the value", () ->
            testCreateSet = {}

            objectUtils.setValue( "foo.bar", testCreateSet, 1 )

            chai.expect( testCreateSet.foo.bar ).to.eql( 1 )
        )
    )
)
chai        = require "chai"
objectUtils = require "../lib/utils.js"

describe( "Prototype pollution", () ->
    describe( "#setValue()", () ->
        it( "Should not pollute value", () ->
            objectUtils.setValue( '__proto__.polluted', {}, true )
            chai.expect( global.polluted ).to.eql( undefined )

            objectUtils.setValue('this.constructor.prototype.polluted', {}, 'yes');
            chai.expect( {}.polluted ).to.eql( undefined )
        )
    )
)