]> source.dussan.org Git - jquery.git/commitdiff
Fix #14492: More correct jQuery.parseJSON. Close gh-1419.
authorRichard Gibson <richard.gibson@gmail.com>
Tue, 5 Nov 2013 04:36:15 +0000 (23:36 -0500)
committerRichard Gibson <richard.gibson@gmail.com>
Tue, 12 Nov 2013 05:07:28 +0000 (00:07 -0500)
src/ajax/parseJSON.js
test/jquery.js
test/unit/core.js

index 735015c9fe7f7b16f52246bc2b3a62a884aab77e..69b5c837d81643aae128308ca9d6c696d77000b8 100644 (file)
@@ -2,39 +2,48 @@ define([
        "../core"
 ], function( jQuery ) {
 
-var rvalidchars = /^[\],:{}\s]*$/,
-       rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g,
-       rvalidescape = /\\(?:["\\\/bfnrt]|u[\da-fA-F]{4})/g,
-       rvalidtokens = /"[^"\\\r\n]*"|true|false|null|-?(?:\d+\.|)\d+(?:[eE][+-]?\d+|)/g;
+var rvalidtokens = /(,)|(\[|{)|(}|])|"(?:[^"\\\r\n]|\\["\\\/bfnrt]|\\u[\da-fA-F]{4})*"\s*:?|true|false|null|-?(?!0\d)\d+(?:\.\d+|)(?:[eE][+-]?\d+|)/g;
 
 jQuery.parseJSON = function( data ) {
        // Attempt to parse using the native JSON parser first
        if ( window.JSON && window.JSON.parse ) {
-               return window.JSON.parse( data );
+               // Support: Android 2.3
+               // Workaround failure to string-cast null input
+               return window.JSON.parse( data + "" );
        }
 
-       if ( data === null ) {
-               return data;
-       }
-
-       if ( typeof data === "string" ) {
+       var requireNonComma,
+               depth = null,
+               str = jQuery.trim( data + "" );
 
-               // Make sure leading/trailing whitespace is removed (IE can't handle it)
-               data = jQuery.trim( data );
+       // Guard against invalid (and possibly dangerous) input by ensuring that nothing remains
+       // after removing valid tokens
+       return str && !jQuery.trim( str.replace( rvalidtokens, function( token, comma, open, close ) {
 
-               if ( data ) {
-                       // Make sure the incoming data is actual JSON
-                       // Logic borrowed from http://json.org/json2.js
-                       if ( rvalidchars.test( data.replace( rvalidescape, "@" )
-                               .replace( rvalidtokens, "]" )
-                               .replace( rvalidbraces, "")) ) {
+               // Force termination if we see a misplaced comma
+               if ( requireNonComma && comma ) {
+                       depth = 0;
+               }
 
-                               return ( new Function( "return " + data ) )();
-                       }
+               // Perform no more replacements after returning to outermost depth
+               if ( depth === 0 ) {
+                       return token;
                }
-       }
 
-       jQuery.error( "Invalid JSON: " + data );
+               // Commas must not follow "[", "{", or ","
+               requireNonComma = open || comma;
+
+               // Determine new depth
+               // array/object open ("[" or "{"): depth += true - false (increment)
+               // array/object close ("]" or "}"): depth += false - true (decrement)
+               // other cases ("," or primitive): depth += true - true (numeric cast)
+               depth += !close - !open;
+
+               // Remove this token
+               return "";
+       }) ) ?
+               ( Function( "return " + str ) )() :
+               jQuery.error( "Invalid JSON: " + data );
 };
 
 return jQuery.parseJSON;
index bdee83c289c16380c6ea32f66606065062aa77d6..e94f55fe894ddcb3bf06c4fb29360ffc3de3b675 100644 (file)
        QUnit.config.urlConfig.push({
                id: "basic",
                label: "Bypass optimizations",
-               tooltip: "Force use of the most basic code by disabling native querySelectorAll; contains; compareDocumentPosition"
+               tooltip: "Force use of the most basic code by disabling native querySelectorAll; contains; compareDocumentPosition; JSON.parse"
        });
        if ( QUnit.urlParams.basic ) {
                document.querySelectorAll = null;
                document.documentElement.contains = null;
                document.documentElement.compareDocumentPosition = null;
+               window.JSON = null;
        }
 
        // iFrames won't load AMD (the iframe tests synchronously expect jQuery to be there)
index 7bd126148734b98978ce4bd45cb0cb15d03777ec..2af9f4a66fc997eb91690dfa34a8f281e88d2571 100644 (file)
@@ -1341,13 +1341,35 @@ test("jQuery.parseHTML", function() {
        equal( jQuery.parseHTML("<td><td>")[ 1 ].parentNode.nodeType, 11, "parentNode should be documentFragment" );
 });
 
-test("jQuery.parseJSON", function(){
-       expect( 9 );
+test("jQuery.parseJSON", function() {
+       expect( 20 );
+
+       strictEqual( jQuery.parseJSON( null ), null, "primitive null" );
+       strictEqual( jQuery.parseJSON("0.88"), 0.88, "Number" );
+       strictEqual(
+               jQuery.parseJSON("\" \\\" \\\\ \\/ \\b \\f \\n \\r \\t \\u007E \\u263a \""),
+               " \" \\ / \b \f \n \r \t ~ \u263A ",
+               "String escapes"
+       );
+       deepEqual( jQuery.parseJSON("{}"), {}, "Empty object" );
+       deepEqual( jQuery.parseJSON("{\"test\":1}"), { "test": 1 }, "Plain object" );
+       deepEqual( jQuery.parseJSON("[0]"), [ 0 ], "Simple array" );
+
+       deepEqual(
+               jQuery.parseJSON("[ \"string\", -4.2, 2.7180e0, 3.14E-1, {}, [], true, false, null ]"),
+               [ "string", -4.2, 2.718, 0.314, {}, [], true, false, null ],
+               "Array of all data types"
+       );
+       deepEqual(
+               jQuery.parseJSON( "{ \"string\": \"\", \"number\": 4.2e+1, \"object\": {}," +
+                       "\"array\": [[]], \"boolean\": [ true, false ], \"null\": null }"),
+               { string: "", number: 42, object: {}, array: [[]], boolean: [ true, false ], "null": null },
+               "Dictionary of all data types"
+       );
+
+       deepEqual( jQuery.parseJSON("\n{\"test\":1}\t"), { "test": 1 },
+               "Leading and trailing whitespace are ignored" );
 
-       equal( jQuery.parseJSON( null ), null, "Actual null returns null" );
-       equal( jQuery.isEmptyObject( jQuery.parseJSON("{}") ), true, "Empty object returns empty object" );
-       deepEqual( jQuery.parseJSON("{\"test\":1}"), { "test": 1 }, "Plain object parses" );
-       deepEqual( jQuery.parseJSON("\n{\"test\":1}"), { "test": 1 }, "Leading whitespaces are ignored." );
        raises(function() {
                jQuery.parseJSON();
        }, null, "Undefined raises an error" );
@@ -1357,12 +1379,53 @@ test("jQuery.parseJSON", function(){
        raises(function() {
                jQuery.parseJSON("''");
        }, null, "Single-quoted string raises an error" );
+       /*
+
+       // Broken on IE8
+       raises(function() {
+               jQuery.parseJSON("\" \\a \"");
+       }, null, "Invalid string escape raises an error" );
+
+       // Broken on IE8, Safari 5.1 Windows
+       raises(function() {
+               jQuery.parseJSON("\"\t\"");
+       }, null, "Unescaped control character raises an error" );
+
+       // Broken on IE8
+       raises(function() {
+               jQuery.parseJSON(".123");
+       }, null, "Number with no integer component raises an error" );
+
+       */
+       raises(function() {
+               var result = jQuery.parseJSON("0101");
+
+               // Support: IE9+
+               // Ensure base-10 interpretation on browsers that erroneously accept leading-zero numbers
+               if ( result === 101 ) {
+                       throw new Error("close enough");
+               }
+       }, null, "Leading-zero number raises an error or is parsed as decimal" );
        raises(function() {
                jQuery.parseJSON("{a:1}");
        }, null, "Unquoted property raises an error" );
        raises(function() {
                jQuery.parseJSON("{'a':1}");
        }, null, "Single-quoted property raises an error" );
+       raises(function() {
+               jQuery.parseJSON("[,]");
+       }, null, "Array element elision raises an error" );
+       raises(function() {
+               jQuery.parseJSON("{},[]");
+       }, null, "Comma expression raises an error" );
+       raises(function() {
+               jQuery.parseJSON("[]\n,{}");
+       }, null, "Newline-containing comma expression raises an error" );
+       raises(function() {
+               jQuery.parseJSON("\"\"\n\"\"");
+       }, null, "Automatic semicolon insertion raises an error" );
+
+       strictEqual( jQuery.parseJSON([ 0 ]), 0, "Input cast to string" );
 });
 
 test("jQuery.parseXML", 8, function(){