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