Launch Week Day 5: Introducing Reachability for PHP.Learn More
Socket
Book a DemoSign in
Socket

bourne

Package Overview
Dependencies
Maintainers
1
Versions
9
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

bourne - npm Package Compare versions

Comparing version
0.4.0
to
1.0.0
+74
lib/index.js
'use strict';
const internals = {};
exports.parse = function (text, ...args) {
// Normalize arguments
const firstOptions = typeof args[0] === 'object' && args[0];
const reviver = args.length > 1 || !firstOptions ? args[0] : undefined;
const options = (args.length > 1 && args[1]) || firstOptions || {};
// Parse normally, allowing exceptions
const obj = JSON.parse(text, reviver);
// options.protoAction: 'error' (default) / 'remove' / 'ignore'
if (options.protoAction === 'ignore') {
return obj;
}
// Ignore null and non-objects
if (!obj ||
typeof obj !== 'object') {
return obj;
}
// Check original string for potential exploit
if (text.indexOf('"__proto__"') === -1) {
return obj;
}
// Scan result for proto keys
internals.scan(obj, options);
return obj;
};
internals.scan = function (obj, options) {
let next = [obj];
while (next.length) {
const nodes = next;
next = [];
for (const node of nodes) {
if (node.hasOwnProperty('__proto__')) {
if (options.protoAction !== 'remove') {
throw new SyntaxError('Object contains forbidden prototype property');
}
delete node.__proto__;
}
for (const key in node) {
const value = node[key];
if (value &&
typeof value === 'object') {
next.push(node[key]);
}
}
}
}
};
Copyright (c) 2019, Project contributors.
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.
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
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 HOLDER 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.
+17
-31
{
"name": "bourne",
"description": "A simple serverless database stored in a JSON file.",
"version": "0.4.0",
"homepage": "https://github.com/andreww8088/bourne",
"author": {
"name": "Andrew Burgess",
"email": "andrew@burgess.io",
"url": "http://andrewburgess.ca"
"description": "JSON parse with prototype poisoning protection",
"version": "1.0.0",
"repository": "git://github.com/hapijs/bourne",
"main": "lib/index.js",
"keywords": [
"JSON",
"parse",
"safe",
"prototype"
],
"dependencies": {
},
"repository": {
"type": "git",
"url": "git://github.com/andreww8088/bourne.git"
"devDependencies": {
"code": "5.x.x",
"lab": "18.x.x"
},
"bugs": {
"url": "https://github.com/andreww8088/bourne/issues"
},
"licenses": [
{
"type": "MIT",
"url": "https://github.com/andreww8088/bourne/blob/master/LICENSE-MIT"
}
],
"main": "lib/bourne",
"engines": {
"node": ">= 0.8.0"
},
"scripts": {
"test": "grunt nodeunit"
"test": "lab -a code -t 100 -L",
"test-cov-html": "lab -a code -r html -o coverage.html"
},
"devDependencies": {
"grunt-contrib-jshint": "~0.6.4",
"grunt-contrib-nodeunit": "~0.2.0",
"grunt-contrib-watch": "~0.5.3",
"grunt": "~0.4.2"
},
"keywords": []
"license": "BSD-3-Clause"
}
+32
-18

@@ -1,27 +0,41 @@

# bourne
# Bourne. JSON Bourne.
A simple serverless database stored in a JSON file.
`JSON.parse()` drop-in replacement with prototype poisoning protection
## Getting Started
Install the module with: `npm install bourne`
## Introduction
```javascript
var bourne = require('bourne');
bourne.awesome(); // "awesome"
Consider this:
```
> const a = '{"__proto__":{ "b":5}}';
'{"__proto__":{ "b":5}}'
## Documentation
_(Coming soon)_
> const b = JSON.parse(a);
{ __proto__: { b: 5 } }
## Examples
_(Coming soon)_
> b.b;
undefined
## Contributing
In lieu of a formal styleguide, take care to maintain the existing coding style. Add unit tests for any new or changed functionality. Lint and test your code using [Grunt](http://gruntjs.com/).
> const c = Object.assign({}, b);
{}
## Release History
_(Nothing yet)_
> c.b
5
```
## License
Copyright (c) 2014 Andrew Burgess
Licensed under the MIT license.
The problem is that `JSON.parse()` retains the `__proto__` property as a plain object key. By
itself, this is not a security issue. However, as soon as that object is assigned to another or
iterated on and values copied, the `__proto__` property leaks and becomes the object's prototype.
## API
### `Bourne.parse(text, [reviver], [options])`
Parses a given JSON-formatted text into an object where:
- `text` - the JSON text string.
- `reviver` - the `JSON.parse()` optional `reviver` argument.
- `options` - optional configuration object where:
- `protoAction` - optional string with one of:
- `'error'` - throw a `SyntaxError` when a `__proto__` key is found. This is the default value.
- `'remove'` - deletes any `__proto__` keys from the result object.
- `'ignore'` - skips all validation (same as calling `JSON.parse()` directly).
{
"curly": true,
"eqeqeq": true,
"immed": true,
"latedef": "nofunc",
"newcap": true,
"noarg": true,
"sub": true,
"undef": true,
"unused": true,
"boss": true,
"eqnull": true,
"node": true
}
'use strict';
module.exports = function(grunt) {
// Project configuration.
grunt.initConfig({
nodeunit: {
files: ['test/**/*_test.js'],
},
jshint: {
options: {
jshintrc: '.jshintrc'
},
gruntfile: {
src: 'Gruntfile.js'
},
lib: {
src: ['lib/**/*.js']
},
test: {
src: ['test/**/*.js']
},
},
watch: {
gruntfile: {
files: '<%= jshint.gruntfile.src %>',
tasks: ['jshint:gruntfile']
},
lib: {
files: '<%= jshint.lib.src %>',
tasks: ['jshint:lib', 'nodeunit']
},
test: {
files: '<%= jshint.test.src %>',
tasks: ['jshint:test', 'nodeunit']
},
},
});
// These plugins provide necessary tasks.
grunt.loadNpmTasks('grunt-contrib-nodeunit');
grunt.loadNpmTasks('grunt-contrib-jshint');
grunt.loadNpmTasks('grunt-contrib-watch');
// Default task.
grunt.registerTask('default', ['jshint', 'nodeunit']);
};
/*
* bourne
* https://github.com/andreww8088/bourne
*
* Copyright (c) 2014 Andrew Burgess
* Licensed under the MIT license.
*/
(function () {
'use strict';
var store = {};
if (typeof require !== 'undefined') {
var fs = require('fs');
store.exists = fs.existsSync.bind(fs);
store.remove = fs.unlinkSync.bind(fs);
store.get = fs.readFileSync.bind(fs);
store.set = fs.writeFile.bind(fs);
} else {
store.exists = function (key) { return localStorage.getItem(key) !== null; };
store.remove = localStorage.removeItem.bind(localStorage);
store.get = localStorage.getItem.bind(localStorage);
store.set = function (key, value, callback) { localStorage.setItem(key, value); callback && callback(); };
}
function noStore() {
var data;
return {
exists: function () { return false; },
remove: function () { },
get : function () { return data; },
set : function (key, value, callback) { data = value; callback && callback(); }
};
}
var Bourne = function (name, options) {
options = options || {};
this.name = name;
this.data = [];
this._id = 1;
if (options.temp) {
this.store = noStore();
} else {
this.store = options.store || store;
}
if (this.store.exists(this.name)) {
if (options.reset) {
this.store.remove(name);
} else {
this.data = JSON.parse(this.store.get(name) || {});
this._id = Math.max.apply(Math, this.data.map(function (r) { return r.id; })) + 1;
}
} else {
this.store.set(this.name, JSON.stringify(this.data));
}
// Lazy Method
// if (this.store.exists(this.name) && options && !option.reset) {
// this.data = json.parse(this.store.get(name) || {});
// this._id = math.max.apply(math, this.data.map(function (r) { return r.id; }));
// }
};
Bourne.prototype.insert = function (record, callback) {
record.id = this._id++;
this.data.push(record);
this.store.set(this.name, JSON.stringify(this.data), function () {
callback && callback(null, record);
});
};
Bourne.prototype.insertAll = function (records, callback) {
var ids = [];
records.forEach(function (record) {
record.id = this._id++;
ids.push(record.id);
this.data.push(record);
}.bind(this));
this.store.set(this.name, JSON.stringify(this.data), function () {
if (callback) this.find({ id: { $in: ids }}, callback);
}.bind(this));
};
var operators = {
$lt: function (key, value, record) {
return record[key] < value;
},
$gt: function (key, value, record) {
return record[key] > value;
},
$lte: function (key, value, record) {
return record[key] <= value;
},
$in: function (key, values, record) {
for (var i = 0; i < values.length; i++) {
if (record[key] === values[i]) {
return true;
}
}
return false;
}
};
Bourne.operator = function (name, fn) {
if (operators[name]) {
throw 'operator "' + name + '" already exists.'
}
operators[name] = fn;
};
function createFilter(query, defaultReturn) {
return function (record) {
for (var key in query) {
if (query.hasOwnProperty(key)) {
if (typeof query[key] !== 'object') {
if (!record[key] || record[key] !== query[key]) {
return defaultReturn;
}
} else {
for (var op in query[key]) {
if (query[key].hasOwnProperty(op)) {
if (!operators[op](key, query[key][op], record)) {
return defaultReturn;
}
}
}
}
}
}
return !defaultReturn;
}
}
Bourne.prototype.find = function (query, callback) {
if (typeof callback === 'undefined') {
callback = query;
query = {};
}
var data = this.data.filter(createFilter(query, false));
callback(null, data);
};
Bourne.prototype.findOne = function (query, callback) {
this.find(query, function (err, records) {
callback(err, records[0]);
});
};
var updateOperators = {
$set: function (record, params) {
for (var prop in params) {
if (params.hasOwnProperty(prop)) {
record[prop] = params[prop];
}
}
},
$unset: function (record, params) {
for (var prop in params) {
if (record.hasOwnProperty(prop)) {
delete record[prop];
}
}
}
};
Bourne.prototype.update = function (query, update, callback) {
var ids = [];
if (update.$set) {
this.find(query, function (err, records) {
records.forEach(function (record) {
updateOperators.$set(record, update.$set);
});
this.store.set(this.name, JSON.stringify(this.data), function () {
if (callback) callback(null, records);
}.bind(this));
}.bind(this));
} else if (update.$unset) {
this.find(query, function (err, records) {
records.forEach(function (record) {
updateOperators.$unset(record, update.$unset);
});
this.store.set(this.name, JSON.stringify(this.data), function () {
if (callback) callback(null, records);
}.bind(this));
}.bind(this));
} else {
this.find(query, function (err, records) {
records.forEach(function (record) {
ids.push(record.id);
});
this.delete(query, function () {
var updatedRecords = ids.map(function (id) {
var u = JSON.parse(JSON.stringify(update));
u.id = id;
return u;
});
this.data = this.data.concat(updatedRecords);
this.store.set(this.name, JSON.stringify(this.data), function () {
if (callback) this.find({ id: { $in: ids }}, callback);
}.bind(this));
}.bind(this));
}.bind(this));
}
};
Bourne.prototype.delete = function (query, callback) {
this.data = this.data.filter(createFilter(query, true));
this.store.set(this.name, JSON.stringify(this.data), function () {
callback && callback(null);
});
};
Bourne.prototype.destroy = function () {
if (this.store.exists(this.name)) {
this.store.remove(this.name);
}
this.name = this._id = this.data = null;
};
if (typeof exports !== 'undefined') {
module.exports = Bourne;
} else {
window.Bourne = Bourne;
}
}.call(this));

Sorry, the diff of this file is not supported yet

'use strict';
if (typeof require !== 'undefined') {
var Bourne = require('../lib/bourne.js');
var Chance = require('chance');
}
var c = new Chance();
var testName = (typeof __dirname !== 'undefined') ? __dirname + '/' : '';
testName += c.string({ pool: 'abcdefghijklmnopqrstuvwxyz' });
var testRecord1 = { firstname: c.first(), lastname: c.last(), age: c.age(), birthday: c.birthday() },
testRecord2 = { firstname: c.first(), lastname: c.last(), age: c.age(), birthday: c.birthday() },
testRecord3 = { firstname: c.first(), lastname: c.last(), age: c.age(), birthday: c.birthday() };
this.bourne_test = {
prepopulated: {
setUp: function(done) {
var db = this.db = new Bourne(testName, { reset: true, temp: true });
db.insert(testRecord1, function () {
db.insert(testRecord2, function () {
db.insert(testRecord3, done);
});
});
},
tearDown: function (done) {
this.db.destroy();
done();
},
'can find records by one key': function (test) {
this.db.find({ firstname: testRecord1.firstname }, function (err, records) {
test.equal(records.length, 1, 'should find one record');
test.equal(records[0].firstname, testRecord1.firstname, 'names should be equal');
test.done();
});
},
'can find records by multiple keys': function (test) {
this.db.find({ firstname: testRecord1.firstname, age: testRecord1.age }, function (err, records) {
test.equal(records.length, 1, 'should find one record');
test.equal(records[0].age, testRecord1.age, 'names should be equal');
test.done();
});
},
'can find multiple records': function (test) {
var r = { firstname: testRecord1.firstname, age: c.age() };
var db = this.db;
db.insert(r, function () {
db.find({ firstname: testRecord1.firstname }, function (err, records) {
test.equal(records.length, 2, '2 records should be found');
test.done();
});
});
},
'can use query operators': function (test) {
var r = { firstname: c.first(), age: 10 };
var db = this.db;
db.insert(r, function () {
db.find({ age: { $lt: 11 } }, function (err, records) {
test.notEqual(records.length, 0, 'should have at least 1 record');
test.done();
});
});
},
'can find a single record': function (test) {
this.db.findOne({ firstname: testRecord1.firstname }, function (err, record) {
test.equal(record.firstname, testRecord1.firstname, 'names should be equal');
test.done();
});
},
'can find all records': function (test) {
this.db.find(function (err, records) {
test.equal(records.length, 3, 'should find 3 records');
test.done();
});
},
'can updated records by replacement': function (test) {
var updatedQuery = { age: 200 };
this.db.update({ firstname: testRecord1.firstname }, updatedQuery, function (err, records) {
updatedQuery.id = records[0].id;
test.deepEqual(records[0], updatedQuery, 'objects match');
test.done();
});
},
'can update records with $set operator': function (test) {
this.db.update({ firstname: testRecord1.firstname }, { $set: { firstname: 'XXX' } }, function (err, records) {
test.equal(records[0].firstname, 'XXX', 'name is updated');
test.done();
});
},
'can update records with $unset operator': function (test) {
this.db.update({ firstname: testRecord1.firstname }, { $unset: { lastname: '' }}, function (err, records) {
test.equal(records[0].lastname, undefined, 'no last name');
test.done()
});
},
'can delete records': function (test) {
var db = this.db;
db.delete({ firstname: testRecord1.firstname }, function () {
db.find({ firstname: testRecord1.firstname }, function (err, records) {
test.equal(records.length, 0, 'no records should be found');
test.done();
});
});
}
},
'can create Bourne instance': function(test) {
test.expect(1);
test.doesNotThrow(function () {
var db = new Bourne(testName, { reset: true, temp: true });
});
test.done();
},
'can insert record': function (test) {
test.expect(2);
var db = new Bourne(testName, { reset: true, temp: true });
db.insert(testRecord1, function (err, record) {
test.equal(testRecord1.firstname, record.firstname, 'names should be equal');
test.equal(record.id, 1, 'id should be 1');
test.done();
});
},
'can restart indexing at the right number': function (test) {
var str = '[{ "id": 1, "attr": "value" }]';
if (typeof require !== 'undefined') {
var fs = require('fs');
fs.writeFileSync(testName, str);
} else {
localStorage.setItem(testName, str);
}
var db = new Bourne(testName, { log: true });
db.insert(testRecord1, function (err, record) {
test.equal(record.id, 2, 'id should be 2');
test.done();
db.destroy();
});
},
'can restart alternate': function (test) {
var db = new Bourne(testName, { reset: true });
db.insert(testRecord1, function (err, record) {
test.equal(record.id, 1, 'first ID is 1');
var db2 = new Bourne(testName, { log: true });
db2.insert(testRecord2, function (err, record2) {
test.equal(record2.id, 2, 'second ID is 2');
test.done();
db.destroy();
db2.destroy();
});
});
},
'can store record persistently': function (test) {
var db1 = new Bourne(testName, { reset: true });
db1.insert(testRecord1, function (err, record) {
var db2 = new Bourne(testName);
test.equal(db2.data[0].firstname, testRecord1.firstname, 'names should be equal');
test.done();
db1.destroy();
db2.destroy();
});
},
'query operators': {
'$lt': function (test) {
operatorTest({ age: 10 }, { age: { $lt: 11 } }, function (err, records) {
test.notEqual(records.length, 0, 'should have at least 1 record');
test.done();
});
},
'$gt': function (test) {
operatorTest({ age: 10 }, { age: { $gt: 9 } }, function (err, records) {
test.notEqual(records.length, 0, 'should have at least 1 record');
test.done();
});
},
'$lte': function (test) {
operatorTest({ age: 10 }, { age: { $lte: 10 } }, function (err, records) {
test.notEqual(records.length, 0, 'should have at least 1 record');
test.done();
});
},
'multiple operators': function (test) {
operatorTest({ age: 10 }, { age: { $lt: 11, $gt: 9 } }, function (err, records) {
test.notEqual(records.length, 0, 'should have at least 1 record');
test.done();
});
}
},
'can insert multiple records': function (test) {
var db = new Bourne(testName, { reset: true });
db.insertAll([testRecord1, testRecord2], function (err, records) {
test.equal(records.length, 2);
test.done();
db.destroy();
});
},
'can work with no persistence': function (test) {
var db = new Bourne(testName, { temp: true });
db.insert(testRecord1, function (err, records) {
var db2 = new Bourne(testName, { temp: true });
test.equal(db2.data.length, 0, 'db2 should have no records');
test.done();
db.destroy();
db2.destroy();
});
}
};
function operatorTest(record, query, cb) {
var db = new Bourne(testName, { reset: true, temp: true });
db.insert(record, function () {
db.find(query, cb);
});
}
/*!
* Styles taken from qunit.css
*/
h1#nodeunit-header, h1.nodeunit-header {
padding: 15px;
font-size: large;
background-color: #06b;
color: white;
font-family: 'trebuchet ms', verdana, arial;
margin: 0;
}
h1#nodeunit-header a {
color: white;
}
h2#nodeunit-banner {
height: 2em;
border-bottom: 1px solid white;
background-color: #eee;
margin: 0;
font-family: 'trebuchet ms', verdana, arial;
}
h2#nodeunit-banner.pass {
background-color: green;
}
h2#nodeunit-banner.fail {
background-color: red;
}
h2#nodeunit-userAgent, h2.nodeunit-userAgent {
padding: 10px;
background-color: #eee;
color: black;
margin: 0;
font-size: small;
font-weight: normal;
font-family: 'trebuchet ms', verdana, arial;
font-size: 10pt;
}
div#nodeunit-testrunner-toolbar {
background: #eee;
border-top: 1px solid black;
padding: 10px;
font-family: 'trebuchet ms', verdana, arial;
margin: 0;
font-size: 10pt;
}
ol#nodeunit-tests {
font-family: 'trebuchet ms', verdana, arial;
font-size: 10pt;
}
ol#nodeunit-tests li strong {
cursor:pointer;
}
ol#nodeunit-tests .pass {
color: green;
}
ol#nodeunit-tests .fail {
color: red;
}
p#nodeunit-testresult {
margin-left: 1em;
font-size: 10pt;
font-family: 'trebuchet ms', verdana, arial;
}

Sorry, the diff of this file is too big to display

<html>
<head>
<title>Nodeunit Test Suite</title>
<link rel="stylesheet" href="nodeunit.css" type="text/css" media="screen" />
<script src="nodeunit.js"></script>
<script src="../lib/bourne.js"></script>
<script src='../node_modules/chance/chance.js'></script>
<script src="bourne_test.js"></script>
</head>
<body>
<h1 id="nodeunit-header">Nodeunit Test Suite</h1>
<script>
nodeunit.run({
'test-bourne': bourne_test
});
</script>
</body>
</html>

Sorry, the diff of this file is not supported yet