/* $Id$ */ /* * Copyright (C) 2013 Teluu Inc. (http://www.teluu.com) * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ #include #include #include #include #include #include #include #define EL_INIT(p_el, nm, typ) do { \ if (nm) { \ p_el->name = *nm; \ } else { \ p_el->name.ptr = (char*)""; \ p_el->name.slen = 0; \ } \ p_el->type = typ; \ } while (0) struct write_state; struct parse_state; #define NO_NAME 1 static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags); static pj_json_elem* parse_elem_throw(struct parse_state *st, pj_json_elem *elem); PJ_DEF(void) pj_json_elem_null(pj_json_elem *el, pj_str_t *name) { EL_INIT(el, name, PJ_JSON_VAL_NULL); } PJ_DEF(void) pj_json_elem_bool(pj_json_elem *el, pj_str_t *name, pj_bool_t val) { EL_INIT(el, name, PJ_JSON_VAL_BOOL); el->value.is_true = val; } PJ_DEF(void) pj_json_elem_number(pj_json_elem *el, pj_str_t *name, float val) { EL_INIT(el, name, PJ_JSON_VAL_NUMBER); el->value.num = val; } PJ_DEF(void) pj_json_elem_string( pj_json_elem *el, pj_str_t *name, pj_str_t *value) { EL_INIT(el, name, PJ_JSON_VAL_STRING); el->value.str = *value; } PJ_DEF(void) pj_json_elem_array(pj_json_elem *el, pj_str_t *name) { EL_INIT(el, name, PJ_JSON_VAL_ARRAY); pj_list_init(&el->value.children); } PJ_DEF(void) pj_json_elem_obj(pj_json_elem *el, pj_str_t *name) { EL_INIT(el, name, PJ_JSON_VAL_OBJ); pj_list_init(&el->value.children); } PJ_DEF(void) pj_json_elem_add(pj_json_elem *el, pj_json_elem *child) { pj_assert(el->type == PJ_JSON_VAL_OBJ || el->type == PJ_JSON_VAL_ARRAY); pj_list_push_back(&el->value.children, child); } struct parse_state { pj_pool_t *pool; pj_scanner scanner; pj_json_err_info *err_info; pj_cis_t float_spec; /* numbers with dot! */ }; static pj_status_t parse_children(struct parse_state *st, pj_json_elem *parent) { char end_quote = (parent->type == PJ_JSON_VAL_ARRAY)? ']' : '}'; pj_scan_get_char(&st->scanner); while (*st->scanner.curptr != end_quote) { pj_json_elem *child; while (*st->scanner.curptr == ',') pj_scan_get_char(&st->scanner); if (*st->scanner.curptr == end_quote) break; child = parse_elem_throw(st, NULL); if (!child) return PJLIB_UTIL_EINJSON; pj_json_elem_add(parent, child); } pj_scan_get_char(&st->scanner); return PJ_SUCCESS; } /* Return 0 if success or the index of the invalid char in the string */ static unsigned parse_quoted_string(struct parse_state *st, pj_str_t *output) { pj_str_t token; char *op, *ip, *iend; pj_scan_get_quote(&st->scanner, '"', '"', &token); /* Remove the quote characters */ token.ptr++; token.slen-=2; if (pj_strchr(&token, '\\') == NULL) { *output = token; return 0; } output->ptr = op = pj_pool_alloc(st->pool, token.slen); ip = token.ptr; iend = token.ptr + token.slen; while (ip != iend) { if (*ip == '\\') { ++ip; if (ip==iend) { goto on_error; } if (*ip == 'u') { ip++; if (iend - ip < 4) { ip = iend -1; goto on_error; } /* Only use the last two hext digits because we're on * ASCII */ *op++ = (char)(pj_hex_digit_to_val(ip[2]) * 16 + pj_hex_digit_to_val(ip[3])); ip += 4; } else if (*ip=='"' || *ip=='\\' || *ip=='/') { *op++ = *ip++; } else if (*ip=='b') { *op++ = '\b'; ip++; } else if (*ip=='f') { *op++ = '\f'; ip++; } else if (*ip=='n') { *op++ = '\n'; ip++; } else if (*ip=='r') { *op++ = '\r'; ip++; } else if (*ip=='t') { *op++ = '\t'; ip++; } else { goto on_error; } } else { *op++ = *ip++; } } output->slen = op - output->ptr; return 0; on_error: output->slen = op - output->ptr; return (unsigned)(ip - token.ptr); } static pj_json_elem* parse_elem_throw(struct parse_state *st, pj_json_elem *elem) { pj_str_t name = {NULL, 0}, value = {NULL, 0}; pj_str_t token; if (!elem) elem = pj_pool_alloc(st->pool, sizeof(*elem)); /* Parse name */ if (*st->scanner.curptr == '"') { pj_scan_get_char(&st->scanner); pj_scan_get_until_ch(&st->scanner, '"', &token); pj_scan_get_char(&st->scanner); if (*st->scanner.curptr == ':') { pj_scan_get_char(&st->scanner); name = token; } else { value = token; } } if (value.slen) { /* Element with string value and no name */ pj_json_elem_string(elem, &name, &value); return elem; } /* Parse value */ if (pj_cis_match(&st->float_spec, *st->scanner.curptr) || *st->scanner.curptr == '-') { float val; pj_bool_t neg = PJ_FALSE; if (*st->scanner.curptr == '-') { pj_scan_get_char(&st->scanner); neg = PJ_TRUE; } pj_scan_get(&st->scanner, &st->float_spec, &token); val = pj_strtof(&token); if (neg) val = -val; pj_json_elem_number(elem, &name, val); } else if (*st->scanner.curptr == '"') { unsigned err; char *start = st->scanner.curptr; err = parse_quoted_string(st, &token); if (err) { st->scanner.curptr = start + err; return NULL; } pj_json_elem_string(elem, &name, &token); } else if (pj_isalpha(*st->scanner.curptr)) { if (pj_scan_strcmp(&st->scanner, "false", 5)==0) { pj_json_elem_bool(elem, &name, PJ_FALSE); pj_scan_advance_n(&st->scanner, 5, PJ_TRUE); } else if (pj_scan_strcmp(&st->scanner, "true", 4)==0) { pj_json_elem_bool(elem, &name, PJ_TRUE); pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); } else if (pj_scan_strcmp(&st->scanner, "null", 4)==0) { pj_json_elem_null(elem, &name); pj_scan_advance_n(&st->scanner, 4, PJ_TRUE); } else { return NULL; } } else if (*st->scanner.curptr == '[') { pj_json_elem_array(elem, &name); if (parse_children(st, elem) != PJ_SUCCESS) return NULL; } else if (*st->scanner.curptr == '{') { pj_json_elem_obj(elem, &name); if (parse_children(st, elem) != PJ_SUCCESS) return NULL; } else { return NULL; } return elem; } static void on_syntax_error(pj_scanner *scanner) { PJ_UNUSED_ARG(scanner); PJ_THROW(11); } PJ_DEF(pj_json_elem*) pj_json_parse(pj_pool_t *pool, char *buffer, unsigned *size, pj_json_err_info *err_info) { pj_cis_buf_t cis_buf; struct parse_state st; pj_json_elem *root; PJ_USE_EXCEPTION; PJ_ASSERT_RETURN(pool && buffer && size, NULL); if (!*size) return NULL; pj_bzero(&st, sizeof(st)); st.pool = pool; st.err_info = err_info; pj_scan_init(&st.scanner, buffer, *size, PJ_SCAN_AUTOSKIP_WS | PJ_SCAN_AUTOSKIP_NEWLINE, &on_syntax_error); pj_cis_buf_init(&cis_buf); pj_cis_init(&cis_buf, &st.float_spec); pj_cis_add_str(&st.float_spec, ".0123456789"); PJ_TRY { root = parse_elem_throw(&st, NULL); } PJ_CATCH_ANY { root = NULL; } PJ_END if (!root && err_info) { err_info->line = st.scanner.line; err_info->col = pj_scan_get_col(&st.scanner) + 1; err_info->err_char = *st.scanner.curptr; } *size = (unsigned)((buffer + *size) - st.scanner.curptr); pj_scan_fini(&st.scanner); return root; } struct buf_writer_data { char *pos; unsigned size; }; static pj_status_t buf_writer(const char *s, unsigned size, void *user_data) { struct buf_writer_data *buf_data = (struct buf_writer_data*)user_data; if (size+1 >= buf_data->size) return PJ_ETOOBIG; pj_memcpy(buf_data->pos, s, size); buf_data->pos += size; buf_data->size -= size; return PJ_SUCCESS; } PJ_DEF(pj_status_t) pj_json_write(const pj_json_elem *elem, char *buffer, unsigned *size) { struct buf_writer_data buf_data; pj_status_t status; PJ_ASSERT_RETURN(elem && buffer && size, PJ_EINVAL); buf_data.pos = buffer; buf_data.size = *size; status = pj_json_writef(elem, &buf_writer, &buf_data); if (status != PJ_SUCCESS) return status; *buf_data.pos = '\0'; *size = (unsigned)(buf_data.pos - buffer); return PJ_SUCCESS; } #define MAX_INDENT 100 #ifndef PJ_JSON_NAME_MIN_LEN # define PJ_JSON_NAME_MIN_LEN 20 #endif #define ESC_BUF_LEN 64 #ifndef PJ_JSON_INDENT_SIZE # define PJ_JSON_INDENT_SIZE 3 #endif struct write_state { pj_json_writer writer; void *user_data; char indent_buf[MAX_INDENT]; int indent; char space[PJ_JSON_NAME_MIN_LEN]; }; #define CHECK(expr) do { \ status=expr; if (status!=PJ_SUCCESS) return status; } \ while (0) static pj_status_t write_string_escaped(const pj_str_t *value, struct write_state *st) { const char *ip = value->ptr; const char *iend = value->ptr + value->slen; char buf[ESC_BUF_LEN]; char *op = buf; char *oend = buf + ESC_BUF_LEN; pj_status_t status; while (ip != iend) { /* Write to buffer to speedup writing instead of calling * the callback one by one for each character. */ while (ip != iend && op != oend) { if (oend - op < 2) break; if (*ip == '"') { *op++ = '\\'; *op++ = '"'; ip++; } else if (*ip == '\\') { *op++ = '\\'; *op++ = '\\'; ip++; } else if (*ip == '/') { *op++ = '\\'; *op++ = '/'; ip++; } else if (*ip == '\b') { *op++ = '\\'; *op++ = 'b'; ip++; } else if (*ip == '\f') { *op++ = '\\'; *op++ = 'f'; ip++; } else if (*ip == '\n') { *op++ = '\\'; *op++ = 'n'; ip++; } else if (*ip == '\r') { *op++ = '\\'; *op++ = 'r'; ip++; } else if (*ip == '\t') { *op++ = '\\'; *op++ = 't'; ip++; } else if ((*ip >= 32 && *ip < 127)) { /* unescaped */ *op++ = *ip++; } else { /* escaped */ if (oend - op < 6) break; *op++ = '\\'; *op++ = 'u'; *op++ = '0'; *op++ = '0'; pj_val_to_hex_digit(*ip, op); op+=2; ip++; } } CHECK( st->writer( buf, (unsigned)(op-buf), st->user_data) ); op = buf; } return PJ_SUCCESS; } static pj_status_t write_children(const pj_json_list *list, const char quotes[2], struct write_state *st) { unsigned flags = (quotes[0]=='[') ? NO_NAME : 0; pj_status_t status; //CHECK( st->writer( st->indent_buf, st->indent, st->user_data) ); CHECK( st->writer( "es[0], 1, st->user_data) ); CHECK( st->writer( " ", 1, st->user_data) ); if (!pj_list_empty(list)) { pj_bool_t indent_added = PJ_FALSE; pj_json_elem *child = list->next; if (child->name.slen == 0) { /* Simple list */ while (child != (pj_json_elem*)list) { status = elem_write(child, st, flags); if (status != PJ_SUCCESS) return status; if (child->next != (pj_json_elem*)list) CHECK( st->writer( ", ", 2, st->user_data) ); child = child->next; } } else { if (st->indent < sizeof(st->indent_buf)) { st->indent += PJ_JSON_INDENT_SIZE; indent_added = PJ_TRUE; } CHECK( st->writer( "\n", 1, st->user_data) ); while (child != (pj_json_elem*)list) { status = elem_write(child, st, flags); if (status != PJ_SUCCESS) return status; if (child->next != (pj_json_elem*)list) CHECK( st->writer( ",\n", 2, st->user_data) ); else CHECK( st->writer( "\n", 1, st->user_data) ); child = child->next; } if (indent_added) { st->indent -= PJ_JSON_INDENT_SIZE; } CHECK( st->writer( st->indent_buf, st->indent, st->user_data) ); } } CHECK( st->writer( "es[1], 1, st->user_data) ); return PJ_SUCCESS; } static pj_status_t elem_write(const pj_json_elem *elem, struct write_state *st, unsigned flags) { pj_status_t status; if (elem->name.slen) { CHECK( st->writer( st->indent_buf, st->indent, st->user_data) ); if ((flags & NO_NAME)==0) { CHECK( st->writer( "\"", 1, st->user_data) ); CHECK( write_string_escaped(&elem->name, st) ); CHECK( st->writer( "\": ", 3, st->user_data) ); if (elem->name.slen < PJ_JSON_NAME_MIN_LEN /*&& elem->type != PJ_JSON_VAL_OBJ && elem->type != PJ_JSON_VAL_ARRAY*/) { CHECK( st->writer( st->space, (unsigned)(PJ_JSON_NAME_MIN_LEN - elem->name.slen), st->user_data) ); } } } switch (elem->type) { case PJ_JSON_VAL_NULL: CHECK( st->writer( "null", 4, st->user_data) ); break; case PJ_JSON_VAL_BOOL: if (elem->value.is_true) CHECK( st->writer( "true", 4, st->user_data) ); else CHECK( st->writer( "false", 5, st->user_data) ); break; case PJ_JSON_VAL_NUMBER: { char num_buf[65]; int len; if (elem->value.num == (int)elem->value.num) len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%d", (int)elem->value.num); else len = pj_ansi_snprintf(num_buf, sizeof(num_buf), "%f", elem->value.num); if (len < 0 || len >= sizeof(num_buf)) return PJ_ETOOBIG; CHECK( st->writer( num_buf, len, st->user_data) ); } break; case PJ_JSON_VAL_STRING: CHECK( st->writer( "\"", 1, st->user_data) ); CHECK( write_string_escaped( &elem->value.str, st) ); CHECK( st->writer( "\"", 1, st->user_data) ); break; case PJ_JSON_VAL_ARRAY: CHECK( write_children(&elem->value.children, "[]", st) ); break; case PJ_JSON_VAL_OBJ: CHECK( write_children(&elem->value.children, "{}", st) ); break; default: pj_assert(!"Unhandled value type"); } return PJ_SUCCESS; } #undef CHECK PJ_DEF(pj_status_t) pj_json_writef( const pj_json_elem *elem, pj_json_writer writer, void *user_data) { struct write_state st; PJ_ASSERT_RETURN(elem && writer, PJ_EINVAL); st.writer = writer; st.user_data = user_data; st.indent = 0; pj_memset(st.indent_buf, ' ', MAX_INDENT); pj_memset(st.space, ' ', PJ_JSON_NAME_MIN_LEN); return elem_write(elem, &st, 0); }