解析函数以字符串形式存储在对象文本/JSON语法中,并进行区分

Parsing functions stored as strings in object literal/JSON syntax and differetiating

本文关键字:语法 JSON 行区 文本 对象 函数 字符串 存储      更新时间:2023-09-26

前面有一个问题(在这里),但是那里的答案并没有完全回答我的问题-接受的答案包含无效的JSON(强制使用eval()),据我所知,这根本不可能做到甚至像这样。

我计划使用我自己的服务器的代码,存储为字符串中的对象字面语法,但是我希望能够在那里存储函数。

目前,我想到了以下可能性:

  1. 只需使用eval()解析字符串
  2. 将函数置于字符串形式(与"'bfunction"之类的东西能够识别它们),在其上运行JSON.parse(),然后使用for-in循环查看是否有任何此类函数需要解析(可能相当慢)
  3. 使用DOM使用<script>标签运行代码,并在那里运行它

这段代码将不包含任何应该是用户可编辑的,但我不确定是否还会有一个安全问题或只是一个速度。使用eval()是否适合我的情况,是否有比手动解析函数或使用eval()更有效的方法?

编辑:另一种语法解析会更好还是会让事情变得更复杂?

编辑2:我正在寻找简单地做下面的事情:

{ "test": function () {}
, "foo": 1
, "bar": 2 }

我是不是寻找只是从字符串解析整个函数,例如

eval('function(){}');

有效性(他们会做他们应该做的吗?)

1、2和3都可以

  1. eval the responseText: This will work fine。如果根据安全性建议#3添加了同源挑战,则必须首先纠正它。

  2. 识别物体中要"复活"的单个物品。我以前使用过这种方法,并且更喜欢对键使用命名约定来确定何时恢复,而不是在值中使用标记。有关恢复器的说明,请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse。

    在第1和第2种情况下,您必须以某种方式使函数文本被解析&执行。这是你的选择。没有一个比其他的更邪恶(参见安全部分)

    如果你使用f=eval(responseText),这是一个"直接eval"。函数将在调用eval的地方具有局部作用域。

    如果你使用f=(1,eval)(responseText),这是一个"间接eval"。该函数将具有全局作用域。见http://perfectionkills.com/global-eval-what-are-the-options/

    如果使用f=new Function(responseText),该函数将在全局作用域下拥有一个单独的局部作用域。

  3. 这个方法被称为JSONP(用于带填充的JSON)。它会起作用,而且可能是最容易实现的。当响应可能包含敏感用户数据(见下文)时,这也不好。

安全(他们不会做他们不应该做的吗?)

如果传递的消息在您的控制之下(100%确定没有用户/攻击者可以修改它):所有选项1,2,3都不会危及用户的浏览器状态(即XSS,例如启用窃取他们的cookie之类的事情)。

如果传递的消息不是100%肯定在你的控制之下:所有方法1、2和3都危及用户的浏览器状态,是不安全的。你的选择是:

  • 接受安全风险(用户/攻击者将能够任意更改您的网站功能,包括但不限于:窃取您用户的域名cookie,窃取用户在您的域名上可以访问的任何信息,将您的用户引导到恶意软件感染的页面以潜在地安装病毒)

  • 将可能不安全的函数传递给webworker并在那里运行它们。它们将被沙盒化,不会影响浏览器窗口。然而,这是更多的工作,并不是所有的浏览器都可用。

  • 将可能不安全的函数传递到单独域中的iframe中,并在那里运行它们。这样可以保护用户的cookie等。但不能阻止攻击者将它们重定向到利用网站安装病毒(尽管你只能希望你的用户有安全的浏览器:-/)

  • 使用你可以控制的白名单函数,并且只传递白名单函数的名称(或者本质上是带有一些灵活参数的白名单函数的工厂函数)

如果您在数据中传递特定于用户的信息:

  • 3对于传递敏感用户数据是不安全的(脚本可以很容易地从任何域的用户的cookie调用),除非您的服务器在返回响应之前对请求的referer/origin HTTP报头进行检查。即便如此,它也可能是不安全的。见http://en.wikipedia.org/wiki/Cross-site_request_forgery

  • 选项1,2的朴素的实现也是不安全的(即XSRF)。例如,恶意网站可以使用cookie代表用户伪造数据请求,然后做他们喜欢的事情)。第三方域可以重载内置的Array构造函数,然后插入一个指向JSON文件的脚本标记。浏览器将使用用户的cookie请求该文件,将用户的JSON返回给第三方站点,javascript引擎将"运行"JSON,这只是一个单独的语句,但是由于Array构造函数已经重载,它可以对数据做任何它想做的事情,因为javascript试图在单个JSON"语句"中构造值

  • 对于XSRF安全的1和2,您应该放置一个语句,如果将其解释为脚本,则会导致中断,例如无效语法或无限循环,然后脚本应该接收文本并修改它以删除错误。这是因为同域脚本在解析请求之前可以读取请求的responseText,而跨域脚本只能通过插入一个脚本标记来访问该信息。

基本上只有两种选择将函数代码带到客户端:

  • 使用JavaScript对象字面值,其中包含函数表达式。就性能而言,使用AJAX + eval还是使用类似jsp的方法处理<script>节点并不重要。AJAX将更加灵活(HTTP方法,同步请求),并且不需要全局回调函数。

    使用JS表达式将给你所有可能的自由,包括自定义数据类型(如Date对象或调用自己的构造函数),甚至是循环结构(如果包装在IEFE中)。但是,将服务器上的这些数据序列化到脚本中将更加困难,可能需要手工编写,并且更容易出错(语法甚至运行时错误都会导致整个解析失败)。

  • 使用有效的JSON,然后从你知道的代码字符串创建函数。使用这种标准化格式将简化服务器端序列化,并使客户端无需JS解释器即可访问数据。这种方法很常见,大多数使用带有自定义原型的对象的人都很习惯这种任务。您只需使用Function构造函数即可。

    根据您的模式,您可以迭代/访问解析的结构并重新定义适当的属性,或者您可以使用reviver回调参数到JSON.parse。一个例子:

    var json = '{"validators":[{"name":"required", "message":"must be filled", "fn":["str", "return str.trim().length > 0;"]}, {…}, …]}';
    var result = JSON.parse(json, function(k, v) {
        if (k != "fn") return v;
        return Function.apply(null, v);
    });
    

有很多方法可以从字符串创建JS函数,但是eval邪恶的,应该尽可能避免。

你可以用不同的方式创建函数,例如使用new Function("...")语句,或者在你的作用域中用那个名字创建一个函数,然后用参数作为字符串调用它。我做了一个JSFiddle测试。最快的方法是eval,但正如我之前所说,它应该避免。DOM和new Function()方法同样快,在1000次迭代中,它们的差异只有几毫秒(fn: 3328, DOM: 3371)现在您有了JSFiddle,做一些测试并得出您自己的结论。

evalnew Function的主要区别在于前者可以访问局部变量,而后者不能。看看这个答案。

您确实没有太多选择,但是有第四种选择(JQuery使用这种方法):

var yourFunction = new Function("var a=1; return a; //this is your function body");

有几件事要考虑您的具体情况。

我认为经验法则应该是,如果你不确定是否使用eval,你可能不应该使用它。我还认为有些人过于关注性能,而每秒的操作数实际上可以忽略不计。你的应用程序是一个巨大的应用程序,依赖于压榨每一盎司的性能?100,000ops/sec和1,000,000ops/sec是否会成为你的应用是否可用的区别?此外,您还必须考虑支持,因为并非所有浏览器都包含JSON原生支持。

然而,我的第一个想法是:

假设您需要通过JSON发送这些所说的函数(这对我来说听起来有点奇怪),并且您可以控制服务器向您发送的内容,将使用js-之类的前缀标记您的函数。如果它们需要在收到后立即执行,可以使用substr剥离js-,并使用括号表示法执行,例如:

    //loop over values
    if (value[i].substr && value[i].indexOf('js-') === 0) {
        var func = value[i].substr(3);
        //assuming this is a global function you're calling
        window[func]();
    }

如果您不打算立即执行它们,可以将它们添加到数组/对象字面量中,以便稍后调用。