PageRenderTime 26ms CodeModel.GetById 14ms app.highlight 9ms RepoModel.GetById 1ms app.codeStats 0ms

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