r19055: JSON-RPC is working! It passes the small qooxdoo JSON-RPC test suite
[Samba/bb.git] / source4 / scripting / ejs / literal.c
blobd50e5afdb916e97281c7f36d00cf0ebe39115faf
1 /*
2 * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd.
3 * Michael Clark <michael@metaparadigm.com>
4 * Copyright (c) 2006 Derrell Lipman
6 * This library is free software; you can redistribute it and/or modify
7 * it under the terms of the MIT license. See COPYING for details.
9 * Derrell Lipman:
10 * This version is modified from the original. It has been modified to
11 * natively use EJS variables rather than the original C object interface, and
12 * to use the talloc() family of functions for memory allocation.
15 #include "includes.h"
16 #include "scripting/ejs/smbcalls.h"
18 enum json_tokener_error {
19 json_tokener_success,
20 json_tokener_error_oom, /* out of memory */
21 json_tokener_error_parse_unexpected,
22 json_tokener_error_parse_null,
23 json_tokener_error_parse_date,
24 json_tokener_error_parse_boolean,
25 json_tokener_error_parse_number,
26 json_tokener_error_parse_array,
27 json_tokener_error_parse_object,
28 json_tokener_error_parse_string,
29 json_tokener_error_parse_comment,
30 json_tokener_error_parse_eof
33 enum json_tokener_state {
34 json_tokener_state_eatws,
35 json_tokener_state_start,
36 json_tokener_state_finish,
37 json_tokener_state_null,
38 json_tokener_state_date,
39 json_tokener_state_comment_start,
40 json_tokener_state_comment,
41 json_tokener_state_comment_eol,
42 json_tokener_state_comment_end,
43 json_tokener_state_string,
44 json_tokener_state_string_escape,
45 json_tokener_state_escape_unicode,
46 json_tokener_state_boolean,
47 json_tokener_state_number,
48 json_tokener_state_array,
49 json_tokener_state_datelist,
50 json_tokener_state_array_sep,
51 json_tokener_state_datelist_sep,
52 json_tokener_state_object,
53 json_tokener_state_object_field_start,
54 json_tokener_state_object_field,
55 json_tokener_state_object_field_end,
56 json_tokener_state_object_value,
57 json_tokener_state_object_sep
60 enum date_field {
61 date_field_year,
62 date_field_month,
63 date_field_day,
64 date_field_hour,
65 date_field_minute,
66 date_field_second,
67 date_field_millisecond
70 struct json_tokener
72 char *source;
73 int pos;
74 void *ctx;
75 void *pb;
78 static const char *json_number_chars = "0123456789.+-e";
79 static const char *json_hex_chars = "0123456789abcdef";
81 #define hexdigit(x) (((x) <= '9') ? (x) - '0' : ((x) & 7) + 9)
83 extern struct MprVar json_tokener_parse(char *s);
84 static struct MprVar json_tokener_do_parse(struct json_tokener *this,
85 enum json_tokener_error *err_p);
88 * literal_to_var() parses a string into an ejs variable. The ejs
89 * variable is returned. Upon error, the javascript variable will be
90 * `undefined`. This was created for parsing JSON, but is generally useful
91 * for parsing the literal forms of objects and arrays, since ejs doesn't
92 * procide that functionality.
94 int literal_to_var(int eid, int argc, char **argv)
96 struct json_tokener tok;
97 struct MprVar obj;
98 enum json_tokener_error err = json_tokener_success;
100 if (argc != 1) {
101 ejsSetErrorMsg(eid,
102 "literal_to_var() requires one parameter: "
103 "the string to be parsed.");
104 return -1;
107 tok.source = argv[0];
108 tok.pos = 0;
109 tok.ctx = talloc_new(mprMemCtx());
110 if (tok.ctx == NULL) {
111 mpr_Return(eid, mprCreateUndefinedVar());
112 return 0;
114 tok.pb = talloc_zero_size(tok.ctx, 1);
115 if (tok.pb == NULL) {
116 mpr_Return(eid, mprCreateUndefinedVar());
117 return 0;
119 obj = json_tokener_do_parse(&tok, &err);
120 talloc_free(tok.pb);
121 if (err != json_tokener_success) {
122 mprDestroyVar(&obj);
123 mpr_Return(eid, mprCreateUndefinedVar());
124 return 0;
126 mpr_Return(eid, obj);
127 return 0;
130 static void *append_string(void *ctx,
131 char *orig,
132 char *append,
133 int size)
135 char c;
136 char *end_p = append + size;
137 void *ret;
140 * We need to null terminate the string to be copied. Save character at
141 * the size limit of the source string.
143 c = *end_p;
145 /* Temporarily null-terminate it */
146 *end_p = '\0';
148 /* Append the requested data */
149 ret = talloc_append_string(ctx, orig, append);
151 /* Restore the original character in place of our temporary null byte */
152 *end_p = c;
154 /* Give 'em what they came for */
155 return ret;
159 static struct MprVar json_tokener_do_parse(struct json_tokener *this,
160 enum json_tokener_error *err_p)
162 enum json_tokener_state state;
163 enum json_tokener_state saved_state;
164 enum date_field date_field;
165 struct MprVar current = mprCreateUndefinedVar();
166 struct MprVar tempObj;
167 struct MprVar obj;
168 enum json_tokener_error err = json_tokener_success;
169 char date_script[] = "JSON_Date.create(0);";
170 char *obj_field_name = NULL;
171 char *emsg = NULL;
172 char quote_char;
173 int deemed_double;
174 int start_offset;
175 char c;
177 state = json_tokener_state_eatws;
178 saved_state = json_tokener_state_start;
181 do {
182 c = this->source[this->pos];
183 switch(state) {
185 case json_tokener_state_eatws:
186 if(isspace(c)) {
187 this->pos++;
188 } else if(c == '/') {
189 state = json_tokener_state_comment_start;
190 start_offset = this->pos++;
191 } else {
192 state = saved_state;
194 break;
196 case json_tokener_state_start:
197 switch(c) {
198 case '{':
199 state = json_tokener_state_eatws;
200 saved_state = json_tokener_state_object;
201 current = mprObject(NULL);
202 this->pos++;
203 break;
204 case '[':
205 state = json_tokener_state_eatws;
206 saved_state = json_tokener_state_array;
207 current = mprArray(NULL);
208 this->pos++;
209 break;
210 case 'N':
211 case 'n':
212 start_offset = this->pos++;
213 if (this->source[this->pos] == 'e') {
214 state = json_tokener_state_date;
215 } else {
216 state = json_tokener_state_null;
218 break;
219 case '"':
220 case '\'':
221 quote_char = c;
222 talloc_free(this->pb);
223 this->pb = talloc_zero_size(this->ctx, 1);
224 if (this->pb == NULL) {
225 *err_p = json_tokener_error_oom;
226 goto out;
228 state = json_tokener_state_string;
229 start_offset = ++this->pos;
230 break;
231 case 'T':
232 case 't':
233 case 'F':
234 case 'f':
235 state = json_tokener_state_boolean;
236 start_offset = this->pos++;
237 break;
238 #if defined(__GNUC__)
239 case '0' ... '9':
240 #else
241 case '0':
242 case '1':
243 case '2':
244 case '3':
245 case '4':
246 case '5':
247 case '6':
248 case '7':
249 case '8':
250 case '9':
251 #endif
252 case '-':
253 deemed_double = 0;
254 state = json_tokener_state_number;
255 start_offset = this->pos++;
256 break;
257 default:
258 err = json_tokener_error_parse_unexpected;
259 goto out;
261 break;
263 case json_tokener_state_finish:
264 goto out;
266 case json_tokener_state_null:
267 if(strncasecmp("null",
268 this->source + start_offset,
269 this->pos - start_offset)) {
270 *err_p = json_tokener_error_parse_null;
271 mprDestroyVar(&current);
272 return mprCreateUndefinedVar();
275 if(this->pos - start_offset == 4) {
276 mprDestroyVar(&current);
277 current = mprCreateNullVar();
278 saved_state = json_tokener_state_finish;
279 state = json_tokener_state_eatws;
280 } else {
281 this->pos++;
283 break;
285 case json_tokener_state_date:
286 if (this->pos - start_offset <= 18) {
287 if (strncasecmp("new Date(Date.UTC(",
288 this->source + start_offset,
289 this->pos - start_offset)) {
290 *err_p = json_tokener_error_parse_date;
291 mprDestroyVar(&current);
292 return mprCreateUndefinedVar();
293 } else {
294 this->pos++;
295 break;
299 this->pos--; /* we went one too far */
300 state = json_tokener_state_eatws;
301 saved_state = json_tokener_state_datelist;
303 /* Create a JsonDate object */
304 if (ejsEvalScript(0,
305 date_script,
306 &tempObj,
307 &emsg) != 0) {
308 *err_p = json_tokener_error_parse_date;
309 mprDestroyVar(&current);
310 return mprCreateUndefinedVar();
312 mprDestroyVar(&current);
313 mprCopyVar(&current, &tempObj, MPR_DEEP_COPY);
314 date_field = date_field_year;
315 break;
317 case json_tokener_state_comment_start:
318 if(c == '*') {
319 state = json_tokener_state_comment;
320 } else if(c == '/') {
321 state = json_tokener_state_comment_eol;
322 } else {
323 err = json_tokener_error_parse_comment;
324 goto out;
326 this->pos++;
327 break;
329 case json_tokener_state_comment:
330 if(c == '*') state = json_tokener_state_comment_end;
331 this->pos++;
332 break;
334 case json_tokener_state_comment_eol:
335 if(c == '\n') {
336 state = json_tokener_state_eatws;
338 this->pos++;
339 break;
341 case json_tokener_state_comment_end:
342 if(c == '/') {
343 state = json_tokener_state_eatws;
344 } else {
345 state = json_tokener_state_comment;
347 this->pos++;
348 break;
350 case json_tokener_state_string:
351 if(c == quote_char) {
352 this->pb = append_string(
353 this->ctx,
354 this->pb,
355 this->source + start_offset,
356 this->pos - start_offset);
357 if (this->pb == NULL) {
358 err = json_tokener_error_oom;
359 goto out;
361 current = mprString(this->pb);
362 saved_state = json_tokener_state_finish;
363 state = json_tokener_state_eatws;
364 } else if(c == '\\') {
365 saved_state = json_tokener_state_string;
366 state = json_tokener_state_string_escape;
368 this->pos++;
369 break;
371 case json_tokener_state_string_escape:
372 switch(c) {
373 case '"':
374 case '\\':
375 this->pb = append_string(
376 this->ctx,
377 this->pb,
378 this->source + start_offset,
379 this->pos - start_offset - 1);
380 if (this->pb == NULL) {
381 err = json_tokener_error_oom;
382 goto out;
384 start_offset = this->pos++;
385 state = saved_state;
386 break;
387 case 'b':
388 case 'n':
389 case 'r':
390 case 't':
391 this->pb = append_string(
392 this->ctx,
393 this->pb,
394 this->source + start_offset,
395 this->pos - start_offset - 1);
396 if (this->pb == NULL) {
397 err = json_tokener_error_oom;
398 goto out;
400 if (c == 'b') {
402 * second param to append_string()
403 * gets temporarily modified; can't
404 * pass string constant.
406 char buf[] = "\b";
407 this->pb = append_string(this->ctx,
408 this->pb,
409 buf,
411 if (this->pb == NULL) {
412 err = json_tokener_error_oom;
413 goto out;
415 } else if (c == 'n') {
416 char buf[] = "\n";
417 this->pb = append_string(this->ctx,
418 this->pb,
419 buf,
421 if (this->pb == NULL) {
422 err = json_tokener_error_oom;
423 goto out;
425 } else if (c == 'r') {
426 char buf[] = "\r";
427 this->pb = append_string(this->ctx,
428 this->pb,
429 buf,
431 if (this->pb == NULL) {
432 err = json_tokener_error_oom;
433 goto out;
435 } else if (c == 't') {
436 char buf[] = "\t";
437 this->pb = append_string(this->ctx,
438 this->pb,
439 buf,
441 if (this->pb == NULL) {
442 err = json_tokener_error_oom;
443 goto out;
446 start_offset = ++this->pos;
447 state = saved_state;
448 break;
449 case 'u':
450 this->pb = append_string(
451 this->ctx,
452 this->pb,
453 this->source + start_offset,
454 this->pos - start_offset - 1);
455 if (this->pb == NULL) {
456 err = json_tokener_error_oom;
457 goto out;
459 start_offset = ++this->pos;
460 state = json_tokener_state_escape_unicode;
461 break;
462 default:
463 err = json_tokener_error_parse_string;
464 goto out;
466 break;
468 case json_tokener_state_escape_unicode:
469 if(strchr(json_hex_chars, c)) {
470 this->pos++;
471 if(this->pos - start_offset == 4) {
472 unsigned char utf_out[3];
473 unsigned int ucs_char =
474 (hexdigit(*(this->source + start_offset)) << 12) +
475 (hexdigit(*(this->source + start_offset + 1)) << 8) +
476 (hexdigit(*(this->source + start_offset + 2)) << 4) +
477 hexdigit(*(this->source + start_offset + 3));
478 if (ucs_char < 0x80) {
479 utf_out[0] = ucs_char;
480 this->pb = append_string(
481 this->ctx,
482 this->pb,
483 (char *) utf_out,
485 if (this->pb == NULL) {
486 err = json_tokener_error_oom;
487 goto out;
489 } else if (ucs_char < 0x800) {
490 utf_out[0] = 0xc0 | (ucs_char >> 6);
491 utf_out[1] = 0x80 | (ucs_char & 0x3f);
492 this->pb = append_string(
493 this->ctx,
494 this->pb,
495 (char *) utf_out,
497 if (this->pb == NULL) {
498 err = json_tokener_error_oom;
499 goto out;
501 } else {
502 utf_out[0] = 0xe0 | (ucs_char >> 12);
503 utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f);
504 utf_out[2] = 0x80 | (ucs_char & 0x3f);
505 this->pb = append_string(
506 this->ctx,
507 this->pb,
508 (char *) utf_out,
510 if (this->pb == NULL) {
511 err = json_tokener_error_oom;
512 goto out;
515 start_offset = this->pos;
516 state = saved_state;
518 } else {
519 err = json_tokener_error_parse_string;
520 goto out;
522 break;
524 case json_tokener_state_boolean:
525 if(strncasecmp("true", this->source + start_offset,
526 this->pos - start_offset) == 0) {
527 if(this->pos - start_offset == 4) {
528 current = mprCreateBoolVar(1);
529 saved_state = json_tokener_state_finish;
530 state = json_tokener_state_eatws;
531 } else {
532 this->pos++;
534 } else if(strncasecmp("false", this->source + start_offset,
535 this->pos - start_offset) == 0) {
536 if(this->pos - start_offset == 5) {
537 current = mprCreateBoolVar(0);
538 saved_state = json_tokener_state_finish;
539 state = json_tokener_state_eatws;
540 } else {
541 this->pos++;
543 } else {
544 err = json_tokener_error_parse_boolean;
545 goto out;
547 break;
549 case json_tokener_state_number:
550 if(!c || !strchr(json_number_chars, c)) {
551 int numi;
552 double numd;
553 char *tmp = talloc_strndup(
554 this->ctx,
555 this->source + start_offset,
556 this->pos - start_offset);
557 if (tmp == NULL) {
558 err = json_tokener_error_oom;
559 goto out;
561 if(!deemed_double && sscanf(tmp, "%d", &numi) == 1) {
562 current = mprCreateIntegerVar(numi);
563 } else if(deemed_double && sscanf(tmp, "%lf", &numd) == 1) {
564 current = mprCreateFloatVar(numd);
565 } else {
566 talloc_free(tmp);
567 err = json_tokener_error_parse_number;
568 goto out;
570 talloc_free(tmp);
571 saved_state = json_tokener_state_finish;
572 state = json_tokener_state_eatws;
573 } else {
574 if(c == '.' || c == 'e') deemed_double = 1;
575 this->pos++;
577 break;
579 case json_tokener_state_array:
580 if(c == ']') {
581 this->pos++;
582 saved_state = json_tokener_state_finish;
583 state = json_tokener_state_eatws;
584 } else {
585 int oldlen;
586 char idx[16];
588 obj = json_tokener_do_parse(this, &err);
589 if (err != json_tokener_success) {
590 goto out;
592 oldlen = mprToInt(mprGetProperty(&current,
593 "length",
594 NULL));
595 mprItoa(oldlen, idx, sizeof(idx));
596 mprSetVar(&current, idx, obj);
597 saved_state = json_tokener_state_array_sep;
598 state = json_tokener_state_eatws;
600 break;
602 case json_tokener_state_datelist:
603 if(c == ')') {
604 if (this->source[this->pos+1] == ')') {
605 this->pos += 2;
606 saved_state = json_tokener_state_finish;
607 state = json_tokener_state_eatws;
608 } else {
609 err = json_tokener_error_parse_date;
610 goto out;
612 } else {
613 obj = json_tokener_do_parse(this, &err);
614 if (err != json_tokener_success) {
615 goto out;
618 /* date list items must be integers */
619 if (obj.type != MPR_TYPE_INT) {
620 err = json_tokener_error_parse_date;
621 goto out;
624 switch(date_field) {
625 case date_field_year:
626 mprSetVar(&current, "year", obj);
627 break;
628 case date_field_month:
629 mprSetVar(&current, "month", obj);
630 break;
631 case date_field_day:
632 mprSetVar(&current, "day", obj);
633 break;
634 case date_field_hour:
635 mprSetVar(&current, "hour", obj);
636 break;
637 case date_field_minute:
638 mprSetVar(&current, "minute", obj);
639 break;
640 case date_field_second:
641 mprSetVar(&current, "second", obj);
642 break;
643 case date_field_millisecond:
644 mprSetVar(&current, "millisecond", obj);
645 break;
646 default:
647 err = json_tokener_error_parse_date;
648 goto out;
651 /* advance to the next date field */
652 date_field++;
654 saved_state = json_tokener_state_datelist_sep;
655 state = json_tokener_state_eatws;
657 break;
659 case json_tokener_state_array_sep:
660 if(c == ']') {
661 this->pos++;
662 saved_state = json_tokener_state_finish;
663 state = json_tokener_state_eatws;
664 } else if(c == ',') {
665 this->pos++;
666 saved_state = json_tokener_state_array;
667 state = json_tokener_state_eatws;
668 } else {
669 *err_p = json_tokener_error_parse_array;
670 mprDestroyVar(&current);
671 return mprCreateUndefinedVar();
673 break;
675 case json_tokener_state_datelist_sep:
676 if(c == ')') {
677 if (this->source[this->pos+1] == ')') {
678 this->pos += 2;
679 saved_state = json_tokener_state_finish;
680 state = json_tokener_state_eatws;
681 } else {
682 err = json_tokener_error_parse_date;
683 goto out;
685 } else if(c == ',') {
686 this->pos++;
687 saved_state = json_tokener_state_datelist;
688 state = json_tokener_state_eatws;
689 } else {
690 *err_p = json_tokener_error_parse_date;
691 mprDestroyVar(&current);
692 return mprCreateUndefinedVar();
694 break;
696 case json_tokener_state_object:
697 state = json_tokener_state_object_field_start;
698 start_offset = this->pos;
699 break;
701 case json_tokener_state_object_field_start:
702 if(c == '}') {
703 this->pos++;
704 saved_state = json_tokener_state_finish;
705 state = json_tokener_state_eatws;
706 } else if (c == '"' || c == '\'') {
707 quote_char = c;
708 talloc_free(this->pb);
709 this->pb = talloc_zero_size(this->ctx, 1);
710 if (this->pb == NULL) {
711 *err_p = json_tokener_error_oom;
712 goto out;
714 state = json_tokener_state_object_field;
715 start_offset = ++this->pos;
716 } else {
717 err = json_tokener_error_parse_object;
718 goto out;
720 break;
722 case json_tokener_state_object_field:
723 if(c == quote_char) {
724 this->pb = append_string(
725 this->ctx,
726 this->pb,
727 this->source + start_offset,
728 this->pos - start_offset);
729 if (this->pb == NULL) {
730 err = json_tokener_error_oom;
731 goto out;
733 obj_field_name = talloc_strdup(this->ctx,
734 this->pb);
735 if (obj_field_name == NULL) {
736 err = json_tokener_error_oom;
737 goto out;
739 saved_state = json_tokener_state_object_field_end;
740 state = json_tokener_state_eatws;
741 } else if(c == '\\') {
742 saved_state = json_tokener_state_object_field;
743 state = json_tokener_state_string_escape;
745 this->pos++;
746 break;
748 case json_tokener_state_object_field_end:
749 if(c == ':') {
750 this->pos++;
751 saved_state = json_tokener_state_object_value;
752 state = json_tokener_state_eatws;
753 } else {
754 *err_p = json_tokener_error_parse_object;
755 mprDestroyVar(&current);
756 return mprCreateUndefinedVar();
758 break;
760 case json_tokener_state_object_value:
761 obj = json_tokener_do_parse(this, &err);
762 if (err != json_tokener_success) {
763 goto out;
765 mprSetVar(&current, obj_field_name, obj);
766 talloc_free(obj_field_name);
767 obj_field_name = NULL;
768 saved_state = json_tokener_state_object_sep;
769 state = json_tokener_state_eatws;
770 break;
772 case json_tokener_state_object_sep:
773 if(c == '}') {
774 this->pos++;
775 saved_state = json_tokener_state_finish;
776 state = json_tokener_state_eatws;
777 } else if(c == ',') {
778 this->pos++;
779 saved_state = json_tokener_state_object;
780 state = json_tokener_state_eatws;
781 } else {
782 err = json_tokener_error_parse_object;
783 goto out;
785 break;
788 } while(c);
790 if(state != json_tokener_state_finish &&
791 saved_state != json_tokener_state_finish)
792 err = json_tokener_error_parse_eof;
794 out:
795 talloc_free(obj_field_name);
796 if(err == json_tokener_success) {
797 return current;
798 } else {
799 mprDestroyVar(&current);
800 *err_p = err;
801 return mprCreateUndefinedVar();
806 void smb_setup_ejs_literal(void)
808 ejsDefineStringCFunction(-1,
809 "literal_to_var",
810 literal_to_var,
811 NULL,
812 MPR_VAR_SCRIPT_HANDLE);