source: Dev/trunk/src/client/dojox/secure/fromJson.js @ 525

Last change on this file since 525 was 483, checked in by hendrikvanantwerpen, 11 years ago

Added Dojo 1.9.3 release.

File size: 7.2 KB
Line 
1dojo.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
9dojox.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})();
Note: See TracBrowser for help on using the repository browser.