【问题标题】:Dynamic formula evaluation in JavaScript: eval() or not eval()JavaScript 中的动态公式求值:eval() 或非 eval()
【发布时间】:2020-12-03 08:54:44
【问题描述】:

我正在开发一个 Web 应用程序,管理员可以在其中定义和调整需要根据最终用户提供的输入值(数字)进行评估的公式。

为了清楚起见,这里是一个简化的例子,我会例外:

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 种可能性:

  1. 只有管理员可以更改公式。因此,执行恶意代码的风险非常低。我需要的是验证/清理这些值(类似于isFloat()),就是这样。

  2. 使用mathjs 库,它提供了一个很好的evaluate() 函数:

const node2 = math.parse('x^a')
const code2 = node2.compile()
let scope = {
    x: 3,
    a: 2
}
code2.evaluate(scope) // 9
  1. 使用像 http://zaa.ch/jison/ 这样的解析器生成器,但这对于我想做的事情来说似乎有点过分了..

说实话,我觉得在我的具体情况下使用eval() 是合理的:具有动态值的动态公式。我可以使用像mathjs 这样的外部库,但我觉得我不需要它来进行如此简单的操作。

我真的很想听听您对这个主题的想法,如果有的话,请听听您的建议!

PS:是的,这个问题已经被问过了。虽然,我发现的最相似的问题是几年前提出的(并得到了答复)。我想就此问题提出新意见。


基于@Sleavely 的回答,以及对该主题的进一步阅读,mathjs 似乎是最合理的解决方案;-)

【问题讨论】:

  • 如何防止用户引入有问题的输入? xkcd.com/327你会消毒吗?
  • 第三方管理员可能是唯一可以编辑代码的人,但它会在哪里实际运行呢?在你的后端?在访客的电脑上?我强烈建议您避免使用 eval(),以免因比特币矿工访问者最终从您的应用程序中签约而受到指责。考虑到这一点,我认为您的第二个选项走在正确的轨道上。根据一组预定义的变量评估公式让我觉得足够安全。看起来 mathjs 主动避免 eval(): github.com/josdejong/mathjs/blob/master/docs/expressions/…
  • 我可以建议mathjs.org/index.html 而不是使用vanilla eval()。
  • @malarres 是的,我当然会清理输入。输入只能是浮点数。
  • @Sleavely 感谢您的链接。 Tbh,我在想 mathjs 在幕后运行eval()。这就是为什么我想知道如果我可以做“同样的事情”,为什么还要添加一个库。我觉得mathjs 是糟糕的eval() 和复杂的(?)jison 解决方案之间的中间解决方案

标签: javascript formula eval


【解决方案1】:

基于@Sleavely 的回答(参见下面引用的评论),以及对该主题的进一步阅读,mathjs 似乎是最合理的解决方案:

第三方管理员可能是唯一可以编辑代码的人,但是 它实际上会在哪里运行?在你的后端?在访客的电脑上?一世 强烈建议您避免使用 eval(),以免受到任何指责 比特币矿工访问者最终会从您的应用程序中签约。和 考虑到这一点,我认为你的第二个走在正确的轨道上 选项。根据一组预定义变量评估公式 让我觉得足够安全。看起来 mathjs 积极地避免了 eval(): https://github.com/josdejong/mathjs/blob/master/docs/expressions/security.md

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-07-05
    • 2012-08-16
    • 2017-12-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2020-03-14
    相关资源
    最近更新 更多