/** * @file parser_json.c * @author Radek Krejci * @brief JSON data parser for libyang * * Copyright (c) 2015 CESNET, z.s.p.o. * * This source code is licensed under BSD 3-Clause License (the "License"). * You may not use this file except in compliance with the License. * You may obtain a copy of the License at * * https://opensource.org/licenses/BSD-3-Clause */ #define _GNU_SOURCE #include #include #include #include #include #include #include "libyang.h" #include "common.h" #include "context.h" #include "parser.h" #include "printer.h" #include "tree_internal.h" #include "validation.h" #include "xml_internal.h" static int lyjson_isspace(int c) { switch(c) { case 0x20: /* space */ case 0x09: /* horizontal tab */ case 0x0a: /* line feed or new line */ case 0x0d: /* carriage return */ return 1; default: return 0; } } static unsigned int skip_ws(const char *data) { unsigned int len = 0; /* skip leading whitespaces */ while (data[len] && lyjson_isspace(data[len])) { len++; } return len; } static char * lyjson_parse_text(struct ly_ctx *ctx, const char *data, unsigned int *len) { #define BUFSIZE 1024 char buf[BUFSIZE]; char *result = NULL, *aux; int o, size = 0; unsigned int r, i; int32_t value; for (*len = o = 0; data[*len] && data[*len] != '"'; o++) { if (o > BUFSIZE - 4) { /* add buffer into the result */ if (result) { size = size + o; aux = ly_realloc(result, size + 1); LY_CHECK_ERR_RETURN(!aux, LOGMEM(ctx), NULL); result = aux; } else { size = o; result = malloc((size + 1) * sizeof *result); LY_CHECK_ERR_RETURN(!result, LOGMEM(ctx), NULL); } memcpy(&result[size - o], buf, o); /* write again into the beginning of the buffer */ o = 0; } if (data[*len] == '\\') { /* parse escape sequence */ (*len)++; i = 1; switch (data[(*len)]) { case '"': /* quotation mark */ value = 0x22; break; case '\\': /* reverse solidus */ value = 0x5c; break; case '/': /* solidus */ value = 0x2f; break; case 'b': /* backspace */ value = 0x08; break; case 'f': /* form feed */ value = 0x0c; break; case 'n': /* line feed */ value = 0x0a; break; case 'r': /* carriage return */ value = 0x0d; break; case 't': /* tab */ value = 0x09; break; case 'u': /* Basic Multilingual Plane character \uXXXX */ (*len)++; for (value = i = 0; i < 4; i++) { if (isdigit(data[(*len) + i])) { r = (data[(*len) + i] - '0'); } else if (data[(*len) + i] > 'F') { r = 10 + (data[(*len) + i] - 'a'); } else { r = 10 + (data[(*len) + i] - 'A'); } value = (16 * value) + r; } break; default: /* invalid escape sequence */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "character escape sequence"); goto error; } r = pututf8(ctx, &buf[o], value); if (!r) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "character UTF8 character"); goto error; } o += r - 1; /* o is ++ in for loop */ (*len) += i; /* number of read characters */ } else if ((unsigned char)(data[*len]) < 0x20) { /* In C, char != unsigned char != signed char, so let's work with ASCII explicitly */ /* control characters must be escaped */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "control character (unescaped)"); goto error; } else { /* unescaped character */ r = copyutf8(ctx, &buf[o], &data[*len]); if (!r) { goto error; } o += r - 1; /* o is ++ in for loop */ (*len) += r; } } #undef BUFSIZE if (o) { if (result) { size = size + o; aux = ly_realloc(result, size + 1); LY_CHECK_ERR_RETURN(!aux, LOGMEM(ctx), NULL); result = aux; } else { size = o; result = malloc((size + 1) * sizeof *result); LY_CHECK_ERR_RETURN(!result, LOGMEM(ctx), NULL); } memcpy(&result[size - o], buf, o); } if (result) { result[size] = '\0'; } else { size = 0; result = strdup(""); LY_CHECK_ERR_RETURN(!result, LOGMEM(ctx), NULL); } return result; error: free(result); return NULL; } static unsigned int lyjson_parse_number(struct ly_ctx *ctx, const char *data) { unsigned int len = 0; if (data[len] == '-') { ++len; } if (data[len] == '0') { ++len; } else if (isdigit(data[len])) { ++len; while (isdigit(data[len])) { ++len; } } else { LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Invalid character in JSON Number value ('%c').", data[len]); return 0; } if (data[len] == '.') { ++len; if (!isdigit(data[len])) { if (data[len]) { LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Invalid character in JSON Number value ('%c').", data[len]); } else { LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Invalid character in JSON Number value (EOF)."); } return 0; } while (isdigit(data[len])) { ++len; } } if ((data[len] == 'e') || (data[len] == 'E')) { ++len; if ((data[len] == '+') || (data[len] == '-')) { ++len; } while (isdigit(data[len])) { ++len; } } if (data[len] && (data[len] != ',') && (data[len] != ']') && (data[len] != '}') && !lyjson_isspace(data[len])) { LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Invalid character in JSON Number value ('%c').", data[len]); return 0; } return len; } static char * lyjson_convert_enumber(struct ly_ctx *ctx, const char *number, unsigned int num_len, char *e_ptr) { char *ptr, *num; const char *number_ptr; long int e_val; int dot_pos, chars_to_dot, minus; unsigned int num_len_no_e; if (*number == '-') { minus = 1; ++number; --num_len; } else { minus = 0; } num_len_no_e = e_ptr - number; errno = 0; ++e_ptr; e_val = strtol(e_ptr, &ptr, 10); if (errno) { LOGVAL(ctx, LYE_SPEC, LY_VLOG_NONE, NULL, "Exponent out-of-bounds in a JSON Number value (%.*s).", num_len - (e_ptr - number), e_ptr); return NULL; } else if (ptr != number + num_len) { /* we checked this already */ LOGINT(ctx); return NULL; } if ((ptr = strnchr(number, '.', num_len_no_e))) { dot_pos = ptr - number; } else { dot_pos = num_len_no_e; } dot_pos += e_val; /* allocate enough memory */ if (dot_pos < 1) { /* (.XXX)XXX[.]XXXX */ num = malloc((minus ? 1 : 0) + -dot_pos + 2 + (num_len_no_e - (ptr ? 1 : 0)) + 1); } else if (dot_pos < (signed)num_len_no_e) { /* XXXX(.)XX.XXX */ num = malloc((minus ? 1 : 0) + num_len_no_e + (ptr ? 0 : 1) + 1); } else { /* XXX[.]XXXX(XXX.) */ num = malloc((minus ? 1 : 0) + (dot_pos - (ptr ? 2 : 1)) + 1); } LY_CHECK_ERR_RETURN(!num, LOGMEM(ctx), NULL); if (minus) { strcpy(num, "-"); } else { num[0] = '\0'; } if (dot_pos < 1) { strcat(num, "0."); } if (dot_pos < 0) { sprintf(num + strlen(num), "%0*d", -dot_pos, 0); } chars_to_dot = dot_pos; for (ptr = num + strlen(num), number_ptr = number; (unsigned)(number_ptr - number) < num_len_no_e; ) { if (!chars_to_dot) { *ptr = '.'; ++ptr; chars_to_dot = -1; } else if (isdigit(*number_ptr)) { *ptr = *number_ptr; ++ptr; ++number_ptr; if (chars_to_dot > 0) { --chars_to_dot; } } else if (*number_ptr == '.') { ++number_ptr; } else { LOGINT(ctx); free(num); return NULL; } } *ptr = '\0'; if (dot_pos > (signed)num_len_no_e) { sprintf(num + strlen(num), "%0*d", dot_pos - num_len_no_e, 0); } return num; } static unsigned int lyjson_parse_boolean(struct ly_ctx *ctx, const char *data) { unsigned int len = 0; if (!strncmp(data, "false", 5)) { len = 5; } else if (!strncmp(data, "true", 4)) { len = 4; } if (data[len] && data[len] != ',' && data[len] != ']' && data[len] != '}' && !lyjson_isspace(data[len])) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON literal value (expected true or false)"); return 0; } return len; } static unsigned int json_get_anydata(struct lyd_node_anydata *any, const char *data) { struct ly_ctx *ctx = any->schema->module->ctx; unsigned int len = 0, c = 0, skip = 0; char *str; if (data[len] == '"') { len = 1; str = lyjson_parse_text(ctx, &data[len], &c); if (!str) { return 0; } if (data[len + c] != '"') { free(str); LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, any, "JSON data (missing quotation-mark at the end of string)"); return 0; } any->value.str = lydict_insert_zc(ctx, str); any->value_type = LYD_ANYDATA_CONSTSTRING; return len + c + 1; } else if (data[len] != '{') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, any, "anydata/anyxml content (not an object nor string)"); return 0; } /* count opening '{' and closing '}' brackets to get the end of the object without its parsing */ c = len = 0; do { switch (data[len]) { case '{': if (!skip) { c++; } break; case '}': if (!skip) { c--; } break; case '\\': /* when parsing a string, ignore escaped quotes to not end it prematurely */ if (skip && data[len + 1]) { len++; } break; case '\"': skip = !skip; break; default: break; } len++; } while (data[len] && c); if (c) { LOGVAL(ctx, LYE_EOF, LY_VLOG_LYD, any); return 0; } any->value_type = LYD_ANYDATA_JSON; any->value.str = lydict_insert(ctx, data, len); return len; } static unsigned int json_get_value(struct lyd_node_leaf_list *leaf, struct lyd_node **first_sibling, const char *data, int options, struct unres_data *unres) { struct lyd_node_leaf_list *new; struct lys_type *stype; struct ly_ctx *ctx; unsigned int len = 0, r; char *str; assert(leaf && data); ctx = leaf->schema->module->ctx; stype = &((struct lys_node_leaf *)leaf->schema)->type; if (leaf->schema->nodetype == LYS_LEAFLIST) { /* expecting begin-array */ if (data[len++] != '[') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expected begin-array)"); return 0; } repeat: len += skip_ws(&data[len]); } /* will be changed in case of union */ leaf->value_type = stype->base; if (data[len] == '"') { if ((leaf->value_type == LY_TYPE_INT8) || (leaf->value_type == LY_TYPE_INT16) || (leaf->value_type == LY_TYPE_INT32) || (leaf->value_type == LY_TYPE_UINT8) || (leaf->value_type == LY_TYPE_UINT16) || (leaf->value_type == LY_TYPE_UINT32)) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expected number, but found string)"); return 0; } if (leaf->value_type == LY_TYPE_BOOL) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expected boolean, but found string)"); return 0; } /* string representations */ ++len; str = lyjson_parse_text(ctx, &data[len], &r); if (!str) { LOGPATH(ctx, LY_VLOG_LYD, leaf); return 0; } leaf->value_str = lydict_insert_zc(ctx, str); if (data[len + r] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (missing quotation-mark at the end of string)"); return 0; } len += r + 1; } else if (data[len] == '-' || isdigit(data[len])) { if ((leaf->value_type == LY_TYPE_INT64) || (leaf->value_type == LY_TYPE_UINT64) || (leaf->value_type == LY_TYPE_STRING)) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expected string, but found number)"); return 0; } /* numeric type */ r = lyjson_parse_number(ctx, &data[len]); if (!r) { LOGPATH(ctx, LY_VLOG_LYD, leaf); return 0; } /* if it's a number with 'e' or 'E', get rid of it first */ if ((str = strnchr(&data[len], 'e', r)) || (str = strnchr(&data[len], 'E', r))) { str = lyjson_convert_enumber(ctx, &data[len], r, str); if (!str) { return 0; } leaf->value_str = lydict_insert_zc(ctx, str); } else { leaf->value_str = lydict_insert(ctx, &data[len], r); } len += r; } else if (data[len] == 'f' || data[len] == 't') { if (leaf->value_type == LY_TYPE_STRING) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expected string, but found boolean)"); return 0; } /* boolean */ r = lyjson_parse_boolean(ctx, &data[len]); if (!r) { LOGPATH(ctx, LY_VLOG_LYD, leaf); return 0; } leaf->value_str = lydict_insert(ctx, &data[len], r); len += r; } else if (data[len] == '[') { /* empty '[' WSP 'null' WSP ']' */ for (r = len + 1; isspace(data[r]); ++r); if (strncmp(&data[r], "null", 4)) { goto inval; } for (r += 4; isspace(data[r]); ++r); if (data[r] != ']') { goto inval; } leaf->value_str = lydict_insert(ctx, "", 0); len = r + 1; } else { inval: /* error */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (unexpected value)"); return 0; } /* the value is here converted to a JSON format if needed in case of LY_TYPE_IDENT and LY_TYPE_INST or to a * canonical form of the value */ if (!lyp_parse_value(&((struct lys_node_leaf *)leaf->schema)->type, &leaf->value_str, NULL, leaf, NULL, NULL, 1, 0)) { return 0; } #ifdef LY_ENABLED_CACHE /* calculate the hash and insert it into parent */ lyd_hash((struct lyd_node *)leaf); lyd_insert_hash((struct lyd_node *)leaf); #endif if (leaf->schema->nodetype == LYS_LEAFLIST) { /* repeat until end-array */ len += skip_ws(&data[len]); if (data[len] == ',') { /* various validation checks */ if (lyv_data_context((struct lyd_node*)leaf, options, 0, unres) || lyv_data_content((struct lyd_node*)leaf, options, unres) || lyv_multicases((struct lyd_node*)leaf, NULL, first_sibling, 0, NULL)) { return 0; } /* another instance of the leaf-list */ new = calloc(1, sizeof(struct lyd_node_leaf_list)); LY_CHECK_ERR_RETURN(!new, LOGMEM(ctx), 0); new->parent = leaf->parent; new->prev = (struct lyd_node *)leaf; leaf->next = (struct lyd_node *)new; /* copy the validity and when flags */ new->validity = leaf->validity; new->when_status = leaf->when_status; /* fix the "last" pointer */ (*first_sibling)->prev = (struct lyd_node *)new; new->schema = leaf->schema; /* repeat value parsing */ leaf = new; len++; goto repeat; } else if (data[len] == ']') { len++; len += skip_ws(&data[len]); } else { /* something unexpected */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, leaf, "JSON data (expecting value-separator or end-array)"); return 0; } } len += skip_ws(&data[len]); return len; } static unsigned int json_parse_attr(struct lys_module *parent_module, struct lyd_attr **attr, const char *data, int options) { struct ly_ctx *ctx = parent_module->ctx; unsigned int len = 0, r; char *str = NULL, *name, *prefix = NULL, *value; struct lys_module *module = parent_module; struct lyd_attr *attr_new, *attr_last = NULL; int ret; *attr = NULL; if (data[len] != '{') { if (!strncmp(&data[len], "null", 4)) { len += 4; len += skip_ws(&data[len]); return len; } LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing begin-object)"); goto error; } repeat: prefix = NULL; len++; len += skip_ws(&data[len]); if (data[len] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing quotation-mark at the beginning of string)"); return 0; } len++; str = lyjson_parse_text(ctx, &data[len], &r); if (!r) { goto error; } else if (data[len + r] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing quotation-mark at the end of string)"); goto error; } if ((name = strchr(str, ':'))) { *name = '\0'; name++; prefix = str; module = (struct lys_module *)ly_ctx_get_module(parent_module->ctx, prefix, NULL, 0); if (!module) { LOGVAL(ctx, LYE_INELEM, LY_VLOG_NONE, NULL, name); goto error; } } else { name = str; } /* prepare data for parsing node content */ len += r + 1; len += skip_ws(&data[len]); if (data[len] != ':') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing name-separator)"); goto error; } len++; len += skip_ws(&data[len]); if (data[len] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing quotation-mark at the beginning of string)"); goto error; } len++; value = lyjson_parse_text(ctx, &data[len], &r); if (!r) { goto error; } else if (data[len + r] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing quotation-mark at the end of string)"); free(value); goto error; } len += r + 1; len += skip_ws(&data[len]); ret = lyp_fill_attr(parent_module->ctx, NULL, NULL, prefix, name, value, NULL, &attr_new); if (ret == -1) { free(value); goto error; } else if (ret == 1) { if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_INMETA, LY_VLOG_NONE, NULL, prefix, name, value); free(value); goto error; } LOGWRN(ctx, "Unknown \"%s:%s\" metadata with value \"%s\", ignoring.", (prefix ? prefix : ""), name, value); free(value); goto next; } free(value); if (!attr_last) { *attr = attr_last = attr_new; } else { attr_last->next = attr_new; attr_last = attr_new; } next: free(str); str = NULL; if (data[len] == ',') { goto repeat; } else if (data[len] != '}') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing end-object)"); goto error; } len++; len += skip_ws(&data[len]); return len; error: free(str); if (*attr) { lyd_free_attr(module->ctx, NULL, *attr, 1); *attr = NULL; } return 0; } struct attr_cont { struct attr_cont *next; struct lyd_attr *attr; struct lys_node *schema; unsigned int index; /** non-zero only in case of leaf-list */ }; static int store_attrs(struct ly_ctx *ctx, struct attr_cont *attrs, struct lyd_node *first, int options) { struct lyd_node *diter; struct attr_cont *iter; struct lyd_attr *aiter; unsigned int flag_leaflist = 0; while (attrs) { iter = attrs; attrs = attrs->next; if (iter->index) { flag_leaflist = 1; } else { flag_leaflist = 0; } LY_TREE_FOR(first, diter) { if (iter->schema != diter->schema) { continue; } if (flag_leaflist && flag_leaflist != iter->index) { flag_leaflist++; continue; } /* we have match */ if (diter->attr) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, diter, "attribute (multiple attribute definitions belong to a single element)"); free(iter); goto error; } diter->attr = iter->attr; for (aiter = iter->attr; aiter; aiter = aiter->next) { aiter->parent = diter; } break; } if (!diter) { LOGVAL(ctx, LYE_XML_MISS, LY_VLOG_NONE, NULL, "element for the specified attribute", iter->attr->name); lyd_free_attr(iter->schema->module->ctx, NULL, iter->attr, 1); free(iter); goto error; } free(iter); /* check edit-config attribute correctness */ if ((options & LYD_OPT_EDIT) && lyp_check_edit_attr(ctx, diter->attr, diter, NULL)) { goto error; } } return 0; error: while (attrs) { iter = attrs; attrs = attrs->next; lyd_free_attr(ctx, NULL, iter->attr, 1); free(iter); } return -1; } /** * @brief Skip subtree (find its end in the input data) of the current JSON item. * @param[in] ctx libyang context for logging * @param[in] parent parent node for logging * @param[in] data input data (pointing to the beginning, @p len is used to go to the current position). * @param[in, out] len Current position in the @p data, will be updated to the end of the element's subtree in the @p data * @retun 0 on success * @return -1 on error. */ static int json_skip_unknown(struct ly_ctx *ctx, struct lyd_node *parent, const char *data, unsigned int *len) { int qstr = 0; int objects = 0; int arrays = 0; while (data[*len]) { switch (data[*len]) { case '\"': if (qstr) { if (data[(*len) - 1] != '\\') { qstr = 0; } } else if (data[(*len) - 1] != '\\') { qstr = 1; } else { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, parent, "JSON data (missing quotation mark for a string data) "); return -1; } break; case '[': if (!qstr) { arrays++; } break; case '{': if (!qstr) { objects++; } break; case ']': if (!qstr) { arrays--; } break; case '}': if (!qstr) { objects--; } break; case ',': if (!qstr && !objects && !arrays) { /* do not eat the comma character */ return 0; } } if (objects < 0) { if (arrays) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, parent, "JSON data (missing end-array)"); return -1; } return 0; } if (arrays < 0) { if (objects) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, parent, "JSON data (missing end-object)"); return -1; } return 0; } (*len)++; } return 0; } static unsigned int json_parse_data(struct ly_ctx *ctx, const char *data, const struct lys_node *schema_parent, struct lyd_node **parent, struct lyd_node *first_sibling, struct lyd_node *prev, struct attr_cont **attrs, int options, struct unres_data *unres, struct lyd_node **act_notif, const char *yang_data_name) { unsigned int len = 0; unsigned int r; unsigned int flag_leaflist = 0; int i; uint8_t pos; char *name, *prefix = NULL, *str = NULL; const struct lys_module *module = NULL; struct lys_node *schema = NULL; const struct lys_node *sparent = NULL; struct lyd_node *result = NULL, *new, *list, *diter = NULL; struct lyd_attr *attr; struct attr_cont *attrs_aux; /* each YANG data node representation starts with string (node identifier) */ if (data[len] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, (*parent), "JSON data (missing quotation-mark at the beginning of string)"); return 0; } len++; str = lyjson_parse_text(ctx, &data[len], &r); if (!str) { goto error; } if (!r) { goto error; } else if (data[len + r] != '"') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, (*parent), "JSON data (missing quotation-mark at the end of string)"); goto error; } if ((name = strchr(str, ':'))) { *name = '\0'; name++; prefix = str; if (prefix[0] == '@') { prefix++; } } else { name = str; if (name[0] == '@') { name++; } } /* prepare data for parsing node content */ len += r + 1; len += skip_ws(&data[len]); if (data[len] != ':') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, (*parent), "JSON data (missing name-separator)"); goto error; } len++; len += skip_ws(&data[len]); if (str[0] == '@' && !str[1]) { /* process attribute of the parent object (container or list) */ if (!(*parent)) { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "attribute with no corresponding element to belongs to"); goto error; } r = json_parse_attr((*parent)->schema->module, &attr, &data[len], options); if (!r) { LOGPATH(ctx, LY_VLOG_LYD, *parent); goto error; } len += r; if ((*parent)->attr) { lyd_free_attr(ctx, NULL, attr, 1); } else { (*parent)->attr = attr; for (; attr; attr = attr->next) { attr->parent = *parent; } } /* check edit-config attribute correctness */ if ((options & LYD_OPT_EDIT) && lyp_check_edit_attr(ctx, (*parent)->attr, *parent, NULL)) { goto error; } free(str); return len; } /* find schema node */ if (!(*parent)) { /* starting in root */ /* get the proper schema */ module = ly_ctx_get_module(ctx, prefix, NULL, 0); if (ctx->data_clb) { if (!module) { module = ctx->data_clb(ctx, prefix, NULL, 0, ctx->data_clb_data); } else if (!module->implemented) { module = ctx->data_clb(ctx, module->name, module->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data); } } if (module && module->implemented) { if (yang_data_name) { sparent = lyp_get_yang_data_template(module, yang_data_name, strlen(yang_data_name)); schema = NULL; if (sparent) { /* get the proper schema node */ while ((schema = (struct lys_node *) lys_getnext(schema, sparent, module, 0))) { if (!strcmp(schema->name, name)) { break; } } } } else { /* get the proper schema node */ while ((schema = (struct lys_node *) lys_getnext(schema, NULL, module, 0))) { if (!strcmp(schema->name, name)) { break; } } } } } else { if (prefix) { /* get the proper module to give the chance to load/implement it */ module = ly_ctx_get_module(ctx, prefix, NULL, 1); if (ctx->data_clb) { if (!module) { ctx->data_clb(ctx, prefix, NULL, 0, ctx->data_clb_data); } else if (!module->implemented) { ctx->data_clb(ctx, module->name, module->ns, LY_MODCLB_NOT_IMPLEMENTED, ctx->data_clb_data); } } } /* go through RPC's input/output following the options' data type */ if ((*parent)->schema->nodetype == LYS_RPC || (*parent)->schema->nodetype == LYS_ACTION) { while ((schema = (struct lys_node *)lys_getnext(schema, (*parent)->schema, NULL, LYS_GETNEXT_WITHINOUT))) { if ((options & LYD_OPT_RPC) && (schema->nodetype == LYS_INPUT)) { break; } else if ((options & LYD_OPT_RPCREPLY) && (schema->nodetype == LYS_OUTPUT)) { break; } } schema_parent = schema; schema = NULL; } if (schema_parent) { while ((schema = (struct lys_node *)lys_getnext(schema, schema_parent, NULL, 0))) { if (!strcmp(schema->name, name) && ((prefix && !strcmp(lys_node_module(schema)->name, prefix)) || (!prefix && (lys_node_module(schema) == lys_node_module(schema_parent))))) { break; } } } else { while ((schema = (struct lys_node *)lys_getnext(schema, (*parent)->schema, NULL, 0))) { if (!strcmp(schema->name, name) && ((prefix && !strcmp(lys_node_module(schema)->name, prefix)) || (!prefix && (lys_node_module(schema) == lyd_node_module(*parent))))) { break; } } } } module = lys_node_module(schema); if (!module || !module->implemented || module->disabled) { if (options & LYD_OPT_STRICT) { LOGVAL(ctx, LYE_INELEM, (*parent ? LY_VLOG_LYD : LY_VLOG_NONE), (*parent), name); goto error; } else { if (json_skip_unknown(ctx, *parent, data, &len)) { goto error; } free(str); return len; } } if (str[0] == '@') { /* attribute for some sibling node */ if (data[len] == '[') { flag_leaflist = 1; len++; len += skip_ws(&data[len]); } attr_repeat: r = json_parse_attr((struct lys_module *)module, &attr, &data[len], options); if (!r) { LOGPATH(ctx, LY_VLOG_LYD, (*parent)); goto error; } len += r; if (attr) { attrs_aux = malloc(sizeof *attrs_aux); LY_CHECK_ERR_GOTO(!attrs_aux, LOGMEM(ctx), error); attrs_aux->attr = attr; attrs_aux->index = flag_leaflist; attrs_aux->schema = schema; attrs_aux->next = *attrs; *attrs = attrs_aux; } if (flag_leaflist) { if (data[len] == ',') { len++; len += skip_ws(&data[len]); flag_leaflist++; goto attr_repeat; } else if (data[len] != ']') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, (*parent), "JSON data (missing end-array)"); goto error; } len++; len += skip_ws(&data[len]); } free(str); return len; } switch (schema->nodetype) { case LYS_CONTAINER: case LYS_LIST: case LYS_NOTIF: case LYS_RPC: case LYS_ACTION: result = calloc(1, sizeof *result); break; case LYS_LEAF: case LYS_LEAFLIST: result = calloc(1, sizeof(struct lyd_node_leaf_list)); break; case LYS_ANYXML: case LYS_ANYDATA: result = calloc(1, sizeof(struct lyd_node_anydata)); break; default: LOGINT(ctx); goto error; } LY_CHECK_ERR_GOTO(!result, LOGMEM(ctx), error); result->prev = result; result->schema = schema; result->parent = *parent; diter = NULL; if (lys_is_key((struct lys_node_leaf *)schema, &pos)) { /* it is key and we need to insert it into a correct place (we must have parent then, a key cannot be top-level) */ assert(*parent); for (i = 0, diter = (*parent)->child; diter && i < pos && diter->schema->nodetype == LYS_LEAF && lys_is_key((struct lys_node_leaf *)diter->schema, NULL); i++, diter = diter->next); if (diter) { /* out of order insertion - insert list's key to the correct position, before the diter */ if ((*parent)->child == diter) { (*parent)->child = result; /* update first_sibling */ first_sibling = result; } if (diter->prev->next) { diter->prev->next = result; } result->prev = diter->prev; diter->prev = result; result->next = diter; } } if (!diter) { /* simplified (faster) insert as the last node */ if (*parent && !(*parent)->child) { (*parent)->child = result; } if (prev) { result->prev = prev; prev->next = result; /* fix the "last" pointer */ first_sibling->prev = result; } else { result->prev = result; first_sibling = result; } } result->validity = ly_new_node_validity(result->schema); if (resolve_applies_when(schema, 0, NULL)) { result->when_status = LYD_WHEN; } /* type specific processing */ switch (schema->nodetype) { case LYS_LEAF: case LYS_LEAFLIST: /* type detection and assigning the value */ r = json_get_value((struct lyd_node_leaf_list *)result, &first_sibling, &data[len], options, unres); if (!r) { goto error; } /* only for leaf-list */ while (result->next && (result->next->schema == result->schema)) { result = result->next; } len += r; len += skip_ws(&data[len]); break; case LYS_ANYDATA: case LYS_ANYXML: r = json_get_anydata((struct lyd_node_anydata *)result, &data[len]); if (!r) { goto error; } #ifdef LY_ENABLED_CACHE /* calculate the hash and insert it into parent */ lyd_hash(result); lyd_insert_hash(result); #endif len += r; len += skip_ws(&data[len]); break; case LYS_CONTAINER: case LYS_RPC: case LYS_ACTION: case LYS_NOTIF: if (schema->nodetype & (LYS_RPC | LYS_ACTION)) { if (!(options & LYD_OPT_RPC) || *act_notif) { LOGVAL(ctx, LYE_INELEM, LY_VLOG_LYD, result, schema->name); LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Unexpected %s node \"%s\".", (schema->nodetype == LYS_RPC ? "rpc" : "action"), schema->name); goto error; } *act_notif = result; } else if (schema->nodetype == LYS_NOTIF) { if (!(options & LYD_OPT_NOTIF) || *act_notif) { LOGVAL(ctx, LYE_INELEM, LY_VLOG_LYD, result, schema->name); LOGVAL(ctx, LYE_SPEC, LY_VLOG_PREV, NULL, "Unexpected notification node \"%s\".", schema->name); goto error; } *act_notif = result; } #ifdef LY_ENABLED_CACHE /* calculate the hash and insert it into parent */ lyd_hash(result); lyd_insert_hash(result); #endif if (data[len] != '{') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing begin-object)"); goto error; } len++; len += skip_ws(&data[len]); if (data[len] != '}') { /* non-empty container */ len--; diter = NULL; attrs_aux = NULL; do { len++; len += skip_ws(&data[len]); r = json_parse_data(ctx, &data[len], NULL, &result, result->child, diter, &attrs_aux, options, unres, act_notif, yang_data_name); if (!r) { goto error; } len += r; if (result->child) { diter = result->child->prev; } } while(data[len] == ','); /* store attributes */ if (store_attrs(ctx, attrs_aux, result->child, options)) { goto error; } } if (data[len] != '}') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing end-object)"); goto error; } len++; len += skip_ws(&data[len]); /* if we have empty non-presence container, mark it as default */ if (schema->nodetype == LYS_CONTAINER && !result->child && !result->attr && !((struct lys_node_container *)schema)->presence) { result->dflt = 1; } break; case LYS_LIST: if (data[len] != '[') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing begin-array)"); goto error; } list = result; do { len++; len += skip_ws(&data[len]); if (data[len] != '{') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing list instance's begin-object)"); goto error; } diter = NULL; attrs_aux = NULL; do { len++; len += skip_ws(&data[len]); r = json_parse_data(ctx, &data[len], NULL, &list, list->child, diter, &attrs_aux, options, unres, act_notif, yang_data_name); if (!r) { goto error; } len += r; if (list->child) { diter = list->child->prev; } } while (data[len] == ','); #ifdef LY_ENABLED_CACHE /* calculate the hash and insert it into parent */ if (!((struct lys_node_list *)list->schema)->keys_size) { lyd_hash(list); lyd_insert_hash(list); } #endif /* store attributes */ if (store_attrs(ctx, attrs_aux, list->child, options)) { goto error; } if (data[len] != '}') { /* expecting end-object */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing list instance's end-object)"); goto error; } len++; len += skip_ws(&data[len]); if (data[len] == ',') { /* various validation checks */ if (lyv_data_context(list, options, 0, unres) || lyv_data_content(list, options, unres) || lyv_multicases(list, NULL, prev ? &first_sibling : NULL, 0, NULL)) { goto error; } /* another instance of the list */ new = calloc(1, sizeof *new); LY_CHECK_ERR_GOTO(!new, LOGMEM(ctx), error); new->parent = list->parent; new->prev = list; list->next = new; /* copy the validity and when flags */ new->validity = list->validity; new->when_status = list->when_status; /* fix the "last" pointer */ first_sibling->prev = new; new->schema = list->schema; list = new; } } while (data[len] == ','); result = list; if (data[len] != ']') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_LYD, result, "JSON data (missing end-array)"); goto error; } len++; len += skip_ws(&data[len]); break; default: LOGINT(ctx); goto error; } /* various validation checks */ if (lyv_data_context(result, options, 0, unres) || lyv_data_content(result, options, unres) || lyv_multicases(result, NULL, prev ? &first_sibling : NULL, 0, NULL)) { goto error; } /* validation successful */ if (result->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) { /* postpone checking of unique when there will be all list/leaflist instances */ result->validity |= LYD_VAL_DUP; } if (!(*parent)) { *parent = result; } free(str); return len; error: /* cleanup */ for (i = unres->count - 1; i >= 0; i--) { /* remove unres items connected with the node being removed */ if (unres->node[i] == result) { unres_data_del(unres, i); } } while (*attrs) { attrs_aux = *attrs; *attrs = (*attrs)->next; lyd_free_attr(ctx, NULL, attrs_aux->attr, 1); free(attrs_aux); } lyd_free(result); free(str); return 0; } struct lyd_node * lyd_parse_json(struct ly_ctx *ctx, const char *data, int options, const struct lyd_node *rpc_act, const struct lyd_node *data_tree, const char *yang_data_name) { struct lyd_node *result = NULL, *next, *iter, *reply_parent = NULL, *reply_top = NULL, *act_notif = NULL; struct unres_data *unres = NULL; unsigned int len = 0, r; int act_cont = 0; struct attr_cont *attrs = NULL; if (!ctx || !data) { LOGARG; return NULL; } /* skip leading whitespaces */ len += skip_ws(&data[len]); /* expect top-level { */ if (data[len] != '{') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing top level begin-object)"); return NULL; } /* check for empty object */ r = len + 1; r += skip_ws(&data[r]); if (data[r] == '}') { if (options & LYD_OPT_DATA_ADD_YANGLIB) { result = ly_ctx_info(ctx); } lyd_validate(&result, options, ctx); return result; } unres = calloc(1, sizeof *unres); LY_CHECK_ERR_RETURN(!unres, LOGMEM(ctx), NULL); /* create RPC/action reply part that is not in the parsed data */ if (rpc_act) { assert(options & LYD_OPT_RPCREPLY); if (rpc_act->schema->nodetype == LYS_RPC) { /* RPC request */ reply_top = reply_parent = _lyd_new(NULL, rpc_act->schema, 0); } else { /* action request */ reply_top = lyd_dup(rpc_act, 1); LY_TREE_DFS_BEGIN(reply_top, iter, reply_parent) { if (reply_parent->schema->nodetype == LYS_ACTION) { break; } LY_TREE_DFS_END(reply_top, iter, reply_parent); } if (!reply_parent) { LOGERR(ctx, LY_EINVAL, "%s: invalid variable parameter (const struct lyd_node *rpc_act).", __func__); goto error; } lyd_free_withsiblings(reply_parent->child); } } iter = NULL; next = reply_parent; do { len++; len += skip_ws(&data[len]); if (!act_cont) { if (!strncmp(&data[len], "\"yang:action\"", 13)) { len += 13; len += skip_ws(&data[len]); if (data[len] != ':') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing top-level begin-object)"); goto error; } ++len; len += skip_ws(&data[len]); if (data[len] != '{') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing top level yang:action object)"); goto error; } ++len; len += skip_ws(&data[len]); act_cont = 1; } else { act_cont = -1; } } r = json_parse_data(ctx, &data[len], NULL, &next, result, iter, &attrs, options, unres, &act_notif, yang_data_name); if (!r) { goto error; } len += r; if (!result) { if (reply_parent) { result = next->child; iter = next->child ? next->child->prev : NULL; } else { for (iter = next; iter && iter->prev->next; iter = iter->prev); result = iter; if (iter && (options & LYD_OPT_DATA_ADD_YANGLIB) && iter->schema->module == ctx->models.list[ctx->internal_module_count - 1]) { /* ietf-yang-library data present, so ignore the option to add them */ options &= ~LYD_OPT_DATA_ADD_YANGLIB; } iter = next; } } else { iter = result->prev; } if (!reply_parent) { next = NULL; } } while (data[len] == ','); if (data[len] != '}') { /* expecting end-object */ LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing top-level end-object)"); goto error; } len++; len += skip_ws(&data[len]); if (act_cont == 1) { if (data[len] != '}') { LOGVAL(ctx, LYE_XML_INVAL, LY_VLOG_NONE, NULL, "JSON data (missing top-level end-object)"); goto error; } len++; len += skip_ws(&data[len]); } /* store attributes */ if (store_attrs(ctx, attrs, result, options)) { goto error; } if (reply_top) { result = reply_top; } if (!result && (options & LYD_OPT_STRICT)) { LOGERR(ctx, LY_EVALID, "Model for the data to be linked with not found."); goto error; } /* order the elements by hand as it is not required of the JSON input */ if ((options & (LYD_OPT_RPC | LYD_OPT_RPCREPLY))) { if (lyd_schema_sort(result, 1)) { goto error; } } if ((options & LYD_OPT_RPCREPLY) && (rpc_act->schema->nodetype != LYS_RPC)) { /* action reply */ act_notif = reply_parent; } else if ((options & (LYD_OPT_RPC | LYD_OPT_NOTIF)) && !act_notif) { if (result) { LOGVAL(ctx, LYE_MISSELEM, LY_VLOG_LYD, result, (options & LYD_OPT_RPC ? "action" : "notification"), result->schema->name); } else { LOGVAL(ctx, LYE_MISSELEM, LY_VLOG_NONE, NULL, (options & LYD_OPT_RPC ? "action" : "notification"), "data"); } goto error; } /* add missing ietf-yang-library if requested */ if (options & LYD_OPT_DATA_ADD_YANGLIB) { if (lyd_merge(result, ly_ctx_info(ctx), LYD_OPT_DESTRUCT | LYD_OPT_EXPLICIT)) { LOGERR(ctx, LY_EINT, "Adding ietf-yang-library data failed."); goto error; } } /* check for uniquness of top-level lists/leaflists because * only the inner instances were tested in lyv_data_content() */ LY_TREE_FOR(result, iter) { if (!(iter->schema->nodetype & (LYS_LIST | LYS_LEAFLIST)) || !(iter->validity & LYD_VAL_DUP)) { continue; } if (lyv_data_dup(iter, result)) { goto error; } } /* add/validate default values, unres */ if (lyd_defaults_add_unres(&result, options, ctx, NULL, 0, data_tree, act_notif, unres, 1)) { goto error; } /* check for missing top level mandatory nodes */ if (!(options & (LYD_OPT_TRUSTED | LYD_OPT_NOTIF_FILTER)) && lyd_check_mandatory_tree((act_notif ? act_notif : result), ctx, NULL, 0, options)) { goto error; } free(unres->node); free(unres->type); free(unres); return result; error: lyd_free_withsiblings(result); if (reply_top && result != reply_top) { lyd_free_withsiblings(reply_top); } free(unres->node); free(unres->type); free(unres); return NULL; }