#include "common/str_util.h"
#include "frozen.h"
#include "mjs_array.h"
#include "mjs_internal.h"
#include "mjs_conversion.h"
#include "mjs_core.h"
#include "mjs_object.h"
#include "mjs_primitive.h"
#include "mjs_string.h"
#define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0)
static int should_skip_for_json(enum mjs_type type) {
int ret;
switch (type) {
case MJS_TYPE_NULL:
case MJS_TYPE_BOOLEAN:
case MJS_TYPE_NUMBER:
case MJS_TYPE_STRING:
case MJS_TYPE_OBJECT_GENERIC:
case MJS_TYPE_OBJECT_ARRAY:
ret = 0;
break;
default:
ret = 1;
break;
}
return ret;
}
static const char *hex_digits = "0123456789abcdef";
static char *append_hex(char *buf, char *limit, uint8_t c) {
if (buf < limit) *buf++ = 'u';
if (buf < limit) *buf++ = '0';
if (buf < limit) *buf++ = '0';
if (buf < limit) *buf++ = hex_digits[(int) ((c >> 4) % 0xf)];
if (buf < limit) *buf++ = hex_digits[(int) (c & 0xf)];
return buf;
}
static int snquote(char *buf, size_t size, const char *s, size_t len) {
char *limit = buf + size;
const char *end;
const char *specials = "btnvfr";
size_t i = 0;
i++;
if (buf < limit) *buf++ = '"';
for (end = s + len; s < end; s++) {
if (*s == '"' || *s == '\\') {
i++;
if (buf < limit) *buf++ = '\\';
} else if (*s >= '\b' && *s <= '\r') {
i += 2;
if (buf < limit) *buf++ = '\\';
if (buf < limit) *buf++ = specials[*s - '\b'];
continue;
} else if ((unsigned char) *s < '\b' || (*s > '\r' && *s < ' ')) {
i += 6 ;
if (buf < limit) *buf++ = '\\';
buf = append_hex(buf, limit, (uint8_t) *s);
continue;
}
i++;
if (buf < limit) *buf++ = *s;
}
i++;
if (buf < limit) *buf++ = '"';
if (buf < limit) {
*buf = '\0';
} else if (size != 0) {
*(buf - 1) = '\0';
}
return i;
}
MJS_PRIVATE mjs_err_t to_json_or_debug(struct mjs *mjs, mjs_val_t v, char *buf,
size_t size, size_t *res_len,
uint8_t is_debug) {
mjs_val_t el;
char *vp;
mjs_err_t rcode = MJS_OK;
size_t len = 0;
if (size > 0) *buf = '\0';
if (!is_debug && should_skip_for_json(mjs_get_type(v))) {
goto clean;
}
for (vp = mjs->json_visited_stack.buf;
vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len;
vp += sizeof(mjs_val_t)) {
if (*(mjs_val_t *) vp == v) {
strncpy(buf, "[Circular]", size);
len = 10;
goto clean;
}
}
switch (mjs_get_type(v)) {
case MJS_TYPE_NULL:
case MJS_TYPE_BOOLEAN:
case MJS_TYPE_NUMBER:
case MJS_TYPE_UNDEFINED:
case MJS_TYPE_FOREIGN:
{
char *p = NULL;
int need_free = 0;
rcode = mjs_to_string(mjs, &v, &p, &len, &need_free);
c_snprintf(buf, size, "%.*s", (int) len, p);
if (need_free) {
free(p);
}
}
goto clean;
case MJS_TYPE_STRING: {
size_t n;
const char *str = mjs_get_string(mjs, &v, &n);
len = snquote(buf, size, str, n);
goto clean;
}
case MJS_TYPE_OBJECT_FUNCTION:
case MJS_TYPE_OBJECT_GENERIC: {
char *b = buf;
struct mjs_property *prop = NULL;
struct mjs_object *o = NULL;
mbuf_append(&mjs->json_visited_stack, (char *) &v, sizeof(v));
b += c_snprintf(b, BUF_LEFT(size, b - buf), "{");
o = get_object_struct(v);
for (prop = o->properties; prop != NULL; prop = prop->next) {
size_t n;
const char *s;
if (!is_debug && should_skip_for_json(mjs_get_type(prop->value))) {
continue;
}
if (b - buf != 1) {
b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
}
s = mjs_get_string(mjs, &prop->name, &n);
b += c_snprintf(b, BUF_LEFT(size, b - buf), "\"%.*s\":", (int) n, s);
{
size_t tmp = 0;
rcode = to_json_or_debug(mjs, prop->value, b, BUF_LEFT(size, b - buf),
&tmp, is_debug);
if (rcode != MJS_OK) {
goto clean_iter;
}
b += tmp;
}
}
b += c_snprintf(b, BUF_LEFT(size, b - buf), "}");
mjs->json_visited_stack.len -= sizeof(v);
clean_iter:
len = b - buf;
goto clean;
}
case MJS_TYPE_OBJECT_ARRAY: {
int has;
char *b = buf;
size_t i, alen = mjs_array_length(mjs, v);
mbuf_append(&mjs->json_visited_stack, (char *) &v, sizeof(v));
b += c_snprintf(b, BUF_LEFT(size, b - buf), "[");
for (i = 0; i < alen; i++) {
el = mjs_array_get2(mjs, v, i, &has);
if (has) {
size_t tmp = 0;
if (!is_debug && should_skip_for_json(mjs_get_type(el))) {
b += c_snprintf(b, BUF_LEFT(size, b - buf), "null");
} else {
rcode = to_json_or_debug(mjs, el, b, BUF_LEFT(size, b - buf), &tmp,
is_debug);
if (rcode != MJS_OK) {
goto clean;
}
}
b += tmp;
} else {
b += c_snprintf(b, BUF_LEFT(size, b - buf), "null");
}
if (i != alen - 1) {
b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
}
}
b += c_snprintf(b, BUF_LEFT(size, b - buf), "]");
mjs->json_visited_stack.len -= sizeof(v);
len = b - buf;
goto clean;
}
case MJS_TYPES_CNT:
abort();
}
abort();
len = 0;
goto clean;
clean:
if (rcode != MJS_OK) {
len = 0;
}
if (res_len != NULL) {
*res_len = len;
}
return rcode;
}
MJS_PRIVATE mjs_err_t mjs_json_stringify(struct mjs *mjs, mjs_val_t v,
char *buf, size_t size, char **res) {
mjs_err_t rcode = MJS_OK;
char *p = buf;
size_t len;
to_json_or_debug(mjs, v, buf, size, &len, 0);
if (len >= size) {
p = (char *) malloc(len + 1);
rcode = mjs_json_stringify(mjs, v, p, len + 1, res);
assert(*res == p);
goto clean;
} else {
*res = p;
goto clean;
}
clean:
if (rcode != MJS_OK && p != buf) {
free(p);
}
return rcode;
}
struct json_parse_frame {
mjs_val_t val;
struct json_parse_frame *up;
};
struct json_parse_ctx {
struct mjs *mjs;
mjs_val_t result;
struct json_parse_frame *frame;
enum mjs_err rcode;
};
static struct json_parse_frame *alloc_json_frame(struct json_parse_ctx *ctx,
mjs_val_t v) {
struct json_parse_frame *frame =
(struct json_parse_frame *) calloc(sizeof(struct json_parse_frame), 1);
frame->val = v;
mjs_own(ctx->mjs, &frame->val);
return frame;
}
static struct json_parse_frame *free_json_frame(
struct json_parse_ctx *ctx, struct json_parse_frame *frame) {
struct json_parse_frame *up = frame->up;
mjs_disown(ctx->mjs, &frame->val);
free(frame);
return up;
}
static void frozen_cb(void *data, const char *name, size_t name_len,
const char *path, const struct json_token *token) {
struct json_parse_ctx *ctx = (struct json_parse_ctx *) data;
mjs_val_t v = MJS_UNDEFINED;
(void) path;
mjs_own(ctx->mjs, &v);
switch (token->type) {
case JSON_TYPE_STRING: {
char *dst;
if (token->len > 0 && (dst = malloc(token->len)) != NULL) {
int len = json_unescape(token->ptr, token->len, dst, token->len);
if (len < 0) {
mjs_prepend_errorf(ctx->mjs, MJS_TYPE_ERROR, "invalid JSON string");
break;
}
v = mjs_mk_string(ctx->mjs, dst, len, 1 );
free(dst);
} else {
v = mjs_mk_string(ctx->mjs, "", 0, 1 );
}
break;
}
case JSON_TYPE_NUMBER:
v = mjs_mk_number(ctx->mjs, strtod(token->ptr, NULL));
break;
case JSON_TYPE_TRUE:
v = mjs_mk_boolean(ctx->mjs, 1);
break;
case JSON_TYPE_FALSE:
v = mjs_mk_boolean(ctx->mjs, 0);
break;
case JSON_TYPE_NULL:
v = MJS_NULL;
break;
case JSON_TYPE_OBJECT_START:
v = mjs_mk_object(ctx->mjs);
break;
case JSON_TYPE_ARRAY_START:
v = mjs_mk_array(ctx->mjs);
break;
case JSON_TYPE_OBJECT_END:
case JSON_TYPE_ARRAY_END: {
ctx->frame = free_json_frame(ctx, ctx->frame);
} break;
default:
LOG(LL_ERROR, ("Wrong token type %d\n", token->type));
break;
}
if (!mjs_is_undefined(v)) {
if (name != NULL && name_len != 0) {
if (mjs_is_object(ctx->frame->val)) {
mjs_set(ctx->mjs, ctx->frame->val, name, name_len, v);
} else if (mjs_is_array(ctx->frame->val)) {
int idx = (int) strtod(name, NULL);
mjs_array_set(ctx->mjs, ctx->frame->val, idx, v);
} else {
LOG(LL_ERROR, ("Current value is neither object nor array\n"));
}
} else {
assert(ctx->frame == NULL);
ctx->result = v;
}
if (token->type == JSON_TYPE_OBJECT_START ||
token->type == JSON_TYPE_ARRAY_START) {
struct json_parse_frame *new_frame = alloc_json_frame(ctx, v);
new_frame->up = ctx->frame;
ctx->frame = new_frame;
}
}
mjs_disown(ctx->mjs, &v);
}
MJS_PRIVATE mjs_err_t
mjs_json_parse(struct mjs *mjs, const char *str, size_t len, mjs_val_t *res) {
struct json_parse_ctx *ctx =
(struct json_parse_ctx *) calloc(sizeof(struct json_parse_ctx), 1);
int json_res;
enum mjs_err rcode = MJS_OK;
ctx->mjs = mjs;
ctx->result = MJS_UNDEFINED;
ctx->frame = NULL;
ctx->rcode = MJS_OK;
mjs_own(mjs, &ctx->result);
{
char *stmp = malloc(len);
memcpy(stmp, str, len);
json_res = json_walk(stmp, len, frozen_cb, ctx);
free(stmp);
stmp = NULL;
str = NULL;
}
if (ctx->rcode != MJS_OK) {
rcode = ctx->rcode;
mjs_prepend_errorf(mjs, rcode, "invalid JSON string");
} else if (json_res < 0) {
rcode = MJS_TYPE_ERROR;
mjs_prepend_errorf(mjs, rcode, "invalid JSON string");
} else {
*res = ctx->result;
assert(ctx->frame == NULL);
}
if (rcode != MJS_OK) {
while (ctx->frame != NULL) {
ctx->frame = free_json_frame(ctx, ctx->frame);
}
}
mjs_disown(mjs, &ctx->result);
free(ctx);
return rcode;
}
MJS_PRIVATE void mjs_op_json_stringify(struct mjs *mjs) {
mjs_val_t ret = MJS_UNDEFINED;
mjs_val_t val = mjs_arg(mjs, 0);
if (mjs_nargs(mjs) < 1) {
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing a value to stringify");
} else {
char *p = NULL;
if (mjs_json_stringify(mjs, val, NULL, 0, &p) == MJS_OK) {
ret = mjs_mk_string(mjs, p, ~0, 1 );
free(p);
}
}
mjs_return(mjs, ret);
}
MJS_PRIVATE void mjs_op_json_parse(struct mjs *mjs) {
mjs_val_t ret = MJS_UNDEFINED;
mjs_val_t arg0 = mjs_arg(mjs, 0);
if (mjs_is_string(arg0)) {
size_t len;
const char *str = mjs_get_string(mjs, &arg0, &len);
mjs_json_parse(mjs, str, len, &ret);
} else {
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "string argument required");
}
mjs_return(mjs, ret);
}