/*
** Copyright (C) 2001-2026 Zabbix SIA
**
** This program is free software: you can redistribute it and/or modify it under the terms of
** the GNU Affero General Public License as published by the Free Software Foundation, version 3.
**
** 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 Affero General Public License for more details.
**
** You should have received a copy of the GNU Affero General Public License along with this program.
** If not, see <https://www.gnu.org/licenses/>.
**/

#include "json.h"

#include "zbxjson.h"
#include "json_parser.h"
#include "jsonpath.h"

#include "zbxnum.h"

/******************************************************************************
 *                                                                            *
 * Purpose: return string describing json error                               *
 *                                                                            *
 * Return value: pointer to the null terminated string                        *
 *                                                                            *
 ******************************************************************************/
#define ZBX_JSON_MAX_STRERROR	255

static ZBX_THREAD_LOCAL char	zbx_json_strerror_message[ZBX_JSON_MAX_STRERROR];

const char	*zbx_json_strerror(void)
{
	return zbx_json_strerror_message;
}

void	zbx_set_json_strerror(const char *fmt, ...)
{
	size_t	sz;
	va_list	args;

	va_start(args, fmt);

	sz = zbx_vsnprintf(zbx_json_strerror_message, sizeof(zbx_json_strerror_message), fmt, args);

	if (sizeof(zbx_json_strerror_message) - 1 == sz)
	{
		/* ensure that the string is not cut in the middle of UTF-8 sequence */
		size_t	idx = sz - 1;
		while (0x80 == (0xc0 & zbx_json_strerror_message[idx]) && 0 < idx)
			idx--;

		if (zbx_utf8_char_len(zbx_json_strerror_message + idx) != sz - idx)
			zbx_json_strerror_message[idx] = '\0';
	}

	va_end(args);
}

static void	__zbx_json_realloc(struct zbx_json *j, size_t need)
{
	int	realloc_json = 0;

	if (NULL == j->buffer)
	{
		if (need > sizeof(j->buf_stat))
		{
			j->buffer_allocated = need;
			j->buffer = (char *)zbx_malloc(j->buffer, j->buffer_allocated);
		}
		else
		{
			j->buffer_allocated = sizeof(j->buf_stat);
			j->buffer = j->buf_stat;
		}
		return;
	}

	while (need > j->buffer_allocated)
	{
		if (0 == j->buffer_allocated)
			j->buffer_allocated = 1024;
		else
			j->buffer_allocated *= 2;
		realloc_json = 1;
	}

	if (1 == realloc_json)
	{
		if (j->buffer == j->buf_stat)
		{
			j->buffer = NULL;
			j->buffer = (char *)zbx_malloc(j->buffer, j->buffer_allocated);
			memcpy(j->buffer, j->buf_stat, sizeof(j->buf_stat));
		}
		else
			j->buffer = (char *)zbx_realloc(j->buffer, j->buffer_allocated);
	}
}

void	zbx_json_init(struct zbx_json *j, size_t allocate)
{
	assert(j);

	j->buffer = NULL;
	j->buffer_allocated = 0;
	j->buffer_offset = 0;
	j->buffer_size = 0;
	j->status = ZBX_JSON_EMPTY;
	j->level = 0;
	__zbx_json_realloc(j, allocate);
	*j->buffer = '\0';

	zbx_json_addobject(j, NULL);
}

void	zbx_json_initarray(struct zbx_json *j, size_t allocate)
{
	assert(j);

	j->buffer = NULL;
	j->buffer_allocated = 0;
	j->buffer_offset = 0;
	j->buffer_size = 0;
	j->status = ZBX_JSON_EMPTY;
	j->level = 0;
	__zbx_json_realloc(j, allocate);
	*j->buffer = '\0';

	zbx_json_addarray(j, NULL);
}

/******************************************************************************
 *                                                                            *
 * Purpose: initialize json with data from another json and position cursor   *
 *          to the closing bracket                                            *
 *                                                                            *
 ******************************************************************************/
void	zbx_json_init_with(struct zbx_json *j, const char *src, size_t len)
{
	if (NULL == src)
	{
		zbx_json_init(j, ZBX_JSON_STAT_BUF_LEN);
		return;
	}

	j->buffer = NULL;
	j->buffer_allocated = 0;
	__zbx_json_realloc(j, len + 1);

	memcpy(j->buffer, src, len);
	j->buffer[len] = '\0';

	j->buffer_size = len;
	j->buffer_offset = len - 1;	/* position to the closing bracket */
	j->level = 1;

	const char	*ptr = j->buffer + j->buffer_offset - 1;

	while (' ' == *ptr && ptr > j->buffer)
		ptr--;

	j->status = ('{' == *ptr ? ZBX_JSON_EMPTY : ZBX_JSON_COMMA);
}

static void	zbx_json_setempty(struct zbx_json *j)
{
	j->buffer_offset = 0;
	j->buffer_size = 0;
	j->status = ZBX_JSON_EMPTY;
	j->level = 0;
	*j->buffer = '\0';
}

void	zbx_json_clean(struct zbx_json *j)
{
	zbx_json_setempty(j);
	zbx_json_addobject(j, NULL);
}

void	zbx_json_free(struct zbx_json *j)
{
	assert(j);

	if (j->buffer != j->buf_stat)
		zbx_free(j->buffer);
}

static size_t	__zbx_json_stringsize_limit(const char *string, zbx_json_type_t type, size_t max_size,
		size_t *full_len, size_t *cutoff)
{
	size_t		len = 0, len_cutoff = 0;
	const char	*sptr, *str_cutoff = NULL;
	char		buffer[] = {"null"};

	for (sptr = (NULL != string ? string : buffer); '\0' != *sptr; sptr++)
	{
		const size_t	cur_len = len;

		switch (*sptr)
		{
			case '"':  /* quotation mark */
			case '\\': /* reverse solidus */
			/* We do not escape '/' (solidus). https://www.rfc-editor.org/errata_search.php?rfc=4627 */
			/* says: "/" and "\/" are both allowed and both produce the same result. */
			case '\b': /* backspace */
			case '\f': /* formfeed */
			case '\n': /* newline */
			case '\r': /* carriage return */
			case '\t': /* horizontal tab */
				len += 2;
				break;
			default:
				/* RFC 8259 requires escaping control characters U+0000 - U+001F */
				if (0x1f >= (unsigned char)*sptr)
					len += 6;
				else
					len++;
		}

		if (NULL == str_cutoff && (0 != max_size && len > max_size))
		{
			str_cutoff = sptr + 1;
			len_cutoff = cur_len;
		}
	}

	if (NULL != full_len)
		*full_len = len;

	if (NULL != string && ZBX_JSON_TYPE_STRING == type)
	{
		len += 2;	/* "" */

		if (NULL != str_cutoff)
			len_cutoff += 2;
	}

	if (NULL != str_cutoff)	/* cut in multi-byte utf-8 sequence */
	{
		while (0 < len_cutoff && 0x80 == (0xc0 & (unsigned char)*(str_cutoff - 1)))
		{
			str_cutoff--;
			len_cutoff--;
		}

		if (NULL != cutoff)
			*cutoff = (size_t)((str_cutoff - string) - 1);
	}

	return (NULL != str_cutoff) ? len_cutoff : len;
}

static size_t	__zbx_json_stringsize(const char *string, zbx_json_type_t type)
{
	return __zbx_json_stringsize_limit(string, type, 0, NULL, NULL);
}

/******************************************************************************
 *                                                                            *
 * Purpose: convert parameter c (0-15) to hexadecimal value ('0'-'f')         *
 *                                                                            *
 * Parameters:                                                                *
 *      c - number 0-15                                                       *
 *                                                                            *
 * Return value:                                                              *
 *      '0'-'f'                                                               *
 *                                                                            *
 ******************************************************************************/
static char	zbx_num2hex(unsigned char c)
{
	if (c >= 10)
		return (char)(c + 0x57);	/* a-f */
	else
		return (char)(c + 0x30);	/* 0-9 */
}

static char	*__zbx_json_insstring_limit(char *p, const char *string, zbx_json_type_t type, size_t str_len)
{
	const char	*sptr, *start;
	char		buffer[] = {"null"};

	if (NULL != string && ZBX_JSON_TYPE_STRING == type)
		*p++ = '"';

	start = (NULL != string ? string : buffer);

	for (sptr = start; (0 == str_len || (size_t)(sptr - start) < str_len) && '\0' != *sptr; sptr++)
	{
		switch (*sptr)
		{
			case '"':		/* quotation mark */
				*p++ = '\\';
				*p++ = '"';
				break;
			case '\\':		/* reverse solidus */
				*p++ = '\\';
				*p++ = '\\';
				break;
			/* We do not escape '/' (solidus). https://www.rfc-editor.org/errata_search.php?rfc=4627 */
			/* says: "/" and "\/" are both allowed and both produce the same result. */
			case '\b':		/* backspace */
				*p++ = '\\';
				*p++ = 'b';
				break;
			case '\f':		/* formfeed */
				*p++ = '\\';
				*p++ = 'f';
				break;
			case '\n':		/* newline */
				*p++ = '\\';
				*p++ = 'n';
				break;
			case '\r':		/* carriage return */
				*p++ = '\\';
				*p++ = 'r';
				break;
			case '\t':		/* horizontal tab */
				*p++ = '\\';
				*p++ = 't';
				break;
			default:
				/* RFC 8259 requires escaping control characters U+0000 - U+001F */
				if (0x1f >= (unsigned char)*sptr)
				{
					*p++ = '\\';
					*p++ = 'u';
					*p++ = '0';
					*p++ = '0';
					*p++ = zbx_num2hex((((unsigned char)*sptr) >> 4) & 0xf);
					*p++ = zbx_num2hex(((unsigned char)*sptr) & 0xf);
				}
				else
					*p++ = *sptr;
		}
	}

	if (NULL != string && ZBX_JSON_TYPE_STRING == type)
		*p++ = '"';

	return p;
}

static char	*__zbx_json_insstring(char *p, const char *string, zbx_json_type_t type)
{
	return __zbx_json_insstring_limit(p, string, type, 0);
}

void	zbx_json_escape(char **string)
{
	size_t	size;
	char	*buffer;

	if (0 == (size = __zbx_json_stringsize(*string, ZBX_JSON_TYPE_UNKNOWN)))
		return;

	buffer = zbx_malloc(NULL, size + 1);
	buffer[size] = '\0';
	__zbx_json_insstring(buffer, *string, ZBX_JSON_TYPE_UNKNOWN);
	zbx_free(*string);
	*string = buffer;
}

static void	__zbx_json_addobject(struct zbx_json *j, const char *name, int object)
{
	size_t	len = 2; /* brackets */
	char	*p, *psrc, *pdst;

	assert(j);

	if (ZBX_JSON_COMMA == j->status)
		len++; /* , */

	if (NULL != name)
	{
		len += __zbx_json_stringsize(name, ZBX_JSON_TYPE_STRING);
		len += 1; /* : */
	}

	__zbx_json_realloc(j, j->buffer_size + len + 1/*'\0'*/);

	psrc = j->buffer + j->buffer_offset;
	pdst = j->buffer + j->buffer_offset + len;

	memmove(pdst, psrc, j->buffer_size - j->buffer_offset + 1/*'\0'*/);

	p = psrc;

	if (ZBX_JSON_COMMA == j->status)
		*p++ = ',';

	if (NULL != name)
	{
		p = __zbx_json_insstring(p, name, ZBX_JSON_TYPE_STRING);
		*p++ = ':';
	}

	*p++ = object ? '{' : '[';
	*p = object ? '}' : ']';

	j->buffer_offset = (size_t)(p - j->buffer);
	j->buffer_size += len;
	j->level++;
	j->status = ZBX_JSON_EMPTY;
}

void	zbx_json_addobject(struct zbx_json *j, const char *name)
{
	__zbx_json_addobject(j, name, 1);
}

void	zbx_json_addarray(struct zbx_json *j, const char *name)
{
	__zbx_json_addobject(j, name, 0);
}

size_t	zbx_json_addstring_limit( struct zbx_json *j, const char *name, const char *string, zbx_json_type_t type,
		size_t max_size)
{
	size_t	len = 0, str_len = 0, orig_len;
	char	*p, *psrc, *pdst;

	assert(j);

	if (ZBX_JSON_COMMA == j->status)
		len++; /* , */

	if (NULL != name)
	{
		len += __zbx_json_stringsize(name, ZBX_JSON_TYPE_STRING);
		len += 1; /* : */
	}
	len += __zbx_json_stringsize_limit(string, type, max_size, &orig_len, &str_len);

	__zbx_json_realloc(j, j->buffer_size + len + 1);	/*'\0'*/

	psrc = j->buffer + j->buffer_offset;
	pdst = j->buffer + j->buffer_offset + len;

	memmove(pdst, psrc, j->buffer_size - j->buffer_offset + 1);	/*'\0'*/

	p = psrc;

	if (ZBX_JSON_COMMA == j->status)
		*p++ = ',';

	if (NULL != name)
	{
		p = __zbx_json_insstring(p, name, ZBX_JSON_TYPE_STRING);
		*p++ = ':';
	}
	p = __zbx_json_insstring_limit(p, string, type, str_len);

	j->buffer_offset = (size_t)(p - j->buffer);
	j->buffer_size += len;
	j->status = ZBX_JSON_COMMA;

	return orig_len;
}

void	zbx_json_addstring(struct zbx_json *j, const char *name, const char *string, zbx_json_type_t type)
{
	zbx_json_addstring_limit(j, name, string, type, 0);
}

void	zbx_json_addraw(struct zbx_json *j, const char *name, const char *data)
{
	size_t	len = 0, len_data;
	char	*p, *psrc, *pdst;

	assert(j);
	len_data = strlen(data);

	if (ZBX_JSON_COMMA == j->status)
		len++; /* , */

	if (NULL != name)
	{
		len += __zbx_json_stringsize(name, ZBX_JSON_TYPE_STRING);
		len += 1; /* : */
	}
	len += len_data;

	__zbx_json_realloc(j, j->buffer_size + len + 1/*'\0'*/);

	psrc = j->buffer + j->buffer_offset;
	pdst = j->buffer + j->buffer_offset + len;

	memmove(pdst, psrc, j->buffer_size - j->buffer_offset + 1/*'\0'*/);

	p = psrc;

	if (ZBX_JSON_COMMA == j->status)
		*p++ = ',';

	if (NULL != name)
	{
		p = __zbx_json_insstring(p, name, ZBX_JSON_TYPE_STRING);
		*p++ = ':';
	}

	memcpy(p, data, len_data);
	p += len_data;

	j->buffer_offset = (size_t)(p - j->buffer);
	j->buffer_size += len;
	j->status = ZBX_JSON_COMMA;
}

void	zbx_json_adduint64(struct zbx_json *j, const char *name, zbx_uint64_t value)
{
	char	buffer[MAX_ID_LEN];

	zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_UI64, value);
	zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
}

void	zbx_json_addint64(struct zbx_json *j, const char *name, zbx_int64_t value)
{
	char	buffer[MAX_ID_LEN];

	zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_I64, value);
	zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
}

void	zbx_json_addfloat(struct zbx_json *j, const char *name, double value)
{
	char	buffer[MAX_ID_LEN];

	zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_DBL, value);
	zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
}

void	zbx_json_adddouble(struct zbx_json *j, const char *name, double value)
{
	char	buffer[ZBX_MAX_DOUBLE_LEN + 1];

	zbx_print_double(buffer, sizeof(buffer), value);
	zbx_json_addstring(j, name, buffer, ZBX_JSON_TYPE_INT);
}

int	zbx_json_close(struct zbx_json *j)
{
	if (1 == j->level)
	{
		zbx_set_json_strerror("cannot close top level object");
		return FAIL;
	}

	j->level--;
	j->buffer_offset++;
	j->status = ZBX_JSON_COMMA;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Return value: type of input value                                          *
 *                                                                            *
 ******************************************************************************/
static zbx_json_type_t	__zbx_json_type(const char *p)
{
	if ('"' == *p)
		return ZBX_JSON_TYPE_STRING;
	if (('0' <= *p && *p <= '9') || '-' == *p)
		return ZBX_JSON_TYPE_INT;
	if ('[' == *p)
		return ZBX_JSON_TYPE_ARRAY;
	if ('{' == *p)
		return ZBX_JSON_TYPE_OBJECT;
	if ('n' == p[0] && 'u' == p[1] && 'l' == p[2] && 'l' == p[3])
		return ZBX_JSON_TYPE_NULL;
	if ('t' == p[0] && 'r' == p[1] && 'u' == p[2] && 'e' == p[3])
		return ZBX_JSON_TYPE_TRUE;
	if ('f' == p[0] && 'a' == p[1] && 'l' == p[2] && 's' == p[3] && 'e' == p[4])
		return ZBX_JSON_TYPE_FALSE;

	zbx_set_json_strerror("invalid type of JSON value \"%.64s\"", p);

	return ZBX_JSON_TYPE_UNKNOWN;
}

/******************************************************************************
 *                                                                            *
 * Return value: position of the right bracket                                *
 *               NULL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
static const char	*__zbx_json_rbracket(const char *p)
{
	int	level = 0;
	int	state = 0; /* 0 - outside string; 1 - inside string */
	char	lbracket, rbracket;

	assert(p);

	lbracket = *p;

	if ('{' != lbracket && '[' != lbracket)
		return NULL;

	rbracket = ('{' == lbracket ? '}' : ']');

	while ('\0' != *p)
	{
		switch (*p)
		{
			case '"':
				state = (0 == state ? 1 : 0);
				break;
			case '\\':
				if (1 == state)
					if ('\0' == *++p)
						return NULL;
				break;
			case '[':
			case '{':
				if (0 == state)
					level++;
				break;
			case ']':
			case '}':
				if (0 == state)
				{
					level--;
					if (0 == level)
						return (rbracket == *p ? p : NULL);
				}
				break;
		}
		p++;
	}

	return NULL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: open json buffer and check for brackets                           *
 *                                                                            *
 * Return value: SUCCESS - processed successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_open(const char *buffer, struct zbx_json_parse *jp)
{
	char		*error = NULL;
	zbx_int64_t	len;

	SKIP_WHITESPACE(buffer);

	/* return immediate failure without logging when opening empty string */
	if ('\0' == *buffer)
		return FAIL;

	jp->start = buffer;
	jp->end = NULL;

	if (0 == (len = zbx_json_validate(jp->start, &error)))
	{
		if (NULL != error)
		{
			zbx_set_json_strerror("cannot parse as a valid JSON object: %s", error);
			zbx_free(error);
		}
		else
		{
			zbx_set_json_strerror("cannot parse as a valid JSON object \"%.64s\"", buffer);
		}

		return FAIL;
	}

	jp->end = jp->start + len - 1;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: locate next pair or element                                       *
 *                                                                            *
 * Return value: NULL - no more values                                        *
 *               NOT NULL - pointer to pair or element                        *
 *      {"name",...    or  "array":["name", ... ,1,null]                      *
 * p =   ^                                         ^                          *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_json_next(const struct zbx_json_parse *jp, const char *p)
{
	int	level = 0;
	int	state = 0;	/* 0 - outside string; 1 - inside string */

	if (1 == jp->end - jp->start)	/* empty object or array */
		return NULL;

	if (NULL == p)
	{
		p = jp->start + 1;
		SKIP_WHITESPACE(p);
		return p;
	}

	while (p <= jp->end)
	{
		switch (*p)
		{
			case '"':
				state = (0 == state) ? 1 : 0;
				break;
			case '\\':
				if (1 == state)
					p++;
				break;
			case '[':
			case '{':
				if (0 == state)
					level++;
				break;
			case ']':
			case '}':
				if (0 == state)
				{
					if (0 == level)
						return NULL;
					level--;
				}
				break;
			case ',':
				if (0 == state && 0 == level)
				{
					p++;
					SKIP_WHITESPACE(p);
					return p;
				}
				break;
		}
		p++;
	}

	return NULL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: check if a 4 character sequence is a valid hex number 0000 - FFFF *
 *                                                                            *
 * Parameters:                                                                *
 *      p - pointer to the 1st character                                      *
 *                                                                            *
 * Return value: SUCCEED or FAIL                                              *
 *                                                                            *
 ******************************************************************************/
static int	zbx_is_valid_json_hex(const char *p)
{
	int	i;

	for (i = 0; i < 4; ++i, ++p)
	{
		if (0 == isxdigit(*p))
			return FAIL;
	}

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: convert hexit c ('0'-'9''a'-'f''A'-'F') to number (0-15)          *
 *                                                                            *
 * Parameters:                                                                *
 *      c - char ('0'-'9''a'-'f''A'-'F')                                      *
 *                                                                            *
 * Return value:                                                              *
 *      0-15                                                                  *
 *                                                                            *
 ******************************************************************************/
static unsigned int	zbx_hex2num(char c)
{
	int	res;

	if (c >= 'a')
		res = c - 'a' + 10;	/* a-f */
	else if (c >= 'A')
		res = c - 'A' + 10;	/* A-F */
	else
		res = c - '0';		/* 0-9 */

	return (unsigned int)res;
}

/******************************************************************************
 *                                                                            *
 * Purpose: decodes JSON escape character into UTF-8                          *
 *                                                                            *
 * Parameters: p - [IN/OUT] a pointer to the first character in string        *
 *             bytes - [OUT] a 4-element array where 1 - 4 bytes of character *
 *                     UTF-8 representation are written                       *
 *                                                                            *
 * Return value: number of UTF-8 bytes written into 'bytes' array or          *
 *               0 on error (invalid escape sequence)                         *
 *                                                                            *
 ******************************************************************************/
unsigned int	zbx_json_decode_character(const char **p, unsigned char *bytes)
{
	bytes[0] = '\0';

	switch (**p)
	{
		case '"':
			bytes[0] = '"';
			break;
		case '\\':
			bytes[0] = '\\';
			break;
		case '/':
			bytes[0] = '/';
			break;
		case 'b':
			bytes[0] = '\b';
			break;
		case 'f':
			bytes[0] = '\f';
			break;
		case 'n':
			bytes[0] = '\n';
			break;
		case 'r':
			bytes[0] = '\r';
			break;
		case 't':
			bytes[0] = '\t';
			break;
		default:
			break;
	}

	if ('\0' != bytes[0])
	{
		++*p;
		return 1;
	}

	if ('u' == **p)		/* \u0000 - \uffff */
	{
		unsigned int	num;

		if (FAIL == zbx_is_valid_json_hex(++*p))
			return 0;

		num = zbx_hex2num(**p) << 12;
		num += zbx_hex2num(*(++*p)) << 8;
		num += zbx_hex2num(*(++*p)) << 4;
		num += zbx_hex2num(*(++*p));
		++*p;

		if (0x007f >= num)	/* 0000 - 007f */
		{
			bytes[0] = (unsigned char)num;
			return 1;
		}
		else if (0x07ff >= num)	/* 0080 - 07ff */
		{
			bytes[0] = (unsigned char)(0xc0 | ((num >> 6) & 0x1f));
			bytes[1] = (unsigned char)(0x80 | (num & 0x3f));
			return 2;
		}
		else if (0xd7ff >= num || 0xe000 <= num)	/* 0800 - d7ff or e000 - ffff */
		{
			bytes[0] = (unsigned char)(0xe0 | ((num >> 12) & 0x0f));
			bytes[1] = (unsigned char)(0x80 | ((num >> 6) & 0x3f));
			bytes[2] = (unsigned char)(0x80 | (num & 0x3f));
			return 3;
		}
		else if (0xd800 <= num && num <= 0xdbff)	/* high surrogate d800 - dbff */
		{
			unsigned int	num_lo, uc;

			/* collect the low surrogate */

			if ('\\' != **p || 'u' != *(++*p) || FAIL == zbx_is_valid_json_hex(++*p))
				return 0;

			num_lo = zbx_hex2num(**p) << 12;
			num_lo += zbx_hex2num(*(++*p)) << 8;
			num_lo += zbx_hex2num(*(++*p)) << 4;
			num_lo += zbx_hex2num(*(++*p));
			++*p;

			if (num_lo < 0xdc00 || 0xdfff < num_lo)		/* low surrogate range is dc00 - dfff */
				return 0;

			/* decode surrogate pair */

			uc = 0x010000 + ((num & 0x03ff) << 10) + (num_lo & 0x03ff);

			bytes[0] = (unsigned char)(0xf0 | ((uc >> 18) & 0x07));
			bytes[1] = (unsigned char)(0x80 | ((uc >> 12) & 0x3f));
			bytes[2] = (unsigned char)(0x80 | ((uc >> 6) & 0x3f));
			bytes[3] = (unsigned char)(0x80 | (uc & 0x3f));
			return 4;
		}
		/* error - low surrogate without high surrogate */
	}

	return 0;
}

/******************************************************************************
 *                                                                            *
 * Purpose: copies json name/string value by omitting leading/trailing " and  *
 *          converting escape sequences                                       *
 *                                                                            *
 * Parameters: p     - [IN] a pointer to the next character in string         *
 *             out   - [OUT] the output buffer                                *
 *             size  - [IN] the output buffer size                            *
 *                                                                            *
 * Return value: A pointer to the next character in input string or NULL if   *
 *               string copying failed.                                       *
 *                                                                            *
 ******************************************************************************/
const char	*json_copy_string(const char *p, char *out, size_t size)
{
	char	*start = out;

	if (0 == size)
		return NULL;

	p++;

	while ('\0' != *p)
	{
		unsigned int	nbytes, i;
		unsigned char	uc[4];	/* decoded Unicode character takes 1-4 bytes in UTF-8 */

		switch (*p)
		{
			case '\\':
				++p;
				if (0 == (nbytes = zbx_json_decode_character(&p, uc)))
					return NULL;

				if ((size_t)(out - start) + nbytes >= size)
					return NULL;

				for (i = 0; i < nbytes; ++i)
					*out++ = (char)uc[i];

				break;
			case '"':
				*out = '\0';
				return ++p;
			default:
				*out++ = *p++;
		}

		if ((size_t)(out - start) == size)
			break;
	}

	return NULL;
}

/******************************************************************************
 *                                                                            *
 * Purpose: copies unquoted (numeric, boolean) json value                     *
 *                                                                            *
 * Parameters: p     - [IN] a pointer to the next character in string         *
 *             len   - [IN] the value length                                  *
 *             out   - [OUT] the output buffer                                *
 *             size  - [IN] the output buffer size                            *
 *                                                                            *
 * Return value: A pointer to the next character in input string or NULL if   *
 *               string copying failed.                                       *
 *                                                                            *
 ******************************************************************************/
static const char	*zbx_json_copy_unquoted_value(const char *p, size_t len, char *out, size_t size)
{
	if (size < len + 1)
		return NULL;

	memcpy(out, p, len);
	out[len] = '\0';

	return p + len;
}

const char	*zbx_json_decodevalue(const char *p, char *string, size_t size, zbx_json_type_t *type)
{
	size_t		len;
	zbx_json_type_t	type_local;

	switch (type_local = __zbx_json_type(p))
	{
		case ZBX_JSON_TYPE_ARRAY:
		case ZBX_JSON_TYPE_OBJECT:
		case ZBX_JSON_TYPE_UNKNOWN:
			/* only primitive values are decoded */
			return NULL;
		default:
			if (0 == (len = json_parse_value(p, NULL, 0, NULL)))
				return NULL;
	}

	if (NULL != type)
		*type = type_local;

	switch (type_local)
	{
		case ZBX_JSON_TYPE_STRING:
			return json_copy_string(p, string, size);
		case ZBX_JSON_TYPE_NULL:
			if (0 == size)
				return NULL;
			*string = '\0';
			return p + len;
		default: /* ZBX_JSON_TYPE_INT, ZBX_JSON_TYPE_TRUE, ZBX_JSON_TYPE_FALSE */
			return zbx_json_copy_unquoted_value(p, len, string, size);
	}
}

const char	*zbx_json_decodevalue_dyn(const char *p, char **string, size_t *string_alloc, zbx_json_type_t *type)
{
	size_t		len;
	zbx_json_type_t	type_local;

	switch (type_local = __zbx_json_type(p))
	{
		case ZBX_JSON_TYPE_ARRAY:
		case ZBX_JSON_TYPE_OBJECT:
		case ZBX_JSON_TYPE_UNKNOWN:
			/* only primitive values are decoded */
			return NULL;
		default:
			if (0 == (len = json_parse_value(p, NULL, 0, NULL)))
				return NULL;
	}

	if (*string_alloc <= len)
	{
		*string_alloc = len + 1;
		*string = (char *)zbx_realloc(*string, *string_alloc);
	}

	if (NULL != type)
		*type = type_local;

	switch (type_local)
	{
		case ZBX_JSON_TYPE_STRING:
			return json_copy_string(p, *string, *string_alloc);
		case ZBX_JSON_TYPE_NULL:
			**string = '\0';
			return p + len;
		default: /* ZBX_JSON_TYPE_INT, ZBX_JSON_TYPE_TRUE, ZBX_JSON_TYPE_FALSE */
			return zbx_json_copy_unquoted_value(p, len, *string, *string_alloc);
	}
}

const char	*zbx_json_pair_next(const struct zbx_json_parse *jp, const char *p, char *name, size_t len)
{
	if (NULL == (p = zbx_json_next(jp, p)))
		return NULL;

	if (ZBX_JSON_TYPE_STRING != __zbx_json_type(p))
		return NULL;

	if (NULL == (p = json_copy_string(p, name, len)))
		return NULL;

	SKIP_WHITESPACE(p);

	if (':' != *p++)
		return NULL;

	SKIP_WHITESPACE(p);

	return p;
}

/******************************************************************************
 *                                                                            *
 * Purpose: find pair by name and return pointer to value                     *
 *                                                                            *
 * Return value: pointer to value                                             *
 *        {"name":["a","b",...]}                                              *
 *                ^ - returned pointer                                        *
 *                                                                            *
 ******************************************************************************/
const char	*zbx_json_pair_by_name(const struct zbx_json_parse *jp, const char *name)
{
	char		buffer[MAX_STRING_LEN];
	const char	*p = NULL;

	while (NULL != (p = zbx_json_pair_next(jp, p, buffer, sizeof(buffer))))
		if (0 == strcmp(name, buffer))
			return p;

	zbx_set_json_strerror("cannot find pair with name \"%s\"", name);

	return NULL;
}

const char	*zbx_json_next_value(const struct zbx_json_parse *jp, const char *p, char *string, size_t len,
		zbx_json_type_t *type)
{
	if (NULL == (p = zbx_json_next(jp, p)))
		return NULL;

	return zbx_json_decodevalue(p, string, len, type);
}

const char	*zbx_json_next_value_dyn(const struct zbx_json_parse *jp, const char *p, char **string,
		size_t *string_alloc, zbx_json_type_t *type)
{
	if (NULL == (p = zbx_json_next(jp, p)))
		return NULL;

	return zbx_json_decodevalue_dyn(p, string, string_alloc, type);
}

/******************************************************************************
 *                                                                            *
 * Purpose: return value by pair name                                         *
 *                                                                            *
 * Return value: SUCCEED - if value successfully parsed, FAIL - otherwise     *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_value_by_name(const struct zbx_json_parse *jp, const char *name, char *string, size_t len,
		zbx_json_type_t *type)
{
	const char	*p;

	if (NULL == (p = zbx_json_pair_by_name(jp, name)))
		return FAIL;

	if (NULL == zbx_json_decodevalue(p, string, len, type))
		return FAIL;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Purpose: return value by pair name                                         *
 *                                                                            *
 * Return value: SUCCEED - if value successfully parsed, FAIL - otherwise     *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_value_by_name_dyn(const struct zbx_json_parse *jp, const char *name, char **string,
		size_t *string_alloc, zbx_json_type_t *type)
{
	const char	*p;

	if (NULL == (p = zbx_json_pair_by_name(jp, name)))
		return FAIL;

	if (NULL == zbx_json_decodevalue_dyn(p, string, string_alloc, type))
		return FAIL;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Return value: SUCCESS - processed successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_brackets_open(const char *p, struct zbx_json_parse *jp)
{
	if (NULL == (jp->end = __zbx_json_rbracket(p)))
	{
		zbx_set_json_strerror("cannot open JSON object or array \"%.64s\"", p);
		return FAIL;
	}

	SKIP_WHITESPACE(p);

	jp->start = p;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Return value: SUCCESS - processed successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_brackets_by_name(const struct zbx_json_parse *jp, const char *name, struct zbx_json_parse *out)
{
	const char	*p;

	if (NULL == (p = zbx_json_pair_by_name(jp, name)))
		return FAIL;

	if (FAIL == zbx_json_brackets_open(p, out))
		return FAIL;

	return SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Return value: SUCCESS - if object is empty                                 *
 *               FAIL - if object contains data                               *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_object_is_empty(const struct zbx_json_parse *jp)
{
	return jp->end - jp->start > 1 ? FAIL : SUCCEED;
}

/******************************************************************************
 *                                                                            *
 * Return value: number of elements in zbx_json_parse object                  *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_count(const struct zbx_json_parse *jp)
{
	int		num = 0;
	const char	*p = NULL;

	while (NULL != (p = zbx_json_next(jp, p)))
		num++;

	return num;
}

/******************************************************************************
 *                                                                            *
 * Purpose: opens an object by definite json path                             *
 *                                                                            *
 * Return value: SUCCESS - processed successfully                             *
 *               FAIL - an error occurred                                     *
 *                                                                            *
 * Comments: Only direct path to single object in dot or bracket notation     *
 *           is supported.                                                    *
 *                                                                            *
 ******************************************************************************/
int	zbx_json_open_path(const struct zbx_json_parse *jp, const char *path, struct zbx_json_parse *out)
{
	int			i, ret = FAIL;
	struct zbx_json_parse	object;
	zbx_jsonpath_t		jsonpath;

	object = *jp;

	if (FAIL == zbx_jsonpath_compile(path, &jsonpath))
		return FAIL;

	if (0 == jsonpath.definite)
	{
		zbx_set_json_strerror("cannot use indefinite path when opening sub element");
		goto out;
	}

	for (i = 0; i < jsonpath.segments_num; i++)
	{
		const char		*p;
		zbx_jsonpath_segment_t	*segment = &jsonpath.segments[i];

		if (ZBX_JSONPATH_SEGMENT_MATCH_LIST != segment->type)
		{
			zbx_set_json_strerror("jsonpath segment %d is not a name or index", i + 1);
			goto out;
		}

		if (ZBX_JSONPATH_LIST_INDEX == segment->data.list.type)
		{
			int	index;

			if ('[' != *object.start)
				goto out;

			memcpy(&index, segment->data.list.values->data, sizeof(int));

			for (p = NULL; NULL != (p = zbx_json_next(&object, p)) && 0 != index; index--)
				;

			if (0 != index || NULL == p)
			{
				zbx_set_json_strerror("array index out of bounds in jsonpath segment %d", i + 1);
				goto out;
			}
		}
		else
		{
			if (NULL == (p = zbx_json_pair_by_name(&object, (char *)&segment->data.list.values->data)))
			{
				zbx_set_json_strerror("object not found in jsonpath segment %d", i + 1);
				goto out;
			}
		}

		object.start = p;

		if (NULL == (object.end = __zbx_json_rbracket(p)))
			object.end = p + json_parse_value(p, NULL, 0, NULL) - 1;
	}

	*out = object;
	ret = SUCCEED;
out:
	zbx_jsonpath_clear(&jsonpath);
	return ret;
}

zbx_json_type_t	zbx_json_valuetype(const char *p)
{
	return __zbx_json_type(p);
}

/******************************************************************************
 *                                                                            *
 * Purpose: creates a copy of the specified JSON structure                    *
 *                                                                            *
 * Return value: The copied JSON structure.                                   *
 *                                                                            *
 ******************************************************************************/
struct zbx_json	*zbx_json_clone(const struct zbx_json *src)
{
	struct zbx_json	*dst;

	dst = (struct zbx_json *)zbx_malloc(NULL, sizeof(struct zbx_json));
	*dst = *src;

	if (src->buffer != src->buf_stat)
	{
		dst->buffer = (char *)zbx_malloc(NULL, dst->buffer_allocated);
		memcpy(dst->buffer, src->buffer, src->buffer_size);
	}
	else
		dst->buffer = dst->buf_stat;

	return dst;
}
