Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

Loading…

default to true in JSON.decode to avoid eval() #2562

Merged
merged 2 commits into from

3 participants

Sergio Crisostomo Olmo Maldonado Tim Wienk
Sergio Crisostomo
Collaborator

Make the secure parameter default to true, to avoid using eval() as
default.

I supose this is better than having eval() as the default parser

This was commented both on Stackoverflow and mootools.net/comments
http://mootools.net/docs/core/Utilities/JSON#comment-1157192420

Olmo Maldonado ibolmo added this to the 1.5 milestone
Olmo Maldonado ibolmo added the api label
Olmo Maldonado
Owner

This would break BC. I suppose the sensible option is to put secure on, and allow them to turn it off when they trust the source. You'll need to supply, though, an entry in the 1.4compat layer, and spec coverage that checks:

  • secure is the default option
  • secure is working (e.g. JSON.decode('alert(...)') returns null)
  • secure and setting it to false would still decode
Source/Utilities/JSON.js
@@ -69,7 +69,7 @@ JSON.encode = JSON.stringify ? function(obj){
JSON.decode = function(string, secure){
if (!string || typeOf(string) != 'string') return null;
-
+ secure = secure == undefined ? true : secure;
Olmo Maldonado Owner
ibolmo added a note

if (secure == undefined) secure = true;

Tim Wienk Owner

Actually:

if (secure == null) secure = true;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Source/Utilities/JSON.js
@@ -69,7 +69,7 @@ JSON.encode = JSON.stringify ? function(obj){
JSON.decode = function(string, secure){
if (!string || typeOf(string) != 'string') return null;
-
+ secure = secure == undefined ? true : secure;
if (secure || JSON.secure){
Olmo Maldonado Owner
ibolmo added a note

Actually you need to account for JSON.secure's value.

See the following:

secure JSON.secure expected result
null null secure
null false not secured
null true secure
false null not secured
false false not secured
false true not secured
true null secured
true false secured
true true secured

I think then we need to add a JSON.secure = true; some where and add if (secure == undefined) secure = JSON.secure;

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sergio Crisostomo
Collaborator

Very nice input! Thanks, will fix.

@ibolmo: about JSON.secure, you are right.
Btw, where/why is it used? Do not find it anywhere else in Mootools?

Will make some specs for this also following your suggestions.
Should I add it in the 1.5base or 1.5client?

Olmo Maldonado
Owner

1.5base, and I don't know but just looking at the code we default for JSON.secure

Olmo Maldonado
Owner

Right I meant the following:

secure JSON.secure expected result
null -- secure
null false not secured
null true secure
false -- not secured
false false not secured
false true not secured
true -- secured
true false secured
true true secured

But since we'll add a JSON.secure = true;

secure JSON.secure expected result
null true secure
null false not secured
null true secure
false true not secured
false false not secured
false true not secured
true true secured
true false secured
true true secured
Sergio Crisostomo
Collaborator

@ibolmo the JSON.secure is a bit weird. It was added in the 2.0 branch c04d545 and I find no other reference to it in all Core & More files.
It is not documented in the docs either afaik.

So if we add JSON.secure = true; it has to be added to the docs and developers wanting to use "the eval way" would have to make a double false. On the function parameters and a JSON.secure = false;

I'm ok with adding it. Also ok with leaving it out (i.e not adding JSON.secure = true;).

Olmo Maldonado
Owner
Sergio Crisostomo
Collaborator

@ibolmo , thanks for your time checking this!
I'm still not sure I follow you. I see 3 scenario:

1- Adding both JSON.secure = true; and if (secure == null) secure = JSON.secure;
This means default behavior is secure. And to make it un-secure user has to use JSON.secure = false; which is not documented.
If user uses JSON.secode(string, false); as documented, that alone would turn to true anyway because of JSON.secure = true;.

2- Adding just JSON.secure = true; (and keeping if (secure == null) secure = true;)
This means default behavior is secure. But to override it and go un-secure user needs to use double false:

JSON.secode(string, false);
JSON.secure = false;

3- Using as it is now, in the PR. ( and the right one in my logic)
This means default behavior is secure. To override user would use, as documented already, just JSON.secode(string, false);


JSON.secure seems to be kind of a backdoor/alternative, not documented.

Olmo Maldonado
Owner
Sergio Crisostomo
Collaborator

@ibolmo I think I figured it out now with the compat layer.

I added a compat layer, and with that I also removed future reference to JSON.secure without the compat.

Let me know if it looks ok.

Source/Utilities/JSON.js
@@ -69,8 +69,13 @@ JSON.encode = JSON.stringify ? function(obj){
JSON.decode = function(string, secure){
if (!string || typeOf(string) != 'string') return null;
+
+ //<1.4compat>
+ if (typeof JSON.secure != "undefined" && !secure) secure = JSON.secure;
Olmo Maldonado Owner
ibolmo added a note

if (secure == null) secure = JSON.secure;

You need to define JSON.secure = true; outside JSON.decode

Olmo Maldonado Owner
ibolmo added a note

and document JSON.secure as a global option.

Sergio Crisostomo Collaborator

@ibolmo I like the idea to have a documented global JSON.secure, didn't think of that option actually.

We could just keep the code as current PR and document the possibility to use a global JSON.secure.
Your last suggestion would make JSON.secure be ignored if secure is false, because if (secure == null) will turn false and we have a new L78 just looking for secure.

As it is it defaults to secure. If we add extra JSON.secure = true; as global, then the old code that followed the docs using secure = false will be forced to secure.

If we keep this PR, there is no break of BC in the code that active defined secure or JSON.secure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Source/Utilities/JSON.js
((8 lines not shown))
- if (secure || JSON.secure){
+ if (secure == null) secure = true;
Olmo Maldonado Owner
ibolmo added a note

This would be if (secure == null) secure = JSON.secure;

I don't understand what you mean. Look at the table I proposed. You would add JSON.secure = false in the 1.4compat block.

I like to think of the right API and make the code work around it. You need to look at what it did before the changes, where the API should be, and make the changes considering those factors.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sergio Crisostomo
Collaborator

@ibolmo I was misunderstanding the compat layer's behaviour. I updated the PR code and docs now.

Specs/1.5base/Utilities/JSON.js
((6 lines not shown))
+description: JSON encoder and decoder.
+
+license: MIT-style license.
+
+SeeAlso: <http://www.json.org/>
+
+requires: [Array, String, Number, Function]
+
+provides: JSON
+
+...
+*/
+describe('JSON', function(){
+
+ var goodString = '{"name":"Jim Cowart","location":{"city":{"name":"Chattanooga","population":167674}}}';
+ var badString = 'alert("I\'m a bad string!")';
Olmo Maldonado Owner
ibolmo added a note

Could you check the spacing. Looks like you have spaces at the beginning of the lines.

Also can you add a spec for JSON.secure global option.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Sergio Crisostomo
Collaborator

This PR has to be refactored after Olmo's new Specs PR is merged.

Olmo Maldonado
Owner
Sergio Crisostomo
Collaborator

@ibolmo will do that.
I supose I don't need the package.yml and Configuration.JS files anymore?

I see also a new directory structure without /1.5base/ so I suppose the mootools-packager script goes forEach directory inside the Specs and the compat layer info goes inside the respective specs .js file, with compat comment tags when needed.

I run the specs adding my new JSON.js specs to the one on JSON.js specs file from your repo and they worked good with grunt.

Olmo Maldonado
Owner
Olmo Maldonado ibolmo modified the milestone: 1.6, 1.5
Sergio Crisostomo
Collaborator

@ibolmo updated the PR with new master.

Sergio Crisostomo
Collaborator

@ibolmo could you merge or give your :+1: on this? would be nice to get this into 1.5 also to avoid eval unnecessarily.

Docs/Utilities/JSON.md
@@ -33,7 +33,9 @@ Converts a JSON string into a JavaScript object.
### Arguments:
1. string - (*string*) The string to evaluate.
-2. secure - (*boolean*, optional: defaults to false) If set to true, checks for any hazardous syntax and returns null if any found.
+2. secure - (*boolean*, optional) If set to true, checks for any hazardous syntax and returns null if any found.
Olmo Maldonado Owner
ibolmo added a note

(*boolean*, optional: defaults to true)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Olmo Maldonado
Owner

One little gotcha, but yep it's :+1:

Olmo Maldonado ibolmo merged commit 2769c9d into from
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Mar 6, 2014
  1. Sergio Crisostomo
Commits on Mar 16, 2014
  1. Sergio Crisostomo
This page is out of date. Refresh to see the latest.
4 Docs/Utilities/JSON.md
View
@@ -33,7 +33,9 @@ Converts a JSON string into a JavaScript object.
### Arguments:
1. string - (*string*) The string to evaluate.
-2. secure - (*boolean*, optional: defaults to false) If set to true, checks for any hazardous syntax and returns null if any found.
+2. secure - (*boolean*, optional: defaults to true) If set to true, checks for any hazardous syntax and returns null if any found.
+
+There is also a global option `JSON.secure` (*boolean*: defaults to true). If the optional `secure` argument is not defined, the value of `JSON.secure` will be used.
### Returns:
10 Source/Utilities/JSON.js
View
@@ -67,10 +67,16 @@ JSON.encode = JSON.stringify ? function(obj){
return null;
};
+JSON.secure = true;
+//<1.4compat>
+JSON.secure = false;
+//</1.4compat>
+
JSON.decode = function(string, secure){
if (!string || typeOf(string) != 'string') return null;
-
- if (secure || JSON.secure){
+
+ if (secure == null) secure = JSON.secure;
+ if (secure){
if (JSON.parse) return JSON.parse(string);
if (!JSON.validate(string)) throw new Error('JSON could not decode the input; security is enabled and the value is not secure.');
}
44 Specs/Utilities/JSON.js
View
@@ -31,3 +31,47 @@ describe('JSON', function(){
});
});
+describe('JSON', function(){
+
+ var goodString = '{"name":"Jim Cowart","location":{"city":{"name":"Chattanooga","population":167674}}}';
+ var badString = 'alert("I\'m a bad string!")';
+
+ it('should parse a valid JSON string by default', function(){
+ expect(typeOf(JSON.decode(goodString))).toEqual("object");
+ });
+
+ it('should parse a valid JSON string when secure is set to false', function(){
+ expect(typeOf(JSON.decode(goodString, false))).toEqual("object");
+ });
+
+ it('should parse a hazarous string when secure is set to false', function(){
+ var _old_alert = window.alert;
+ window.alert = function (string) {
+ if (string == "I'm a bad string!") return true;
+ return false;
+ };
+ expect(JSON.decode(badString, false)).toEqual(true);
+ window.alert = _old_alert;
+ });
+ it('should parse a hazarous string when JSON.secure is set to false and secure is not defined', function(){
+ var _old_alert = window.alert;
+ window.alert = function (string) {
+ if (string == "I'm a bad string!") return true;
+ return false;
+ };
+ JSON.secure = false;
+ expect(JSON.decode(badString)).toEqual(true);
+ window.alert = _old_alert;
+ JSON.secure = true;
+ });
+ it('should NOT parse a hazarous string by default', function(){
+ var err;
+ try {
+ JSON.decode(badString);
+ } catch (e){
+ err = !!e;
+ };
+ expect(err).toEqual(true);
+ });
+
+});
Something went wrong with that request. Please try again.