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