/** * @file json.c * @author Radek Krejci * @brief Generic JSON format parser for libyang * * Copyright (c) 2020 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 */ #include #include #include #include #include #include #include "common.h" #include "in_internal.h" #include "json.h" #include "tree_schema_internal.h" const char * lyjson_token2str(enum LYJSON_PARSER_STATUS status) { switch (status) { case LYJSON_ERROR: return "error"; case LYJSON_ROOT: return "document root"; case LYJSON_FALSE: return "false"; case LYJSON_TRUE: return "true"; case LYJSON_NULL: return "null"; case LYJSON_OBJECT: return "object"; case LYJSON_OBJECT_CLOSED: return "object closed"; case LYJSON_OBJECT_EMPTY: return "empty object"; case LYJSON_ARRAY: return "array"; case LYJSON_ARRAY_CLOSED: return "array closed"; case LYJSON_ARRAY_EMPTY: return "empty array"; case LYJSON_NUMBER: return "number"; case LYJSON_STRING: return "string"; case LYJSON_END: return "end of input"; } return ""; } static LY_ERR skip_ws(struct lyjson_ctx *jsonctx) { /* skip leading whitespaces */ while (*jsonctx->in->current != '\0' && is_jsonws(*jsonctx->in->current)) { if (*jsonctx->in->current == '\n') { LY_IN_NEW_LINE(jsonctx->in); } ly_in_skip(jsonctx->in, 1); } if (*jsonctx->in->current == '\0') { LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_END); } return LY_SUCCESS; } /* * @brief Set value corresponding to the current context's status */ static void lyjson_ctx_set_value(struct lyjson_ctx *jsonctx, const char *value, size_t value_len, ly_bool dynamic) { assert(jsonctx); if (jsonctx->dynamic) { free((char *)jsonctx->value); } jsonctx->value = value; jsonctx->value_len = value_len; jsonctx->dynamic = dynamic; } static LY_ERR lyjson_check_next(struct lyjson_ctx *jsonctx) { if (jsonctx->status.count == 1) { /* top level value (JSON-text), ws expected */ if ((*jsonctx->in->current == '\0') || is_jsonws(*jsonctx->in->current)) { return LY_SUCCESS; } } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_OBJECT) { LY_CHECK_RET(skip_ws(jsonctx)); if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == '}')) { return LY_SUCCESS; } } else if (lyjson_ctx_status(jsonctx, 1) == LYJSON_ARRAY) { LY_CHECK_RET(skip_ws(jsonctx)); if ((*jsonctx->in->current == ',') || (*jsonctx->in->current == ']')) { return LY_SUCCESS; } } LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Unexpected character \"%c\" after JSON %s.", *jsonctx->in->current, lyjson_token2str(lyjson_ctx_status(jsonctx, 0))); return LY_EVALID; } /** * Input is expected to start after the opening quotation-mark. * When succeeds, input is moved after the closing quotation-mark. */ static LY_ERR lyjson_string_(struct lyjson_ctx *jsonctx) { #define BUFSIZE 24 #define BUFSIZE_STEP 128 const char *in = jsonctx->in->current, *start; char *buf = NULL; size_t offset; /* read offset in input buffer */ size_t len; /* length of the output string (write offset in output buffer) */ size_t size = 0; /* size of the output buffer */ size_t u; uint64_t start_line; assert(jsonctx); /* init */ start = in; start_line = jsonctx->in->line; offset = len = 0; /* parse */ while (in[offset]) { if (in[offset] == '\\') { /* escape sequence */ const char *slash = &in[offset]; uint32_t value; uint8_t i = 1; if (!buf) { /* prepare output buffer */ buf = malloc(BUFSIZE); LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); size = BUFSIZE; } /* allocate enough for the offset and next character, * we will need 4 bytes at most since we support only the predefined * (one-char) entities and character references */ if (len + offset + 4 >= size) { size_t increment; for (increment = BUFSIZE_STEP; len + offset + 4 >= size + increment; increment += BUFSIZE_STEP) {} buf = ly_realloc(buf, size + increment); LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); size += BUFSIZE_STEP; } if (offset) { /* store what we have so far */ memcpy(&buf[len], in, offset); len += offset; in += offset; offset = 0; } switch (in[++offset]) { 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 */ offset++; for (value = i = 0; i < 4; i++) { if (!in[offset + i]) { LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid basic multilingual plane character \"%s\".", slash); goto error; } else if (isdigit(in[offset + i])) { u = (in[offset + i] - '0'); } else if (in[offset + i] > 'F') { u = LY_BASE_DEC + (in[offset + i] - 'a'); } else { u = LY_BASE_DEC + (in[offset + i] - 'A'); } value = (LY_BASE_HEX * value) + u; } break; default: /* invalid escape sequence */ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character escape sequence \\%c.", in[offset]); goto error; } offset += i; /* add read escaped characters */ LY_CHECK_ERR_GOTO(ly_pututf8(&buf[len], value, &u), LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character reference \"%.*s\" (0x%08x).", (int)(&in[offset] - slash), slash, value), error); len += u; /* update number of bytes in buffer */ in += offset; /* move the input by the processed bytes stored in the buffer ... */ offset = 0; /* ... and reset the offset index for future moving data into buffer */ } else if (in[offset] == '"') { /* end of string */ if (buf) { /* realloc exact size string */ buf = ly_realloc(buf, len + offset + 1); LY_CHECK_ERR_RET(!buf, LOGMEM(jsonctx->ctx), LY_EMEM); size = len + offset + 1; if (offset) { memcpy(&buf[len], in, offset); } /* set terminating NULL byte */ buf[len + offset] = '\0'; } len += offset; ++offset; in += offset; goto success; } else { /* get it as UTF-8 character for check */ const char *c = &in[offset]; uint32_t code = 0; size_t code_len = 0; LY_CHECK_ERR_GOTO(ly_getutf8(&c, &code, &code_len), LOGVAL(jsonctx->ctx, LY_VCODE_INCHAR, in[offset]), error); LY_CHECK_ERR_GOTO(!is_jsonstrchar(code), LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON string \"%.*s\" (0x%08x).", (int)(&in[offset] - start + code_len), start, code), error); /* character is ok, continue */ offset += code_len; } } /* EOF reached before endchar */ LOGVAL(jsonctx->ctx, LY_VCODE_EOF); LOGVAL_LINE(jsonctx->ctx, start_line, LYVE_SYNTAX, "Missing quotation-mark at the end of a JSON string."); error: free(buf); return LY_EVALID; success: jsonctx->in->current = in; if (buf) { lyjson_ctx_set_value(jsonctx, buf, len, 1); } else { lyjson_ctx_set_value(jsonctx, start, len, 0); } return LY_SUCCESS; #undef BUFSIZE #undef BUFSIZE_STEP } /* * * Wrapper around lyjson_string_() adding LYJSON_STRING status into context to allow using lyjson_string_() for parsing object's name. */ static LY_ERR lyjson_string(struct lyjson_ctx *jsonctx) { LY_CHECK_RET(lyjson_string_(jsonctx)); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_STRING); LY_CHECK_RET(lyjson_check_next(jsonctx)); return LY_SUCCESS; } /** * @brief Calculate how many @p c characters there are in a row. * * @param[in] str Count from this position. * @param[in] end Position after the last checked character. * @param[in] c Checked character. * @param[in] backwards Set to 1, if to proceed from end-1 to str. * @return Number of characters in a row. */ static uint32_t lyjson_count_in_row(const char *str, const char *end, char c, ly_bool backwards) { uint32_t cnt; assert(str && end); if (str >= end) { return 0; } if (!backwards) { for (cnt = 0; (str != end) && (*str == c); ++str, ++cnt) {} } else { --end; --str; for (cnt = 0; (str != end) && (*end == c); --end, ++cnt) {} } return cnt; } /** * @brief Check if the number can be shortened to zero. * * @param[in] in Start of input string; * @param[in] end End of input string; * @return 1 if number is zero, otherwise 0. */ static ly_bool lyjson_number_is_zero(const char *in, const char *end) { assert(in < end); if ((in[0] == '-') || (in[0] == '+')) { in++; assert(in < end); } if ((in[0] == '0') && (in[1] == '.')) { in += 2; if (!(in < end)) { return 1; } } return lyjson_count_in_row(in, end, '0', 0) == end - in; } /** * @brief Allocate buffer for number in string format. * * @param[in] jsonctx JSON context. * @param[in] num_len Required space in bytes for a number. * Terminating null byte is added by default. * @param[out] buffer Output allocated buffer. * @return LY_ERR value. */ static LY_ERR lyjson_get_buffer_for_number(const struct ly_ctx *ctx, uint64_t num_len, char **buffer) { *buffer = NULL; LY_CHECK_ERR_RET((num_len + 1) > LY_NUMBER_MAXLEN, LOGVAL(ctx, LYVE_SEMANTICS, "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit."), LY_EVALID); /* allocate buffer for the result (add NULL-byte) */ *buffer = malloc(num_len + 1); LY_CHECK_ERR_RET(!(*buffer), LOGMEM(ctx), LY_EMEM); return LY_SUCCESS; } /** * @brief Copy the 'numeric part' (@p num) except its decimal point * (@p dec_point) and insert the new decimal point (@p dp_position) * only if it is to be placed in the 'numeric part' range (@p num). * * @param[in] num Begin of the 'numeric part'. * @param[in] num_len Length of the 'numeric part'. * @param[in] dec_point Pointer to the old decimal point. * If it has a NULL value, it is ignored. * @param[in] dp_position Position of the new decimal point. * If it has a negative value, it is ignored. * @param[out] dst Memory into which the copied result is written. * @return Number of characters written to the @p dst. */ static uint32_t lyjson_exp_number_copy_num_part(const char *num, uint32_t num_len, char *dec_point, int32_t dp_position, char *dst) { int32_t dec_point_idx; int32_t n, d; assert(num && dst); dec_point_idx = dec_point ? dec_point - num : INT32_MAX; assert((dec_point_idx >= 0) && (dec_point_idx != dp_position)); for (n = 0, d = 0; (uint32_t)n < num_len; n++) { if (n == dec_point_idx) { continue; } else if (d == dp_position) { dst[d++] = '.'; dst[d++] = num[n]; } else { dst[d++] = num[n]; } } return d; } /** * @brief Convert JSON number with exponent into the representation * used by YANG. * * The input numeric string must be syntactically valid. Also, before * calling this function, checks should be performed using the * ::lyjson_number_is_zero(). * * @param[in] ctx Context for the error message. * @param[in] in Beginning of the string containing the number. * @param[in] exponent Pointer to the letter E/e. * @param[in] total_len Total size of the input number. * @param[out] res Conversion result. * @param[out] res_len Length of the result. * @return LY_ERR value. */ static LY_ERR lyjson_exp_number(const struct ly_ctx *ctx, const char *in, const char *exponent, uint64_t total_len, char **res, size_t *res_len) { #define MAYBE_WRITE_MINUS(ARRAY, INDEX, FLAG) \ if (FLAG) { \ ARRAY[INDEX++] = '-'; \ } /* Length of leading zero followed by the decimal point. */ #define LEADING_ZERO 1 /* Flags for the ::lyjson_count_in_row() */ #define FORWARD 0 #define BACKWARD 1 /* Buffer where the result is stored. */ char *buf; /* Size without space for terminating NULL-byte. */ uint64_t buf_len; /* Index to buf. */ uint32_t i = 0; /* A 'numeric part' doesn't contain a minus sign or an leading zero. * For example, in 0.45, there is the leading zero. */ const char *num; /* Length of the 'numeric part' ends before E/e. */ uint16_t num_len; /* Position of decimal point in the num. */ char *dec_point; /* Final position of decimal point in the buf. */ int32_t dp_position; /* Exponent as integer. */ long long e_val; /* Byte for the decimal point. */ int8_t dot; /* Required additional byte for the minus sign. */ uint8_t minus; /* The number of zeros. */ long zeros; /* If the number starts with leading zero followed by the decimal point. */ ly_bool leading_zero; assert(ctx && in && exponent && res && res_len && (total_len > 2)); assert((in < exponent) && ((*exponent == 'e') || (*exponent == 'E'))); if ((exponent - in) > UINT16_MAX) { LOGVAL(ctx, LYVE_SEMANTICS, "JSON number is too long."); return LY_EVALID; } /* Convert exponent. */ errno = 0; e_val = strtoll(exponent + 1, NULL, LY_BASE_DEC); if (errno || (e_val > UINT16_MAX) || (e_val < -UINT16_MAX)) { LOGVAL(ctx, LYVE_SEMANTICS, "Exponent out-of-bounds in a JSON Number value (%.*s).", (int)total_len, in); return LY_EVALID; } minus = in[0] == '-'; if (in[minus] == '0') { assert(in[minus + 1] == '.'); leading_zero = 1; /* The leading zero has been found, it will be skipped. */ num = &in[minus + 1]; } else { leading_zero = 0; /* Set to the first number. */ num = &in[minus]; } num_len = exponent - num; /* Find the location of the decimal points. */ dec_point = ly_strnchr(num, '.', num_len); dp_position = dec_point ? dec_point - num + e_val : num_len + e_val; /* Remove zeros after the decimal point from the end of * the 'numeric part' because these are useless. * (For example, in 40.001000 these are the last 3). */ num_len -= dp_position > 0 ? lyjson_count_in_row(num + dp_position - 1, exponent, '0', BACKWARD) : lyjson_count_in_row(num, exponent, '0', BACKWARD); /* Decide what to do with the dot from the 'numeric part'. */ if (dec_point && ((int32_t)(num_len - 1) == dp_position)) { /* Decimal point in the last place is useless. */ dot = -1; } else if (dec_point) { /* Decimal point is shifted. */ dot = 0; } else { /* Additional byte for the decimal point is requred. */ dot = 1; } /* Final composition of the result. */ if (dp_position <= 0) { /* Adding decimal point before the integer with adding additional zero(s). */ zeros = labs(dp_position); buf_len = minus + LEADING_ZERO + dot + zeros + num_len; LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); MAYBE_WRITE_MINUS(buf, i, minus); buf[i++] = '0'; buf[i++] = '.'; memset(buf + i, '0', zeros); i += zeros; dp_position = -1; lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); } else if (leading_zero && (dp_position < (ssize_t)num_len)) { /* Insert decimal point between the integer's digits. */ /* Set a new range of 'numeric part'. Old decimal point is skipped. */ num++; num_len--; dp_position--; /* Get the number of useless zeros between the old * and new decimal point. For example, in the number 0.005E1, * there is one useless zero. */ zeros = lyjson_count_in_row(num, num + dp_position + 1, '0', FORWARD); /* If the new decimal point will be in the place of the first non-zero subnumber. */ if (zeros == (dp_position + 1)) { /* keep one zero as leading zero */ zeros--; /* new decimal point will be behind the leading zero */ dp_position = 1; dot = 1; } else { dot = 0; } buf_len = minus + dot + (num_len - zeros); LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); MAYBE_WRITE_MINUS(buf, i, minus); /* Skip useless zeros and copy. */ lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i); } else if (dp_position < (ssize_t)num_len) { /* Insert decimal point between the integer's digits. */ buf_len = minus + dot + num_len; LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); MAYBE_WRITE_MINUS(buf, i, minus); lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); } else if (leading_zero) { /* Adding decimal point after the decimal value make the integer result. */ /* Set a new range of 'numeric part'. Old decimal point is skipped. */ num++; num_len--; /* Get the number of useless zeros. */ zeros = lyjson_count_in_row(num, num + num_len, '0', FORWARD); buf_len = minus + dp_position - zeros; LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); MAYBE_WRITE_MINUS(buf, i, minus); /* Skip useless zeros and copy. */ i += lyjson_exp_number_copy_num_part(num + zeros, num_len - zeros, NULL, dp_position, buf + i); /* Add multiples of ten behind the 'numeric part'. */ memset(buf + i, '0', buf_len - i); } else { /* Adding decimal point after the decimal value make the integer result. */ buf_len = minus + dp_position; LY_CHECK_RET(lyjson_get_buffer_for_number(ctx, buf_len, &buf)); MAYBE_WRITE_MINUS(buf, i, minus); i += lyjson_exp_number_copy_num_part(num, num_len, dec_point, dp_position, buf + i); /* Add multiples of ten behind the 'numeric part'. */ memset(buf + i, '0', buf_len - i); } buf[buf_len] = '\0'; *res = buf; *res_len = buf_len; #undef MAYBE_WRITE_MINUS #undef LEADING_ZERO #undef FORWARD #undef BACKWARD return LY_SUCCESS; } static LY_ERR lyjson_number(struct lyjson_ctx *jsonctx) { size_t offset = 0, num_len; const char *in = jsonctx->in->current, *exponent = NULL; uint8_t minus = 0; char *num; if (in[offset] == '-') { ++offset; minus = 1; } if (in[offset] == '0') { ++offset; } else if (isdigit(in[offset])) { ++offset; while (isdigit(in[offset])) { ++offset; } } else { invalid_character: if (in[offset]) { LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Invalid character in JSON Number value (\"%c\").", in[offset]); } else { LOGVAL(jsonctx->ctx, LY_VCODE_EOF); } return LY_EVALID; } if (in[offset] == '.') { ++offset; if (!isdigit(in[offset])) { goto invalid_character; } while (isdigit(in[offset])) { ++offset; } } if ((in[offset] == 'e') || (in[offset] == 'E')) { exponent = &in[offset]; ++offset; if ((in[offset] == '+') || (in[offset] == '-')) { ++offset; } if (!isdigit(in[offset])) { goto invalid_character; } while (isdigit(in[offset])) { ++offset; } } if (lyjson_number_is_zero(in, exponent ? exponent : &in[offset])) { lyjson_ctx_set_value(jsonctx, in, minus + 1, 0); } else if (exponent && lyjson_number_is_zero(exponent + 1, &in[offset])) { lyjson_ctx_set_value(jsonctx, in, exponent - in, 0); } else if (exponent) { LY_CHECK_RET(lyjson_exp_number(jsonctx->ctx, in, exponent, offset, &num, &num_len)); lyjson_ctx_set_value(jsonctx, num, num_len, 1); } else { if (offset > LY_NUMBER_MAXLEN) { LOGVAL(jsonctx->ctx, LYVE_SEMANTICS, "Number encoded as a string exceeded the LY_NUMBER_MAXLEN limit."); return LY_EVALID; } lyjson_ctx_set_value(jsonctx, in, offset, 0); } ly_in_skip(jsonctx->in, offset); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NUMBER); LY_CHECK_RET(lyjson_check_next(jsonctx)); return LY_SUCCESS; } static LY_ERR lyjson_object_name(struct lyjson_ctx *jsonctx) { if (*jsonctx->in->current != '"') { LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, "a JSON object's member"); return LY_EVALID; } ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(lyjson_string_(jsonctx)); LY_CHECK_RET(skip_ws(jsonctx)); if (*jsonctx->in->current != ':') { LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, "a JSON object's name-separator ':'"); return LY_EVALID; } ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(skip_ws(jsonctx)); return LY_SUCCESS; } static LY_ERR lyjson_object(struct lyjson_ctx *jsonctx) { LY_CHECK_RET(skip_ws(jsonctx)); if (*jsonctx->in->current == '}') { assert(jsonctx->depth); jsonctx->depth--; /* empty object */ ly_in_skip(jsonctx->in, 1); lyjson_ctx_set_value(jsonctx, NULL, 0, 0); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT_EMPTY); return LY_SUCCESS; } LY_CHECK_RET(lyjson_object_name(jsonctx)); /* output data are set by lyjson_string_() */ LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_OBJECT); return LY_SUCCESS; } /** * @brief Process JSON array envelope * * @param[in] jsonctx JSON parser context * @return LY_SUCCESS or LY_EMEM */ static LY_ERR lyjson_array(struct lyjson_ctx *jsonctx) { LY_CHECK_RET(skip_ws(jsonctx)); if (*jsonctx->in->current == ']') { /* empty array */ ly_in_skip(jsonctx->in, 1); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY_EMPTY); } else { LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_ARRAY); } /* erase previous values, array has no value on its own */ lyjson_ctx_set_value(jsonctx, NULL, 0, 0); return LY_SUCCESS; } static LY_ERR lyjson_value(struct lyjson_ctx *jsonctx) { if (jsonctx->status.count && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { return LY_SUCCESS; } if ((*jsonctx->in->current == 'f') && !strncmp(jsonctx->in->current, "false", ly_strlen_const("false"))) { /* false */ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("false"), 0); ly_in_skip(jsonctx->in, ly_strlen_const("false")); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_FALSE); LY_CHECK_RET(lyjson_check_next(jsonctx)); } else if ((*jsonctx->in->current == 't') && !strncmp(jsonctx->in->current, "true", ly_strlen_const("true"))) { /* true */ lyjson_ctx_set_value(jsonctx, jsonctx->in->current, ly_strlen_const("true"), 0); ly_in_skip(jsonctx->in, ly_strlen_const("true")); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_TRUE); LY_CHECK_RET(lyjson_check_next(jsonctx)); } else if ((*jsonctx->in->current == 'n') && !strncmp(jsonctx->in->current, "null", ly_strlen_const("null"))) { /* none */ lyjson_ctx_set_value(jsonctx, "", 0, 0); ly_in_skip(jsonctx->in, ly_strlen_const("null")); LYJSON_STATUS_PUSH_RET(jsonctx, LYJSON_NULL); LY_CHECK_RET(lyjson_check_next(jsonctx)); } else if (*jsonctx->in->current == '"') { /* string */ ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(lyjson_string(jsonctx)); } else if (*jsonctx->in->current == '[') { /* array */ ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(lyjson_array(jsonctx)); } else if (*jsonctx->in->current == '{') { jsonctx->depth++; if (jsonctx->depth > LY_MAX_BLOCK_DEPTH) { LOGERR(jsonctx->ctx, LY_EINVAL, "The maximum number of block nestings has been exceeded."); return LY_EINVAL; } /* object */ ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(lyjson_object(jsonctx)); } else if ((*jsonctx->in->current == '-') || ((*jsonctx->in->current >= '0') && (*jsonctx->in->current <= '9'))) { /* number */ LY_CHECK_RET(lyjson_number(jsonctx)); } else { /* unexpected value */ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, "a JSON value"); return LY_EVALID; } return LY_SUCCESS; } LY_ERR lyjson_ctx_new(const struct ly_ctx *ctx, struct ly_in *in, ly_bool subtree, struct lyjson_ctx **jsonctx_p) { LY_ERR ret = LY_SUCCESS; struct lyjson_ctx *jsonctx; assert(ctx); assert(in); assert(jsonctx_p); /* new context */ jsonctx = calloc(1, sizeof *jsonctx); LY_CHECK_ERR_RET(!jsonctx, LOGMEM(ctx), LY_EMEM); jsonctx->ctx = ctx; jsonctx->in = in; LOG_LOCSET(NULL, NULL, NULL, in); /* parse JSON value, if any */ LY_CHECK_GOTO(ret = skip_ws(jsonctx), cleanup); if (lyjson_ctx_status(jsonctx, 0) == LYJSON_END) { /* empty data input */ goto cleanup; } if (subtree) { ret = lyjson_object(jsonctx); jsonctx->depth++; } else { ret = lyjson_value(jsonctx); } if ((jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { LOGVAL(jsonctx->ctx, LY_VCODE_EOF); ret = LY_EVALID; } cleanup: if (ret) { lyjson_ctx_free(jsonctx); } else { *jsonctx_p = jsonctx; } return ret; } void lyjson_ctx_backup(struct lyjson_ctx *jsonctx) { if (jsonctx->backup.dynamic) { free((char *)jsonctx->backup.value); } jsonctx->backup.status = lyjson_ctx_status(jsonctx, 0); jsonctx->backup.status_count = jsonctx->status.count; jsonctx->backup.value = jsonctx->value; jsonctx->backup.value_len = jsonctx->value_len; jsonctx->backup.input = jsonctx->in->current; jsonctx->backup.dynamic = jsonctx->dynamic; jsonctx->backup.depth = jsonctx->depth; jsonctx->dynamic = 0; } void lyjson_ctx_restore(struct lyjson_ctx *jsonctx) { if (jsonctx->dynamic) { free((char *)jsonctx->value); } jsonctx->status.count = jsonctx->backup.status_count; jsonctx->status.objs[jsonctx->backup.status_count - 1] = (void *)jsonctx->backup.status; jsonctx->value = jsonctx->backup.value; jsonctx->value_len = jsonctx->backup.value_len; jsonctx->in->current = jsonctx->backup.input; jsonctx->dynamic = jsonctx->backup.dynamic; jsonctx->depth = jsonctx->backup.depth; jsonctx->backup.dynamic = 0; } LY_ERR lyjson_ctx_next(struct lyjson_ctx *jsonctx, enum LYJSON_PARSER_STATUS *status) { LY_ERR ret = LY_SUCCESS; ly_bool toplevel = 0; enum LYJSON_PARSER_STATUS prev; assert(jsonctx); prev = lyjson_ctx_status(jsonctx, 0); if ((prev == LYJSON_OBJECT) || (prev == LYJSON_ARRAY)) { /* get value for the object's member OR the first value in the array */ ret = lyjson_value(jsonctx); goto result; } else { /* the previous token is closed and should be completely processed */ LYJSON_STATUS_POP_RET(jsonctx); prev = lyjson_ctx_status(jsonctx, 0); } if (!jsonctx->status.count) { /* we are done with the top level value */ toplevel = 1; } LY_CHECK_RET(skip_ws(jsonctx)); if (toplevel && !jsonctx->status.count) { /* EOF expected, but there are some data after the top level token */ LOGVAL(jsonctx->ctx, LYVE_SYNTAX, "Expecting end-of-input, but some data follows the top level JSON value."); return LY_EVALID; } if (toplevel) { /* we are done */ goto result; } /* continue with the next token */ assert(prev == LYJSON_OBJECT || prev == LYJSON_ARRAY); if (*jsonctx->in->current == ',') { /* sibling item in the ... */ ly_in_skip(jsonctx->in, 1); LY_CHECK_RET(skip_ws(jsonctx)); if (prev == LYJSON_OBJECT) { /* ... object - get another object's member */ ret = lyjson_object_name(jsonctx); } else { /* LYJSON_ARRAY */ /* ... array - get another complete value */ ret = lyjson_value(jsonctx); } } else if (((prev == LYJSON_OBJECT) && (*jsonctx->in->current == '}')) || ((prev == LYJSON_ARRAY) && (*jsonctx->in->current == ']'))) { if (*jsonctx->in->current == '}') { assert(jsonctx->depth); jsonctx->depth--; } ly_in_skip(jsonctx->in, 1); LYJSON_STATUS_POP_RET(jsonctx); LYJSON_STATUS_PUSH_RET(jsonctx, prev + 1); } else { /* unexpected value */ LOGVAL(jsonctx->ctx, LY_VCODE_INSTREXP, LY_VCODE_INSTREXP_len(jsonctx->in->current), jsonctx->in->current, prev == LYJSON_ARRAY ? "another JSON value in array" : "another JSON object's member"); return LY_EVALID; } result: if ((ret == LY_SUCCESS) && (jsonctx->status.count > 1) && (lyjson_ctx_status(jsonctx, 0) == LYJSON_END)) { LOGVAL(jsonctx->ctx, LY_VCODE_EOF); ret = LY_EVALID; } if ((ret == LY_SUCCESS) && status) { *status = lyjson_ctx_status(jsonctx, 0); } return ret; } enum LYJSON_PARSER_STATUS lyjson_ctx_status(struct lyjson_ctx *jsonctx, uint32_t index) { assert(jsonctx); if (jsonctx->status.count < index) { return LYJSON_ERROR; } else if (jsonctx->status.count == index) { return LYJSON_ROOT; } else { return (enum LYJSON_PARSER_STATUS)(uintptr_t)jsonctx->status.objs[jsonctx->status.count - (index + 1)]; } } void lyjson_ctx_free(struct lyjson_ctx *jsonctx) { if (!jsonctx) { return; } LOG_LOCBACK(0, 0, 0, 1); if (jsonctx->dynamic) { free((char *)jsonctx->value); } if (jsonctx->backup.dynamic) { free((char *)jsonctx->backup.value); } ly_set_erase(&jsonctx->status, NULL); free(jsonctx); }