/* * Copyright (c) 2004, 2005 Metaparadigm Pte. Ltd. * Michael Clark * Copyright (c) 2006 Derrell Lipman * * This library is free software; you can redistribute it and/or modify * it under the terms of the MIT license. See COPYING for details. * * Derrell Lipman: * This version is modified from the original. It has been modified to * natively use EJS variables rather than the original C object interface, and * to use the talloc() family of functions for memory allocation. */ #include "includes.h" #include "scripting/ejs/smbcalls.h" enum json_tokener_error { json_tokener_success, json_tokener_error_oom, /* out of memory */ json_tokener_error_parse_unexpected, json_tokener_error_parse_null, json_tokener_error_parse_boolean, json_tokener_error_parse_number, json_tokener_error_parse_array, json_tokener_error_parse_object, json_tokener_error_parse_string, json_tokener_error_parse_comment, json_tokener_error_parse_eof }; enum json_tokener_state { json_tokener_state_eatws, /* 0 */ json_tokener_state_start, /* 1 */ json_tokener_state_finish, /* 2 */ json_tokener_state_null, /* 3 */ json_tokener_state_comment_start, /* 4 */ json_tokener_state_comment, /* 5 */ json_tokener_state_comment_eol, /* 6 */ json_tokener_state_comment_end, /* 7 */ json_tokener_state_string, /* 8 */ json_tokener_state_string_escape, /* 9 */ json_tokener_state_escape_unicode, /* 10 */ json_tokener_state_boolean, /* 11 */ json_tokener_state_number, /* 12 */ json_tokener_state_array, /* 13 */ json_tokener_state_array_sep, /* 14 */ json_tokener_state_object, /* 15 */ json_tokener_state_object_field_start, /* 16 */ json_tokener_state_object_field, /* 17 */ json_tokener_state_object_field_end, /* 18 */ json_tokener_state_object_value, /* 19 */ json_tokener_state_object_sep /* 20 */ }; struct json_tokener { char *source; int pos; void *ctx; void *pb; }; static const char *json_number_chars = "0123456789.+-e"; static const char *json_hex_chars = "0123456789abcdef"; #define hexdigit(x) (((x) <= '9') ? (x) - '0' : ((x) & 7) + 9) extern struct MprVar json_tokener_parse(char *s); static struct MprVar json_tokener_do_parse(struct json_tokener *this, enum json_tokener_error *err_p); /* * literal_to_var() parses a string into an ejs variable. The ejs * variable is returned. Upon error, the javascript variable will be * `undefined`. This was created for parsing JSON, but is generally useful * for parsing the literal forms of objects and arrays, since ejs doesn't * procide that functionality. */ int literal_to_var(int eid, int argc, char **argv) { struct json_tokener tok; struct MprVar obj; enum json_tokener_error err = json_tokener_success; if (argc != 1) { ejsSetErrorMsg(eid, "literal_to_var() requires one parameter: " "the string to be parsed."); return -1; } tok.source = argv[0]; tok.pos = 0; tok.ctx = talloc_new(NULL); if (tok.ctx == NULL) { mpr_Return(eid, mprCreateUndefinedVar()); return 0; } tok.pb = talloc_zero_size(tok.ctx, 1); if (tok.pb == NULL) { mpr_Return(eid, mprCreateUndefinedVar()); return 0; } obj = json_tokener_do_parse(&tok, &err); talloc_free(tok.pb); if (err != json_tokener_success) { mprDestroyVar(&obj); mpr_Return(eid, mprCreateUndefinedVar()); return 0; } mpr_Return(eid, obj); return 0; } static void *append_string(void *ctx, char *orig, char *append, int size) { char c; char *end_p = append + size; void *ret; /* * We need to null terminate the string to be copied. Save character at * the size limit of the source string. */ c = *end_p; /* Temporarily null-terminate it */ *end_p = '\0'; /* Append the requested data */ ret = talloc_append_string(ctx, orig, append); /* Restore the original character in place of our temporary null byte */ *end_p = c; /* Give 'em what they came for */ return ret; } static struct MprVar json_tokener_do_parse(struct json_tokener *this, enum json_tokener_error *err_p) { enum json_tokener_state state, saved_state; struct MprVar current = mprCreateUndefinedVar(); struct MprVar obj; enum json_tokener_error err = json_tokener_success; char *obj_field_name = NULL; char quote_char; int deemed_double; int start_offset; char c; state = json_tokener_state_eatws; saved_state = json_tokener_state_start; do { c = this->source[this->pos]; switch(state) { case json_tokener_state_eatws: if(isspace(c)) { this->pos++; } else if(c == '/') { state = json_tokener_state_comment_start; start_offset = this->pos++; } else { state = saved_state; } break; case json_tokener_state_start: switch(c) { case '{': state = json_tokener_state_eatws; saved_state = json_tokener_state_object; current = mprObject(NULL); this->pos++; break; case '[': state = json_tokener_state_eatws; saved_state = json_tokener_state_array; current = mprArray(NULL); this->pos++; break; case 'N': case 'n': state = json_tokener_state_null; start_offset = this->pos++; break; case '"': case '\'': quote_char = c; talloc_free(this->pb); this->pb = talloc_zero_size(this->ctx, 1); if (this->pb == NULL) { *err_p = json_tokener_error_oom; goto out; } state = json_tokener_state_string; start_offset = ++this->pos; break; case 'T': case 't': case 'F': case 'f': state = json_tokener_state_boolean; start_offset = this->pos++; break; #if defined(__GNUC__) case '0' ... '9': #else case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': #endif case '-': deemed_double = 0; state = json_tokener_state_number; start_offset = this->pos++; break; default: err = json_tokener_error_parse_unexpected; goto out; } break; case json_tokener_state_finish: goto out; case json_tokener_state_null: if(strncasecmp("null", this->source + start_offset, this->pos - start_offset)) { *err_p = json_tokener_error_parse_null; mprDestroyVar(¤t); return mprCreateUndefinedVar(); } if(this->pos - start_offset == 4) { mprDestroyVar(¤t); current = mprCreateNullVar(); saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else { this->pos++; } break; case json_tokener_state_comment_start: if(c == '*') { state = json_tokener_state_comment; } else if(c == '/') { state = json_tokener_state_comment_eol; } else { err = json_tokener_error_parse_comment; goto out; } this->pos++; break; case json_tokener_state_comment: if(c == '*') state = json_tokener_state_comment_end; this->pos++; break; case json_tokener_state_comment_eol: if(c == '\n') { state = json_tokener_state_eatws; } this->pos++; break; case json_tokener_state_comment_end: if(c == '/') { state = json_tokener_state_eatws; } else { state = json_tokener_state_comment; } this->pos++; break; case json_tokener_state_string: if(c == quote_char) { if ((this->pb = append_string( this->ctx, this->pb, this->source + start_offset, this->pos - start_offset)) == NULL) { err = json_tokener_error_oom; goto out; } current = mprString(this->pb); saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else if(c == '\\') { saved_state = json_tokener_state_string; state = json_tokener_state_string_escape; } this->pos++; break; case json_tokener_state_string_escape: switch(c) { case '"': case '\\': if ((this->pb = append_string( this->ctx, this->pb, this->source + start_offset, this->pos - start_offset - 1)) == NULL) { err = json_tokener_error_oom; goto out; } start_offset = this->pos++; state = saved_state; break; case 'b': case 'n': case 'r': case 't': if ((this->pb = append_string( this->ctx, this->pb, this->source + start_offset, this->pos - start_offset - 1)) == NULL) { err = json_tokener_error_oom; goto out; } if (c == 'b') { /* * second param to append_string() * gets temporarily modified; can't * pass string constant. */ char buf[] = "\b"; if ((this->pb = append_string( this->ctx, this->pb, buf, 1)) == NULL) { err = json_tokener_error_oom; goto out; } } else if (c == 'n') { char buf[] = "\n"; if ((this->pb = append_string( this->ctx, this->pb, buf, 1)) == NULL) { err = json_tokener_error_oom; goto out; } } else if (c == 'r') { char buf[] = "\r"; if ((this->pb = append_string( this->ctx, this->pb, buf, 1)) == NULL) { err = json_tokener_error_oom; goto out; } } else if (c == 't') { char buf[] = "\t"; if ((this->pb = append_string( this->ctx, this->pb, buf, 1)) == NULL) { err = json_tokener_error_oom; goto out; } } start_offset = ++this->pos; state = saved_state; break; case 'u': if ((this->pb = append_string( this->ctx, this->pb, this->source + start_offset, this->pos - start_offset - 1)) == NULL) { err = json_tokener_error_oom; goto out; } start_offset = ++this->pos; state = json_tokener_state_escape_unicode; break; default: err = json_tokener_error_parse_string; goto out; } break; case json_tokener_state_escape_unicode: if(strchr(json_hex_chars, c)) { this->pos++; if(this->pos - start_offset == 4) { unsigned char utf_out[3]; unsigned int ucs_char = (hexdigit(*(this->source + start_offset)) << 12) + (hexdigit(*(this->source + start_offset + 1)) << 8) + (hexdigit(*(this->source + start_offset + 2)) << 4) + hexdigit(*(this->source + start_offset + 3)); if (ucs_char < 0x80) { utf_out[0] = ucs_char; if ((this->pb = append_string( this->ctx, this->pb, (char *) utf_out, 1)) == NULL) { err = json_tokener_error_oom; goto out; } } else if (ucs_char < 0x800) { utf_out[0] = 0xc0 | (ucs_char >> 6); utf_out[1] = 0x80 | (ucs_char & 0x3f); if ((this->pb = append_string( this->ctx, this->pb, (char *) utf_out, 2)) == NULL) { err = json_tokener_error_oom; goto out; } } else { utf_out[0] = 0xe0 | (ucs_char >> 12); utf_out[1] = 0x80 | ((ucs_char >> 6) & 0x3f); utf_out[2] = 0x80 | (ucs_char & 0x3f); if ((this->pb = append_string( this->ctx, this->pb, (char *) utf_out, 3)) == NULL) { err = json_tokener_error_oom; goto out; } } start_offset = this->pos; state = saved_state; } } else { err = json_tokener_error_parse_string; goto out; } break; case json_tokener_state_boolean: if(strncasecmp("true", this->source + start_offset, this->pos - start_offset) == 0) { if(this->pos - start_offset == 4) { current = mprCreateBoolVar(1); saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else { this->pos++; } } else if(strncasecmp("false", this->source + start_offset, this->pos - start_offset) == 0) { if(this->pos - start_offset == 5) { current = mprCreateBoolVar(0); saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else { this->pos++; } } else { err = json_tokener_error_parse_boolean; goto out; } break; case json_tokener_state_number: if(!c || !strchr(json_number_chars, c)) { int numi; double numd; char *tmp = talloc_strndup( this->ctx, this->source + start_offset, this->pos - start_offset); if (tmp == NULL) { err = json_tokener_error_oom; goto out; } if(!deemed_double && sscanf(tmp, "%d", &numi) == 1) { current = mprCreateIntegerVar(numi); } else if(deemed_double && sscanf(tmp, "%lf", &numd) == 1) { current = mprCreateFloatVar(numd); } else { talloc_free(tmp); err = json_tokener_error_parse_number; goto out; } talloc_free(tmp); saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else { if(c == '.' || c == 'e') deemed_double = 1; this->pos++; } break; case json_tokener_state_array: if(c == ']') { this->pos++; saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else { int oldlen; char idx[16]; obj = json_tokener_do_parse(this, &err); if (err != json_tokener_success) { goto out; } oldlen = mprToInt(mprGetProperty(¤t, "length", NULL)); mprItoa(oldlen, idx, sizeof(idx)); mprSetVar(¤t, idx, obj); saved_state = json_tokener_state_array_sep; state = json_tokener_state_eatws; } break; case json_tokener_state_array_sep: if(c == ']') { this->pos++; saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else if(c == ',') { this->pos++; saved_state = json_tokener_state_array; state = json_tokener_state_eatws; } else { *err_p = json_tokener_error_parse_array; mprDestroyVar(¤t); return mprCreateUndefinedVar(); } break; case json_tokener_state_object: state = json_tokener_state_object_field_start; start_offset = this->pos; break; case json_tokener_state_object_field_start: if(c == '}') { this->pos++; saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else if (c == '"' || c == '\'') { quote_char = c; talloc_free(this->pb); this->pb = talloc_zero_size(this->ctx, 1); if (this->pb == NULL) { *err_p = json_tokener_error_oom; goto out; } state = json_tokener_state_object_field; start_offset = ++this->pos; } else { err = json_tokener_error_parse_object; goto out; } break; case json_tokener_state_object_field: if(c == quote_char) { if ((this->pb = append_string( this->ctx, this->pb, this->source + start_offset, this->pos - start_offset)) == NULL) { err = json_tokener_error_oom; goto out; } if ((obj_field_name = talloc_strdup(this->ctx, this->pb)) == NULL) { err = json_tokener_error_oom; goto out; } saved_state = json_tokener_state_object_field_end; state = json_tokener_state_eatws; } else if(c == '\\') { saved_state = json_tokener_state_object_field; state = json_tokener_state_string_escape; } this->pos++; break; case json_tokener_state_object_field_end: if(c == ':') { this->pos++; saved_state = json_tokener_state_object_value; state = json_tokener_state_eatws; } else { *err_p = json_tokener_error_parse_object; mprDestroyVar(¤t); return mprCreateUndefinedVar(); } break; case json_tokener_state_object_value: obj = json_tokener_do_parse(this, &err); if (err != json_tokener_success) { goto out; } mprSetVar(¤t, obj_field_name, obj); talloc_free(obj_field_name); obj_field_name = NULL; saved_state = json_tokener_state_object_sep; state = json_tokener_state_eatws; break; case json_tokener_state_object_sep: if(c == '}') { this->pos++; saved_state = json_tokener_state_finish; state = json_tokener_state_eatws; } else if(c == ',') { this->pos++; saved_state = json_tokener_state_object; state = json_tokener_state_eatws; } else { err = json_tokener_error_parse_object; goto out; } break; } } while(c); if(state != json_tokener_state_finish && saved_state != json_tokener_state_finish) err = json_tokener_error_parse_eof; out: talloc_free(obj_field_name); if(err == json_tokener_success) { return current; } mprDestroyVar(¤t); *err_p = err; return mprCreateUndefinedVar(); } void smb_setup_ejs_literal(void) { ejsDefineStringCFunction(-1, "literal_to_var", literal_to_var, NULL, MPR_VAR_SCRIPT_HANDLE); }