json-logic-js
Advanced tools
Comparing version 2.0.1 to 2.0.2
# Change Log | ||
## 2.0.2 | ||
Thanks [@panzi](https://github.com/panzi) for rebuilding the test system and removing Gulp as a dev dependency. | ||
## 2.0.1 | ||
The operations object could be exploited to run arbitrary code. Resolves [SNYK-JS-JSONLOGICJS-674308](https://security.snyk.io/vuln/SNYK-JS-JSONLOGICJS-674308), thanks Arel Cordero for reporting. | ||
## 2.0.0 | ||
@@ -4,0 +12,0 @@ |
34
logic.js
@@ -223,3 +223,2 @@ /* globals define,module */ | ||
var scopedData; | ||
var filtered; | ||
var initial; | ||
@@ -318,3 +317,3 @@ | ||
// All of an empty set is false. Note, some and none have correct fallback after the for loop | ||
if ( ! scopedData.length) { | ||
if ( ! Array.isArray(scopedData) || ! scopedData.length) { | ||
return false; | ||
@@ -329,7 +328,27 @@ } | ||
} else if (op === "none") { | ||
filtered = jsonLogic.apply({filter: values}, data); | ||
return filtered.length === 0; | ||
scopedData = jsonLogic.apply(values[0], data); | ||
scopedLogic = values[1]; | ||
if ( ! Array.isArray(scopedData) || ! scopedData.length) { | ||
return true; | ||
} | ||
for (i=0; i < scopedData.length; i+=1) { | ||
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) { | ||
return false; // First truthy, short circuit | ||
} | ||
} | ||
return true; // None were truthy | ||
} else if (op === "some") { | ||
filtered = jsonLogic.apply({filter: values}, data); | ||
return filtered.length > 0; | ||
scopedData = jsonLogic.apply(values[0], data); | ||
scopedLogic = values[1]; | ||
if ( ! Array.isArray(scopedData) || ! scopedData.length) { | ||
return false; | ||
} | ||
for (i=0; i < scopedData.length; i+=1) { | ||
if ( jsonLogic.truthy( jsonLogic.apply(scopedLogic, scopedData[i]) )) { | ||
return true; // First truthy, short circuit | ||
} | ||
} | ||
return false; // None were truthy | ||
} | ||
@@ -352,3 +371,2 @@ | ||
for (i = 0; i < sub_ops.length; i++) { | ||
if (!operation.hasOwnProperty(sub_ops[i])) { | ||
@@ -384,3 +402,3 @@ throw new Error("Unrecognized operation " + op + | ||
// Recursion! | ||
values.map(function(val) { | ||
values.forEach(function(val) { | ||
collection.push.apply(collection, jsonLogic.uses_data(val) ); | ||
@@ -387,0 +405,0 @@ }); |
{ | ||
"name": "json-logic-js", | ||
"version": "2.0.1", | ||
"version": "2.0.2", | ||
"description": "Build complex rules, serialize them as JSON, and execute them in JavaScript", | ||
@@ -9,12 +9,9 @@ "main": "logic.js", | ||
}, | ||
"dependencies": {}, | ||
"devDependencies": { | ||
"eslint": "^7.11.0", | ||
"eslint": "^7.32.0", | ||
"eslint-config-google": "^0.14.0", | ||
"gulp": "^3.9.0", | ||
"qunit": "^0.7.7", | ||
"request": "^2.65.0" | ||
"qunit": "^2.16.0" | ||
}, | ||
"scripts": { | ||
"test": "gulp test" | ||
"test": "qunit ./tests/tests.js" | ||
}, | ||
@@ -21,0 +18,0 @@ "repository": { |
@@ -0,284 +1,310 @@ | ||
var QUnit = require("qunit"); | ||
var jsonLogic = require("../logic.js"); | ||
var http = require("http"); | ||
var https = require("https"); | ||
var fs = require("fs"); | ||
var path = require("path"); | ||
var download = function(url, dest, cb) { | ||
async function download(url, dest) { | ||
var file = fs.createWriteStream(dest); | ||
http.get(url, function(response) { | ||
response.pipe(file); | ||
file.on("finish", function() { | ||
file.close(cb); // close() is async, call cb after close completes. | ||
return new Promise((resolve, reject) => { | ||
https.get(url, function(response) { | ||
if (response.statusCode !== 200) { | ||
return reject(new Error(response.statusCode + ' ' + response.statusMessage)); | ||
} | ||
response.pipe(file); | ||
file.on("finish", function() { | ||
// close() is async, call cb after close completes. | ||
file.close(err => err ? reject(err) : resolve()); | ||
}); | ||
}).on("error", function(err) { // Handle errors | ||
fs.unlink(dest, () => { | ||
// Delete the file async. (But we don't check the result) | ||
reject(err); | ||
}); | ||
}); | ||
}).on("error", function(err) { // Handle errors | ||
fs.unlink(dest); // Delete the file async. (But we don't check the result) | ||
if (cb) cb(err.message); | ||
}); | ||
}; | ||
} | ||
var remote_or_cache = function(remote_url, local_file, description, runner) { | ||
var parse_and_iterate = function(local_file, description, runner) { | ||
fs.readFile(local_file, "utf8", function(error, body) { | ||
var tests; | ||
try { | ||
tests = JSON.parse(body); | ||
} catch (e) { | ||
throw new Error("Trouble parsing " + description + ": " + e.message); | ||
} | ||
async function remote_or_cache(remote_url, local_file, description) { | ||
local_file = path.join(__dirname, local_file); | ||
async function parse_and_iterate(local_file, description) { | ||
var body = await fs.promises.readFile(local_file, "utf8"); | ||
var tests; | ||
try { | ||
tests = JSON.parse(body); | ||
} catch (e) { | ||
throw new Error("Trouble parsing " + description + ": " + e.message); | ||
} | ||
// Remove comments | ||
tests = tests.filter(function(test) { | ||
return typeof test !== "string"; | ||
}); | ||
// Remove comments | ||
tests = tests.filter(test => typeof test !== "string"); | ||
console.log("Including "+tests.length+" "+description); | ||
console.log("Including "+tests.length+" "+description); | ||
return tests; | ||
} | ||
QUnit.test(description, function(assert) { | ||
tests.map(runner); | ||
}); | ||
try { | ||
await fs.promises.stat(local_file); | ||
} catch (err) { | ||
console.log("Downloading " + description + " from JsonLogic.com"); | ||
await download(remote_url, local_file); | ||
return parse_and_iterate(local_file, description); | ||
} | ||
start(); | ||
console.log("Using cached " + description); | ||
return parse_and_iterate(local_file, description); | ||
}; | ||
var appliesTests; | ||
QUnit.module("applies() tests", { | ||
before(assert) { | ||
var done = assert.async(); | ||
remote_or_cache( | ||
"https://jsonlogic.com/tests.json", | ||
"tests.json", | ||
"applies() tests" | ||
).then(tests => { | ||
appliesTests = tests; | ||
done(); | ||
}); | ||
}; | ||
} | ||
}, () => { | ||
QUnit.test("all", (assert) => { | ||
for (const test of appliesTests) { | ||
var rule = test[0]; | ||
var data = test[1]; | ||
var expected = test[2]; | ||
// Only waiting on the request() is async | ||
stop(); | ||
fs.stat(local_file, function(err, stats) { | ||
if (err) { | ||
console.log("Downloading " + description + " from JsonLogic.com"); | ||
download(remote_url, local_file, function() { | ||
parse_and_iterate(local_file, description, runner); | ||
}); | ||
} else { | ||
console.log("Using cached " + description); | ||
parse_and_iterate(local_file, description, runner); | ||
assert.deepEqual( | ||
jsonLogic.apply(rule, data), | ||
expected, | ||
"jsonLogic.apply("+ JSON.stringify(rule) +"," + | ||
JSON.stringify(data) +") === " + | ||
JSON.stringify(expected) | ||
); | ||
} | ||
}); | ||
}; | ||
}); | ||
remote_or_cache( | ||
"http://jsonlogic.com/tests.json", | ||
"tests.json", | ||
"applies() tests", | ||
function(test) { | ||
var rule = test[0]; | ||
var data = test[1]; | ||
var expected = test[2]; | ||
var ruleTests; | ||
QUnit.module("rule_like() tests", { | ||
before(assert) { | ||
var done = assert.async(); | ||
assert.deepEqual( | ||
jsonLogic.apply(rule, data), | ||
expected, | ||
"jsonLogic.apply("+ JSON.stringify(rule) +"," + | ||
JSON.stringify(data) +") === " + | ||
JSON.stringify(expected) | ||
); | ||
remote_or_cache( | ||
"https://jsonlogic.com/rule_like.json", | ||
"rule_like.json", | ||
"rule_like() tests" | ||
).then(tests => { | ||
ruleTests = tests; | ||
done(); | ||
}) | ||
} | ||
); | ||
}, () => { | ||
QUnit.test("all", (assert) => { | ||
for (const test of ruleTests) { | ||
var rule = test[0]; | ||
var pattern = test[1]; | ||
var expected = test[2]; | ||
remote_or_cache( | ||
"http://jsonlogic.com/rule_like.json", | ||
"rule_like.json", | ||
"rule_like() tests", | ||
function(test) { | ||
var rule = test[0]; | ||
var pattern = test[1]; | ||
var expected = test[2]; | ||
assert.deepEqual( | ||
jsonLogic.rule_like(rule, pattern), | ||
expected, | ||
"jsonLogic.rule_like("+ JSON.stringify(rule) +"," + | ||
JSON.stringify(pattern) +") === " + | ||
JSON.stringify(expected) | ||
); | ||
} | ||
}); | ||
}); | ||
assert.deepEqual( | ||
jsonLogic.rule_like(rule, pattern), | ||
expected, | ||
"jsonLogic.rule_like("+ JSON.stringify(rule) +"," + | ||
JSON.stringify(pattern) +") === " + | ||
JSON.stringify(expected) | ||
QUnit.module('basic', () => { | ||
QUnit.test( "Bad operator", function( assert ) { | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"fubar": []}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
} | ||
); | ||
}); | ||
QUnit.test( "Bad operator", function( assert ) { | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"fubar": []}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
}); | ||
QUnit.test( "logging", function( assert ) { | ||
var last_console; | ||
console.log = function(logged) { | ||
last_console = logged; | ||
}; | ||
assert.equal( jsonLogic.apply({"log": [1]}), 1 ); | ||
assert.equal( last_console, 1 ); | ||
}); | ||
QUnit.test( "edge cases", function( assert ) { | ||
assert.equal( jsonLogic.apply(), undefined, "Called with no arguments" ); | ||
QUnit.test( "logging", function( assert ) { | ||
var last_console; | ||
console.log = function(logged) { | ||
last_console = logged; | ||
}; | ||
assert.equal( jsonLogic.apply({"log": [1]}), 1 ); | ||
assert.equal( last_console, 1 ); | ||
}); | ||
assert.equal( jsonLogic.apply({ var: "" }, 0), 0, "Var when data is 'falsy'" ); | ||
assert.equal( jsonLogic.apply({ var: "" }, null), null, "Var when data is null" ); | ||
assert.equal( jsonLogic.apply({ var: "" }, undefined), undefined, "Var when data is undefined" ); | ||
QUnit.test( "edge cases", function( assert ) { | ||
assert.equal( jsonLogic.apply(), undefined, "Called with no arguments" ); | ||
assert.equal( jsonLogic.apply({ var: ["a", "fallback"] }, undefined), "fallback", "Fallback works when data is a non-object" ); | ||
}); | ||
assert.equal( jsonLogic.apply({ var: "" }, 0), 0, "Var when data is 'falsy'" ); | ||
assert.equal( jsonLogic.apply({ var: "" }, null), null, "Var when data is null" ); | ||
assert.equal( jsonLogic.apply({ var: "" }, undefined), undefined, "Var when data is undefined" ); | ||
QUnit.test( "Expanding functionality with add_operator", function( assert) { | ||
// Operator is not yet defined | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"add_to_a": []}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
assert.equal( jsonLogic.apply({ var: ["a", "fallback"] }, undefined), "fallback", "Fallback works when data is a non-object" ); | ||
}); | ||
// Set up some outside data, and build a basic function operator | ||
var a = 0; | ||
var add_to_a = function(b) { | ||
if (b === undefined) { | ||
b=1; | ||
} return a += b; | ||
}; | ||
jsonLogic.add_operation("add_to_a", add_to_a); | ||
// New operation executes, returns desired result | ||
// No args | ||
assert.equal( jsonLogic.apply({"add_to_a": []}), 1 ); | ||
// Unary syntactic sugar | ||
assert.equal( jsonLogic.apply({"add_to_a": 41}), 42 ); | ||
// New operation had side effects. | ||
assert.equal(a, 42); | ||
QUnit.test( "Expanding functionality with add_operator", function( assert) { | ||
// Operator is not yet defined | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"add_to_a": []}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
var fives = { | ||
add: function(i) { | ||
return i + 5; | ||
}, | ||
subtract: function(i) { | ||
return i - 5; | ||
}, | ||
}; | ||
// Set up some outside data, and build a basic function operator | ||
var a = 0; | ||
var add_to_a = function(b) { | ||
if (b === undefined) { | ||
b=1; | ||
} return a += b; | ||
}; | ||
jsonLogic.add_operation("add_to_a", add_to_a); | ||
// New operation executes, returns desired result | ||
// No args | ||
assert.equal( jsonLogic.apply({"add_to_a": []}), 1 ); | ||
// Unary syntactic sugar | ||
assert.equal( jsonLogic.apply({"add_to_a": 41}), 42 ); | ||
// New operation had side effects. | ||
assert.equal(a, 42); | ||
jsonLogic.add_operation("fives", fives); | ||
assert.equal( jsonLogic.apply({"fives.add": 37}), 42 ); | ||
assert.equal( jsonLogic.apply({"fives.subtract": [47]}), 42 ); | ||
var fives = { | ||
add: function(i) { | ||
return i + 5; | ||
}, | ||
subtract: function(i) { | ||
return i - 5; | ||
}, | ||
}; | ||
// Calling a method with multiple var as arguments. | ||
jsonLogic.add_operation("times", function(a, b) { | ||
return a*b; | ||
}); | ||
assert.equal( | ||
jsonLogic.apply( | ||
{"times": [{"var": "a"}, {"var": "b"}]}, | ||
{a: 6, b: 7} | ||
), | ||
42 | ||
); | ||
jsonLogic.add_operation("fives", fives); | ||
assert.equal( jsonLogic.apply({"fives.add": 37}), 42 ); | ||
assert.equal( jsonLogic.apply({"fives.subtract": [47]}), 42 ); | ||
// Remove operation: | ||
jsonLogic.rm_operation("times"); | ||
// Calling a method with multiple var as arguments. | ||
jsonLogic.add_operation("times", function(a, b) { | ||
return a*b; | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"times": [2, 2]}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
// Calling a method that takes an array, but the inside of the array has rules, too | ||
jsonLogic.add_operation("array_times", function(a) { | ||
return a[0]*a[1]; | ||
}); | ||
assert.equal( | ||
jsonLogic.apply( | ||
{"array_times": [[{"var": "a"}, {"var": "b"}]]}, | ||
{a: 6, b: 7} | ||
), | ||
42 | ||
); | ||
}); | ||
assert.equal( | ||
jsonLogic.apply( | ||
{"times": [{"var": "a"}, {"var": "b"}]}, | ||
{a: 6, b: 7} | ||
), | ||
42 | ||
); | ||
// Remove operation: | ||
jsonLogic.rm_operation("times"); | ||
QUnit.test("Control structures don't eval depth-first", function(assert) { | ||
// Depth-first recursion was wasteful but not harmful until we added custom operations that could have side-effects. | ||
assert.throws( | ||
function() { | ||
jsonLogic.apply({"times": [2, 2]}); | ||
}, | ||
/Unrecognized operation/ | ||
); | ||
// If operations run the condition, if truthy, it runs and returns that consequent. | ||
// Consequents of falsy conditions should not run. | ||
// After one truthy condition, no other condition should run | ||
var conditions = []; | ||
var consequents = []; | ||
jsonLogic.add_operation("push.if", function(v) { | ||
conditions.push(v); return v; | ||
}); | ||
jsonLogic.add_operation("push.then", function(v) { | ||
consequents.push(v); return v; | ||
}); | ||
jsonLogic.add_operation("push.else", function(v) { | ||
consequents.push(v); return v; | ||
}); | ||
// Calling a method that takes an array, but the inside of the array has rules, too | ||
jsonLogic.add_operation("array_times", function(a) { | ||
return a[0]*a[1]; | ||
}); | ||
assert.equal( | ||
jsonLogic.apply( | ||
{"array_times": [[{"var": "a"}, {"var": "b"}]]}, | ||
{a: 6, b: 7} | ||
), | ||
42 | ||
); | ||
}); | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [true]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [false]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [true]); | ||
assert.deepEqual(consequents, ["first"]); | ||
QUnit.test("Control structures don't eval depth-first", function(assert) { | ||
// Depth-first recursion was wasteful but not harmful until we added custom operations that could have side-effects. | ||
conditions = []; | ||
consequents = []; | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [false]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [true]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [false, true]); | ||
assert.deepEqual(consequents, ["second"]); | ||
// If operations run the condition, if truthy, it runs and returns that consequent. | ||
// Consequents of falsy conditions should not run. | ||
// After one truthy condition, no other condition should run | ||
var conditions = []; | ||
var consequents = []; | ||
jsonLogic.add_operation("push.if", function(v) { | ||
conditions.push(v); return v; | ||
}); | ||
jsonLogic.add_operation("push.then", function(v) { | ||
consequents.push(v); return v; | ||
}); | ||
jsonLogic.add_operation("push.else", function(v) { | ||
consequents.push(v); return v; | ||
}); | ||
conditions = []; | ||
consequents = []; | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [false]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [false]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [false, false]); | ||
assert.deepEqual(consequents, ["third"]); | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [true]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [false]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [true]); | ||
assert.deepEqual(consequents, ["first"]); | ||
conditions = []; | ||
consequents = []; | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [false]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [true]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [false, true]); | ||
assert.deepEqual(consequents, ["second"]); | ||
jsonLogic.add_operation("push", function(arg) { | ||
i.push(arg); return arg; | ||
}); | ||
var i = []; | ||
conditions = []; | ||
consequents = []; | ||
jsonLogic.apply({"if": [ | ||
{"push.if": [false]}, | ||
{"push.then": ["first"]}, | ||
{"push.if": [false]}, | ||
{"push.then": ["second"]}, | ||
{"push.else": ["third"]}, | ||
]}); | ||
assert.deepEqual(conditions, [false, false]); | ||
assert.deepEqual(consequents, ["third"]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [false]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [false]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [true]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [true, false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [true]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [true, true]); | ||
jsonLogic.add_operation("push", function(arg) { | ||
i.push(arg); return arg; | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [false]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [false, false]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [false]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [false, true]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [true]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [true]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [true]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [true]); | ||
}); | ||
var i = []; | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [false]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [false]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [true]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [true, false]); | ||
i = []; | ||
jsonLogic.apply({"and": [{"push": [true]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [true, true]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [false]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [false, false]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [false]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [false, true]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [true]}, {"push": [false]}]}); | ||
assert.deepEqual(i, [true]); | ||
i = []; | ||
jsonLogic.apply({"or": [{"push": [true]}, {"push": [true]}]}); | ||
assert.deepEqual(i, [true]); | ||
}); |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 1 instance in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
283570
3
812
1
19