/src/json_sans_eval.js

http://json-sans-eval.googlecode.com/ · JavaScript · 238 lines · 138 code · 15 blank · 85 comment · 32 complexity · 36ff5136abf4856e52e56b0034ee52de MD5 · raw file

  1. // This source code is free for use in the public domain.
  2. // NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
  3. // http://code.google.com/p/json-sans-eval/
  4. /**
  5. * Parses a string of well-formed JSON text.
  6. *
  7. * If the input is not well-formed, then behavior is undefined, but it is
  8. * deterministic and is guaranteed not to modify any object other than its
  9. * return value.
  10. *
  11. * This does not use `eval` so is less likely to have obscure security bugs than
  12. * json2.js.
  13. * It is optimized for speed, so is much faster than json_parse.js.
  14. *
  15. * This library should be used whenever security is a concern (when JSON may
  16. * come from an untrusted source), speed is a concern, and erroring on malformed
  17. * JSON is *not* a concern.
  18. *
  19. * Pros Cons
  20. * +-----------------------+-----------------------+
  21. * json_sans_eval.js | Fast, secure | Not validating |
  22. * +-----------------------+-----------------------+
  23. * json_parse.js | Validating, secure | Slow |
  24. * +-----------------------+-----------------------+
  25. * json2.js | Fast, some validation | Potentially insecure |
  26. * +-----------------------+-----------------------+
  27. *
  28. * json2.js is very fast, but potentially insecure since it calls `eval` to
  29. * parse JSON data, so an attacker might be able to supply strange JS that
  30. * looks like JSON, but that executes arbitrary javascript.
  31. * If you do have to use json2.js with untrusted data, make sure you keep
  32. * your version of json2.js up to date so that you get patches as they're
  33. * released.
  34. *
  35. * @param {string} json per RFC 4627
  36. * @param {function (this:Object, string, *):*} opt_reviver optional function
  37. * that reworks JSON objects post-parse per Chapter 15.12 of EcmaScript3.1.
  38. * If supplied, the function is called with a string key, and a value.
  39. * The value is the property of 'this'. The reviver should return
  40. * the value to use in its place. So if dates were serialized as
  41. * {@code { "type": "Date", "time": 1234 }}, then a reviver might look like
  42. * {@code
  43. * function (key, value) {
  44. * if (value && typeof value === 'object' && 'Date' === value.type) {
  45. * return new Date(value.time);
  46. * } else {
  47. * return value;
  48. * }
  49. * }}.
  50. * If the reviver returns {@code undefined} then the property named by key
  51. * will be deleted from its container.
  52. * {@code this} is bound to the object containing the specified property.
  53. * @return {Object|Array}
  54. * @author Mike Samuel <mikesamuel@gmail.com>
  55. */
  56. var jsonParse = (function () {
  57. var number
  58. = '(?:-?\\b(?:0|[1-9][0-9]*)(?:\\.[0-9]+)?(?:[eE][+-]?[0-9]+)?\\b)';
  59. var oneChar = '(?:[^\\0-\\x08\\x0a-\\x1f\"\\\\]'
  60. + '|\\\\(?:[\"/\\\\bfnrt]|u[0-9A-Fa-f]{4}))';
  61. var string = '(?:\"' + oneChar + '*\")';
  62. // Will match a value in a well-formed JSON file.
  63. // If the input is not well-formed, may match strangely, but not in an unsafe
  64. // way.
  65. // Since this only matches value tokens, it does not match whitespace, colons,
  66. // or commas.
  67. var jsonToken = new RegExp(
  68. '(?:false|true|null|[\\{\\}\\[\\]]'
  69. + '|' + number
  70. + '|' + string
  71. + ')', 'g');
  72. // Matches escape sequences in a string literal
  73. var escapeSequence = new RegExp('\\\\(?:([^u])|u(.{4}))', 'g');
  74. // Decodes escape sequences in object literals
  75. var escapes = {
  76. '"': '"',
  77. '/': '/',
  78. '\\': '\\',
  79. 'b': '\b',
  80. 'f': '\f',
  81. 'n': '\n',
  82. 'r': '\r',
  83. 't': '\t'
  84. };
  85. function unescapeOne(_, ch, hex) {
  86. return ch ? escapes[ch] : String.fromCharCode(parseInt(hex, 16));
  87. }
  88. // A non-falsy value that coerces to the empty string when used as a key.
  89. var EMPTY_STRING = new String('');
  90. var SLASH = '\\';
  91. // Constructor to use based on an open token.
  92. var firstTokenCtors = { '{': Object, '[': Array };
  93. var hop = Object.hasOwnProperty;
  94. return function (json, opt_reviver) {
  95. // Split into tokens
  96. var toks = json.match(jsonToken);
  97. // Construct the object to return
  98. var result;
  99. var tok = toks[0];
  100. var topLevelPrimitive = false;
  101. if ('{' === tok) {
  102. result = {};
  103. } else if ('[' === tok) {
  104. result = [];
  105. } else {
  106. // The RFC only allows arrays or objects at the top level, but the JSON.parse
  107. // defined by the EcmaScript 5 draft does allow strings, booleans, numbers, and null
  108. // at the top level.
  109. result = [];
  110. topLevelPrimitive = true;
  111. }
  112. // If undefined, the key in an object key/value record to use for the next
  113. // value parsed.
  114. var key;
  115. // Loop over remaining tokens maintaining a stack of uncompleted objects and
  116. // arrays.
  117. var stack = [result];
  118. for (var i = 1 - topLevelPrimitive, n = toks.length; i < n; ++i) {
  119. tok = toks[i];
  120. var cont;
  121. switch (tok.charCodeAt(0)) {
  122. default: // sign or digit
  123. cont = stack[0];
  124. cont[key || cont.length] = +(tok);
  125. key = void 0;
  126. break;
  127. case 0x22: // '"'
  128. tok = tok.substring(1, tok.length - 1);
  129. if (tok.indexOf(SLASH) !== -1) {
  130. tok = tok.replace(escapeSequence, unescapeOne);
  131. }
  132. cont = stack[0];
  133. if (!key) {
  134. if (cont instanceof Array) {
  135. key = cont.length;
  136. } else {
  137. key = tok || EMPTY_STRING; // Use as key for next value seen.
  138. break;
  139. }
  140. }
  141. cont[key] = tok;
  142. key = void 0;
  143. break;
  144. case 0x5b: // '['
  145. cont = stack[0];
  146. stack.unshift(cont[key || cont.length] = []);
  147. key = void 0;
  148. break;
  149. case 0x5d: // ']'
  150. stack.shift();
  151. break;
  152. case 0x66: // 'f'
  153. cont = stack[0];
  154. cont[key || cont.length] = false;
  155. key = void 0;
  156. break;
  157. case 0x6e: // 'n'
  158. cont = stack[0];
  159. cont[key || cont.length] = null;
  160. key = void 0;
  161. break;
  162. case 0x74: // 't'
  163. cont = stack[0];
  164. cont[key || cont.length] = true;
  165. key = void 0;
  166. break;
  167. case 0x7b: // '{'
  168. cont = stack[0];
  169. stack.unshift(cont[key || cont.length] = {});
  170. key = void 0;
  171. break;
  172. case 0x7d: // '}'
  173. stack.shift();
  174. break;
  175. }
  176. }
  177. // Fail if we've got an uncompleted object.
  178. if (topLevelPrimitive) {
  179. if (stack.length !== 1) { throw new Error(); }
  180. result = result[0];
  181. } else {
  182. if (stack.length) { throw new Error(); }
  183. }
  184. if (opt_reviver) {
  185. // Based on walk as implemented in http://www.json.org/json2.js
  186. var walk = function (holder, key) {
  187. var value = holder[key];
  188. if (value && typeof value === 'object') {
  189. var toDelete = null;
  190. for (var k in value) {
  191. if (hop.call(value, k) && value !== holder) {
  192. // Recurse to properties first. This has the effect of causing
  193. // the reviver to be called on the object graph depth-first.
  194. // Since 'this' is bound to the holder of the property, the
  195. // reviver can access sibling properties of k including ones
  196. // that have not yet been revived.
  197. // The value returned by the reviver is used in place of the
  198. // current value of property k.
  199. // If it returns undefined then the property is deleted.
  200. var v = walk(value, k);
  201. if (v !== void 0) {
  202. value[k] = v;
  203. } else {
  204. // Deleting properties inside the loop has vaguely defined
  205. // semantics in ES3 and ES3.1.
  206. if (!toDelete) { toDelete = []; }
  207. toDelete.push(k);
  208. }
  209. }
  210. }
  211. if (toDelete) {
  212. for (var i = toDelete.length; --i >= 0;) {
  213. delete value[toDelete[i]];
  214. }
  215. }
  216. }
  217. return opt_reviver.call(holder, key, value);
  218. };
  219. result = walk({ '': result }, '');
  220. }
  221. return result;
  222. };
  223. })();