1 |
/* -*- Mode: C; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- |
2 |
* vim: set ts=8 sw=4 et tw=99: |
3 |
* |
4 |
* ***** BEGIN LICENSE BLOCK ***** |
5 |
* Version: MPL 1.1/GPL 2.0/LGPL 2.1 |
6 |
* |
7 |
* The contents of this file are subject to the Mozilla Public License Version |
8 |
* 1.1 (the "License"); you may not use this file except in compliance with |
9 |
* the License. You may obtain a copy of the License at |
10 |
* http://www.mozilla.org/MPL/ |
11 |
* |
12 |
* Software distributed under the License is distributed on an "AS IS" basis, |
13 |
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License |
14 |
* for the specific language governing rights and limitations under the |
15 |
* License. |
16 |
* |
17 |
* The Original Code is SpiderMonkey JSON. |
18 |
* |
19 |
* The Initial Developer of the Original Code is |
20 |
* Mozilla Corporation. |
21 |
* Portions created by the Initial Developer are Copyright (C) 1998-1999 |
22 |
* the Initial Developer. All Rights Reserved. |
23 |
* |
24 |
* Contributor(s): |
25 |
* Robert Sayre <sayrer@gmail.com> |
26 |
* |
27 |
* Alternatively, the contents of this file may be used under the terms of |
28 |
* either of the GNU General Public License Version 2 or later (the "GPL"), |
29 |
* or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), |
30 |
* in which case the provisions of the GPL or the LGPL are applicable instead |
31 |
* of those above. If you wish to allow use of your version of this file only |
32 |
* under the terms of either the GPL or the LGPL, and not to allow others to |
33 |
* use your version of this file under the terms of the MPL, indicate your |
34 |
* decision by deleting the provisions above and replace them with the notice |
35 |
* and other provisions required by the GPL or the LGPL. If you do not delete |
36 |
* the provisions above, a recipient may use your version of this file under |
37 |
* the terms of any one of the MPL, the GPL or the LGPL. |
38 |
* |
39 |
* ***** END LICENSE BLOCK ***** */ |
40 |
|
41 |
#include <string.h> /* memset */ |
42 |
#include "jsapi.h" |
43 |
#include "jsarena.h" |
44 |
#include "jsarray.h" |
45 |
#include "jsatom.h" |
46 |
#include "jsbool.h" |
47 |
#include "jscntxt.h" |
48 |
#include "jsdtoa.h" |
49 |
#include "jsfun.h" |
50 |
#include "jsinterp.h" |
51 |
#include "jsiter.h" |
52 |
#include "jsnum.h" |
53 |
#include "jsobj.h" |
54 |
#include "jsprf.h" |
55 |
#include "jsscan.h" |
56 |
#include "jsstr.h" |
57 |
#include "jstypes.h" |
58 |
#include "jsutil.h" |
59 |
#include "jsxml.h" |
60 |
|
61 |
#include "json.h" |
62 |
|
63 |
JSClass js_JSONClass = { |
64 |
js_JSON_str, |
65 |
JSCLASS_HAS_CACHED_PROTO(JSProto_JSON), |
66 |
JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, |
67 |
JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, |
68 |
JSCLASS_NO_OPTIONAL_MEMBERS |
69 |
}; |
70 |
|
71 |
JSBool |
72 |
js_json_parse(JSContext *cx, uintN argc, jsval *vp) |
73 |
{ |
74 |
JSString *s = NULL; |
75 |
jsval *argv = vp + 2; |
76 |
jsval reviver = JSVAL_NULL; |
77 |
JSAutoTempValueRooter(cx, 1, &reviver); |
78 |
|
79 |
if (!JS_ConvertArguments(cx, argc, argv, "S / v", &s, &reviver)) |
80 |
return JS_FALSE; |
81 |
|
82 |
JSONParser *jp = js_BeginJSONParse(cx, vp); |
83 |
JSBool ok = jp != NULL; |
84 |
if (ok) { |
85 |
ok = js_ConsumeJSONText(cx, jp, JS_GetStringChars(s), JS_GetStringLength(s)); |
86 |
ok &= js_FinishJSONParse(cx, jp, reviver); |
87 |
} |
88 |
|
89 |
return ok; |
90 |
} |
91 |
|
92 |
class WriterContext |
93 |
{ |
94 |
public: |
95 |
WriterContext(JSContext *cx) : cx(cx), didWrite(JS_FALSE) |
96 |
{ |
97 |
js_InitStringBuffer(&sb); |
98 |
} |
99 |
|
100 |
~WriterContext() |
101 |
{ |
102 |
js_FinishStringBuffer(&sb); |
103 |
} |
104 |
|
105 |
JSStringBuffer sb; |
106 |
JSContext *cx; |
107 |
JSBool didWrite; |
108 |
}; |
109 |
|
110 |
static JSBool |
111 |
WriteCallback(const jschar *buf, uint32 len, void *data) |
112 |
{ |
113 |
WriterContext *wc = static_cast<WriterContext*>(data); |
114 |
wc->didWrite = JS_TRUE; |
115 |
|
116 |
js_AppendUCString(&wc->sb, buf, len); |
117 |
if (!STRING_BUFFER_OK(&wc->sb)) { |
118 |
JS_ReportOutOfMemory(wc->cx); |
119 |
return JS_FALSE; |
120 |
} |
121 |
|
122 |
return JS_TRUE; |
123 |
} |
124 |
|
125 |
JSBool |
126 |
js_json_stringify(JSContext *cx, uintN argc, jsval *vp) |
127 |
{ |
128 |
jsval *argv = vp + 2; |
129 |
JSObject *replacer = NULL; |
130 |
jsval space = JSVAL_NULL; |
131 |
JSAutoTempValueRooter(cx, replacer); |
132 |
JSAutoTempValueRooter(cx, 1, &space); |
133 |
|
134 |
// Must throw an Error if there isn't a first arg |
135 |
if (!JS_ConvertArguments(cx, argc, argv, "v / o v", vp, &replacer, &space)) |
136 |
return JS_FALSE; |
137 |
|
138 |
WriterContext wc(cx); |
139 |
|
140 |
if (!js_Stringify(cx, vp, replacer, space, &WriteCallback, &wc)) |
141 |
return JS_FALSE; |
142 |
|
143 |
// XXX This can never happen to nsJSON.cpp, but the JSON object |
144 |
// needs to support returning undefined. So this is a little awkward |
145 |
// for the API, because we want to support streaming writers. |
146 |
if (wc.didWrite) { |
147 |
JSStringBuffer *sb = &wc.sb; |
148 |
JSString *s = JS_NewUCStringCopyN(cx, sb->base, STRING_BUFFER_OFFSET(sb)); |
149 |
if (!s) |
150 |
return JS_FALSE; |
151 |
*vp = STRING_TO_JSVAL(s); |
152 |
} else { |
153 |
*vp = JSVAL_VOID; |
154 |
} |
155 |
|
156 |
return JS_TRUE; |
157 |
} |
158 |
|
159 |
JSBool |
160 |
js_TryJSON(JSContext *cx, jsval *vp) |
161 |
{ |
162 |
// Checks whether the return value implements toJSON() |
163 |
JSBool ok = JS_TRUE; |
164 |
|
165 |
if (!JSVAL_IS_PRIMITIVE(*vp)) { |
166 |
JSObject *obj = JSVAL_TO_OBJECT(*vp); |
167 |
ok = js_TryMethod(cx, obj, cx->runtime->atomState.toJSONAtom, 0, NULL, vp); |
168 |
} |
169 |
|
170 |
return ok; |
171 |
} |
172 |
|
173 |
|
174 |
static const jschar quote = jschar('"'); |
175 |
static const jschar backslash = jschar('\\'); |
176 |
static const jschar unicodeEscape[] = {'\\', 'u', '0', '0'}; |
177 |
static const jschar null_ucstr[] = {'n', 'u', 'l', 'l'}; |
178 |
static const jschar true_ucstr[] = {'t', 'r', 'u', 'e'}; |
179 |
static const jschar false_ucstr[] = {'f', 'a', 'l', 's', 'e'}; |
180 |
|
181 |
static JSBool |
182 |
write_string(JSContext *cx, JSONWriteCallback callback, void *data, const jschar *buf, uint32 len) |
183 |
{ |
184 |
if (!callback("e, 1, data)) |
185 |
return JS_FALSE; |
186 |
|
187 |
uint32 mark = 0; |
188 |
uint32 i; |
189 |
for (i = 0; i < len; ++i) { |
190 |
if (buf[i] == quote || buf[i] == backslash) { |
191 |
if (!callback(&buf[mark], i - mark, data) || !callback(&backslash, 1, data) || |
192 |
!callback(&buf[i], 1, data)) { |
193 |
return JS_FALSE; |
194 |
} |
195 |
mark = i + 1; |
196 |
} else if (buf[i] <= 31 || buf[i] == 127) { |
197 |
if (!callback(&buf[mark], i - mark, data) || !callback(unicodeEscape, 4, data)) |
198 |
return JS_FALSE; |
199 |
char ubuf[3]; |
200 |
size_t len = JS_snprintf(ubuf, sizeof(ubuf), "%.2x", buf[i]); |
201 |
JS_ASSERT(len == 2); |
202 |
jschar wbuf[3]; |
203 |
size_t wbufSize = JS_ARRAY_LENGTH(wbuf); |
204 |
if (!js_InflateStringToBuffer(cx, ubuf, len, wbuf, &wbufSize) || |
205 |
!callback(wbuf, wbufSize, data)) { |
206 |
return JS_FALSE; |
207 |
} |
208 |
mark = i + 1; |
209 |
} |
210 |
} |
211 |
|
212 |
if (mark < len && !callback(&buf[mark], len - mark, data)) |
213 |
return JS_FALSE; |
214 |
|
215 |
return callback("e, 1, data); |
216 |
} |
217 |
|
218 |
class StringifyContext |
219 |
{ |
220 |
public: |
221 |
StringifyContext(JSONWriteCallback callback, JSObject *replacer, void *data) |
222 |
: callback(callback), replacer(replacer), data(data), depth(0) |
223 |
{ |
224 |
js_InitStringBuffer(&gap); |
225 |
} |
226 |
|
227 |
~StringifyContext() |
228 |
{ |
229 |
if (STRING_BUFFER_OK(&gap)) |
230 |
js_FinishStringBuffer(&gap); |
231 |
} |
232 |
|
233 |
JSONWriteCallback callback; |
234 |
JSStringBuffer gap; |
235 |
JSObject *replacer; |
236 |
void *data; |
237 |
uint32 depth; |
238 |
}; |
239 |
|
240 |
static JSBool CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder, |
241 |
StringifyContext *scx, jsval *vp); |
242 |
static JSBool Str(JSContext *cx, jsid id, JSObject *holder, |
243 |
StringifyContext *scx, jsval *vp, bool callReplacer = true); |
244 |
|
245 |
static JSBool |
246 |
WriteIndent(JSContext *cx, StringifyContext *scx, uint32 limit) |
247 |
{ |
248 |
if (STRING_BUFFER_OFFSET(&scx->gap) > 0) { |
249 |
jschar c = jschar('\n'); |
250 |
if (!scx->callback(&c, 1, scx->data)) |
251 |
return JS_FALSE; |
252 |
for (uint32 i = 0; i < limit; i++) { |
253 |
if (!scx->callback(scx->gap.base, STRING_BUFFER_OFFSET(&scx->gap), scx->data)) |
254 |
return JS_FALSE; |
255 |
} |
256 |
} |
257 |
|
258 |
return JS_TRUE; |
259 |
} |
260 |
|
261 |
static JSBool |
262 |
JO(JSContext *cx, jsval *vp, StringifyContext *scx) |
263 |
{ |
264 |
JSObject *obj = JSVAL_TO_OBJECT(*vp); |
265 |
|
266 |
jschar c = jschar('{'); |
267 |
if (!scx->callback(&c, 1, scx->data)) |
268 |
return JS_FALSE; |
269 |
|
270 |
jsval vec[3] = {JSVAL_NULL, JSVAL_NULL, JSVAL_NULL}; |
271 |
JSAutoTempValueRooter tvr(cx, 3, vec); |
272 |
jsval& key = vec[0]; |
273 |
jsval& outputValue = vec[1]; |
274 |
|
275 |
JSObject *iterObj = NULL; |
276 |
jsval *keySource = vp; |
277 |
bool usingWhitelist = false; |
278 |
|
279 |
// if the replacer is an array, we use the keys from it |
280 |
if (scx->replacer && JS_IsArrayObject(cx, scx->replacer)) { |
281 |
usingWhitelist = true; |
282 |
vec[2] = OBJECT_TO_JSVAL(scx->replacer); |
283 |
keySource = &vec[2]; |
284 |
} |
285 |
|
286 |
if (!js_ValueToIterator(cx, JSITER_ENUMERATE, keySource)) |
287 |
return JS_FALSE; |
288 |
iterObj = JSVAL_TO_OBJECT(*keySource); |
289 |
|
290 |
JSBool memberWritten = JS_FALSE; |
291 |
JSBool ok; |
292 |
|
293 |
do { |
294 |
outputValue = JSVAL_VOID; |
295 |
ok = js_CallIteratorNext(cx, iterObj, &key); |
296 |
if (!ok) |
297 |
break; |
298 |
if (key == JSVAL_HOLE) |
299 |
break; |
300 |
|
301 |
jsuint index = 0; |
302 |
if (usingWhitelist) { |
303 |
// skip non-index properties |
304 |
if (!js_IdIsIndex(key, &index)) |
305 |
continue; |
306 |
|
307 |
jsval newKey; |
308 |
if (!OBJ_GET_PROPERTY(cx, scx->replacer, key, &newKey)) |
309 |
return JS_FALSE; |
310 |
key = newKey; |
311 |
} |
312 |
|
313 |
JSString *ks; |
314 |
if (JSVAL_IS_STRING(key)) { |
315 |
ks = JSVAL_TO_STRING(key); |
316 |
} else { |
317 |
ks = js_ValueToString(cx, key); |
318 |
if (!ks) { |
319 |
ok = JS_FALSE; |
320 |
break; |
321 |
} |
322 |
} |
323 |
JSAutoTempValueRooter keyStringRoot(cx, ks); |
324 |
|
325 |
// Don't include prototype properties, since this operation is |
326 |
// supposed to be implemented as if by ES3.1 Object.keys() |
327 |
jsid id; |
328 |
jsval v = JS_FALSE; |
329 |
if (!js_ValueToStringId(cx, STRING_TO_JSVAL(ks), &id) || |
330 |
!js_HasOwnProperty(cx, obj->map->ops->lookupProperty, obj, id, &v)) { |
331 |
ok = JS_FALSE; |
332 |
break; |
333 |
} |
334 |
|
335 |
if (v != JSVAL_TRUE) |
336 |
continue; |
337 |
|
338 |
ok = JS_GetPropertyById(cx, obj, id, &outputValue); |
339 |
if (!ok) |
340 |
break; |
341 |
|
342 |
if (JSVAL_IS_OBJECT(outputValue)) { |
343 |
ok = js_TryJSON(cx, &outputValue); |
344 |
if (!ok) |
345 |
break; |
346 |
} |
347 |
|
348 |
// call this here, so we don't write out keys if the replacer function |
349 |
// wants to elide the value. |
350 |
if (!CallReplacerFunction(cx, id, obj, scx, &outputValue)) |
351 |
return JS_FALSE; |
352 |
|
353 |
JSType type = JS_TypeOfValue(cx, outputValue); |
354 |
|
355 |
// elide undefined values and functions and XML |
356 |
if (outputValue == JSVAL_VOID || type == JSTYPE_FUNCTION || type == JSTYPE_XML) |
357 |
continue; |
358 |
|
359 |
// output a comma unless this is the first member to write |
360 |
if (memberWritten) { |
361 |
c = jschar(','); |
362 |
ok = scx->callback(&c, 1, scx->data); |
363 |
if (!ok) |
364 |
break; |
365 |
} |
366 |
memberWritten = JS_TRUE; |
367 |
|
368 |
if (!WriteIndent(cx, scx, scx->depth)) |
369 |
return JS_FALSE; |
370 |
|
371 |
// Be careful below, this string is weakly rooted |
372 |
JSString *s = js_ValueToString(cx, key); |
373 |
if (!s) { |
374 |
ok = JS_FALSE; |
375 |
break; |
376 |
} |
377 |
|
378 |
ok = write_string(cx, scx->callback, scx->data, JS_GetStringChars(s), JS_GetStringLength(s)); |
379 |
if (!ok) |
380 |
break; |
381 |
|
382 |
c = jschar(':'); |
383 |
ok = scx->callback(&c, 1, scx->data); |
384 |
if (!ok) |
385 |
break; |
386 |
|
387 |
ok = Str(cx, id, obj, scx, &outputValue, false); |
388 |
if (!ok) |
389 |
break; |
390 |
|
391 |
} while (ok); |
392 |
|
393 |
if (iterObj) { |
394 |
// Always close the iterator, but make sure not to stomp on OK |
395 |
JS_ASSERT(OBJECT_TO_JSVAL(iterObj) == *keySource); |
396 |
ok &= js_CloseIterator(cx, *keySource); |
397 |
} |
398 |
|
399 |
if (!ok) |
400 |
return JS_FALSE; |
401 |
|
402 |
if (memberWritten && !WriteIndent(cx, scx, scx->depth - 1)) |
403 |
return JS_FALSE; |
404 |
|
405 |
c = jschar('}'); |
406 |
|
407 |
return scx->callback(&c, 1, scx->data); |
408 |
} |
409 |
|
410 |
static JSBool |
411 |
JA(JSContext *cx, jsval *vp, StringifyContext *scx) |
412 |
{ |
413 |
JSObject *obj = JSVAL_TO_OBJECT(*vp); |
414 |
|
415 |
jschar c = jschar('['); |
416 |
if (!scx->callback(&c, 1, scx->data)) |
417 |
return JS_FALSE; |
418 |
|
419 |
jsuint length; |
420 |
if (!js_GetLengthProperty(cx, obj, &length)) |
421 |
return JS_FALSE; |
422 |
|
423 |
jsval outputValue = JSVAL_NULL; |
424 |
JSAutoTempValueRooter tvr(cx, 1, &outputValue); |
425 |
|
426 |
jsid id; |
427 |
jsuint i; |
428 |
for (i = 0; i < length; i++) { |
429 |
id = INT_TO_JSID(i); |
430 |
|
431 |
if (!OBJ_GET_PROPERTY(cx, obj, id, &outputValue)) |
432 |
return JS_FALSE; |
433 |
|
434 |
if (!Str(cx, id, obj, scx, &outputValue)) |
435 |
return JS_FALSE; |
436 |
|
437 |
if (outputValue == JSVAL_VOID) { |
438 |
if (!scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data)) |
439 |
return JS_FALSE; |
440 |
} |
441 |
|
442 |
if (i < length - 1) { |
443 |
c = jschar(','); |
444 |
if (!scx->callback(&c, 1, scx->data)) |
445 |
return JS_FALSE; |
446 |
if (!WriteIndent(cx, scx, scx->depth)) |
447 |
return JS_FALSE; |
448 |
} |
449 |
} |
450 |
|
451 |
if (length != 0 && !WriteIndent(cx, scx, scx->depth - 1)) |
452 |
return JS_FALSE; |
453 |
|
454 |
c = jschar(']'); |
455 |
|
456 |
return scx->callback(&c, 1, scx->data); |
457 |
} |
458 |
|
459 |
static JSBool |
460 |
CallReplacerFunction(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, jsval *vp) |
461 |
{ |
462 |
if (scx->replacer && js_IsCallable(scx->replacer, cx)) { |
463 |
jsval vec[2] = {ID_TO_VALUE(id), *vp}; |
464 |
if (!JS_CallFunctionValue(cx, holder, OBJECT_TO_JSVAL(scx->replacer), 2, vec, vp)) |
465 |
return JS_FALSE; |
466 |
} |
467 |
|
468 |
return JS_TRUE; |
469 |
} |
470 |
|
471 |
static JSBool |
472 |
Str(JSContext *cx, jsid id, JSObject *holder, StringifyContext *scx, jsval *vp, bool callReplacer) |
473 |
{ |
474 |
JS_CHECK_RECURSION(cx, return JS_FALSE); |
475 |
|
476 |
if (!OBJ_GET_PROPERTY(cx, holder, id, vp)) |
477 |
return JS_FALSE; |
478 |
|
479 |
if (!JSVAL_IS_PRIMITIVE(*vp) && !js_TryJSON(cx, vp)) |
480 |
return JS_FALSE; |
481 |
|
482 |
if (callReplacer && !CallReplacerFunction(cx, id, holder, scx, vp)) |
483 |
return JS_FALSE; |
484 |
|
485 |
// catches string and number objects with no toJSON |
486 |
if (!JSVAL_IS_PRIMITIVE(*vp)) { |
487 |
JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(*vp)); |
488 |
if (clasp == &js_StringClass || clasp == &js_NumberClass) |
489 |
*vp = JSVAL_TO_OBJECT(*vp)->fslots[JSSLOT_PRIVATE]; |
490 |
} |
491 |
|
492 |
if (JSVAL_IS_STRING(*vp)) { |
493 |
JSString *s = JSVAL_TO_STRING(*vp); |
494 |
return write_string(cx, scx->callback, scx->data, JS_GetStringChars(s), JS_GetStringLength(s)); |
495 |
} |
496 |
|
497 |
if (JSVAL_IS_NULL(*vp)) { |
498 |
return scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data); |
499 |
} |
500 |
|
501 |
if (JSVAL_IS_BOOLEAN(*vp)) { |
502 |
uint32 len = JS_ARRAY_LENGTH(true_ucstr); |
503 |
const jschar *chars = true_ucstr; |
504 |
JSBool b = JSVAL_TO_BOOLEAN(*vp); |
505 |
|
506 |
if (!b) { |
507 |
chars = false_ucstr; |
508 |
len = JS_ARRAY_LENGTH(false_ucstr); |
509 |
} |
510 |
|
511 |
return scx->callback(chars, len, scx->data); |
512 |
} |
513 |
|
514 |
if (JSVAL_IS_NUMBER(*vp)) { |
515 |
if (JSVAL_IS_DOUBLE(*vp)) { |
516 |
jsdouble d = *JSVAL_TO_DOUBLE(*vp); |
517 |
if (!JSDOUBLE_IS_FINITE(d)) |
518 |
return scx->callback(null_ucstr, JS_ARRAY_LENGTH(null_ucstr), scx->data); |
519 |
} |
520 |
|
521 |
char numBuf[DTOSTR_STANDARD_BUFFER_SIZE], *numStr; |
522 |
jsdouble d = JSVAL_IS_INT(*vp) ? jsdouble(JSVAL_TO_INT(*vp)) : *JSVAL_TO_DOUBLE(*vp); |
523 |
numStr = JS_dtostr(numBuf, sizeof numBuf, DTOSTR_STANDARD, 0, d); |
524 |
if (!numStr) { |
525 |
JS_ReportOutOfMemory(cx); |
526 |
return JS_FALSE; |
527 |
} |
528 |
|
529 |
jschar dstr[DTOSTR_STANDARD_BUFFER_SIZE]; |
530 |
size_t dbufSize = DTOSTR_STANDARD_BUFFER_SIZE; |
531 |
if (!js_InflateStringToBuffer(cx, numStr, strlen(numStr), dstr, &dbufSize)) |
532 |
return JS_FALSE; |
533 |
|
534 |
return scx->callback(dstr, dbufSize, scx->data); |
535 |
} |
536 |
|
537 |
if (JSVAL_IS_OBJECT(*vp) && !VALUE_IS_FUNCTION(cx, *vp) && !VALUE_IS_XML(cx, *vp)) { |
538 |
JSBool ok; |
539 |
|
540 |
scx->depth++; |
541 |
ok = (JS_IsArrayObject(cx, JSVAL_TO_OBJECT(*vp)) ? JA : JO)(cx, vp, scx); |
542 |
scx->depth--; |
543 |
|
544 |
return ok; |
545 |
} |
546 |
|
547 |
*vp = JSVAL_VOID; |
548 |
return JS_TRUE; |
549 |
} |
550 |
|
551 |
static JSBool |
552 |
WriteStringGap(JSContext *cx, jsval space, JSStringBuffer *sb) |
553 |
{ |
554 |
JSString *s = js_ValueToString(cx, space); |
555 |
if (!s) |
556 |
return JS_FALSE; |
557 |
|
558 |
js_AppendUCString(sb, JS_GetStringChars(s), JS_GetStringLength(s)); |
559 |
if (!STRING_BUFFER_OK(sb)) { |
560 |
JS_ReportOutOfMemory(cx); |
561 |
return JS_FALSE; |
562 |
} |
563 |
|
564 |
return JS_TRUE; |
565 |
} |
566 |
|
567 |
static JSBool |
568 |
InitializeGap(JSContext *cx, jsval space, JSStringBuffer *sb) |
569 |
{ |
570 |
if (!JSVAL_IS_PRIMITIVE(space)) { |
571 |
JSClass *clasp = OBJ_GET_CLASS(cx, JSVAL_TO_OBJECT(space)); |
572 |
if (clasp == &js_StringClass || clasp == &js_NumberClass) |
573 |
return WriteStringGap(cx, space, sb); |
574 |
} |
575 |
|
576 |
if (JSVAL_IS_STRING(space)) |
577 |
return WriteStringGap(cx, space, sb); |
578 |
|
579 |
if (JSVAL_IS_NUMBER(space)) { |
580 |
uint32 i; |
581 |
if (!JS_ValueToECMAUint32(cx, space, &i)) |
582 |
return JS_FALSE; |
583 |
|
584 |
js_RepeatChar(sb, jschar(' '), i); |
585 |
|
586 |
if (!STRING_BUFFER_OK(sb)) { |
587 |
JS_ReportOutOfMemory(cx); |
588 |
return JS_FALSE; |
589 |
} |
590 |
} |
591 |
|
592 |
return JS_TRUE; |
593 |
} |
594 |
|
595 |
JSBool |
596 |
js_Stringify(JSContext *cx, jsval *vp, JSObject *replacer, jsval space, |
597 |
JSONWriteCallback callback, void *data) |
598 |
{ |
599 |
// XXX stack |
600 |
JSObject *stack = JS_NewArrayObject(cx, 0, NULL); |
601 |
if (!stack) |
602 |
return JS_FALSE; |
603 |
|
604 |
StringifyContext scx(callback, replacer, data); |
605 |
if (!InitializeGap(cx, space, &scx.gap)) |
606 |
return JS_FALSE; |
607 |
|
608 |
JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0); |
609 |
if (!obj) |
610 |
return JS_FALSE; |
611 |
|
612 |
if (!OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), |
613 |
*vp, NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
614 |
return JS_FALSE; |
615 |
} |
616 |
|
617 |
return Str(cx, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), obj, &scx, vp); |
618 |
} |
619 |
|
620 |
// helper to determine whether a character could be part of a number |
621 |
static JSBool IsNumChar(jschar c) |
622 |
{ |
623 |
return ((c <= '9' && c >= '0') || c == '.' || c == '-' || c == '+' || c == 'e' || c == 'E'); |
624 |
} |
625 |
|
626 |
static JSBool HandleData(JSContext *cx, JSONParser *jp, JSONDataType type); |
627 |
static JSBool PopState(JSContext *cx, JSONParser *jp); |
628 |
|
629 |
static JSBool |
630 |
DestroyIdArrayOnError(JSContext *cx, JSIdArray *ida) { |
631 |
JS_DestroyIdArray(cx, ida); |
632 |
return JS_FALSE; |
633 |
} |
634 |
|
635 |
static JSBool |
636 |
Walk(JSContext *cx, jsid id, JSObject *holder, jsval reviver, jsval *vp) |
637 |
{ |
638 |
JS_CHECK_RECURSION(cx, return JS_FALSE); |
639 |
|
640 |
if (!OBJ_GET_PROPERTY(cx, holder, id, vp)) |
641 |
return JS_FALSE; |
642 |
|
643 |
JSObject *obj; |
644 |
|
645 |
if (!JSVAL_IS_PRIMITIVE(*vp) && !js_IsCallable(obj = JSVAL_TO_OBJECT(*vp), cx)) { |
646 |
jsval propValue = JSVAL_NULL; |
647 |
JSAutoTempValueRooter tvr(cx, 1, &propValue); |
648 |
|
649 |
if(OBJ_IS_ARRAY(cx, obj)) { |
650 |
jsuint length = 0; |
651 |
if (!js_GetLengthProperty(cx, obj, &length)) |
652 |
return JS_FALSE; |
653 |
|
654 |
for (jsuint i = 0; i < length; i++) { |
655 |
jsid index; |
656 |
if (!js_IndexToId(cx, i, &index)) |
657 |
return JS_FALSE; |
658 |
|
659 |
if (!Walk(cx, index, obj, reviver, &propValue)) |
660 |
return JS_FALSE; |
661 |
|
662 |
if (!OBJ_DEFINE_PROPERTY(cx, obj, index, propValue, |
663 |
NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
664 |
return JS_FALSE; |
665 |
} |
666 |
} |
667 |
} else { |
668 |
JSIdArray *ida = JS_Enumerate(cx, obj); |
669 |
if (!ida) |
670 |
return JS_FALSE; |
671 |
|
672 |
JSAutoTempValueRooter idaroot(cx, JS_ARRAY_LENGTH(ida), (jsval*)ida); |
673 |
|
674 |
for(jsint i = 0; i < ida->length; i++) { |
675 |
jsid idName = ida->vector[i]; |
676 |
if (!Walk(cx, idName, obj, reviver, &propValue)) |
677 |
return DestroyIdArrayOnError(cx, ida); |
678 |
if (propValue == JSVAL_VOID) { |
679 |
if (!js_DeleteProperty(cx, obj, idName, &propValue)) |
680 |
return DestroyIdArrayOnError(cx, ida); |
681 |
} else { |
682 |
if (!OBJ_DEFINE_PROPERTY(cx, obj, idName, propValue, |
683 |
NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
684 |
return DestroyIdArrayOnError(cx, ida); |
685 |
} |
686 |
} |
687 |
} |
688 |
|
689 |
JS_DestroyIdArray(cx, ida); |
690 |
} |
691 |
} |
692 |
|
693 |
// return reviver.call(holder, key, value); |
694 |
jsval value = *vp; |
695 |
JSString *key = js_ValueToString(cx, ID_TO_VALUE(id)); |
696 |
if (!key) |
697 |
return JS_FALSE; |
698 |
|
699 |
jsval vec[2] = {STRING_TO_JSVAL(key), value}; |
700 |
jsval reviverResult; |
701 |
if (!JS_CallFunctionValue(cx, holder, reviver, 2, vec, &reviverResult)) |
702 |
return JS_FALSE; |
703 |
|
704 |
*vp = reviverResult; |
705 |
|
706 |
return JS_TRUE; |
707 |
} |
708 |
|
709 |
static JSBool |
710 |
Revive(JSContext *cx, jsval reviver, jsval *vp) |
711 |
{ |
712 |
|
713 |
JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0); |
714 |
if (!obj) |
715 |
return JS_FALSE; |
716 |
|
717 |
jsval v = OBJECT_TO_JSVAL(obj); |
718 |
JSAutoTempValueRooter tvr(cx, 1, &v); |
719 |
if (!OBJ_DEFINE_PROPERTY(cx, obj, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), |
720 |
*vp, NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
721 |
return JS_FALSE; |
722 |
} |
723 |
|
724 |
return Walk(cx, ATOM_TO_JSID(cx->runtime->atomState.emptyAtom), obj, reviver, vp); |
725 |
} |
726 |
|
727 |
#define JSON_INITIAL_BUFSIZE 256 |
728 |
|
729 |
JSONParser * |
730 |
js_BeginJSONParse(JSContext *cx, jsval *rootVal) |
731 |
{ |
732 |
if (!cx) |
733 |
return NULL; |
734 |
|
735 |
JSObject *arr = js_NewArrayObject(cx, 0, NULL); |
736 |
if (!arr) |
737 |
return NULL; |
738 |
|
739 |
JSONParser *jp = (JSONParser*) JS_malloc(cx, sizeof(JSONParser)); |
740 |
if (!jp) |
741 |
return NULL; |
742 |
memset(jp, 0, sizeof *jp); |
743 |
|
744 |
jp->objectStack = arr; |
745 |
if (!js_AddRoot(cx, &jp->objectStack, "JSON parse stack")) |
746 |
goto bad; |
747 |
|
748 |
jp->statep = jp->stateStack; |
749 |
*jp->statep = JSON_PARSE_STATE_INIT; |
750 |
jp->rootVal = rootVal; |
751 |
|
752 |
js_InitStringBuffer(&jp->objectKey); |
753 |
js_InitStringBuffer(&jp->buffer); |
754 |
|
755 |
if (!jp->buffer.grow(&jp->buffer, JSON_INITIAL_BUFSIZE)) { |
756 |
JS_ReportOutOfMemory(cx); |
757 |
goto bad; |
758 |
} |
759 |
return jp; |
760 |
|
761 |
bad: |
762 |
js_FinishJSONParse(cx, jp, JSVAL_NULL); |
763 |
return NULL; |
764 |
} |
765 |
|
766 |
JSBool |
767 |
js_FinishJSONParse(JSContext *cx, JSONParser *jp, jsval reviver) |
768 |
{ |
769 |
if (!jp) |
770 |
return JS_TRUE; |
771 |
|
772 |
JSBool early_ok = JS_TRUE; |
773 |
|
774 |
// Check for unprocessed primitives at the root. This doesn't happen for |
775 |
// strings because a closing quote triggers value processing. |
776 |
if ((jp->statep - jp->stateStack) == 1) { |
777 |
if (*jp->statep == JSON_PARSE_STATE_KEYWORD) { |
778 |
early_ok = HandleData(cx, jp, JSON_DATA_KEYWORD); |
779 |
if (early_ok) |
780 |
PopState(cx, jp); |
781 |
} else if (*jp->statep == JSON_PARSE_STATE_NUMBER) { |
782 |
early_ok = HandleData(cx, jp, JSON_DATA_NUMBER); |
783 |
if (early_ok) |
784 |
PopState(cx, jp); |
785 |
} |
786 |
} |
787 |
|
788 |
js_FinishStringBuffer(&jp->objectKey); |
789 |
js_FinishStringBuffer(&jp->buffer); |
790 |
|
791 |
/* This internal API is infallible, in spite of its JSBool return type. */ |
792 |
js_RemoveRoot(cx->runtime, &jp->objectStack); |
793 |
|
794 |
JSBool ok = *jp->statep == JSON_PARSE_STATE_FINISHED; |
795 |
jsval *vp = jp->rootVal; |
796 |
JS_free(cx, jp); |
797 |
|
798 |
if (!early_ok) |
799 |
return JS_FALSE; |
800 |
|
801 |
if (!ok) { |
802 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
803 |
return JS_FALSE; |
804 |
} |
805 |
|
806 |
if (!JSVAL_IS_PRIMITIVE(reviver) && js_IsCallable(JSVAL_TO_OBJECT(reviver), cx)) |
807 |
ok = Revive(cx, reviver, vp); |
808 |
|
809 |
return ok; |
810 |
} |
811 |
|
812 |
static JSBool |
813 |
PushState(JSContext *cx, JSONParser *jp, JSONParserState state) |
814 |
{ |
815 |
if (*jp->statep == JSON_PARSE_STATE_FINISHED) { |
816 |
// extra input |
817 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
818 |
return JS_FALSE; |
819 |
} |
820 |
|
821 |
jp->statep++; |
822 |
if ((uint32)(jp->statep - jp->stateStack) >= JS_ARRAY_LENGTH(jp->stateStack)) { |
823 |
// too deep |
824 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
825 |
return JS_FALSE; |
826 |
} |
827 |
|
828 |
*jp->statep = state; |
829 |
|
830 |
return JS_TRUE; |
831 |
} |
832 |
|
833 |
static JSBool |
834 |
PopState(JSContext *cx, JSONParser *jp) |
835 |
{ |
836 |
jp->statep--; |
837 |
if (jp->statep < jp->stateStack) { |
838 |
jp->statep = jp->stateStack; |
839 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
840 |
return JS_FALSE; |
841 |
} |
842 |
|
843 |
if (*jp->statep == JSON_PARSE_STATE_INIT) |
844 |
*jp->statep = JSON_PARSE_STATE_FINISHED; |
845 |
|
846 |
return JS_TRUE; |
847 |
} |
848 |
|
849 |
static JSBool |
850 |
PushValue(JSContext *cx, JSONParser *jp, JSObject *parent, jsval value) |
851 |
{ |
852 |
JSBool ok; |
853 |
if (OBJ_IS_ARRAY(cx, parent)) { |
854 |
jsuint len; |
855 |
ok = js_GetLengthProperty(cx, parent, &len); |
856 |
if (ok) { |
857 |
jsid index; |
858 |
if (!js_IndexToId(cx, len, &index)) |
859 |
return JS_FALSE; |
860 |
ok = OBJ_DEFINE_PROPERTY(cx, parent, index, value, |
861 |
NULL, NULL, JSPROP_ENUMERATE, NULL); |
862 |
} |
863 |
} else { |
864 |
ok = JS_DefineUCProperty(cx, parent, jp->objectKey.base, |
865 |
STRING_BUFFER_OFFSET(&jp->objectKey), value, |
866 |
NULL, NULL, JSPROP_ENUMERATE); |
867 |
js_RewindStringBuffer(&jp->objectKey); |
868 |
} |
869 |
|
870 |
return ok; |
871 |
} |
872 |
|
873 |
static JSBool |
874 |
PushObject(JSContext *cx, JSONParser *jp, JSObject *obj) |
875 |
{ |
876 |
jsuint len; |
877 |
if (!js_GetLengthProperty(cx, jp->objectStack, &len)) |
878 |
return JS_FALSE; |
879 |
if (len >= JSON_MAX_DEPTH) { |
880 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
881 |
return JS_FALSE; |
882 |
} |
883 |
|
884 |
jsval v = OBJECT_TO_JSVAL(obj); |
885 |
JSAutoTempValueRooter tvr(cx, v); |
886 |
|
887 |
// Check if this is the root object |
888 |
if (len == 0) { |
889 |
*jp->rootVal = v; |
890 |
// This property must be enumerable to keep the array dense |
891 |
if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(0), *jp->rootVal, |
892 |
NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
893 |
return JS_FALSE; |
894 |
} |
895 |
return JS_TRUE; |
896 |
} |
897 |
|
898 |
jsval p; |
899 |
if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &p)) |
900 |
return JS_FALSE; |
901 |
|
902 |
JS_ASSERT(JSVAL_IS_OBJECT(p)); |
903 |
JSObject *parent = JSVAL_TO_OBJECT(p); |
904 |
if (!PushValue(cx, jp, parent, v)) |
905 |
return JS_FALSE; |
906 |
|
907 |
// This property must be enumerable to keep the array dense |
908 |
if (!OBJ_DEFINE_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len), v, |
909 |
NULL, NULL, JSPROP_ENUMERATE, NULL)) { |
910 |
return JS_FALSE; |
911 |
} |
912 |
|
913 |
return JS_TRUE; |
914 |
} |
915 |
|
916 |
static JSBool |
917 |
OpenObject(JSContext *cx, JSONParser *jp) |
918 |
{ |
919 |
JSObject *obj = js_NewObject(cx, &js_ObjectClass, NULL, NULL, 0); |
920 |
if (!obj) |
921 |
return JS_FALSE; |
922 |
|
923 |
return PushObject(cx, jp, obj); |
924 |
} |
925 |
|
926 |
static JSBool |
927 |
OpenArray(JSContext *cx, JSONParser *jp) |
928 |
{ |
929 |
// Add an array to an existing array or object |
930 |
JSObject *arr = js_NewArrayObject(cx, 0, NULL); |
931 |
if (!arr) |
932 |
return JS_FALSE; |
933 |
|
934 |
return PushObject(cx, jp, arr); |
935 |
} |
936 |
|
937 |
static JSBool |
938 |
CloseObject(JSContext *cx, JSONParser *jp) |
939 |
{ |
940 |
jsuint len; |
941 |
if (!js_GetLengthProperty(cx, jp->objectStack, &len)) |
942 |
return JS_FALSE; |
943 |
if (!js_SetLengthProperty(cx, jp->objectStack, len - 1)) |
944 |
return JS_FALSE; |
945 |
|
946 |
return JS_TRUE; |
947 |
} |
948 |
|
949 |
static JSBool |
950 |
CloseArray(JSContext *cx, JSONParser *jp) |
951 |
{ |
952 |
return CloseObject(cx, jp); |
953 |
} |
954 |
|
955 |
static JSBool |
956 |
PushPrimitive(JSContext *cx, JSONParser *jp, jsval value) |
957 |
{ |
958 |
JSAutoTempValueRooter tvr(cx, 1, &value); |
959 |
|
960 |
jsuint len; |
961 |
if (!js_GetLengthProperty(cx, jp->objectStack, &len)) |
962 |
return JS_FALSE; |
963 |
|
964 |
if (len > 0) { |
965 |
jsval o; |
966 |
if (!OBJ_GET_PROPERTY(cx, jp->objectStack, INT_TO_JSID(len - 1), &o)) |
967 |
return JS_FALSE; |
968 |
|
969 |
JS_ASSERT(!JSVAL_IS_PRIMITIVE(o)); |
970 |
return PushValue(cx, jp, JSVAL_TO_OBJECT(o), value); |
971 |
} |
972 |
|
973 |
// root value must be primitive |
974 |
*jp->rootVal = value; |
975 |
return JS_TRUE; |
976 |
} |
977 |
|
978 |
static JSBool |
979 |
HandleNumber(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) |
980 |
{ |
981 |
const jschar *ep; |
982 |
double val; |
983 |
if (!js_strtod(cx, buf, buf + len, &ep, &val)) |
984 |
return JS_FALSE; |
985 |
if (ep != buf + len) { |
986 |
// bad number input |
987 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
988 |
return JS_FALSE; |
989 |
} |
990 |
|
991 |
jsval numVal; |
992 |
if (!JS_NewNumberValue(cx, val, &numVal)) |
993 |
return JS_FALSE; |
994 |
|
995 |
return PushPrimitive(cx, jp, numVal); |
996 |
} |
997 |
|
998 |
static JSBool |
999 |
HandleString(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) |
1000 |
{ |
1001 |
JSString *str = js_NewStringCopyN(cx, buf, len); |
1002 |
if (!str) |
1003 |
return JS_FALSE; |
1004 |
|
1005 |
return PushPrimitive(cx, jp, STRING_TO_JSVAL(str)); |
1006 |
} |
1007 |
|
1008 |
static JSBool |
1009 |
HandleKeyword(JSContext *cx, JSONParser *jp, const jschar *buf, uint32 len) |
1010 |
{ |
1011 |
jsval keyword; |
1012 |
JSTokenType tt = js_CheckKeyword(buf, len); |
1013 |
if (tt != TOK_PRIMARY) { |
1014 |
// bad keyword |
1015 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1016 |
return JS_FALSE; |
1017 |
} |
1018 |
|
1019 |
if (buf[0] == 'n') { |
1020 |
keyword = JSVAL_NULL; |
1021 |
} else if (buf[0] == 't') { |
1022 |
keyword = JSVAL_TRUE; |
1023 |
} else if (buf[0] == 'f') { |
1024 |
keyword = JSVAL_FALSE; |
1025 |
} else { |
1026 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1027 |
return JS_FALSE; |
1028 |
} |
1029 |
|
1030 |
return PushPrimitive(cx, jp, keyword); |
1031 |
} |
1032 |
|
1033 |
static JSBool |
1034 |
HandleData(JSContext *cx, JSONParser *jp, JSONDataType type) |
1035 |
{ |
1036 |
JSBool ok; |
1037 |
|
1038 |
switch (type) { |
1039 |
case JSON_DATA_STRING: |
1040 |
ok = HandleString(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer)); |
1041 |
break; |
1042 |
|
1043 |
case JSON_DATA_KEYSTRING: |
1044 |
js_AppendUCString(&jp->objectKey, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer)); |
1045 |
ok = STRING_BUFFER_OK(&jp->objectKey); |
1046 |
if (!ok) |
1047 |
JS_ReportOutOfMemory(cx); |
1048 |
break; |
1049 |
|
1050 |
case JSON_DATA_NUMBER: |
1051 |
ok = HandleNumber(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer)); |
1052 |
break; |
1053 |
|
1054 |
default: |
1055 |
JS_ASSERT(type == JSON_DATA_KEYWORD); |
1056 |
ok = HandleKeyword(cx, jp, jp->buffer.base, STRING_BUFFER_OFFSET(&jp->buffer)); |
1057 |
break; |
1058 |
} |
1059 |
|
1060 |
if (ok) { |
1061 |
ok = STRING_BUFFER_OK(&jp->buffer); |
1062 |
if (ok) |
1063 |
js_RewindStringBuffer(&jp->buffer); |
1064 |
else |
1065 |
JS_ReportOutOfMemory(cx); |
1066 |
} |
1067 |
return ok; |
1068 |
} |
1069 |
|
1070 |
JSBool |
1071 |
js_ConsumeJSONText(JSContext *cx, JSONParser *jp, const jschar *data, uint32 len) |
1072 |
{ |
1073 |
uint32 i; |
1074 |
|
1075 |
if (*jp->statep == JSON_PARSE_STATE_INIT) { |
1076 |
PushState(cx, jp, JSON_PARSE_STATE_VALUE); |
1077 |
} |
1078 |
|
1079 |
for (i = 0; i < len; i++) { |
1080 |
jschar c = data[i]; |
1081 |
switch (*jp->statep) { |
1082 |
case JSON_PARSE_STATE_VALUE: |
1083 |
if (c == ']') { |
1084 |
// empty array |
1085 |
if (!PopState(cx, jp)) |
1086 |
return JS_FALSE; |
1087 |
|
1088 |
if (*jp->statep != JSON_PARSE_STATE_ARRAY) { |
1089 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1090 |
return JS_FALSE; |
1091 |
} |
1092 |
|
1093 |
if (!CloseArray(cx, jp) || !PopState(cx, jp)) |
1094 |
return JS_FALSE; |
1095 |
|
1096 |
break; |
1097 |
} |
1098 |
|
1099 |
if (c == '}') { |
1100 |
// we should only find these in OBJECT_KEY state |
1101 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1102 |
return JS_FALSE; |
1103 |
} |
1104 |
|
1105 |
if (c == '"') { |
1106 |
*jp->statep = JSON_PARSE_STATE_STRING; |
1107 |
break; |
1108 |
} |
1109 |
|
1110 |
if (IsNumChar(c)) { |
1111 |
*jp->statep = JSON_PARSE_STATE_NUMBER; |
1112 |
js_FastAppendChar(&jp->buffer, c); |
1113 |
break; |
1114 |
} |
1115 |
|
1116 |
if (JS7_ISLET(c)) { |
1117 |
*jp->statep = JSON_PARSE_STATE_KEYWORD; |
1118 |
js_FastAppendChar(&jp->buffer, c); |
1119 |
break; |
1120 |
} |
1121 |
|
1122 |
// fall through in case the value is an object or array |
1123 |
case JSON_PARSE_STATE_OBJECT_VALUE: |
1124 |
if (c == '{') { |
1125 |
*jp->statep = JSON_PARSE_STATE_OBJECT; |
1126 |
if (!OpenObject(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR)) |
1127 |
return JS_FALSE; |
1128 |
} else if (c == '[') { |
1129 |
*jp->statep = JSON_PARSE_STATE_ARRAY; |
1130 |
if (!OpenArray(cx, jp) || !PushState(cx, jp, JSON_PARSE_STATE_VALUE)) |
1131 |
return JS_FALSE; |
1132 |
} else if (!JS_ISXMLSPACE(c)) { |
1133 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1134 |
return JS_FALSE; |
1135 |
} |
1136 |
break; |
1137 |
|
1138 |
case JSON_PARSE_STATE_OBJECT: |
1139 |
if (c == '}') { |
1140 |
if (!CloseObject(cx, jp) || !PopState(cx, jp)) |
1141 |
return JS_FALSE; |
1142 |
} else if (c == ',') { |
1143 |
if (!PushState(cx, jp, JSON_PARSE_STATE_OBJECT_PAIR)) |
1144 |
return JS_FALSE; |
1145 |
} else if (c == ']' || !JS_ISXMLSPACE(c)) { |
1146 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1147 |
return JS_FALSE; |
1148 |
} |
1149 |
break; |
1150 |
|
1151 |
case JSON_PARSE_STATE_ARRAY : |
1152 |
if (c == ']') { |
1153 |
if (!CloseArray(cx, jp) || !PopState(cx, jp)) |
1154 |
return JS_FALSE; |
1155 |
} else if (c == ',') { |
1156 |
if (!PushState(cx, jp, JSON_PARSE_STATE_VALUE)) |
1157 |
return JS_FALSE; |
1158 |
} else if (!JS_ISXMLSPACE(c)) { |
1159 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1160 |
return JS_FALSE; |
1161 |
} |
1162 |
break; |
1163 |
|
1164 |
case JSON_PARSE_STATE_OBJECT_PAIR : |
1165 |
if (c == '"') { |
1166 |
// we want to be waiting for a : when the string has been read |
1167 |
*jp->statep = JSON_PARSE_STATE_OBJECT_IN_PAIR; |
1168 |
if (!PushState(cx, jp, JSON_PARSE_STATE_STRING)) |
1169 |
return JS_FALSE; |
1170 |
} else if (c == '}') { |
1171 |
// pop off the object pair state and the object state |
1172 |
if (!CloseObject(cx, jp) || !PopState(cx, jp) || !PopState(cx, jp)) |
1173 |
return JS_FALSE; |
1174 |
} else if (c == ']' || !JS_ISXMLSPACE(c)) { |
1175 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1176 |
return JS_FALSE; |
1177 |
} |
1178 |
break; |
1179 |
|
1180 |
case JSON_PARSE_STATE_OBJECT_IN_PAIR: |
1181 |
if (c == ':') { |
1182 |
*jp->statep = JSON_PARSE_STATE_VALUE; |
1183 |
} else if (!JS_ISXMLSPACE(c)) { |
1184 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1185 |
return JS_FALSE; |
1186 |
} |
1187 |
break; |
1188 |
|
1189 |
case JSON_PARSE_STATE_STRING: |
1190 |
if (c == '"') { |
1191 |
if (!PopState(cx, jp)) |
1192 |
return JS_FALSE; |
1193 |
JSONDataType jdt; |
1194 |
if (*jp->statep == JSON_PARSE_STATE_OBJECT_IN_PAIR) { |
1195 |
jdt = JSON_DATA_KEYSTRING; |
1196 |
} else { |
1197 |
jdt = JSON_DATA_STRING; |
1198 |
} |
1199 |
if (!HandleData(cx, jp, jdt)) |
1200 |
return JS_FALSE; |
1201 |
} else if (c == '\\') { |
1202 |
*jp->statep = JSON_PARSE_STATE_STRING_ESCAPE; |
1203 |
} else { |
1204 |
js_FastAppendChar(&jp->buffer, c); |
1205 |
} |
1206 |
break; |
1207 |
|
1208 |
case JSON_PARSE_STATE_STRING_ESCAPE: |
1209 |
switch (c) { |
1210 |
case '"': |
1211 |
case '\\': |
1212 |
case '/': |
1213 |
break; |
1214 |
case 'b' : c = '\b'; break; |
1215 |
case 'f' : c = '\f'; break; |
1216 |
case 'n' : c = '\n'; break; |
1217 |
case 'r' : c = '\r'; break; |
1218 |
case 't' : c = '\t'; break; |
1219 |
default : |
1220 |
if (c == 'u') { |
1221 |
jp->numHex = 0; |
1222 |
jp->hexChar = 0; |
1223 |
*jp->statep = JSON_PARSE_STATE_STRING_HEX; |
1224 |
continue; |
1225 |
} else { |
1226 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1227 |
return JS_FALSE; |
1228 |
} |
1229 |
} |
1230 |
|
1231 |
js_FastAppendChar(&jp->buffer, c); |
1232 |
*jp->statep = JSON_PARSE_STATE_STRING; |
1233 |
break; |
1234 |
|
1235 |
case JSON_PARSE_STATE_STRING_HEX: |
1236 |
if (('0' <= c) && (c <= '9')) { |
1237 |
jp->hexChar = (jp->hexChar << 4) | (c - '0'); |
1238 |
} else if (('a' <= c) && (c <= 'f')) { |
1239 |
jp->hexChar = (jp->hexChar << 4) | (c - 'a' + 0x0a); |
1240 |
} else if (('A' <= c) && (c <= 'F')) { |
1241 |
jp->hexChar = (jp->hexChar << 4) | (c - 'A' + 0x0a); |
1242 |
} else { |
1243 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1244 |
return JS_FALSE; |
1245 |
} |
1246 |
|
1247 |
if (++(jp->numHex) == 4) { |
1248 |
js_FastAppendChar(&jp->buffer, jp->hexChar); |
1249 |
jp->hexChar = 0; |
1250 |
jp->numHex = 0; |
1251 |
*jp->statep = JSON_PARSE_STATE_STRING; |
1252 |
} |
1253 |
break; |
1254 |
|
1255 |
case JSON_PARSE_STATE_KEYWORD: |
1256 |
if (JS7_ISLET(c)) { |
1257 |
js_FastAppendChar(&jp->buffer, c); |
1258 |
} else { |
1259 |
// this character isn't part of the keyword, process it again |
1260 |
i--; |
1261 |
if (!PopState(cx, jp)) |
1262 |
return JS_FALSE; |
1263 |
|
1264 |
if (!HandleData(cx, jp, JSON_DATA_KEYWORD)) |
1265 |
return JS_FALSE; |
1266 |
} |
1267 |
break; |
1268 |
|
1269 |
case JSON_PARSE_STATE_NUMBER: |
1270 |
if (IsNumChar(c)) { |
1271 |
js_FastAppendChar(&jp->buffer, c); |
1272 |
} else { |
1273 |
// this character isn't part of the number, process it again |
1274 |
i--; |
1275 |
if (!PopState(cx, jp)) |
1276 |
return JS_FALSE; |
1277 |
if (!HandleData(cx, jp, JSON_DATA_NUMBER)) |
1278 |
return JS_FALSE; |
1279 |
} |
1280 |
break; |
1281 |
|
1282 |
case JSON_PARSE_STATE_FINISHED: |
1283 |
if (!JS_ISXMLSPACE(c)) { |
1284 |
// extra input |
1285 |
JS_ReportErrorNumber(cx, js_GetErrorMessage, NULL, JSMSG_JSON_BAD_PARSE); |
1286 |
return JS_FALSE; |
1287 |
} |
1288 |
break; |
1289 |
|
1290 |
default: |
1291 |
JS_NOT_REACHED("Invalid JSON parser state"); |
1292 |
} |
1293 |
|
1294 |
if (!STRING_BUFFER_OK(&jp->buffer)) { |
1295 |
JS_ReportOutOfMemory(cx); |
1296 |
return JS_FALSE; |
1297 |
} |
1298 |
} |
1299 |
|
1300 |
*jp->buffer.ptr = 0; |
1301 |
return JS_TRUE; |
1302 |
} |
1303 |
|
1304 |
#if JS_HAS_TOSOURCE |
1305 |
static JSBool |
1306 |
json_toSource(JSContext *cx, uintN argc, jsval *vp) |
1307 |
{ |
1308 |
*vp = ATOM_KEY(CLASS_ATOM(cx, JSON)); |
1309 |
return JS_TRUE; |
1310 |
} |
1311 |
#endif |
1312 |
|
1313 |
static JSFunctionSpec json_static_methods[] = { |
1314 |
#if JS_HAS_TOSOURCE |
1315 |
JS_FN(js_toSource_str, json_toSource, 0, 0), |
1316 |
#endif |
1317 |
JS_FN("parse", js_json_parse, 1, 0), |
1318 |
JS_FN("stringify", js_json_stringify, 1, 0), |
1319 |
JS_FS_END |
1320 |
}; |
1321 |
|
1322 |
JSObject * |
1323 |
js_InitJSONClass(JSContext *cx, JSObject *obj) |
1324 |
{ |
1325 |
JSObject *JSON; |
1326 |
|
1327 |
JSON = JS_NewObject(cx, &js_JSONClass, NULL, obj); |
1328 |
if (!JSON) |
1329 |
return NULL; |
1330 |
if (!JS_DefineProperty(cx, obj, js_JSON_str, OBJECT_TO_JSVAL(JSON), |
1331 |
JS_PropertyStub, JS_PropertyStub, 0)) |
1332 |
return NULL; |
1333 |
|
1334 |
if (!JS_DefineFunctions(cx, JSON, json_static_methods)) |
1335 |
return NULL; |
1336 |
|
1337 |
return JSON; |
1338 |
} |