2

There is a previous question (here), however the answers there does not answer my question exactly - the accepted answer contained invalid JSON (mandating the use of eval()), which is simply not possible to do even something like that as far as I am aware.

I'm planning to use code from my own server that is stored as object literal syntax in a string, however I'd like the ability to store functions in there as well.

Currently, I've thought of the following possibilities:

  1. Simply use eval() to parse the string
  2. Place functions in string form (with something like "\bfunction" to be able to identify them), running JSON.parse() on it and then use a for-in loop to see whether any such functions need to be parsed (probably quite slow)
  3. Use the DOM to run the code using a <script> tag and just run it there

This code will not contain anything that's supposed to be user-editable, however I'm not sure whether there would still be a safety issue or just a speed one. Would using eval() be appropriate for my situation, and is there a more effective method of doing this than parsing for functions manually or using eval()?

EDIT: would an alternative syntax to parse be better or would that just make things even more complicated?

EDIT2: I'm looking simply to do something like the following:

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

I'm not looking to just parse an entire function from a string, e.g.

eval('function(){}');
Community
  • 1
  • 1
Qantas 94 Heavy
  • 15,750
  • 31
  • 68
  • 83
  • 1
    use the reviver parameter to JSON.parse and try{} finding functions within. on the other hand, if all the data comes from you, eval() is perfectly safe to use, and just about anything you can do with eval() you can do with Function(). – dandavis Sep 16 '13 at 16:18
  • What do you need the functions for? Can't you simply make a generic function (factory) and send only the JSONifiable parameters for that? – Bergi Sep 17 '13 at 01:56
  • @Bergi: I'd like the flexibility of defining custom functions, some which need to be added simply for capabilities added through the JSON. – Qantas 94 Heavy Sep 17 '13 at 02:21

5 Answers5

2

You basically have only two options to bring function code to the client:

  • Use a JavaScript object literal, in which you include function expressions. Whether you use AJAX + eval or a JSONP-like approach with a <script> node doesn't really matter in terms of performance. AJAX will be more flexible though (HTTP methods, synchronous requests) and not need a global callback function.

    Using a JS expression will give you all possible freedom, that includes custom data types (like Date objects or invoking your own constructors) or even circular structures (if wrapped within an IEFE). However, serializing such data on the server into a script will be more difficult, might need to be hand-crafted and is more error-prone (syntax or even runtime errors will cause the whole parsing to fail).

  • Use valid JSON, and then create functions from the code strings of which you know. Using that standardized format will simplify the serverside serialization, and make your data accessible to clients without a JS interpreter. This approach is very common, most people who use objects with custom prototypes are well accustomed to this task. You just will use the Function constructor instead.

    Depending on your schema, you can either iterate/access the parsed structure and redefine the appropriate properties, or you use the reviver callback argument to JSON.parse. An example:

    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);
    });
    
Bergi
  • 630,263
  • 148
  • 957
  • 1,375
2

Effectiveness (Will they do what they should do?)

All of 1, 2, and 3 will work

  1. eval the responseText: This will work fine. If you add in a same-origin challenge per security recommendation #3, you must correct it first.

  2. Identify individual items in the object to "revive". I've used this approach before, and prefer to use a naming convention for the keys to determine when to revive, rather than a marker in the value. See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse for a description of revivers.

    In both cases 1 and 2, you will have to somehow cause the function text to be parsed & executed. Here are your options. None is really less evil than the others (see section on security)

    If you use f=eval(responseText), this is a "direct eval". The function will have local scope where the eval is called.

    If you use f=(1,eval)(responseText), this is an "indirect eval". The function will have global scope. See http://perfectionkills.com/global-eval-what-are-the-options/

    If you use f=new Function(responseText), the function will have a separate local scope under the global scope.

  3. This method is called JSONP (for JSON with padding). It will work and is probably the easiest to implement. It is also not good when the response may contain sensitive user data (see below).

Security (Will they not do what they should not do?)

IF the delivered message IS under your control (100% sure no user/attacker can modify it): All of options 1, 2, and 3 do NOT compromise the user's browser state (i.e. XSS, e.g. enabling things like stealing their cookies).

IF the delivered message IS NOT 100% certainly under your control: All of methods 1, 2, and 3 DO compromise the user's browser state and are unsafe. Your options are:

  • Accept the security risk (users/attackers will be able to arbitrarily change your site functionality, including but not limited to: stealing your users' cookies for the domain, stealing any information the user has access to on your domain, directing your users to malware-infested pages to potentially install viruses)

  • Pass the potentially unsafe functions into a webworker and run them there. They will be sandboxed and cannot affect the browser window. However, this is more work and not available in all browsers.

  • Pass the potentially unsafe functions into an iframe on a separate domain and run them there. This protects your users' cookies,etc. but does not prevent attackers from redirecting them to exploit sites to install viruses (though you can just hope your users have secure browsers :-/ )

  • Use whitelisted functions that you do control and just pass around the name of the whitelisted function (or factory functions that are essentially whitelisted functions with a few flexible parameters)

IF you pass user-specific information in your data:

  • 3 is not safe for passing around sensitive user data (the script can easily be called with the user's cookies from any domain), unless your server implements checks on the referer/origin HTTP headers of the request before returning the response. Even then it can supposedly be unsafe. See http://en.wikipedia.org/wiki/Cross-site_request_forgery

  • The naive implementations of options 1, 2 are also unsafe in this way (i.e. XSRF. e.g. a malicious site can forge a request for the data on behalf of the user using their cookies and then do what they like with it). A third party domain can overload the built-in Array constructor and then insert a script tag pointing to the JSON file. The browser will request this file with the user's cookies, return the user's JSON to the third-party site, and the javascript engine will "run" the JSON, which is just a single statement, but because the Array constructor has been overloaded, it can do whatever it wants with the data as javascript tries to construct the value in that single JSON "statement"

  • For 1 and 2 to be XSRF safe, you should put a statement that would cause it to break if interpreted as a script, such as invalid syntax or an infinite loop, then your script should receive the text and modify it to remove the error. This is because same-domain scripts have access to read the responseText of a request before they parse it, whereas cross-domain scripts can only access this information by inserting a script tag with this as the source.

Fabio Beltramini
  • 2,441
  • 1
  • 16
  • 25
1

There are a lot of ways to create a JS function from a string, but eval is evil and should be avoided when it's possible.

You can create the function in different ways, for example using the new Function("...") statement, or creating a function in your scope with that name and then call it with parameters as string. I have done a JSFiddle with a test. The fastest way is eval, but as I said before it should be avoided. DOM and new Function() ways are equally fast, on 1000 iterations they have a difference of few milliseconds(fn: 3328, DOM: 3371) Here you have the JSFiddle, do some tests and draw your own conclusions.

The main difference between eval and new Function is that the former can access the local variables while the latter can't. See this answer.

Community
  • 1
  • 1
Niccolò Campolungo
  • 11,824
  • 4
  • 32
  • 39
0

You really don't have many options, but there's a fourth alternative (JQuery uses this approach) :

var yourFunction = new Function("var a=1; return a; //this is your function body");
Alcides Queiroz
  • 9,456
  • 3
  • 28
  • 43
0

There are a few things to consider for your specific situation.

I think the rule of thumb should be, if you're not sure whether or not to use eval, you probably shouldn't use it. I also think some people obsess over performance where operations per sec could really negligible. Is your app a huge app that relies on squeezing every ounce of performance out? Is 100,000ops/sec vs 1,000,000ops/sec going to be the difference between whether or not your app is usable? Also you'll have to consider support as all browsers do not include JSON support natively.

Nevertheless, my first thought:

Assuming you need to have these said functions being sent via JSON (which sounds a little odd to me) and you have control over what the server is sending you, would be to label your functions with a prefix like js-. If they needed to be executed as soon as they are received, you could strip the js- off using substr and execute it using bracket notation something like:

    //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]();
    }

If you're not trying to execute them immediately, maybe add them to an array/object literal for calling later.

okcoker
  • 1,329
  • 9
  • 16