执行从ajax调用返回的js时避免eval

时间:2011-03-23 02:20:51

标签: javascript

我想进行一个返回json对象的ajax调用。其中一个JSON对象的属性将是要在客户端中执行的函数的字符串。我意识到这可以通过使用eval轻松解决,但是看到eval的many disadvantages,我宁愿避免它。我的问题是:

我能以某种方式从服务器返回一些js代码并执行它而不需要使用eval吗?

根据要求,这是一些示例代码:

服务器(Node.js):

var testFunc = function() {
    alert('h1');
};

app.get('/testPack', function(req, res) {
    var template = jade.render('h1 hi');
    res.send({
        template : template,
        entity : testFunc.toString(),
        data : { 
            id: "OMG I love this"
        }
    });
});

客户端:

$(document).ready(function() {
    $.ajax({
        url: '/testPack',
        success: function(data) {
            $('body').append($(data.template))
            alert(data.data.id);
            var entity = eval(data.entity);
            entity();
        }
    })
})

当然,被称为实体的返回函数不会做这么愚蠢的事情,它会暴露返回小部件的API。

为了澄清,我想避免单独调用javascript本身。我宁愿用模板和数据捆绑它来渲染。

9 个答案:

答案 0 :(得分:4)

最简单的方法是,不是通过ajax调用服务器,而是在页面上创建一个新的脚本标记,其中url指向一个可以输出纯JavaScript(而不是JSON)的RESTful Web服务。这样,您的输出将由浏览器直接评估,而无需使用eval。

为了扩大我的答案: 要解决在全局上下文中运行脚本的问题,您可以做一些技巧。例如,当您向头部添加脚本标记时,您可以绑定onload事件(或者更确切地说是假的onload事件,因为IE不支持脚本标记上的onload),并且如果您的服务器响应始终是包含在具有已知名称的函数中,您可以在对象中应用该函数。下面的示例代码(这只是一个例子):

function test ()
{
    this.init = function ()
    {
        var script = document.createElement("script");
        script.type = "text/javascript";
        script.language = "javascript";
        script.src = "test.js";
        var me = this;
        window.callMe = function () { me.scriptReady(me); };
        var head = document.getElementsByTagName("head")[0];
        head.appendChild(script);
    };

    this.scriptReady = function (object)
    {
        serverResponse.call(object);
    };

    this.name = "From inside the object";

    this.init();
}

var t=new test();

服务器响应应如下所示:

function serverResponse()
{
    alert(this.name);
}

window.callMe();

在这种情况下,serverResponse()内的所有内容都将您的对象用作“this”。现在,如果您以这种方式修改服务器响应:

function serverResponse()
{
    this.serverJSONString = { "testVar1": "1", "testVar2": 2 };

    function Test()
    {
        alert("From the server");
    }

    Test();
}

window.callMe();

您可以从服务器返回多个内容,只需一个响应。如果您不喜欢只设置变量,那么在主对象中创建一个函数来处理您可以通过从响应中调用此函数来提供的JSON字符串。

正如你所看到的,这一切都是可行的,它看起来并不漂亮,但话说回来,你要做的事情并不是很开始。

P.S。只是在标签内插入一个字符串不适用于IE,它不允许你这样做。如果您不必支持IE,那么您可以在新创建的脚本标记中插入服务器响应并完成它。

P.P.S。请不要按原样使用此代码,因为我没有花太多时间写它。这很丑陋,但只是作为一个例子: - )

答案 1 :(得分:1)

不,您无法按照定义执行此操作,因为JavaScript函数无效JSON。请参阅此处的规范:

如果你要返回一个字符串,那就是它:它只是一个字符串。如果没有eval,您无法对其进行评估。无论你想要什么,你都可以打电话给任何其他人,但请不要称之为JSON。

答案 2 :(得分:1)

以下是我认为这可行的一个例子。

json对象表示从服务器返回的内容。 cd属性包含函数名称作为字符串。如果这些函数是页面中存在的其他对象的属性,那么您应该能够使用对象[“property”]访问器来调用它们。

在jsFiddle上查看它:http://jsfiddle.net/WUY4n/1/

// This function is a child of the window object
window.winScopedFunction = function() {
    alert("ROCK THE WIN");    
}

// This function is a child of another object
var myObject = {
  myFunction : function() {
    alert("ROCK ON");
  }
};

// pretend that this json object was the result of an ajax call.
var jsonResultFromServer= {
    a : 1,
    b : 2,
    c : "myFunction", 
    d : "winScopedFunction" 
};

// you can call the local functions like so
myObject[jsonResultFromServer.c]();

window[jsonResultFromServer.d]();

答案 3 :(得分:1)

是的,有一种方法,但它与eval具有完全相同的缺点。

您可以使用Function构造函数创建新函数,然后调用它。例如:

new Function(code)();

答案 4 :(得分:1)

http://code.google.com/p/json-sans-eval/是一个不使用eval的快速JSON解析器,JSON.parse在新浏览器中的使用越来越广泛。两者都是解析JSON的eval的绝佳替代品。

答案 5 :(得分:0)

你有一些例子吗?我能想到的一些事情是你可以在你的js文件中有一个常规函数,你的服务器将返回一些参数供你的函数执行。您甚至可以指定要使用的功能! (这不是很棒吗?)

// your js file
var some_namespace = {
  some_function : function(a, b){
    // stuff
  }
}

// your server output
{
  some_other_data: "123",
  execute: {
    func: "some_namespace.some_function",
    params: [1, 2]
  }
}

// your ajax callback
function(r){
  window[r.execute.func].apply(this, r.execute.params);
}

答案 6 :(得分:0)

您可以使用Google对Google Charts的诀窍。

<html>
<head>
  <script>
   function onWorkDone(data) {
    console.log(data);
   }
  </script>
  <script src="callback.js"></script>
</head>
</html>

然后你的callback.js是:

function doWork(callback) {
  callback({result: 'foo'});
}
doWork(onWorkDone);

基本上,当onWorkDone完成后,您的脚本会调用doWork。你可以在这里看到一个有效的例子: http://jsfiddle.net/ea9Gc/

答案 7 :(得分:0)

不使用eval的原因

嗯,你自己已经说过了。不要使用eval。但是你有关于为什么的错误图片。

不是 eval是邪恶的。你弄错了。除了性能方面的考虑,使用eval这种方式允许一个草率的程序员在客户端上执行从服务器传递的代码。注意“从服务器传递”部分。

为什么永远不执行从服务器传递的代码

为什么不想执行从服务器传递的代码(顺便说一句,这是你打算做的)?

当浏览器在网页上执行脚本时,只要网站有效 - 即你的网站有效,而不是冒充你自己试图欺骗用户的恶意软件网站 - 你可以合理地确定浏览器运行的每一段代码都由您自己编写

黑客的天堂 - 剧本注入攻击

现在,如果您要将数据从服务器传递到Web应用程序,并且该数据包含可执行函数,那么您就会遇到麻烦。在从服务器到客户端浏览器的漫长而扭曲的数据旅程中,它通过称为互联网的狂野西部,可能通过多层代理,过滤器和转换器,其中大多数是您无法控制的。

现在,如果黑客隐藏在中间的某个地方,从服务器获取数据,将代码修改为非常糟糕的功能,并将其发送到客户端,然后客户端浏览器获取数据并执行代码。瞧!坏事发生了。更糟糕的是:你(在服务器端)永远不会知道你的客户被黑了。

这称为“脚本注入攻击”,是一种严重的安全风险

因此,规则是:永远不要执行从服务器返回的函数。

仅从服务器传递数据

如果您只接受来自服务器的数据,那么黑客可能会发生的最多事情就是您的客户端会看到奇怪的数据回来,希望您的脚本会过滤掉它们或者将它们作为错误数据处理。您的客户端浏览器不会运行黑客高兴地编写的任何任意代码

在您的客户端脚本中,您当然坚持黄金法则:不要相信通过互联网传输的任何数据。因此,在使用JSON数据之前,您已经进行了类型检查和验证,并禁止任何看似可疑的数据。

不要这样做 - 从服务器传递函数并在客户端执行

所以,长话短说:不要做

考虑在浏览器上指定可插入功能的另一种方法 - 有多种方法。

答案 8 :(得分:0)

我有同样的问题,我这样解决了问题:

文件: functions.js.php?f = 1,3

$functions=array(
    'showMessage' => 'function(msg){ alert(msg); }',
    'confirmAction' => 'function(action){
                          return confirm("Are you sure you want to "+action+"?");
                        }',
    'getName' => 'function getName(){
                    return prompt("What is your name?");
                  }'
);

$queried = explode($_REQUEST['f']);

echo 'var FuncUtils = {'; // begin javascript object

$counter=1;
foreach($functions as $name=>$function){
    if(in_array($counter, $queried))
        echo '"'.$name.'":,'.$function.',';
    $counter++;
}

echo '"dummy":null };'; // end javascript object

文件: data5.json

{
    "action" : ['confirmAction','exit']
}

文件: test.js

$(document).ready(function(){
    $.getScript('functions.js.php?f=1,3');
});

function onBeforeExit(){
    $.getJSON('data5.json', function(data) {
        var func = data.action.shift();
        FuncUtils[func].apply(null, data.action);
    });
}