CVE-2020-28274: Insufficient Information (NVD-CWE-noinfo)
value: Prototype pollution vulnerability in 'deepref' versions 1.1.1 through 1.2.1 allows attacker to cause a denial of service and may lead to remote code execution.
CWE description: There is insufficient information about the issue to classify it; details are unkown or unspecified.
CVSS v3 base score: 9.8
Published: 2020-12-08T22:15Z
Repository: https://github.com/isaymatato/deepref
Language: Other
Files changed: .gitignore, .jscsrc, .jshintignore, .jshintrc, Makefile, README.md, index.js, package.json, test/index.js
Commit date: 2020-11-23 09:28:03 -0600
Commit message: Disallow __proto__ assignment

Disallow assignment to the prototype using `set()`.

This resolves a Prototype Pollution vulnerability. An attacker could
have assigned a value to the target object's prototype, which can modify
how objects behave throughout the entire application.

Diff: diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..123ae94
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,27 @@
+# Logs
+logs
+*.log
+
+# Runtime data
+pids
+*.pid
+*.seed
+
+# Directory for instrumented libs generated by jscoverage/JSCover
+lib-cov
+
+# Coverage directory used by tools like istanbul
+coverage
+
+# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
+.grunt
+
+# node-waf configuration
+.lock-wscript
+
+# Compiled binary addons (http://nodejs.org/api/addons.html)
+build/Release
+
+# Dependency directory
+# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
+node_modules
diff --git a/.jscsrc b/.jscsrc
new file mode 100644
index 0000000..9e20981
--- /dev/null
+++ b/.jscsrc
@@ -0,0 +1,86 @@
+{
+  "esnext": true,
+  "verbose": true,
+  "disallowEmptyBlocks": true,
+  "disallowKeywordsOnNewLine": ["else"],
+  "disallowMultipleLineBreaks": true,
+  "disallowMultipleVarDecl": {
+    "allExcept": ["undefined", "require"]
+  },
+  "disallowNewlineBeforeBlockStatements": true,
+  "disallowSpaceAfterObjectKeys": true,
+  "disallowSpaceAfterPrefixUnaryOperators": true,
+  "disallowSpaceBeforePostfixUnaryOperators": true,
+  "disallowSpaceBeforeSemicolon": true,
+  "disallowSpacesInFunction": {
+    "beforeOpeningRoundBrace": true
+  },
+  "disallowSpacesInsideParentheses": true,
+  "disallowSpacesInCallExpression": true,
+  "disallowTabs": true,
+  "disallowTrailingComma": false,
+  "disallowTrailingWhitespace": true,
+  "disallowVar": false,
+  "excludeFiles": ["public/libs"],
+  "maximumLineLength": 80,
+  "requireArrayDestructuring": false,
+  "requireBlocksOnNewline": 1,
+  "requireCamelCaseOrUpperCaseIdentifiers": true,
+  "requireCapitalizedConstructors": true,
+  "requireCommaBeforeLineBreak": true,
+  "requireCurlyBraces": [
+    "if",
+    "else",
+    "for",
+    "while",
+    "do",
+    "try",
+    "catch",
+    "switch"
+  ],
+  "requireDotNotation": true,
+  "requireEnhancedObjectLiterals": false,
+  "requireLineBreakAfterVariableAssignment": true,
+  "requireObjectDestructuring": false,
+  "requireOperatorBeforeLineBreak": ["."],
+  "requireParenthesesAroundArrowParam": false,
+  "requireSemicolons": true,
+  "requireSpaceAfterBinaryOperators": true,
+  "requireSpaceAfterKeywords": [
+    "do",
+    "for",
+    "if",
+    "else",
+    "switch",
+    "case",
+    "try",
+    "void",
+    "while",
+    "with",
+    "return",
+    "typeof"
+  ],
+  "requireSpaceAfterLineComment": true,
+  "requireSpaceBeforeBinaryOperators": true,
+  "requireSpaceBeforeBlockStatements": true,
+  "requireSpaceBeforeKeywords": [
+    "else",
+    "while",
+    "catch"
+  ],
+  "requireSpaceBeforeObjectValues": true,
+  "requireSpaceBetweenArguments": true,
+  "requireSpacesInFunction": {
+    "beforeOpeningCurlyBrace": true
+  },
+  "requireSpacesInConditionalExpression": true,
+  "requireSpacesInForStatement": true,
+  "requireSpacesInsideObjectBrackets": "all",
+  "requireTrailingComma": false,
+  "validateIndentation": 2,
+  "validateParameterSeparator": ", ",
+  "validateQuoteMarks": {
+    "mark": "'",
+    "escape": true
+  }
+}
diff --git a/.jshintignore b/.jshintignore
new file mode 100644
index 0000000..f7c4c1a
--- /dev/null
+++ b/.jshintignore
@@ -0,0 +1 @@
+public/libs/*
diff --git a/.jshintrc b/.jshintrc
new file mode 100644
index 0000000..20c95b8
--- /dev/null
+++ b/.jshintrc
@@ -0,0 +1,27 @@
+{
+  "asi": false,
+  "bitwise": true,
+  "browser": true,
+  "curly": true,
+  "forin": true,
+  "latedef": "nofunc",
+  "noarg": true,
+  "node": true,
+  "nonew": true,
+  "predef": [
+    "$",
+    "jQuery",
+
+    "jasmine",
+    "beforeEach",
+    "describe",
+    "expect",
+    "it",
+
+    "angular",
+    "inject",
+    "module"
+  ],
+  "undef": true,
+  "unused": true
+}
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..4444601
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,8 @@
+REPORTER = spec
+
+test:
+	./node_modules/mocha/bin/mocha \
+	--reporter $(REPORTER) \
+	test/*.js
+
+.PHONY: test
\ No newline at end of file
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..83aec03
--- /dev/null
+++ b/README.md
@@ -0,0 +1,81 @@
+DeepRef
+=========
+
+Write to a deeply nested reference within an object.
+
+## Installation
+  ```
+  npm install deepref --save
+  ```
+
+## Usage
+  ```javascript
+  var deep = require('deepref');
+
+  var obj = {};
+
+  deep.set(obj, 'a', 'obj.a is set to this');
+
+  console.dir(obj);
+  // { a: 'obj.a is set to this' }
+
+  deep.set(obj, 'b.c', 'we can set nested fields');
+
+  console.dir(obj);
+  /*
+  * { a: 'obj.a is set to this',
+  *   b: { c: 'we can set nested fields' } }
+  */
+
+  deep.set(obj, 'd.0', 'we can even..');
+  deep.set(obj, 'd.1', '..create arrays..');
+  deep.set(obj, 'd.2', '..by using an integer as the index');
+
+  console.dir(obj);
+  /*
+   *{ a: 'obj.a is set to this',
+   *  b: { c: 'we can set nested fields' },
+   *  d:
+   *   [ 'we can even..',
+   *     '..create arrays..',
+   *     '..by using an integer as the index' ] }
+  */
+  ```
+
+## Decorate
+Use deep.decorate(obj) to give the object the 'setKey' method, which works the same as deep.set
+  ```javascript
+  var obj = {};
+
+  deep.decorate(obj);
+
+  obj.setKey('a.b.c', 123);
+
+  console.log(obj.a.b.c);
+  // Now set to to 123;
+  ```
+  
+Use deep.undecorate(obj) to remove 'setKey'
+  ```javascript
+  var obj = {};
+
+  deep.decorate(obj);
+
+  obj.setKey('a.b.c', 123);
+
+  deep.undecorate(obj);
+
+  console.log(typeof obj.setKey);
+  // obj.setKey is now undefined
+  ```
+
+## Tests
+  ```
+  npm test
+  ```
+
+## Contributing
+
+Please use the included style guide.  If you change anything, please test
+and add unit tests for any new functionality.  If you're fixing a bug, please
+add a unit test that would have failed before the bug was fixed.  Thanks!
\ No newline at end of file
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..ff2668e
--- /dev/null
+++ b/index.js
@@ -0,0 +1,150 @@
+module.exports = {
+  /**
+  * Set a deeply nested value of an object
+  * This alters the original object
+  *
+  * @param  {Object} object The object you want to alter
+  * @param  {String} path The reference path
+  * @param  {*} value The value you want to set
+  * @return {Object} object Retuns the altered object
+  */
+  set: set,
+
+  decorate: function(object) {
+    object.setKey = function(path, value) {
+      return set(object, path, value);
+    };
+  },
+  undecorate: function(object) {
+    if (typeof object.setKey === 'function') {
+      delete object.setKey;
+    }
+  }
+};
+
+function set(object, path, value) {
+  var reference = Reference.createFromPath(object, path);
+  reference.set(value);
+  return reference.resolve();
+}
+
+function Reference(pointer, field, parent) {
+  this.pointer = pointer;
+  this.field = field;
+  this.parent = parent || null;
+}
+
+Reference.prototype.set = function(value) {
+  if (typeof this.field === 'undefined') {
+    throw new Error('Cannot set, field is undefined');
+    return null;
+  }
+  this.pointer[this.field] = value;
+  return this;
+};
+
+Reference.prototype.resolve = function() {
+  var reference = this;
+  while (reference.parent) {
+    reference = reference.getParent();
+  }
+  return reference.pointer;
+};
+
+Reference.createFromPath = function(pointer, path) {
+  var reference = new Reference(pointer);
+  path = path.split('.');
+  path.forEach(function(field) {
+    if (field === '__proto__') {
+      throw new Error('Cannot assign reference to prototype')
+    }
+
+    var isNumber = checkIfStringIsNumber(field);
+    var hasParent = reference.getParent() ? true : false;
+    if (isNumber) {
+      field = parseInt(field);
+    }
+
+    reference = reference.getChild(field);
+
+    if (isNumber && hasParent) {
+      reference.convertToArray();
+    }
+  });
+
+  return reference;
+};
+
+function checkIfStringIsNumber(field) {
+  return /^\d+$/.test(field);
+}
+
+Reference.prototype.getParent = function() {
+  return this.parent || null;
+};
+
+Reference.prototype.getChild = function(field) {
+  var parent = this;
+
+  if (!this.field) {
+    return new Reference(this.pointer, field, parent);
+  }
+
+  this.initializeChildAsObject();
+  var pointer = this.get();
+
+  return new Reference(pointer, field, parent);
+};
+
+Reference.prototype.get = function() {
+  if (!this.field) {
+    return this.pointer;
+  } else {
+    return this.pointer[this.field];
+  }
+};
+
+Reference.prototype.initializeChildAsObject = function() {
+  if (this.getType() !== 'object') {
+    this.set({});
+  }
+  return this;
+};
+
+Reference.prototype.getType = function(value) {
+  return typeof this.pointer[this.field];
+};
+
+Reference.prototype.convertToArray = function() {
+  if (!this.parent) {
+    throw new Error('Cannot convert to array without a parent reference!');
+    return null;
+  }
+
+  this.pointer = this.parent.convertChildToArray();
+};
+
+Reference.prototype.convertChildToArray = function() {
+  var child = this.get();
+  if (!Array.isArray(child)) {
+    this.set([]);
+    this.mapKeys(child);
+  }
+  return this.get();
+};
+
+Reference.prototype.mapKeys = function(source) {
+  if (typeof source !== 'object') {
+    throw new Error('Cannot map from source, source is not an object');
+    return null;
+  }
+
+  Object.keys(source).forEach(function(key) {
+    this.pointer[ this.field ][key] = pointer[key];
+  });
+
+  return this;
+};
+
+
+
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..8dad996
--- /dev/null
+++ b/package.json
@@ -0,0 +1,29 @@
+{
+  "name": "deepref",
+  "version": "1.2.1",
+  "description": "Write to a deeply nested reference within an object",
+  "main": "index.js",
+  "scripts": {
+    "test": "make test"
+  },
+  "repository": {
+    "type": "git",
+    "url": "git+https://github.com/isaymatato/deepref.git"
+  },
+  "keywords": [
+    "deep",
+    "reference",
+    "nested",
+    "object"
+  ],
+  "author": "Mateo Williford <mateowilliford@gmail.com>",
+  "license": "ISC",
+  "bugs": {
+    "url": "https://github.com/isaymatato/deepref/issues"
+  },
+  "homepage": "https://github.com/isaymatato/deepref#readme",
+  "devDependencies": {
+    "chai": "^3.5.0",
+    "mocha": "^2.4.5"
+  }
+}
diff --git a/test/index.js b/test/index.js
new file mode 100644
index 0000000..f90affc
--- /dev/null
+++ b/test/index.js
@@ -0,0 +1,78 @@
+var chai = require('chai');
+var assert = chai.assert,
+    expect = chai.expect,
+    should = chai.should();
+var deepref = require('../index');
+var set = deepref.set;
+var decorate = deepref.decorate;
+var undecorate = deepref.undecorate;
+
+describe('#set', function() {
+  it('sets obj[0] to true', function() {
+    var obj = set({}, '0', true);
+    obj[0].should.equal(true);
+  });
+
+  it('sets obj.a to true', function() {
+    var obj = set({}, 'a', true);
+    obj.a.should.equal(true);
+  });
+
+  it('sets obj.a.b[0] to true', function() {
+    var obj = set({}, 'a.b.0', true);
+    obj.a.b[0].should.equal(true);
+  });
+
+  it('sets obj to ["A","B","C"]', function() {
+    var obj = {};
+    obj = set(obj, '0', 'A');
+    obj = set(obj, '1', 'B');
+    obj = set(obj, '2', 'C');
+    obj[0].should.equal('A');
+    obj[1].should.equal('B');
+    obj[2].should.equal('C');
+  });
+
+  it('sets obj.a to ["A","B","C"]', function() {
+    var obj = {};
+    obj = set(obj, 'a.0', 'A');
+    obj = set(obj, 'a.1', 'B');
+    obj = set(obj, 'a.2', 'C');
+    obj.a[0].should.equal('A');
+    obj.a[1].should.equal('B');
+    obj.a[2].should.equal('C');
+  });
+
+  context('attempt to modify prototype', function() {
+    it('throws an error', function() {
+      assert.throws(
+        () => set({}, '__proto__.foo', 'hacker_attack!'),
+        Error, 'Cannot assign reference to prototype')
+    })
+  })
+});
+
+describe('#decorate', function() {
+  it('gives obj setKey method', function() {
+    var obj = {};
+    decorate(obj);
+    expect(obj.setKey).to.be.a('function');
+  });
+
+  it('obj.setKey(\'a.b\') sets obj.a.b to 1', function() {
+    var obj = {};
+    decorate(obj);
+    obj.setKey('a.b', 1);
+    obj.a.b.should.equal(1);
+  });
+});
+
+describe('#undecorate', function() {
+  it('removes setKey method from object', function() {
+    var obj = {};
+    decorate(obj);
+    expect(obj.setKey).to.be.a('function');
+    undecorate(obj);
+    expect(obj.setKey).to.not.be.a('function');
+  });
+});

Fixed code: # Logs
logs
*.log

# Runtime data
pids
*.pid
*.seed

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# node-waf configuration
.lock-wscript

# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release

# Dependency directory
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git
node_modules
{
  "esnext": true,
  "verbose": true,
  "disallowEmptyBlocks": true,
  "disallowKeywordsOnNewLine": ["else"],
  "disallowMultipleLineBreaks": true,
  "disallowMultipleVarDecl": {
    "allExcept": ["undefined", "require"]
  },
  "disallowNewlineBeforeBlockStatements": true,
  "disallowSpaceAfterObjectKeys": true,
  "disallowSpaceAfterPrefixUnaryOperators": true,
  "disallowSpaceBeforePostfixUnaryOperators": true,
  "disallowSpaceBeforeSemicolon": true,
  "disallowSpacesInFunction": {
    "beforeOpeningRoundBrace": true
  },
  "disallowSpacesInsideParentheses": true,
  "disallowSpacesInCallExpression": true,
  "disallowTabs": true,
  "disallowTrailingComma": false,
  "disallowTrailingWhitespace": true,
  "disallowVar": false,
  "excludeFiles": ["public/libs"],
  "maximumLineLength": 80,
  "requireArrayDestructuring": false,
  "requireBlocksOnNewline": 1,
  "requireCamelCaseOrUpperCaseIdentifiers": true,
  "requireCapitalizedConstructors": true,
  "requireCommaBeforeLineBreak": true,
  "requireCurlyBraces": [
    "if",
    "else",
    "for",
    "while",
    "do",
    "try",
    "catch",
    "switch"
  ],
  "requireDotNotation": true,
  "requireEnhancedObjectLiterals": false,
  "requireLineBreakAfterVariableAssignment": true,
  "requireObjectDestructuring": false,
  "requireOperatorBeforeLineBreak": ["."],
  "requireParenthesesAroundArrowParam": false,
  "requireSemicolons": true,
  "requireSpaceAfterBinaryOperators": true,
  "requireSpaceAfterKeywords": [
    "do",
    "for",
    "if",
    "else",
    "switch",
    "case",
    "try",
    "void",
    "while",
    "with",
    "return",
    "typeof"
  ],
  "requireSpaceAfterLineComment": true,
  "requireSpaceBeforeBinaryOperators": true,
  "requireSpaceBeforeBlockStatements": true,
  "requireSpaceBeforeKeywords": [
    "else",
    "while",
    "catch"
  ],
  "requireSpaceBeforeObjectValues": true,
  "requireSpaceBetweenArguments": true,
  "requireSpacesInFunction": {
    "beforeOpeningCurlyBrace": true
  },
  "requireSpacesInConditionalExpression": true,
  "requireSpacesInForStatement": true,
  "requireSpacesInsideObjectBrackets": "all",
  "requireTrailingComma": false,
  "validateIndentation": 2,
  "validateParameterSeparator": ", ",
  "validateQuoteMarks": {
    "mark": "'",
    "escape": true
  }
}
public/libs/*
{
  "asi": false,
  "bitwise": true,
  "browser": true,
  "curly": true,
  "forin": true,
  "latedef": "nofunc",
  "noarg": true,
  "node": true,
  "nonew": true,
  "predef": [
    "$",
    "jQuery",

    "jasmine",
    "beforeEach",
    "describe",
    "expect",
    "it",

    "angular",
    "inject",
    "module"
  ],
  "undef": true,
  "unused": true
}
REPORTER = spec

test:
	./node_modules/mocha/bin/mocha \
	--reporter $(REPORTER) \
	test/*.js

.PHONY: test
DeepRef
=========

Write to a deeply nested reference within an object.

## Installation
  ```
  npm install deepref --save
  ```

## Usage
  ```javascript
  var deep = require('deepref');

  var obj = {};

  deep.set(obj, 'a', 'obj.a is set to this');

  console.dir(obj);
  // { a: 'obj.a is set to this' }

  deep.set(obj, 'b.c', 'we can set nested fields');

  console.dir(obj);
  /*
  * { a: 'obj.a is set to this',
  *   b: { c: 'we can set nested fields' } }
  */

  deep.set(obj, 'd.0', 'we can even..');
  deep.set(obj, 'd.1', '..create arrays..');
  deep.set(obj, 'd.2', '..by using an integer as the index');

  console.dir(obj);
  /*
   *{ a: 'obj.a is set to this',
   *  b: { c: 'we can set nested fields' },
   *  d:
   *   [ 'we can even..',
   *     '..create arrays..',
   *     '..by using an integer as the index' ] }
  */
  ```

## Decorate
Use deep.decorate(obj) to give the object the 'setKey' method, which works the same as deep.set
  ```javascript
  var obj = {};

  deep.decorate(obj);

  obj.setKey('a.b.c', 123);

  console.log(obj.a.b.c);
  // Now set to to 123;
  ```
  
Use deep.undecorate(obj) to remove 'setKey'
  ```javascript
  var obj = {};

  deep.decorate(obj);

  obj.setKey('a.b.c', 123);

  deep.undecorate(obj);

  console.log(typeof obj.setKey);
  // obj.setKey is now undefined
  ```

## Tests
  ```
  npm test
  ```

## Contributing

Please use the included style guide.  If you change anything, please test
and add unit tests for any new functionality.  If you're fixing a bug, please
add a unit test that would have failed before the bug was fixed.  Thanks!
module.exports = {
  /**
  * Set a deeply nested value of an object
  * This alters the original object
  *
  * @param  {Object} object The object you want to alter
  * @param  {String} path The reference path
  * @param  {*} value The value you want to set
  * @return {Object} object Retuns the altered object
  */
  set: set,

  decorate: function(object) {
    object.setKey = function(path, value) {
      return set(object, path, value);
    };
  },
  undecorate: function(object) {
    if (typeof object.setKey === 'function') {
      delete object.setKey;
    }
  }
};

function set(object, path, value) {
  var reference = Reference.createFromPath(object, path);
  reference.set(value);
  return reference.resolve();
}

function Reference(pointer, field, parent) {
  this.pointer = pointer;
  this.field = field;
  this.parent = parent || null;
}

Reference.prototype.set = function(value) {
  if (typeof this.field === 'undefined') {
    throw new Error('Cannot set, field is undefined');
    return null;
  }
  this.pointer[this.field] = value;
  return this;
};

Reference.prototype.resolve = function() {
  var reference = this;
  while (reference.parent) {
    reference = reference.getParent();
  }
  return reference.pointer;
};

Reference.createFromPath = function(pointer, path) {
  var reference = new Reference(pointer);
  path = path.split('.');
  path.forEach(function(field) {
    if (field === '__proto__') {
      throw new Error('Cannot assign reference to prototype')
    }

    var isNumber = checkIfStringIsNumber(field);
    var hasParent = reference.getParent() ? true : false;
    if (isNumber) {
      field = parseInt(field);
    }

    reference = reference.getChild(field);

    if (isNumber && hasParent) {
      reference.convertToArray();
    }
  });

  return reference;
};

function checkIfStringIsNumber(field) {
  return /^\d+$/.test(field);
}

Reference.prototype.getParent = function() {
  return this.parent || null;
};

Reference.prototype.getChild = function(field) {
  var parent = this;

  if (!this.field) {
    return new Reference(this.pointer, field, parent);
  }

  this.initializeChildAsObject();
  var pointer = this.get();

  return new Reference(pointer, field, parent);
};

Reference.prototype.get = function() {
  if (!this.field) {
    return this.pointer;
  } else {
    return this.pointer[this.field];
  }
};

Reference.prototype.initializeChildAsObject = function() {
  if (this.getType() !== 'object') {
    this.set({});
  }
  return this;
};

Reference.prototype.getType = function(value) {
  return typeof this.pointer[this.field];
};

Reference.prototype.convertToArray = function() {
  if (!this.parent) {
    throw new Error('Cannot convert to array without a parent reference!');
    return null;
  }

  this.pointer = this.parent.convertChildToArray();
};

Reference.prototype.convertChildToArray = function() {
  var child = this.get();
  if (!Array.isArray(child)) {
    this.set([]);
    this.mapKeys(child);
  }
  return this.get();
};

Reference.prototype.mapKeys = function(source) {
  if (typeof source !== 'object') {
    throw new Error('Cannot map from source, source is not an object');
    return null;
  }

  Object.keys(source).forEach(function(key) {
    this.pointer[ this.field ][key] = pointer[key];
  });

  return this;
};



{
  "name": "deepref",
  "version": "1.2.1",
  "description": "Write to a deeply nested reference within an object",
  "main": "index.js",
  "scripts": {
    "test": "make test"
  },
  "repository": {
    "type": "git",
    "url": "git+https://github.com/isaymatato/deepref.git"
  },
  "keywords": [
    "deep",
    "reference",
    "nested",
    "object"
  ],
  "author": "Mateo Williford <mateowilliford@gmail.com>",
  "license": "ISC",
  "bugs": {
    "url": "https://github.com/isaymatato/deepref/issues"
  },
  "homepage": "https://github.com/isaymatato/deepref#readme",
  "devDependencies": {
    "chai": "^3.5.0",
    "mocha": "^2.4.5"
  }
}
var chai = require('chai');
var assert = chai.assert,
    expect = chai.expect,
    should = chai.should();
var deepref = require('../index');
var set = deepref.set;
var decorate = deepref.decorate;
var undecorate = deepref.undecorate;

describe('#set', function() {
  it('sets obj[0] to true', function() {
    var obj = set({}, '0', true);
    obj[0].should.equal(true);
  });

  it('sets obj.a to true', function() {
    var obj = set({}, 'a', true);
    obj.a.should.equal(true);
  });

  it('sets obj.a.b[0] to true', function() {
    var obj = set({}, 'a.b.0', true);
    obj.a.b[0].should.equal(true);
  });

  it('sets obj to ["A","B","C"]', function() {
    var obj = {};
    obj = set(obj, '0', 'A');
    obj = set(obj, '1', 'B');
    obj = set(obj, '2', 'C');
    obj[0].should.equal('A');
    obj[1].should.equal('B');
    obj[2].should.equal('C');
  });

  it('sets obj.a to ["A","B","C"]', function() {
    var obj = {};
    obj = set(obj, 'a.0', 'A');
    obj = set(obj, 'a.1', 'B');
    obj = set(obj, 'a.2', 'C');
    obj.a[0].should.equal('A');
    obj.a[1].should.equal('B');
    obj.a[2].should.equal('C');
  });

  context('attempt to modify prototype', function() {
    it('throws an error', function() {
      assert.throws(
        () => set({}, '__proto__.foo', 'hacker_attack!'),
        Error, 'Cannot assign reference to prototype')
    })
  })
});

describe('#decorate', function() {
  it('gives obj setKey method', function() {
    var obj = {};
    decorate(obj);
    expect(obj.setKey).to.be.a('function');
  });

  it('obj.setKey(\'a.b\') sets obj.a.b to 1', function() {
    var obj = {};
    decorate(obj);
    obj.setKey('a.b', 1);
    obj.a.b.should.equal(1);
  });
});

describe('#undecorate', function() {
  it('removes setKey method from object', function() {
    var obj = {};
    decorate(obj);
    expect(obj.setKey).to.be.a('function');
    undecorate(obj);
    expect(obj.setKey).to.not.be.a('function');
  });
});