JavaScript 中的动态公式求值:eval() 或非 eval()
Dynamic formula evaluation in JavaScript: eval() or not eval()
我正在开发一个网络应用程序,管理员可以在其中定义和调整需要根据最终用户提供的输入值(数字)进行评估的公式。
为了清楚起见,这里是我要排除的内容的简化示例:
const obj = {
type: "External wall in contact with the ground",
layer: {
base: {
type: "Reinforced concrete (reinforcement 5 vol.%)",
thickness: 100, // <-- user value
lambda: 2.3, // <-- user value
_r: "{{thickness}}/{{lambda}}/1000", // <-- admin defined
r: 0
},
waterproofing: {
type: "Bitumen sheets (single layer)",
share: 1, // <-- user value
_r: "{{share}}", // <-- admin defined
r: 0,
},
insulation: {
type: "XPS",
thickness: 100, // <-- user value
share: 1, // <-- user value
lambda: 0.040, // <-- user value
_r: "{{thickness}}*{{share}}/{{lambda}}/1000", // <-- admin defined
r: 0
}
}
}
Object.entries(obj.layer).forEach(([key, object]) => {
var formula = object._r
Object.keys(object).forEach(k =>
formula = formula.replace(`{{${k}}}`, object[k])
)
obj.layer[key].r = eval(formula)
})
console.log(obj)
_r
是管理员定义的公式。 {{value}}
是最终用户提供的值。
循环通过 obj.layer
属性计算公式并将答案保存在 r
中。
结果将是这个对象:
{
type: 'External wall in contact with the ground',
layer: {
base: {
type: 'Reinforced concrete (reinforcement 5 vol.%)',
thickness: 100,
lambda: 2.3,
_r: '{{thickness}}/{{lambda}}/1000',
r: 0.043478260869565216
},
waterproofing: {
type: 'Bitumen sheets (single layer)',
share: 1,
_r: '{{share}}',
r: 1
},
insulation: {
type: 'XPS',
thickness: 100,
share: 1,
lambda: 0.04,
_r: '{{thickness}}*{{share}}/{{lambda}}/1000',
r: 2.5
}
}
}
让我们跳过我没有验证对象结构并确保所有值都可用的事实。
我知道 eval()
被认为是“危险的”。一个不太好的选择是 Function()
。然而,并不完美。
到目前为止,我看到了 3 种可能性:
只有管理员可以更改公式。因此,执行恶意代码的风险非常低。我需要的是 validate/sanitize 值(类似于 isFloat()
),仅此而已。
使用 mathjs
库,它提供了一个不错的 evaluate()
功能:
const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
x: 3,
a: 2
}
code2.evaluate(scope) // 9
- 使用像 http://zaa.ch/jison/ 这样的解析器生成器,但对于我想做的事情来说似乎有些过分了..
老实说,我觉得在我的具体情况下使用 eval()
是合理的:具有动态值的动态公式。我可以使用像 mathjs
这样的外部库,但我觉得我不需要它来进行如此简单的操作。
我真的很想知道你对这个问题的想法,如果有的话,我想听听你的建议!
PS: 是的,这个问题已经被问过了。虽然,我发现的最相似的问题是几年前提出(和回答)的。我想就此事提出新的意见。
基于@Sleavely 的回答,并进一步阅读该主题,mathjs
似乎是最合理的解决方案;-)
基于@Sleavely 的回答(见下面引用的评论),并进一步阅读该主题,mathjs
似乎是最合理的解决方案:
Third-party admins may be the only ones who can edit the code, but
where will it actually run? In your backend? On visitors' computers? I
strongly recommend you avoid eval(), lest you be blamed for whatever
Bitcoin miner visitors end up contracting from your application. With
that in mind, I think that you're on the right track with your 2nd
option. Evaluating the formula against a set of predefined variables
strikes me as safe enough. Looks like mathjs actively avoids eval():
https://github.com/josdejong/mathjs/blob/master/docs/expressions/security.md
我正在开发一个网络应用程序,管理员可以在其中定义和调整需要根据最终用户提供的输入值(数字)进行评估的公式。
为了清楚起见,这里是我要排除的内容的简化示例:
const obj = {
type: "External wall in contact with the ground",
layer: {
base: {
type: "Reinforced concrete (reinforcement 5 vol.%)",
thickness: 100, // <-- user value
lambda: 2.3, // <-- user value
_r: "{{thickness}}/{{lambda}}/1000", // <-- admin defined
r: 0
},
waterproofing: {
type: "Bitumen sheets (single layer)",
share: 1, // <-- user value
_r: "{{share}}", // <-- admin defined
r: 0,
},
insulation: {
type: "XPS",
thickness: 100, // <-- user value
share: 1, // <-- user value
lambda: 0.040, // <-- user value
_r: "{{thickness}}*{{share}}/{{lambda}}/1000", // <-- admin defined
r: 0
}
}
}
Object.entries(obj.layer).forEach(([key, object]) => {
var formula = object._r
Object.keys(object).forEach(k =>
formula = formula.replace(`{{${k}}}`, object[k])
)
obj.layer[key].r = eval(formula)
})
console.log(obj)
_r
是管理员定义的公式。 {{value}}
是最终用户提供的值。
循环通过 obj.layer
属性计算公式并将答案保存在 r
中。
结果将是这个对象:
{
type: 'External wall in contact with the ground',
layer: {
base: {
type: 'Reinforced concrete (reinforcement 5 vol.%)',
thickness: 100,
lambda: 2.3,
_r: '{{thickness}}/{{lambda}}/1000',
r: 0.043478260869565216
},
waterproofing: {
type: 'Bitumen sheets (single layer)',
share: 1,
_r: '{{share}}',
r: 1
},
insulation: {
type: 'XPS',
thickness: 100,
share: 1,
lambda: 0.04,
_r: '{{thickness}}*{{share}}/{{lambda}}/1000',
r: 2.5
}
}
}
让我们跳过我没有验证对象结构并确保所有值都可用的事实。
我知道 eval()
被认为是“危险的”。一个不太好的选择是 Function()
。然而,并不完美。
到目前为止,我看到了 3 种可能性:
只有管理员可以更改公式。因此,执行恶意代码的风险非常低。我需要的是 validate/sanitize 值(类似于
isFloat()
),仅此而已。使用
mathjs
库,它提供了一个不错的evaluate()
功能:
const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
x: 3,
a: 2
}
code2.evaluate(scope) // 9
- 使用像 http://zaa.ch/jison/ 这样的解析器生成器,但对于我想做的事情来说似乎有些过分了..
老实说,我觉得在我的具体情况下使用 eval()
是合理的:具有动态值的动态公式。我可以使用像 mathjs
这样的外部库,但我觉得我不需要它来进行如此简单的操作。
我真的很想知道你对这个问题的想法,如果有的话,我想听听你的建议!
PS: 是的,这个问题已经被问过了。虽然,我发现的最相似的问题是几年前提出(和回答)的。我想就此事提出新的意见。
基于@Sleavely 的回答,并进一步阅读该主题,mathjs
似乎是最合理的解决方案;-)
基于@Sleavely 的回答(见下面引用的评论),并进一步阅读该主题,mathjs
似乎是最合理的解决方案:
Third-party admins may be the only ones who can edit the code, but where will it actually run? In your backend? On visitors' computers? I strongly recommend you avoid eval(), lest you be blamed for whatever Bitcoin miner visitors end up contracting from your application. With that in mind, I think that you're on the right track with your 2nd option. Evaluating the formula against a set of predefined variables strikes me as safe enough. Looks like mathjs actively avoids eval(): https://github.com/josdejong/mathjs/blob/master/docs/expressions/security.md