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