You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
239 lines
7.2 KiB
239 lines
7.2 KiB
dojo.provide("dojox.secure.fromJson"); |
|
|
|
// Used with permission from Mike Samuel of Google (has CCLA), from the json-sans-eval project: |
|
// http://code.google.com/p/json-sans-eval/ |
|
// Mike Samuel <mikesamuel@gmail.com> |
|
|
|
|
|
|
|
dojox.secure.fromJson = typeof JSON != "undefined" ? JSON.parse : |
|
// summary: |
|
// Parses a string of well-formed JSON text. |
|
// description: |
|
// Parses a string of well-formed JSON text. If the input is not well-formed, |
|
// then behavior is undefined, but it is |
|
// deterministic and is guaranteed not to modify any object other than its |
|
// return value. |
|
// |
|
// This does not use `eval` so is less likely to have obscure security bugs than |
|
// json2.js. |
|
// It is optimized for speed, so is much faster than json_parse.js. |
|
// |
|
// This library should be used whenever security is a concern (when JSON may |
|
// come from an untrusted source), speed is a concern, and erroring on malformed |
|
// JSON is *not* a concern. |
|
// |
|
// json2.js is very fast, but potentially insecure since it calls `eval` to |
|
// parse JSON data, so an attacker might be able to supply strange JS that |
|
// looks like JSON, but that executes arbitrary javascript. |
|
// |
|
// To configure dojox.secure.fromJson as the JSON parser for all Dojo |
|
// JSON parsing, simply do: |
|
// | dojo.require("dojox.secure.fromJson"); |
|
// | dojo.fromJson = dojox.secure.fromJson; |
|
// or alternately you could configure dojox.secure.fromJson to only handle |
|
// XHR responses: |
|
// | dojo._contentHandlers.json = function(xhr){ |
|
// | return dojox.secure.fromJson.fromJson(xhr.responseText); |
|
// | }; |
|
// json: String |
|
// per RFC 4627 |
|
// optReviver: Function (this:Object, string, *) |
|
// optional function |
|
// that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1. |
|
// If supplied, the function is called with a string key, and a value. |
|
// The value is the property of 'this'. The reviver should return |
|
// the value to use in its place. So if dates were serialized as |
|
// `{ "type": "Date", "time": 1234 }`, then a reviver might look like |
|
// | function (key, value) { |
|
// | if (value && typeof value === 'object' && 'Date' === value.type) { |
|
// | return new Date(value.time); |
|
// | } else { |
|
// | return value; |
|
// | } |
|
// | }}. |
|
// If the reviver returns {@code undefined} then the property named by key |
|
// will be deleted from its container. |
|
// `this` is bound to the object containing the specified property. |
|
// returns: Object|Array |
|
(function () { |
|
var number |
|
= '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)'; |
|
var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]' |
|
+ '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))'; |
|
var string = '(?:\"' + oneChar + '*\")'; |
|
|
|
// Will match a value in a well-formed JSON file. |
|
// If the input is not well-formed, may match strangely, but not in an unsafe |
|
// way. |
|
// Since this only matches value tokens, it does not match whitespace, colons, |
|
// or commas. |
|
var jsonToken = new RegExp( |
|
'(?:false|true|null|[\\{\\}\\[\\]]' |
|
+ '|' + number |
|
+ '|' + string |
|
+ ')', 'g'); |
|
|
|
// Matches escape sequences in a string literal |
|
var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g'); |
|
|
|
// Decodes escape sequences in object literals |
|
var escapes = { |
|
'"': '"', |
|
'/': '/', |
|
'\\': '\\', |
|
'b': '\b', |
|
'f': '\f', |
|
'n': '\n', |
|
'r': '\r', |
|
't': '\t' |
|
}; |
|
function unescapeOne(_, ch, hex) { |
|
return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16)); |
|
} |
|
|
|
// A non-falsy value that coerces to the empty string when used as a key. |
|
var EMPTY_STRING = new String(''); |
|
var SLASH = '\\'; |
|
|
|
// Constructor to use based on an open token. |
|
var firstTokenCtors = { '{': Object, '[': Array }; |
|
|
|
var hop = Object.hasOwnProperty; |
|
|
|
return function (json, opt_reviver) { |
|
// Split into tokens |
|
var toks = json.match(jsonToken); |
|
// Construct the object to return |
|
var result; |
|
var tok = toks[0]; |
|
var topLevelPrimitive = false; |
|
if ('{' === tok) { |
|
result = {}; |
|
} else if ('[' === tok) { |
|
result = []; |
|
} else { |
|
// The RFC only allows arrays or objects at the top level, but the JSON.parse |
|
// defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null |
|
// at the top level. |
|
result = []; |
|
topLevelPrimitive = true; |
|
} |
|
|
|
// If undefined, the key in an object key/value record to use for the next |
|
// value parsed. |
|
var key; |
|
// Loop over remaining tokens maintaining a stack of uncompleted objects and |
|
// arrays. |
|
var stack = [result]; |
|
for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) { |
|
tok = toks[i]; |
|
|
|
var cont; |
|
switch (tok.charCodeAt(0)) { |
|
default: // sign or digit |
|
cont = stack[0]; |
|
cont[key || cont.length] = +(tok); |
|
key = void 0; |
|
break; |
|
case 0x22: // '"' |
|
tok = tok.substring(1, tok.length - 1); |
|
if (tok.indexOf(SLASH) !== -1) { |
|
tok = tok.replace(escapeSequence, unescapeOne); |
|
} |
|
cont = stack[0]; |
|
if (!key) { |
|
if (cont instanceof Array) { |
|
key = cont.length; |
|
} else { |
|
key = tok || EMPTY_STRING; // Use as key for next value seen. |
|
break; |
|
} |
|
} |
|
cont[key] = tok; |
|
key = void 0; |
|
break; |
|
case 0x5b: // '[' |
|
cont = stack[0]; |
|
stack.unshift(cont[key || cont.length] = []); |
|
key = void 0; |
|
break; |
|
case 0x5d: // ']' |
|
stack.shift(); |
|
break; |
|
case 0x66: // 'f' |
|
cont = stack[0]; |
|
cont[key || cont.length] = false; |
|
key = void 0; |
|
break; |
|
case 0x6e: // 'n' |
|
cont = stack[0]; |
|
cont[key || cont.length] = null; |
|
key = void 0; |
|
break; |
|
case 0x74: // 't' |
|
cont = stack[0]; |
|
cont[key || cont.length] = true; |
|
key = void 0; |
|
break; |
|
case 0x7b: // '{' |
|
cont = stack[0]; |
|
stack.unshift(cont[key || cont.length] = {}); |
|
key = void 0; |
|
break; |
|
case 0x7d: // '}' |
|
stack.shift(); |
|
break; |
|
} |
|
} |
|
// Fail if we've got an uncompleted object. |
|
if (topLevelPrimitive) { |
|
if (stack.length !== 1) { throw new Error(); } |
|
result = result[0]; |
|
} else { |
|
if (stack.length) { throw new Error(); } |
|
} |
|
|
|
if (opt_reviver) { |
|
// Based on walk as implemented in http://www.json.org/json2.js |
|
var walk = function (holder, key) { |
|
var value = holder[key]; |
|
if (value && typeof value === 'object') { |
|
var toDelete = null; |
|
for (var k in value) { |
|
if (hop.call(value, k) && value !== holder) { |
|
// Recurse to properties first. This has the effect of causing |
|
// the reviver to be called on the object graph depth-first. |
|
|
|
// Since 'this' is bound to the holder of the property, the |
|
// reviver can access sibling properties of k including ones |
|
// that have not yet been revived. |
|
|
|
// The value returned by the reviver is used in place of the |
|
// current value of property k. |
|
// If it returns undefined then the property is deleted. |
|
var v = walk(value, k); |
|
if (v !== void 0) { |
|
value[k] = v; |
|
} else { |
|
// Deleting properties inside the loop has vaguely defined |
|
// semantics in ES3 and ES3.1. |
|
if (!toDelete) { toDelete = []; } |
|
toDelete.push(k); |
|
} |
|
} |
|
} |
|
if (toDelete) { |
|
for (var i = toDelete.length; --i >= 0;) { |
|
delete value[toDelete[i]]; |
|
} |
|
} |
|
} |
|
return opt_reviver.call(holder, key, value); |
|
}; |
|
result = walk({ '': result }, ''); |
|
} |
|
|
|
return result; |
|
}; |
|
})(); |