source: Dev/branches/rest-dojo-ui/client/dojox/secure/fromJson.js @ 256

Last change on this file since 256 was 256, checked in by hendrikvanantwerpen, 13 years ago

Reworked project structure based on REST interaction and Dojo library. As
soon as this is stable, the old jQueryUI branch can be removed (it's
kept for reference).

File size: 7.3 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//
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})();
Note: See TracBrowser for help on using the repository browser.