安全地解析和评估用户输入

时间:2014-06-20 11:50:27

标签: javascript node.js parsing eval

我正在开发一个本质上是模板化特定领域语言的项目。在我的项目中,我接受以下形式的用户输入行:

'{{index(1, 5)}}'
'{{firstName()}} X. {{lastName()}}'
'{{floating(-0.5, 0.5)}}'
'{{text(5, "words")}}'

双花括号({{ }})之间的任何命令都有一个相应的Javascript方法,在遇到该命令时应该调用它。 (例如,在第一个的情况下为function index(min, max) {...}。)

我很难确定如何安全地接受输入并调用适当的功能。我知道我现在这样做的方式并不安全。我只是eval()两组花括号之间的任何东西。

如何解析这些输入字符串,以便我可以灵活地匹配花括号之间的函数调用,并使用给定的任何参数执行该函数,同时仍然不会盲目地用代码调用eval()

我考虑过制作一个映射(如果命令是index(),请调用function index() {}),但这似乎不太灵活;我如何收集并传递任何参数(例如{{index(2, 5)}}),如果有的话?

这是用Node.js编写的。

3 个答案:

答案 0 :(得分:1)

这个问题分解为:

  1. 解析字符串

  2. 评估结果函数图

  3. 调度到每个函数(作为上面#2的一部分)

  4. 解析字符串

    不幸的是,根据您的要求,解析{{...}}字符串非常复杂。您至少要处理以下问题:

    1. 函数可以嵌套{{function1(function2(), 2, 3)}}

    2. 字符串可以包含(转义)引号,并且可以包含逗号,因此即使没有上面的要求#1,找到离散参数(在逗号上拆分)的简单方法也行不通。

      < / LI>

      所以......你需要一个合适的解析器。您可以尝试将其中的一个拼凑在一起,但这是解析器生成器进入图片的位置,如PEG.jsJison(这些只是示例,不一定是建议 - 我确实碰巧注意到其中一个Jison的例子是JSON parser,大约是战斗的一半)。编写解析器超出了回答问题的范围,我担心。 : - )

      评估结果函数图

      根据您使用的工具,您的解析器生成器可能会为您处理此问题。 (例如,我很确定PEG.js和Jison都会这样做。)

      如果没有,那么在解析之后你可能会得到某种类型的对象图,它会为你提供函数及其参数(可能是带参数的函数......可能是......)。 / p>

      • 泛函
        • 1
        • “二”
        • functionB
          • “一”
          • functionC
            • 42
        • functionD
        • 27

      functionA有五个参数,第三个是带有两个参数的functionB,依此类推。

      接下来的任务是评估最深的(并且在相同的深度,从左到右)并在相关参数列表中将它们替换为结果,这样您我需要一个depth-first traversal algorithm。通过最深的第一个和从左到右(上面的项目符号列表中从上到下)我的意思是在上面的列表中,你必须首先调用functionC,然后调用functionB,然后调用functionD,最后功能A.

      调度到每个函数

      根据您使用的工具,它也可以处理这个位。我再次怀疑PEG.js会这样做,如果Jison也这样做我也不会感到惊讶。

      当你准备调用一个函数(不再)将函数调用作为参数时,你可能会有函数名和一个参数数组。假设您将函数存储在地图中:

      var functions = {
          index: function() { /* ... */ },
          firstName: function() { /* ... */ },
          // ...
      };
      

      ......打电话给他们很容易:

      functionResult = functions[functionName].apply(undefined, functionArguments);
      

      我很抱歉不能说“只做X,你就在那里”,但这确实不是一个小问题。我会把工具扔给它,我不会自己发明这个轮子。

答案 1 :(得分:0)

您可以尝试这样的事情:

假设你有这样的功能:

'{{floating(-0.5, 0.5)}}'

所有实际函数都在对象中引用,如下所示:

var myFunctions = {
    'index': function(){/* Do stuff */},
    'firstName': function(){}
}

然后,这应该有效:

function parse(var input){
    var temp = input.replace('{{','').replace(')}}','').split('('),
        fn = temp[0];
        arguments = temp[1].split(',');
    myFunctions[fn].apply(this, arguments);
}

请注意,这仅适用于没有嵌套作为参数的函数的简单函数调用。它还将所有参数作为字符串传递,而不是可能的类型(数字,布尔值等)。

如果您想处理更复杂的字符串,则需要使用正确的解析器或模板引擎,@T.J. Crowder suggested in the comments

答案 2 :(得分:0)

  1. 如果可能,请不要评估用户输入。
  2. 如果您需要对其进行评估,请在受控范围和环境中对其进行评估。
  3. 最后一个意味着而不是使用eval()使用new Function()或专门设计的库,例如https://github.com/dtao/lemming.js

    有关eval与新功能()

    的更多信息,请参阅http://www.2ality.com/2014/01/eval.html

    对于更复杂的方法,尝试创建自己的解析器,请检查https://stackoverflow.com/a/2630085/481422

    https://github.com/douglascrockford/JSLint/blob/master/jslint.js

    中搜索评论// ECMAScript parser