CVE-2022-4742: Improperly Controlled Modification of Object Prototype Attributes ('Prototype Pollution') (CWE-1321)
value: A vulnerability, which was classified as critical, has been found in json-pointer up to 0.6.1. Affected by this issue is the function set of the file index.js. The manipulation leads to improperly controlled modification of object prototype attributes ('prototype pollution'). The attack may be launched remotely. Upgrading to version 0.6.2 is able to address this issue. The patch is identified as 859c9984b6c407fc2d5a0a7e47c7274daa681941. It is recommended to upgrade the affected component. VDB-216794 is the identifier assigned to this vulnerability.
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: 9.8
Published: 2022-12-26T08:15Z
Repository: https://github.com/manuelstofer/json-pointer
Language: Other
Files changed: .gitignore, .npmignore, .travis.yml, LICENSE, Makefile, Readme.md, index.js, package.json, test/test-runner.html, test/test.js
Commit date: 2022-02-17 15:51:53 +0100
Commit message: Merge pull request #36 from hhomar/fix-prototype-pollution

Fix prototype pollution when pointer is not a string or number
Diff: diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..94b7562
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,5 @@
+node_modules
+components
+build
+.nyc_output
+coverage
diff --git a/.npmignore b/.npmignore
new file mode 100644
index 0000000..421dcb5
--- /dev/null
+++ b/.npmignore
@@ -0,0 +1,3 @@
+.idea
+components
+build
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..4e2d385
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,10 @@
+language: node_js
+sudo: false
+node_js:
+  - "0.10"
+  - "0.12"
+  - "4"
+  - "5"
+  - "6"
+after_script:
+  - coveralls < coverage/lcov.info
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..918ad11
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,21 @@
+The MIT License (MIT)
+
+Copyright (c) 2016 Manuel Stofer
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..1bf90e6
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,18 @@
+install:
+	@component install
+
+build: install
+	@echo build ...
+	@component build
+
+test:
+	@echo test
+	@./node_modules/.bin/mocha \
+		--require chai \
+		--reporter spec
+
+test-phantom: install build
+	@echo test in browser
+	@mocha-phantomjs test/test-runner.html
+
+.PHONY: test build
diff --git a/Readme.md b/Readme.md
new file mode 100644
index 0000000..c9fb03a
--- /dev/null
+++ b/Readme.md
@@ -0,0 +1,161 @@
+# json-pointer
+
+[![Build Status](https://travis-ci.org/manuelstofer/json-pointer.svg?branch=master)](https://travis-ci.org/manuelstofer/json-pointer)
+[![npm version](https://badge.fury.io/js/json-pointer.svg)](https://www.npmjs.com/package/json-pointer)
+[![Coverage Status](https://coveralls.io/repos/github/manuelstofer/json-pointer/badge.svg?branch=master&service=github)](https://coveralls.io/github/manuelstofer/json-pointer?branch=master)
+
+Some utilities for JSON pointers described by RFC 6901
+
+Provides some additional stuff i needed but is not included in [node-jsonpointer](https://github.com/janl/node-jsonpointer)
+
+
+## Installation
+
+[node.js](http://nodejs.org)
+
+```bash
+$ npm install json-pointer
+```
+
+
+## API
+
+```Javascript
+var pointer = require('json-pointer');
+```
+
+
+### .get(object, pointer)
+
+Looks up a JSON pointer in an object.
+
+Array of reference tokens, e.g. returned by api.parse, can be passed as a pointer to .get, .set and .remove methods.
+
+```Javascript
+var obj = {
+    example: {
+        bla: 'hello'
+    }
+};
+pointer.get(obj, '/example/bla');
+```
+
+
+### .set(object, pointer, value)
+
+Sets a new value on object at the location described by pointer.
+
+```Javascript
+var obj = {};
+pointer.set(obj, '/example/bla', 'hello');
+```
+
+
+### .remove(object, pointer)
+
+Removes an attribute of object referenced by pointer.
+
+```Javascript
+var obj = {
+    example: 'hello'
+};
+pointer.remove(obj, '/example');
+// obj -> {}
+```
+
+
+### .dict(object)
+
+Creates a dictionary object (pointer -> value).
+
+```Javascript
+var obj = {
+    hello: {bla: 'example'}
+};
+pointer.dict(obj);
+
+// Returns:
+// {
+//    '/hello/bla': 'example'
+// }
+```
+
+
+### .walk(object, iterator)
+
+Just like:
+
+```Javascript
+each(pointer.dict(obj), iterator);
+```
+
+
+### .has(object, pointer)
+
+Tests if an object has a value for a JSON pointer.
+
+```Javascript
+var obj = {
+    bla: 'hello'
+};
+
+pointer.has(obj, '/bla');               // -> true
+pointer.has(obj, '/non/existing');      // -> false
+```
+
+
+### .escape(str)
+
+Escapes a reference token.
+
+```Javascript
+pointer.escape('hello~bla');            // -> 'hello~0bla'
+pointer.escape('hello/bla');            // -> 'hello~1bla'
+```
+
+
+### .unescape(str)
+
+Unescape a reference token.
+
+```Javascript
+pointer.unescape('hello~0bla');         // -> 'hello~bla'
+pointer.unescape('hello~1bla');         // -> 'hello/bla'
+```
+
+
+### .parse(str)
+
+Converts a JSON pointer into an array of reference tokens.
+
+```Javascript
+pointer.parse('/hello/bla');            // -> ['hello', 'bla']
+```
+
+
+### .compile(array)
+
+Builds a json pointer from an array of reference tokens.
+
+```Javascript
+pointer.compile(['hello', 'bla']);      // -> '/hello/bla'
+```
+
+
+### pointer(object, [pointer, [value]])
+
+Convenience wrapper around the api.
+
+```Javascript
+pointer(object)                 // bind object
+pointer(object, pointer)        // get
+pointer(object, pointer, value) // set
+```
+
+The wrapper supports chainable object oriented style.
+
+```Javascript
+var obj = {anything: 'bla'};
+var objPointer = pointer(obj);
+objPointer.set('/example', 'bla').dict();
+```
diff --git a/index.js b/index.js
new file mode 100644
index 0000000..afda009
--- /dev/null
+++ b/index.js
@@ -0,0 +1,232 @@
+'use strict';
+
+var each = require('foreach');
+module.exports = api;
+
+
+/**
+ * Convenience wrapper around the api.
+ * Calls `.get` when called with an `object` and a `pointer`.
+ * Calls `.set` when also called with `value`.
+ * If only supplied `object`, returns a partially applied function, mapped to the object.
+ *
+ * @param {Object} obj
+ * @param {String|Array} pointer
+ * @param value
+ * @returns {*}
+ */
+
+function api (obj, pointer, value) {
+    // .set()
+    if (arguments.length === 3) {
+        return api.set(obj, pointer, value);
+    }
+    // .get()
+    if (arguments.length === 2) {
+        return api.get(obj, pointer);
+    }
+    // Return a partially applied function on `obj`.
+    var wrapped = api.bind(api, obj);
+
+    // Support for oo style
+    for (var name in api) {
+        if (api.hasOwnProperty(name)) {
+            wrapped[name] = api[name].bind(wrapped, obj);
+        }
+    }
+    return wrapped;
+}
+
+
+/**
+ * Lookup a json pointer in an object
+ *
+ * @param {Object} obj
+ * @param {String|Array} pointer
+ * @returns {*}
+ */
+api.get = function get (obj, pointer) {
+    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer);
+
+    for (var i = 0; i < refTokens.length; ++i) {
+        var tok = refTokens[i];
+        if (!(typeof obj == 'object' && tok in obj)) {
+            throw new Error('Invalid reference token: ' + tok);
+        }
+        obj = obj[tok];
+    }
+    return obj;
+};
+
+/**
+ * Sets a value on an object
+ *
+ * @param {Object} obj
+ * @param {String|Array} pointer
+ * @param value
+ */
+api.set = function set (obj, pointer, value) {
+    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer),
+      nextTok = refTokens[0];
+
+    if (refTokens.length === 0) {
+      throw Error('Can not set the root object');
+    }
+
+    for (var i = 0; i < refTokens.length - 1; ++i) {
+        var tok = refTokens[i];
+        if (typeof tok !== 'string' && typeof tok !== 'number') {
+          tok = String(tok)
+        }
+        if (tok === "__proto__" || tok === "constructor" || tok === "prototype") {
+            continue
+        }
+        if (tok === '-' && Array.isArray(obj)) {
+          tok = obj.length;
+        }
+        nextTok = refTokens[i + 1];
+
+        if (!(tok in obj)) {
+            if (nextTok.match(/^(\d+|-)$/)) {
+                obj[tok] = [];
+            } else {
+                obj[tok] = {};
+            }
+        }
+        obj = obj[tok];
+    }
+    if (nextTok === '-' && Array.isArray(obj)) {
+      nextTok = obj.length;
+    }
+    obj[nextTok] = value;
+    return this;
+};
+
+/**
+ * Removes an attribute
+ *
+ * @param {Object} obj
+ * @param {String|Array} pointer
+ */
+api.remove = function (obj, pointer) {
+    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer);
+    var finalToken = refTokens[refTokens.length -1];
+    if (finalToken === undefined) {
+        throw new Error('Invalid JSON pointer for remove: "' + pointer + '"');
+    }
+
+    var parent = api.get(obj, refTokens.slice(0, -1));
+    if (Array.isArray(parent)) {
+      var index = +finalToken;
+      if (finalToken === '' && isNaN(index)) {
+        throw new Error('Invalid array index: "' + finalToken + '"');
+      }
+
+      Array.prototype.splice.call(parent, index, 1);
+    } else {
+      delete parent[finalToken];
+    }
+};
+
+/**
+ * Returns a (pointer -> value) dictionary for an object
+ *
+ * @param obj
+ * @param {function} descend
+ * @returns {}
+ */
+api.dict = function dict (obj, descend) {
+    var results = {};
+    api.walk(obj, function (value, pointer) {
+        results[pointer] = value;
+    }, descend);
+    return results;
+};
+
+/**
+ * Iterates over an object
+ * Iterator: function (value, pointer) {}
+ *
+ * @param obj
+ * @param {function} iterator
+ * @param {function} descend
+ */
+api.walk = function walk (obj, iterator, descend) {
+    var refTokens = [];
+
+    descend = descend || function (value) {
+        var type = Object.prototype.toString.call(value);
+        return type === '[object Object]' || type === '[object Array]';
+    };
+
+    (function next (cur) {
+        each(cur, function (value, key) {
+            refTokens.push(String(key));
+            if (descend(value)) {
+                next(value);
+            } else {
+                iterator(value, api.compile(refTokens));
+            }
+            refTokens.pop();
+        });
+    }(obj));
+};
+
+/**
+ * Tests if an object has a value for a json pointer
+ *
+ * @param obj
+ * @param pointer
+ * @returns {boolean}
+ */
+api.has = function has (obj, pointer) {
+    try {
+        api.get(obj, pointer);
+    } catch (e) {
+        return false;
+    }
+    return true;
+};
+
+/**
+ * Escapes a reference token
+ *
+ * @param str
+ * @returns {string}
+ */
+api.escape = function escape (str) {
+    return str.toString().replace(/~/g, '~0').replace(/\//g, '~1');
+};
+
+/**
+ * Unescapes a reference token
+ *
+ * @param str
+ * @returns {string}
+ */
+api.unescape = function unescape (str) {
+    return str.replace(/~1/g, '/').replace(/~0/g, '~');
+};
+
+/**
+ * Converts a json pointer into a array of reference tokens
+ *
+ * @param pointer
+ * @returns {Array}
+ */
+api.parse = function parse (pointer) {
+    if (pointer === '') { return []; }
+    if (pointer.charAt(0) !== '/') { throw new Error('Invalid JSON pointer: ' + pointer); }
+    return pointer.substring(1).split(/\//).map(api.unescape);
+};
+
+/**
+ * Builds a json pointer from a array of reference tokens
+ *
+ * @param refTokens
+ * @returns {string}
+ */
+api.compile = function compile (refTokens) {
+    if (refTokens.length === 0) { return ''; }
+    return '/' + refTokens.map(api.escape).join('/');
+};
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..067d3c5
--- /dev/null
+++ b/package.json
@@ -0,0 +1,35 @@
+{
+  "name": "json-pointer",
+  "description": "Some utilities for JSON pointers described by RFC 6901",
+  "version": "0.6.1",
+  "author": "Manuel Stofer <manuel@smallpdf.com>",
+  "license": "MIT",
+  "dependencies": {
+    "foreach": "^2.0.4"
+  },
+  "homepage": "https://github.com/manuelstofer/json-pointer",
+  "repository": {
+    "type": "git",
+    "url": "git://github.com/manuelstofer/json-pointer.git"
+  },
+  "devDependencies": {
+    "chai": "^1.9.1",
+    "coveralls": "^2.11.14",
+    "mocha": "^1.9.0",
+    "nyc": "^8.3.1",
+    "seamless-immutable": "^5.1.1"
+  },
+  "scripts": {
+    "test": "nyc make test"
+  },
+  "nyc": {
+    "exclude": [
+      "test",
+      "node_modules"
+    ],
+    "reporter": [
+      "lcov",
+      "text-summary"
+    ]
+  }
+}
diff --git a/test/test-runner.html b/test/test-runner.html
new file mode 100644
index 0000000..cff4d21
--- /dev/null
+++ b/test/test-runner.html
@@ -0,0 +1,26 @@
+<html>
+<head>
+    <title> Tests </title>
+    <meta charset="utf-8"/>
+    <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
+</head>
+<body>
+<div id="mocha"></div>
+<script src="../node_modules/mocha/mocha.js"></script>
+<script src="../node_modules/chai/chai.js"></script>
+<script>
+    mocha.ui('bdd');
+    mocha.reporter('html');
+</script>
+<script src="../build/build.js"></script>
+<script>
+    var pointer = require('json-pointer'),
+        each    = require('manuelstofer-foreach');
+</script>
+<script src="test.js"></script>
+<script>
+    if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
+    else { mocha.run(); }
+</script>
+</body>
+</html>
diff --git a/test/test.js b/test/test.js
new file mode 100644
index 0000000..1be310d
--- /dev/null
+++ b/test/test.js
@@ -0,0 +1,475 @@
+/*global describe, it, beforeEach*/
+if (typeof pointer === 'undefined') {
+    var pointer   = require('..'),
+        chai      = require('chai'),
+        each      = require('foreach'),
+        immutable = require('seamless-immutable');
+}
+
+var expect = chai.expect;
+
+
+chai.should();
+
+describe('json-api', function () {
+    'use strict';
+
+    var rfcExample,
+        rfcValues,
+        rfcParsed;
+
+    function resetExamples() {
+        rfcExample = {
+            "foo":      ["bar", "baz"],
+            "bar":      {"baz": 10},
+            "":         0,
+            "a/b":      1,
+            "c%d":      2,
+            "e^f":      3,
+            "g|h":      4,
+            "i\\j":     5,
+            "k\"l":     6,
+            " ":        7,
+            "m~n":      8
+        };
+
+        rfcValues = {
+            "":         rfcExample,
+            "/foo":     rfcExample.foo,
+            "/foo/0":   "bar",
+            "/bar":     rfcExample.bar,
+            "/bar/baz": 10,
+            "/":        0,
+            "/a~1b":    1,
+            "/c%d":     2,
+            "/e^f":     3,
+            "/g|h":     4,
+            "/i\\j":    5,
+            "/k\"l":    6,
+            "/ ":       7,
+            "/m~0n":    8
+        };
+
+        rfcParsed = {
+            "":         { tokens: [],             value: rfcExample },
+            "/foo":     { tokens: ["foo"],        value: rfcExample.foo },
+            "/foo/0":   { tokens: ["foo", "0"],   value: "bar" },
+            "/bar":     { tokens: ["bar"],        value: rfcExample.bar },
+            "/bar/baz": { tokens: ["bar", "baz"], value: 10 },
+            "/":        { tokens: [""],           value: 0 },
+            "/a~1b":    { tokens: ["a/b"],        value: 1 },
+            "/c%d":     { tokens: ["c%d"],        value: 2 },
+            "/e^f":     { tokens: ["e^f"],        value: 3 },
+            "/g|h":     { tokens: ["g|h"],        value: 4 },
+            "/i\\j":    { tokens: ["i\\j"],       value: 5 },
+            "/k\"l":    { tokens: ["k\"l"],       value: 6 },
+            "/ ":       { tokens: [" "],          value: 7 },
+            "/m~0n":    { tokens: ["m~n"],        value: 8 }
+        };
+    }
+    resetExamples();
+    beforeEach(resetExamples);
+
+    describe('#get', function () {
+
+        it('should work for root element', function () {
+            var obj = {};
+            pointer.get(obj, '').should.equal(obj);
+        });
+
+        each(Object.keys(rfcValues), function (p) {
+            it('should work for "' + p + '"', function () {
+                var expectedValue = rfcValues[p];
+                pointer.get(rfcExample, p).should.equal(expectedValue);
+            });
+        });
+
+        each(Object.keys(rfcParsed), function (p) {
+            var tokens = immutable(rfcParsed[p].tokens);
+            it('should work for ' + JSON.stringify(tokens), function () {
+                var expectedValue = rfcParsed[p].value;
+                pointer.get(rfcExample, tokens).should.equal(expectedValue);
+            });
+        });
+
+        it('should work for with inherited properties', function () {
+            function O () {}
+            O.prototype.x = 10;
+            pointer.get(new O, '/x').should.equal(10);
+            pointer.get(Object.create({ x: 10 }), '/x').should.equal(10)
+        });
+    });
+
+    describe('#set', function () {
+        it('should throw when try to set the root object', function () {
+            expect(pointer.set.bind(pointer, {}, '', 'bla')).to.throw(Error);
+        });
+
+        it('should set a value on an object with pointer', function () {
+            var obj = {
+                existing: 'bla'
+            };
+
+            pointer.set(obj, '/new-value/bla', 'expected');
+            obj['new-value'].bla.should.equal('expected');
+        });
+
+        it('should set a value on an object with tokens', function () {
+            var obj = {
+                existing: 'bla'
+            };
+
+            pointer.set(obj, immutable(['new-value', 'bla']), 'expected');
+            obj['new-value'].bla.should.equal('expected');
+        });
+
+        it('should work on first level with pointer', function () {
+            var obj = {
+                existing: 'bla'
+            };
+
+            pointer.set(obj, '/first-level', 'expected');
+            obj['first-level'].should.equal('expected');
+        });
+
+        it('should work on first level with tokens', function () {
+            var obj = {
+                existing: 'bla'
+            };
+
+            pointer.set(obj, immutable(['first-level']), 'expected');
+            obj['first-level'].should.equal('expected');
+        });
+
+        it('should create arrays for numeric reference tokens and objects for other tokens', function () {
+            var obj = [];
+            pointer.set(obj, '/0/test/0', 'expected');
+            Array.isArray(obj).should.be.true;
+            Array.isArray(obj[0]).should.be.false;
+            Array.isArray(obj[0].test).should.be.true;
+        });
+
+        it('should create arrays for numeric reference tokens and objects for other tokens when tokens are passed', function () {
+            var obj = [];
+            pointer.set(obj, immutable(['0', 'test', '0']), 'expected');
+            Array.isArray(obj).should.be.true;
+            Array.isArray(obj[0]).should.be.false;
+            Array.isArray(obj[0].test).should.be.true;
+        });
+
+        it('should create arrays for - and reference the (nonexistent) member after the last array element.', function () {
+            var obj = ['foo'];
+            pointer.set(obj, '/-/test/-', 'expected');
+            Array.isArray(obj).should.be.true;
+            obj.should.have.length(2);
+            Array.isArray(obj[1].test).should.be.true;
+            obj[1].test.should.have.length(1);
+            obj[1].test[0].should.equal('expected');
+        });
+
+        it('should create arrays for - and reference the (nonexistent) member after the last array element  when tokens are passed.', function () {
+            var obj = ['foo'];
+            pointer.set(obj, immutable(['-', 'test', '-']), 'expected');
+            Array.isArray(obj).should.be.true;
+            obj.should.have.length(2);
+            Array.isArray(obj[1].test).should.be.true;
+            obj[1].test.should.have.length(1);
+            obj[1].test[0].should.equal('expected');
+        });
+    });
+
+    describe('#remove', function () {
+        each(Object.keys(rfcValues), function (p) {
+            if (p === '' || p === '/foo/0') return;
+
+            it('should work for "' + p + '"', function () {
+                pointer.remove(rfcExample, p);
+                expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
+            });
+        });
+
+        it('should work for "/foo/0"', function () {
+            var p = '/foo/0';
+            pointer.remove(rfcExample, p);
+            expect(pointer.get(rfcExample, p)).to.equal('baz');
+        });
+
+        it('should work for "/foo/1"', function () {
+            var p = '/foo/1';
+            pointer.remove(rfcExample, p);
+            expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
+        });
+
+        each(Object.keys(rfcParsed), function (p) {
+            if (p === '' || p === '/foo/0') return;
+
+            it('should work for ' + JSON.stringify(rfcParsed[p].tokens), function () {
+                pointer.remove(rfcExample, immutable(rfcParsed[p].tokens));
+                expect(function() {
+                    pointer.get(rfcExample, immutable(rfcParsed[p].tokens));
+                }).to.throw(Error);
+            });
+        });
+
+        it('should work for ["foo","0"]', function () {
+            var p = immutable(['foo', '0']);
+            pointer.remove(rfcExample, p);
+            expect(pointer.get(rfcExample, p)).to.equal('baz');
+        });
+
+        it('should work for ["foo","1"]', function () {
+            var p = immutable(['foo', '1']);
+            pointer.remove(rfcExample, p);
+            expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
+        });
+
+    });
+
+    describe('#dict', function () {
+
+        it('should return a dictionary (pointer -> value)', function () {
+            var obj = {
+                    bla: {
+                        test: 'expected'
+                    },
+                    abc: 'bla'
+                },
+                dict = pointer.dict(obj);
+
+            dict['/bla/test'].should.equal('expected');
+            dict['/abc'].should.equal('bla');
+        });
+
+        it('should work with arrays', function () {
+            var obj = {
+                    "users": [
+                        {"name": "example 1"},
+                        {"name": "example 2"}
+                    ]
+                },
+                dict = pointer.dict(obj),
+                pointers = Object.keys(dict);
+
+            pointers.length.should.equal(2);
+            pointers[0].should.equal('/users/0/name');
+            pointers[1].should.equal('/users/1/name');
+        });
+
+        it('should work with other arrays', function () {
+            var obj = {
+                    bla : {
+                        bli : [4, 5, 6]
+                    }
+                },
+                dict = pointer.dict(obj);
+            dict['/bla/bli/0'].should.equal(4);
+            dict['/bla/bli/1'].should.equal(5);
+            dict['/bla/bli/2'].should.equal(6);
+        });
+    });
+
+    describe('#has', function () {
+
+        it('should return true when the pointer exists', function () {
+            var obj = {
+                    bla: {
+                        test: 'expected'
+                    },
+                    foo: [['hello']],
+                    abc: 'bla'
+                };
+            pointer.has(obj, '/bla').should.be.true;
+            pointer.has(obj, '/abc').should.be.true;
+            pointer.has(obj, '/foo/0/0').should.be.true;
+            pointer.has(obj, '/bla/test').should.be.true;
+        });
+
+        it('should return true when the tokens point to value', function () {
+            var obj = {
+                    bla: {
+                        test: 'expected'
+                    },
+                    foo: [['hello']],
+                    abc: 'bla'
+                };
+            pointer.has(obj, immutable(['bla'])).should.be.true;
+            pointer.has(obj, immutable(['abc'])).should.be.true;
+            pointer.has(obj, immutable(['foo', '0', '0'])).should.be.true;
+            pointer.has(obj, immutable(['bla', 'test'])).should.be.true;
+        });
+
+        it('should return false when the pointer does not exist', function () {
+            var obj = {
+                bla: {
+                    test: 'expected'
+                },
+                abc: 'bla'
+            };
+            pointer.has(obj, '/not-existing').should.be.false;
+            pointer.has(obj, '/not-existing/bla').should.be.false;
+            pointer.has(obj, '/test/1/bla').should.be.false;
+            pointer.has(obj, '/bla/test1').should.be.false;
+        });
+
+        it('should return false when the tokens do not point to value', function () {
+            var obj = {
+                bla: {
+                    test: 'expected'
+                },
+                abc: 'bla'
+            };
+            pointer.has(obj, immutable(['not-existing'])).should.be.false;
+            pointer.has(obj, immutable(['not-existing', 'bla'])).should.be.false;
+            pointer.has(obj, immutable(['test', '1', 'bla'])).should.be.false;
+            pointer.has(obj, immutable(['bla', 'test1'])).should.be.false;
+        });
+    });
+
+    describe('#walk', function () {
+
+        it('should iterate over an object', function () {
+            pointer.walk({bla: {test: 'expected'}}, function (value, pointer) {
+                pointer.should.equal('/bla/test');
+                value.should.equal('expected');
+            });
+        });
+    });
+
+    describe('#parse', function () {
+
+        it('should work with top level path', function () {
+            pointer.parse('/bla')[0].should.equal('bla');
+            pointer.parse('/bla').length.should.equal(1);
+        });
+
+        it('should convert a pointer to an array of reference tokens', function () {
+            pointer.parse('/hello~0bla/test~1bla')[0].should.equal('hello~bla');
+            pointer.parse('/hello~0bla/test~1bla')[1].should.equal('test/bla');
+        });
+    });
+
+    describe('#compile', function () {
+
+        it('should build a json pointer from an array of reference tokens', function () {
+            pointer.compile(['hello~bla', 'test/bla']).should.equal('/hello~0bla/test~1bla');
+        });
+    });
+
+    describe('#parse and then #compile pointer', function () {
+        each(Object.keys(rfcValues), function (p) {
+
+            it('should equal for "' + p + '"', function () {
+                pointer.compile(immutable(pointer.parse(p))).should.equal(p);
+            });
+        });
+    });
+});
+
+describe('convenience api wrapper', function() {
+
+    it('should call #get when passed 2 args', function() {
+        var obj = {
+            existing: 'expected'
+        };
+
+        pointer(obj, '/existing');
+        obj.existing.should.equal('expected');
+    });
+
+    it('should call #get when passed 2 args with tokens', function() {
+        var obj = {
+            existing: 'expected'
+        };
+
+        pointer(obj, immutable(['existing']));
+        obj.existing.should.equal('expected');
+    });
+
+    it('should call #set when passed 3 args', function() {
+        var obj = {
+            existing: 'bla'
+        };
+
+        pointer(obj, '/new-value/bla', 'expected');
+        obj['new-value'].bla.should.equal('expected');
+    });
+
+    it('should call #set when passed 3 args with tokens', function() {
+        var obj = {
+            existing: 'bla'
+        };
+
+        pointer(obj, immutable(['new-value', 'bla']), 'expected');
+        obj['new-value'].bla.should.equal('expected');
+    });
+
+    it('should return a partially applied function when passed 1 arg', function() {
+        var obj = {
+                existing: 'bla'
+            },
+            objPointer = pointer(obj);
+
+        objPointer('/new-value/bla', 'expected');
+        objPointer('/new-value').bla.should.equal('expected');
+        objPointer(['new-value', 'bla'], 'expected');
+        objPointer(['new-value']).bla.should.equal('expected');
+    });
+
+    it('should support chainable oo-style', function() {
+        var obj = {
+                existing: 'bla'
+            },
+            objPointer = pointer(obj);
+
+        objPointer.set('/oo-style', 'bla').set('/example/0', 'bla2');
+        objPointer.get('/oo-style').should.equal('bla');
+        objPointer.get('/example/0').should.equal('bla2');
+    });
+
+    it('should support chainable oo-style with tokens', function() {
+        var obj = {
+                existing: 'bla'
+            },
+            objPointer = pointer(obj);
+
+        objPointer.set(immutable(['oo-style']), 'bla').set(['example', '0'], 'bla2');
+        objPointer.get(immutable(['oo-style'])).should.equal('bla');
+        objPointer.get(immutable(['example', '0'])).should.equal('bla2');
+    });
+
+    it('should not set __proto__', function () {
+        var obj = {}, objPointer = pointer(obj);
+        expect(obj.polluted).to.be.undefined();
+        objPointer.set('/__proto__/polluted', true);
+        expect(obj.polluted).to.be.undefined();
+        var obj2 = {};
+        expect(obj2.polluted).to.be.undefined();
+    });
+
+    it('should not set __proto__ (array)', function () {
+        var obj = {}, objPointer = pointer(obj);
+        expect(obj.polluted).to.be.undefined();
+        objPointer.set([['__proto__'], 'polluted'], true);
+        expect(obj.polluted).to.be.undefined();
+        var obj2 = {};
+        expect(obj2.polluted).to.be.undefined();
+    });
+
+    it('should not set prototype', function () {
+        var obj = {}, objPointer = pointer(obj);
+        expect(obj.polluted).to.be.undefined();
+        objPointer.set('/prototype/polluted', true);
+        expect(obj.polluted).to.be.undefined();
+        var obj2 = {};
+        expect(obj2.polluted).to.be.undefined();
+    });
+
+    it('should not set constructor', function () {
+        var obj = {}, objPointer = pointer(obj);
+        expect(obj.polluted).to.be.undefined();
+        objPointer.set('/constructor/prototype/polluted', true);
+        expect(obj.polluted).to.be.undefined();
+        var obj2 = {};
+        expect(obj2.polluted).to.be.undefined();
+    });
+});

Fixed code: node_modules
components
build
.nyc_output
coverage
.idea
components
build
language: node_js
sudo: false
node_js:
  - "0.10"
  - "0.12"
  - "4"
  - "5"
  - "6"
after_script:
  - coveralls < coverage/lcov.info
The MIT License (MIT)

Copyright (c) 2016 Manuel Stofer

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
install:
	@component install

build: install
	@echo build ...
	@component build

test:
	@echo test
	@./node_modules/.bin/mocha \
		--require chai \
		--reporter spec

test-phantom: install build
	@echo test in browser
	@mocha-phantomjs test/test-runner.html

.PHONY: test build
# json-pointer

[![Build Status](https://travis-ci.org/manuelstofer/json-pointer.svg?branch=master)](https://travis-ci.org/manuelstofer/json-pointer)
[![npm version](https://badge.fury.io/js/json-pointer.svg)](https://www.npmjs.com/package/json-pointer)
[![Coverage Status](https://coveralls.io/repos/github/manuelstofer/json-pointer/badge.svg?branch=master&service=github)](https://coveralls.io/github/manuelstofer/json-pointer?branch=master)

Some utilities for JSON pointers described by RFC 6901

Provides some additional stuff i needed but is not included in [node-jsonpointer](https://github.com/janl/node-jsonpointer)


## Installation

[node.js](http://nodejs.org)

```bash
$ npm install json-pointer
```


## API

```Javascript
var pointer = require('json-pointer');
```


### .get(object, pointer)

Looks up a JSON pointer in an object.

Array of reference tokens, e.g. returned by api.parse, can be passed as a pointer to .get, .set and .remove methods.

```Javascript
var obj = {
    example: {
        bla: 'hello'
    }
};
pointer.get(obj, '/example/bla');
```


### .set(object, pointer, value)

Sets a new value on object at the location described by pointer.

```Javascript
var obj = {};
pointer.set(obj, '/example/bla', 'hello');
```


### .remove(object, pointer)

Removes an attribute of object referenced by pointer.

```Javascript
var obj = {
    example: 'hello'
};
pointer.remove(obj, '/example');
// obj -> {}
```


### .dict(object)

Creates a dictionary object (pointer -> value).

```Javascript
var obj = {
    hello: {bla: 'example'}
};
pointer.dict(obj);

// Returns:
// {
//    '/hello/bla': 'example'
// }
```


### .walk(object, iterator)

Just like:

```Javascript
each(pointer.dict(obj), iterator);
```


### .has(object, pointer)

Tests if an object has a value for a JSON pointer.

```Javascript
var obj = {
    bla: 'hello'
};

pointer.has(obj, '/bla');               // -> true
pointer.has(obj, '/non/existing');      // -> false
```


### .escape(str)

Escapes a reference token.

```Javascript
pointer.escape('hello~bla');            // -> 'hello~0bla'
pointer.escape('hello/bla');            // -> 'hello~1bla'
```


### .unescape(str)

Unescape a reference token.

```Javascript
pointer.unescape('hello~0bla');         // -> 'hello~bla'
pointer.unescape('hello~1bla');         // -> 'hello/bla'
```


### .parse(str)

Converts a JSON pointer into an array of reference tokens.

```Javascript
pointer.parse('/hello/bla');            // -> ['hello', 'bla']
```


### .compile(array)

Builds a json pointer from an array of reference tokens.

```Javascript
pointer.compile(['hello', 'bla']);      // -> '/hello/bla'
```


### pointer(object, [pointer, [value]])

Convenience wrapper around the api.

```Javascript
pointer(object)                 // bind object
pointer(object, pointer)        // get
pointer(object, pointer, value) // set
```

The wrapper supports chainable object oriented style.

```Javascript
var obj = {anything: 'bla'};
var objPointer = pointer(obj);
objPointer.set('/example', 'bla').dict();
```
'use strict';

var each = require('foreach');
module.exports = api;


/**
 * Convenience wrapper around the api.
 * Calls `.get` when called with an `object` and a `pointer`.
 * Calls `.set` when also called with `value`.
 * If only supplied `object`, returns a partially applied function, mapped to the object.
 *
 * @param {Object} obj
 * @param {String|Array} pointer
 * @param value
 * @returns {*}
 */

function api (obj, pointer, value) {
    // .set()
    if (arguments.length === 3) {
        return api.set(obj, pointer, value);
    }
    // .get()
    if (arguments.length === 2) {
        return api.get(obj, pointer);
    }
    // Return a partially applied function on `obj`.
    var wrapped = api.bind(api, obj);

    // Support for oo style
    for (var name in api) {
        if (api.hasOwnProperty(name)) {
            wrapped[name] = api[name].bind(wrapped, obj);
        }
    }
    return wrapped;
}


/**
 * Lookup a json pointer in an object
 *
 * @param {Object} obj
 * @param {String|Array} pointer
 * @returns {*}
 */
api.get = function get (obj, pointer) {
    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer);

    for (var i = 0; i < refTokens.length; ++i) {
        var tok = refTokens[i];
        if (!(typeof obj == 'object' && tok in obj)) {
            throw new Error('Invalid reference token: ' + tok);
        }
        obj = obj[tok];
    }
    return obj;
};

/**
 * Sets a value on an object
 *
 * @param {Object} obj
 * @param {String|Array} pointer
 * @param value
 */
api.set = function set (obj, pointer, value) {
    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer),
      nextTok = refTokens[0];

    if (refTokens.length === 0) {
      throw Error('Can not set the root object');
    }

    for (var i = 0; i < refTokens.length - 1; ++i) {
        var tok = refTokens[i];
        if (typeof tok !== 'string' && typeof tok !== 'number') {
          tok = String(tok)
        }
        if (tok === "__proto__" || tok === "constructor" || tok === "prototype") {
            continue
        }
        if (tok === '-' && Array.isArray(obj)) {
          tok = obj.length;
        }
        nextTok = refTokens[i + 1];

        if (!(tok in obj)) {
            if (nextTok.match(/^(\d+|-)$/)) {
                obj[tok] = [];
            } else {
                obj[tok] = {};
            }
        }
        obj = obj[tok];
    }
    if (nextTok === '-' && Array.isArray(obj)) {
      nextTok = obj.length;
    }
    obj[nextTok] = value;
    return this;
};

/**
 * Removes an attribute
 *
 * @param {Object} obj
 * @param {String|Array} pointer
 */
api.remove = function (obj, pointer) {
    var refTokens = Array.isArray(pointer) ? pointer : api.parse(pointer);
    var finalToken = refTokens[refTokens.length -1];
    if (finalToken === undefined) {
        throw new Error('Invalid JSON pointer for remove: "' + pointer + '"');
    }

    var parent = api.get(obj, refTokens.slice(0, -1));
    if (Array.isArray(parent)) {
      var index = +finalToken;
      if (finalToken === '' && isNaN(index)) {
        throw new Error('Invalid array index: "' + finalToken + '"');
      }

      Array.prototype.splice.call(parent, index, 1);
    } else {
      delete parent[finalToken];
    }
};

/**
 * Returns a (pointer -> value) dictionary for an object
 *
 * @param obj
 * @param {function} descend
 * @returns {}
 */
api.dict = function dict (obj, descend) {
    var results = {};
    api.walk(obj, function (value, pointer) {
        results[pointer] = value;
    }, descend);
    return results;
};

/**
 * Iterates over an object
 * Iterator: function (value, pointer) {}
 *
 * @param obj
 * @param {function} iterator
 * @param {function} descend
 */
api.walk = function walk (obj, iterator, descend) {
    var refTokens = [];

    descend = descend || function (value) {
        var type = Object.prototype.toString.call(value);
        return type === '[object Object]' || type === '[object Array]';
    };

    (function next (cur) {
        each(cur, function (value, key) {
            refTokens.push(String(key));
            if (descend(value)) {
                next(value);
            } else {
                iterator(value, api.compile(refTokens));
            }
            refTokens.pop();
        });
    }(obj));
};

/**
 * Tests if an object has a value for a json pointer
 *
 * @param obj
 * @param pointer
 * @returns {boolean}
 */
api.has = function has (obj, pointer) {
    try {
        api.get(obj, pointer);
    } catch (e) {
        return false;
    }
    return true;
};

/**
 * Escapes a reference token
 *
 * @param str
 * @returns {string}
 */
api.escape = function escape (str) {
    return str.toString().replace(/~/g, '~0').replace(/\//g, '~1');
};

/**
 * Unescapes a reference token
 *
 * @param str
 * @returns {string}
 */
api.unescape = function unescape (str) {
    return str.replace(/~1/g, '/').replace(/~0/g, '~');
};

/**
 * Converts a json pointer into a array of reference tokens
 *
 * @param pointer
 * @returns {Array}
 */
api.parse = function parse (pointer) {
    if (pointer === '') { return []; }
    if (pointer.charAt(0) !== '/') { throw new Error('Invalid JSON pointer: ' + pointer); }
    return pointer.substring(1).split(/\//).map(api.unescape);
};

/**
 * Builds a json pointer from a array of reference tokens
 *
 * @param refTokens
 * @returns {string}
 */
api.compile = function compile (refTokens) {
    if (refTokens.length === 0) { return ''; }
    return '/' + refTokens.map(api.escape).join('/');
};
{
  "name": "json-pointer",
  "description": "Some utilities for JSON pointers described by RFC 6901",
  "version": "0.6.1",
  "author": "Manuel Stofer <manuel@smallpdf.com>",
  "license": "MIT",
  "dependencies": {
    "foreach": "^2.0.4"
  },
  "homepage": "https://github.com/manuelstofer/json-pointer",
  "repository": {
    "type": "git",
    "url": "git://github.com/manuelstofer/json-pointer.git"
  },
  "devDependencies": {
    "chai": "^1.9.1",
    "coveralls": "^2.11.14",
    "mocha": "^1.9.0",
    "nyc": "^8.3.1",
    "seamless-immutable": "^5.1.1"
  },
  "scripts": {
    "test": "nyc make test"
  },
  "nyc": {
    "exclude": [
      "test",
      "node_modules"
    ],
    "reporter": [
      "lcov",
      "text-summary"
    ]
  }
}
<html>
<head>
    <title> Tests </title>
    <meta charset="utf-8"/>
    <link rel="stylesheet" href="../node_modules/mocha/mocha.css" />
</head>
<body>
<div id="mocha"></div>
<script src="../node_modules/mocha/mocha.js"></script>
<script src="../node_modules/chai/chai.js"></script>
<script>
    mocha.ui('bdd');
    mocha.reporter('html');
</script>
<script src="../build/build.js"></script>
<script>
    var pointer = require('json-pointer'),
        each    = require('manuelstofer-foreach');
</script>
<script src="test.js"></script>
<script>
    if (window.mochaPhantomJS) { mochaPhantomJS.run(); }
    else { mocha.run(); }
</script>
</body>
</html>
/*global describe, it, beforeEach*/
if (typeof pointer === 'undefined') {
    var pointer   = require('..'),
        chai      = require('chai'),
        each      = require('foreach'),
        immutable = require('seamless-immutable');
}

var expect = chai.expect;


chai.should();

describe('json-api', function () {
    'use strict';

    var rfcExample,
        rfcValues,
        rfcParsed;

    function resetExamples() {
        rfcExample = {
            "foo":      ["bar", "baz"],
            "bar":      {"baz": 10},
            "":         0,
            "a/b":      1,
            "c%d":      2,
            "e^f":      3,
            "g|h":      4,
            "i\\j":     5,
            "k\"l":     6,
            " ":        7,
            "m~n":      8
        };

        rfcValues = {
            "":         rfcExample,
            "/foo":     rfcExample.foo,
            "/foo/0":   "bar",
            "/bar":     rfcExample.bar,
            "/bar/baz": 10,
            "/":        0,
            "/a~1b":    1,
            "/c%d":     2,
            "/e^f":     3,
            "/g|h":     4,
            "/i\\j":    5,
            "/k\"l":    6,
            "/ ":       7,
            "/m~0n":    8
        };

        rfcParsed = {
            "":         { tokens: [],             value: rfcExample },
            "/foo":     { tokens: ["foo"],        value: rfcExample.foo },
            "/foo/0":   { tokens: ["foo", "0"],   value: "bar" },
            "/bar":     { tokens: ["bar"],        value: rfcExample.bar },
            "/bar/baz": { tokens: ["bar", "baz"], value: 10 },
            "/":        { tokens: [""],           value: 0 },
            "/a~1b":    { tokens: ["a/b"],        value: 1 },
            "/c%d":     { tokens: ["c%d"],        value: 2 },
            "/e^f":     { tokens: ["e^f"],        value: 3 },
            "/g|h":     { tokens: ["g|h"],        value: 4 },
            "/i\\j":    { tokens: ["i\\j"],       value: 5 },
            "/k\"l":    { tokens: ["k\"l"],       value: 6 },
            "/ ":       { tokens: [" "],          value: 7 },
            "/m~0n":    { tokens: ["m~n"],        value: 8 }
        };
    }
    resetExamples();
    beforeEach(resetExamples);

    describe('#get', function () {

        it('should work for root element', function () {
            var obj = {};
            pointer.get(obj, '').should.equal(obj);
        });

        each(Object.keys(rfcValues), function (p) {
            it('should work for "' + p + '"', function () {
                var expectedValue = rfcValues[p];
                pointer.get(rfcExample, p).should.equal(expectedValue);
            });
        });

        each(Object.keys(rfcParsed), function (p) {
            var tokens = immutable(rfcParsed[p].tokens);
            it('should work for ' + JSON.stringify(tokens), function () {
                var expectedValue = rfcParsed[p].value;
                pointer.get(rfcExample, tokens).should.equal(expectedValue);
            });
        });

        it('should work for with inherited properties', function () {
            function O () {}
            O.prototype.x = 10;
            pointer.get(new O, '/x').should.equal(10);
            pointer.get(Object.create({ x: 10 }), '/x').should.equal(10)
        });
    });

    describe('#set', function () {
        it('should throw when try to set the root object', function () {
            expect(pointer.set.bind(pointer, {}, '', 'bla')).to.throw(Error);
        });

        it('should set a value on an object with pointer', function () {
            var obj = {
                existing: 'bla'
            };

            pointer.set(obj, '/new-value/bla', 'expected');
            obj['new-value'].bla.should.equal('expected');
        });

        it('should set a value on an object with tokens', function () {
            var obj = {
                existing: 'bla'
            };

            pointer.set(obj, immutable(['new-value', 'bla']), 'expected');
            obj['new-value'].bla.should.equal('expected');
        });

        it('should work on first level with pointer', function () {
            var obj = {
                existing: 'bla'
            };

            pointer.set(obj, '/first-level', 'expected');
            obj['first-level'].should.equal('expected');
        });

        it('should work on first level with tokens', function () {
            var obj = {
                existing: 'bla'
            };

            pointer.set(obj, immutable(['first-level']), 'expected');
            obj['first-level'].should.equal('expected');
        });

        it('should create arrays for numeric reference tokens and objects for other tokens', function () {
            var obj = [];
            pointer.set(obj, '/0/test/0', 'expected');
            Array.isArray(obj).should.be.true;
            Array.isArray(obj[0]).should.be.false;
            Array.isArray(obj[0].test).should.be.true;
        });

        it('should create arrays for numeric reference tokens and objects for other tokens when tokens are passed', function () {
            var obj = [];
            pointer.set(obj, immutable(['0', 'test', '0']), 'expected');
            Array.isArray(obj).should.be.true;
            Array.isArray(obj[0]).should.be.false;
            Array.isArray(obj[0].test).should.be.true;
        });

        it('should create arrays for - and reference the (nonexistent) member after the last array element.', function () {
            var obj = ['foo'];
            pointer.set(obj, '/-/test/-', 'expected');
            Array.isArray(obj).should.be.true;
            obj.should.have.length(2);
            Array.isArray(obj[1].test).should.be.true;
            obj[1].test.should.have.length(1);
            obj[1].test[0].should.equal('expected');
        });

        it('should create arrays for - and reference the (nonexistent) member after the last array element  when tokens are passed.', function () {
            var obj = ['foo'];
            pointer.set(obj, immutable(['-', 'test', '-']), 'expected');
            Array.isArray(obj).should.be.true;
            obj.should.have.length(2);
            Array.isArray(obj[1].test).should.be.true;
            obj[1].test.should.have.length(1);
            obj[1].test[0].should.equal('expected');
        });
    });

    describe('#remove', function () {
        each(Object.keys(rfcValues), function (p) {
            if (p === '' || p === '/foo/0') return;

            it('should work for "' + p + '"', function () {
                pointer.remove(rfcExample, p);
                expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
            });
        });

        it('should work for "/foo/0"', function () {
            var p = '/foo/0';
            pointer.remove(rfcExample, p);
            expect(pointer.get(rfcExample, p)).to.equal('baz');
        });

        it('should work for "/foo/1"', function () {
            var p = '/foo/1';
            pointer.remove(rfcExample, p);
            expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
        });

        each(Object.keys(rfcParsed), function (p) {
            if (p === '' || p === '/foo/0') return;

            it('should work for ' + JSON.stringify(rfcParsed[p].tokens), function () {
                pointer.remove(rfcExample, immutable(rfcParsed[p].tokens));
                expect(function() {
                    pointer.get(rfcExample, immutable(rfcParsed[p].tokens));
                }).to.throw(Error);
            });
        });

        it('should work for ["foo","0"]', function () {
            var p = immutable(['foo', '0']);
            pointer.remove(rfcExample, p);
            expect(pointer.get(rfcExample, p)).to.equal('baz');
        });

        it('should work for ["foo","1"]', function () {
            var p = immutable(['foo', '1']);
            pointer.remove(rfcExample, p);
            expect(pointer.get.bind(pointer, rfcExample, p)).to.throw(Error);
        });

    });

    describe('#dict', function () {

        it('should return a dictionary (pointer -> value)', function () {
            var obj = {
                    bla: {
                        test: 'expected'
                    },
                    abc: 'bla'
                },
                dict = pointer.dict(obj);

            dict['/bla/test'].should.equal('expected');
            dict['/abc'].should.equal('bla');
        });

        it('should work with arrays', function () {
            var obj = {
                    "users": [
                        {"name": "example 1"},
                        {"name": "example 2"}
                    ]
                },
                dict = pointer.dict(obj),
                pointers = Object.keys(dict);

            pointers.length.should.equal(2);
            pointers[0].should.equal('/users/0/name');
            pointers[1].should.equal('/users/1/name');
        });

        it('should work with other arrays', function () {
            var obj = {
                    bla : {
                        bli : [4, 5, 6]
                    }
                },
                dict = pointer.dict(obj);
            dict['/bla/bli/0'].should.equal(4);
            dict['/bla/bli/1'].should.equal(5);
            dict['/bla/bli/2'].should.equal(6);
        });
    });

    describe('#has', function () {

        it('should return true when the pointer exists', function () {
            var obj = {
                    bla: {
                        test: 'expected'
                    },
                    foo: [['hello']],
                    abc: 'bla'
                };
            pointer.has(obj, '/bla').should.be.true;
            pointer.has(obj, '/abc').should.be.true;
            pointer.has(obj, '/foo/0/0').should.be.true;
            pointer.has(obj, '/bla/test').should.be.true;
        });

        it('should return true when the tokens point to value', function () {
            var obj = {
                    bla: {
                        test: 'expected'
                    },
                    foo: [['hello']],
                    abc: 'bla'
                };
            pointer.has(obj, immutable(['bla'])).should.be.true;
            pointer.has(obj, immutable(['abc'])).should.be.true;
            pointer.has(obj, immutable(['foo', '0', '0'])).should.be.true;
            pointer.has(obj, immutable(['bla', 'test'])).should.be.true;
        });

        it('should return false when the pointer does not exist', function () {
            var obj = {
                bla: {
                    test: 'expected'
                },
                abc: 'bla'
            };
            pointer.has(obj, '/not-existing').should.be.false;
            pointer.has(obj, '/not-existing/bla').should.be.false;
            pointer.has(obj, '/test/1/bla').should.be.false;
            pointer.has(obj, '/bla/test1').should.be.false;
        });

        it('should return false when the tokens do not point to value', function () {
            var obj = {
                bla: {
                    test: 'expected'
                },
                abc: 'bla'
            };
            pointer.has(obj, immutable(['not-existing'])).should.be.false;
            pointer.has(obj, immutable(['not-existing', 'bla'])).should.be.false;
            pointer.has(obj, immutable(['test', '1', 'bla'])).should.be.false;
            pointer.has(obj, immutable(['bla', 'test1'])).should.be.false;
        });
    });

    describe('#walk', function () {

        it('should iterate over an object', function () {
            pointer.walk({bla: {test: 'expected'}}, function (value, pointer) {
                pointer.should.equal('/bla/test');
                value.should.equal('expected');
            });
        });
    });

    describe('#parse', function () {

        it('should work with top level path', function () {
            pointer.parse('/bla')[0].should.equal('bla');
            pointer.parse('/bla').length.should.equal(1);
        });

        it('should convert a pointer to an array of reference tokens', function () {
            pointer.parse('/hello~0bla/test~1bla')[0].should.equal('hello~bla');
            pointer.parse('/hello~0bla/test~1bla')[1].should.equal('test/bla');
        });
    });

    describe('#compile', function () {

        it('should build a json pointer from an array of reference tokens', function () {
            pointer.compile(['hello~bla', 'test/bla']).should.equal('/hello~0bla/test~1bla');
        });
    });

    describe('#parse and then #compile pointer', function () {
        each(Object.keys(rfcValues), function (p) {

            it('should equal for "' + p + '"', function () {
                pointer.compile(immutable(pointer.parse(p))).should.equal(p);
            });
        });
    });
});

describe('convenience api wrapper', function() {

    it('should call #get when passed 2 args', function() {
        var obj = {
            existing: 'expected'
        };

        pointer(obj, '/existing');
        obj.existing.should.equal('expected');
    });

    it('should call #get when passed 2 args with tokens', function() {
        var obj = {
            existing: 'expected'
        };

        pointer(obj, immutable(['existing']));
        obj.existing.should.equal('expected');
    });

    it('should call #set when passed 3 args', function() {
        var obj = {
            existing: 'bla'
        };

        pointer(obj, '/new-value/bla', 'expected');
        obj['new-value'].bla.should.equal('expected');
    });

    it('should call #set when passed 3 args with tokens', function() {
        var obj = {
            existing: 'bla'
        };

        pointer(obj, immutable(['new-value', 'bla']), 'expected');
        obj['new-value'].bla.should.equal('expected');
    });

    it('should return a partially applied function when passed 1 arg', function() {
        var obj = {
                existing: 'bla'
            },
            objPointer = pointer(obj);

        objPointer('/new-value/bla', 'expected');
        objPointer('/new-value').bla.should.equal('expected');
        objPointer(['new-value', 'bla'], 'expected');
        objPointer(['new-value']).bla.should.equal('expected');
    });

    it('should support chainable oo-style', function() {
        var obj = {
                existing: 'bla'
            },
            objPointer = pointer(obj);

        objPointer.set('/oo-style', 'bla').set('/example/0', 'bla2');
        objPointer.get('/oo-style').should.equal('bla');
        objPointer.get('/example/0').should.equal('bla2');
    });

    it('should support chainable oo-style with tokens', function() {
        var obj = {
                existing: 'bla'
            },
            objPointer = pointer(obj);

        objPointer.set(immutable(['oo-style']), 'bla').set(['example', '0'], 'bla2');
        objPointer.get(immutable(['oo-style'])).should.equal('bla');
        objPointer.get(immutable(['example', '0'])).should.equal('bla2');
    });

    it('should not set __proto__', function () {
        var obj = {}, objPointer = pointer(obj);
        expect(obj.polluted).to.be.undefined();
        objPointer.set('/__proto__/polluted', true);
        expect(obj.polluted).to.be.undefined();
        var obj2 = {};
        expect(obj2.polluted).to.be.undefined();
    });

    it('should not set __proto__ (array)', function () {
        var obj = {}, objPointer = pointer(obj);
        expect(obj.polluted).to.be.undefined();
        objPointer.set([['__proto__'], 'polluted'], true);
        expect(obj.polluted).to.be.undefined();
        var obj2 = {};
        expect(obj2.polluted).to.be.undefined();
    });

    it('should not set prototype', function () {
        var obj = {}, objPointer = pointer(obj);
        expect(obj.polluted).to.be.undefined();
        objPointer.set('/prototype/polluted', true);
        expect(obj.polluted).to.be.undefined();
        var obj2 = {};
        expect(obj2.polluted).to.be.undefined();
    });

    it('should not set constructor', function () {
        var obj = {}, objPointer = pointer(obj);
        expect(obj.polluted).to.be.undefined();
        objPointer.set('/constructor/prototype/polluted', true);
        expect(obj.polluted).to.be.undefined();
        var obj2 = {};
        expect(obj2.polluted).to.be.undefined();
    });
});