/*
** Copyright (C) 2001-2025 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 .
**/
#include "zbxxml.h"
#include "zbxalgo.h"
#include "zbxjson.h"
#ifdef HAVE_LIBXML2
# include "zbxvariant.h"
# include "zbxstr.h"
# include
# include
#endif
typedef struct _zbx_xml_node_t zbx_xml_node_t;
ZBX_PTR_VECTOR_DECL(xml_node_ptr, zbx_xml_node_t *)
struct _zbx_xml_node_t
{
char *name;
char *value;
zbx_vector_str_t attributes;
zbx_vector_xml_node_ptr_t chnodes;
int is_array;
};
ZBX_PTR_VECTOR_IMPL(xml_node_ptr, zbx_xml_node_t *)
static char data_static[ZBX_MAX_B64_LEN];
/******************************************************************************
* *
* Purpose: get DATA from DATA *
* *
* !!! Attention: static !!! Not thread-safe *
* *
******************************************************************************/
int zbx_xml_get_data_dyn(const char *xml, const char *tag, char **data)
{
size_t len, sz;
const char *start, *end;
sz = sizeof(data_static);
len = zbx_snprintf(data_static, sz, "<%s>", tag);
if (NULL == (start = strstr(xml, data_static)))
return FAIL;
zbx_snprintf(data_static, sz, "%s>", tag);
if (NULL == (end = strstr(xml, data_static)))
return FAIL;
if (end < start)
return FAIL;
start += len;
len = end - start;
if (len > sz - 1)
*data = (char *)zbx_malloc(*data, len + 1);
else
*data = data_static;
zbx_strlcpy(*data, start, len + 1);
return SUCCEED;
}
/******************************************************************************
* *
* !!! Attention: static !!! Not thread-safe *
* *
******************************************************************************/
void zbx_xml_free_data_dyn(char **data)
{
if (*data == data_static)
*data = NULL;
else
zbx_free(*data);
}
/******************************************************************************
* *
* Purpose: replace <> symbols in string with <> so the resulting *
* string can be written into xml field *
* *
* Parameters: data - [IN] the input string *
* *
* Return value: an allocated string containing escaped input string *
* *
* Comments: The caller must free the returned string after it has been used. *
* *
******************************************************************************/
char *zbx_xml_escape_dyn(const char *data)
{
char *out, *ptr_out;
const char *ptr_in;
int size = 0;
if (NULL == data)
return zbx_strdup(NULL, "");
for (ptr_in = data; '\0' != *ptr_in; ptr_in++)
{
switch (*ptr_in)
{
case '<':
case '>':
size += 4;
break;
case '&':
size += 5;
break;
case '"':
case '\'':
size += 6;
break;
default:
size++;
}
}
size++;
out = (char *)zbx_malloc(NULL, size);
for (ptr_out = out, ptr_in = data; '\0' != *ptr_in; ptr_in++)
{
switch (*ptr_in)
{
case '<':
*ptr_out++ = '&';
*ptr_out++ = 'l';
*ptr_out++ = 't';
*ptr_out++ = ';';
break;
case '>':
*ptr_out++ = '&';
*ptr_out++ = 'g';
*ptr_out++ = 't';
*ptr_out++ = ';';
break;
case '&':
*ptr_out++ = '&';
*ptr_out++ = 'a';
*ptr_out++ = 'm';
*ptr_out++ = 'p';
*ptr_out++ = ';';
break;
case '"':
*ptr_out++ = '&';
*ptr_out++ = 'q';
*ptr_out++ = 'u';
*ptr_out++ = 'o';
*ptr_out++ = 't';
*ptr_out++ = ';';
break;
case '\'':
*ptr_out++ = '&';
*ptr_out++ = 'a';
*ptr_out++ = 'p';
*ptr_out++ = 'o';
*ptr_out++ = 's';
*ptr_out++ = ';';
break;
default:
*ptr_out++ = *ptr_in;
}
}
*ptr_out = '\0';
return out;
}
/**********************************************************************************
* *
* Purpose: calculate a string size after symbols escaping *
* *
* Parameters: string - [IN] the string to check *
* *
* Return value: new size of the string *
* *
**********************************************************************************/
static size_t zbx_xml_escape_xpath_stringsize(const char *string)
{
size_t len = 0;
const char *sptr;
if (NULL == string)
return 0;
for (sptr = string; '\0' != *sptr; sptr++)
len += (('"' == *sptr) ? 2 : 1);
return len;
}
/**********************************************************************************
* *
* Purpose: replace " symbol in string with "" *
* *
* Parameters: string - [IN] the xpath string to escape *
* p - [OUT] the result string *
* *
**********************************************************************************/
static void zbx_xml_escape_xpath_string(char *p, const char *string)
{
const char *sptr = string;
while ('\0' != *sptr)
{
if ('"' == *sptr)
*p++ = '"';
*p++ = *sptr++;
}
}
/**********************************************************************************
* *
* Purpose: escaping of symbols for using in xpath expression *
* *
* Parameters: data - [IN/OUT] the string to update *
* *
**********************************************************************************/
void zbx_xml_escape_xpath(char **data)
{
size_t size;
char *buffer;
if (0 == (size = zbx_xml_escape_xpath_stringsize(*data)))
return;
buffer = zbx_malloc(NULL, size + 1);
buffer[size] = '\0';
zbx_xml_escape_xpath_string(buffer, *data);
zbx_free(*data);
*data = buffer;
}
static int query_xpath(zbx_variant_t *value, const char *params, int *is_empty, char **errmsg)
{
#ifndef HAVE_LIBXML2
ZBX_UNUSED(value);
ZBX_UNUSED(params);
ZBX_UNUSED(is_empty);
*errmsg = zbx_dsprintf(*errmsg, "Zabbix was compiled without libxml2 support");
return FAIL;
#else
int ret = FAIL;
char buffer[32], *ptr;
xmlDoc *doc = NULL;
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNodeSetPtr nodeset;
const xmlError *pErr;
xmlBufferPtr xmlBufferLocal;
zbx_fs_size_t len = strlen(value->data.str);
if (NULL == (doc = xmlReadMemory(value->data.str, len, "noname.xml", NULL, XML_PARSE_NOERROR)))
{
if (NULL != (pErr = xmlGetLastError()))
{
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value from data of length " ZBX_FS_SIZE_T
" bytes: %s", len, pErr->message);
xmlResetLastError();
}
else
{
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value from data of length " ZBX_FS_SIZE_T
" bytes", len);
}
return FAIL;
}
xpathCtx = xmlXPathNewContext(doc);
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)params, xpathCtx)))
{
if (NULL != (pErr = xmlGetLastError()))
{
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xpath: %s", pErr->message);
xmlResetLastError();
}
else
{
*errmsg = zbx_strdup(*errmsg, "cannot parse xpath");
}
goto out;
}
/* set is_empty before switch because of different possible XPATH types */
if (NULL != is_empty)
*is_empty = FAIL;
switch (xpathObj->type)
{
case XPATH_NODESET:
if (NULL == (xmlBufferLocal = xmlBufferCreate()))
break;
if (0 == xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
{
nodeset = xpathObj->nodesetval;
if (0 == nodeset->nodeNr && NULL != is_empty)
*is_empty = SUCCEED;
for (int i = 0; i < nodeset->nodeNr; i++)
xmlNodeDump(xmlBufferLocal, doc, nodeset->nodeTab[i], 0, 0);
}
else if (NULL != is_empty)
*is_empty = SUCCEED;
zbx_variant_clear(value);
zbx_variant_set_str(value, zbx_strdup(NULL, (const char *)xmlBufferLocal->content));
xmlBufferFree(xmlBufferLocal);
ret = SUCCEED;
break;
case XPATH_STRING:
zbx_variant_clear(value);
zbx_variant_set_str(value, zbx_strdup(NULL, (const char *)xpathObj->stringval));
ret = SUCCEED;
break;
case XPATH_BOOLEAN:
zbx_variant_clear(value);
zbx_variant_set_str(value, zbx_dsprintf(NULL, "%d", xpathObj->boolval));
ret = SUCCEED;
break;
case XPATH_NUMBER:
zbx_variant_clear(value);
zbx_snprintf(buffer, sizeof(buffer), ZBX_FS_DBL, xpathObj->floatval);
/* check for nan/inf values - isnan(), isinf() is not supported by c89/90 */
/* so simply check the result starts with digit (accounting for -inf) */
if ('-' == *(ptr = buffer))
ptr++;
if (0 != isdigit(*ptr))
{
zbx_del_zeros(buffer);
zbx_variant_set_str(value, zbx_strdup(NULL, buffer));
ret = SUCCEED;
}
else
*errmsg = zbx_strdup(*errmsg, "Invalid numeric value");
break;
default:
*errmsg = zbx_dsprintf(*errmsg, "Unknown XPath object type %d", (int)xpathObj->type);
break;
}
out:
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
xmlFreeDoc(doc);
return ret;
#endif
}
/******************************************************************************
* *
* Purpose: execute xpath query *
* *
* Parameters: value - [IN/OUT] the value to process *
* params - [IN] the operation parameters *
* errmsg - [OUT] error message *
* *
* Return value: SUCCEED - the value was processed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_query_xpath(zbx_variant_t *value, const char *params, char **errmsg)
{
return query_xpath(value, params, NULL, errmsg);
}
/******************************************************************************
* *
* Purpose: execute xpath query and return the contents of the result *
* *
* Parameters: value - [IN/OUT] the value to process *
* params - [IN] the operation parameters *
* is_empty - [OUT] whether the xpath returned empty nodeset *
* errmsg - [OUT] error message *
* *
* Return value: SUCCEED - the value was processed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_query_xpath_contents(zbx_variant_t *value, const char *params, int *is_empty, char **errmsg)
{
return query_xpath(value, params, is_empty, errmsg);
}
#ifdef HAVE_LIBXML2
#define XML_TEXT_NAME "text"
#define XML_CDATA_NAME "cdata"
#define XML_TEXT_TAG "#text"
#define XML_JSON_TRUE 1
#define XML_JSON_FALSE 0
/******************************************************************************
* *
* Purpose: compare two xml nodes by name *
* *
* Comments: This function is used to sort xml nodes by name *
* *
******************************************************************************/
static int compare_xml_nodes_by_name(const void *d1, const void *d2)
{
zbx_xml_node_t *p1 = *(zbx_xml_node_t * const *)d1;
zbx_xml_node_t *p2 = *(zbx_xml_node_t * const *)d2;
return strcmp(p1->name, p2->name);
}
static void zbx_xml_node_free(zbx_xml_node_t *node)
{
zbx_vector_xml_node_ptr_clear_ext(&node->chnodes, zbx_xml_node_free);
zbx_vector_xml_node_ptr_destroy(&node->chnodes);
zbx_vector_str_clear_ext(&node->attributes, zbx_str_free);
zbx_vector_str_destroy(&node->attributes);
zbx_free(node->name);
zbx_free(node->value);
zbx_free(node);
}
/******************************************************************************
* *
* Purpose: to collect content of XML document nodes into vector *
* *
* Parameters: xml_node - [IN] parent XML node structure *
* nodes - [OUT] vector of child XML nodes *
* *
******************************************************************************/
static void xml_to_vector(xmlNode *xml_node, zbx_vector_xml_node_ptr_t *nodes)
{
int index;
xmlChar *value;
xmlAttr *attr;
zbx_vector_xml_node_ptr_t nodes_local;
zbx_vector_xml_node_ptr_create(&nodes_local);
for (; NULL != xml_node; xml_node = xml_node->next)
{
zbx_xml_node_t *node;
node = (zbx_xml_node_t *)zbx_malloc(NULL, sizeof(zbx_xml_node_t));
if (NULL != xml_node->name)
node->name = zbx_strdup(NULL, (const char *)xml_node->name);
else
node->name = NULL;
node->value = NULL;
node->is_array = XML_JSON_FALSE;
zbx_vector_xml_node_ptr_create(&node->chnodes);
zbx_vector_str_create(&node->attributes);
switch (xml_node->type)
{
case XML_TEXT_NODE:
if (NULL == (value = xmlNodeGetContent(xml_node)))
break;
node->value = zbx_strdup(NULL, (const char *)value);
xmlFree(value);
break;
case XML_CDATA_SECTION_NODE:
if (NULL == (value = xmlNodeGetContent(xml_node)))
break;
node->value = zbx_strdup(NULL, (const char *)value);
node->name = zbx_strdup(node->name, XML_CDATA_NAME);
xmlFree(value);
break;
case XML_ELEMENT_NODE:
for (attr = xml_node->properties; NULL != attr; attr = attr->next)
{
char *attr_name = NULL;
size_t attr_name_alloc = 0, attr_name_offset = 0;
if (NULL == attr->name)
continue;
zbx_snprintf_alloc(&attr_name, &attr_name_alloc, &attr_name_offset, "@%s",
attr->name);
zbx_vector_str_append(&node->attributes, attr_name);
if (NULL != (value = xmlGetProp(xml_node, attr->name)))
{
zbx_vector_str_append(&node->attributes, zbx_strdup(NULL,
(const char *)value));
xmlFree(value);
}
else
zbx_vector_str_append(&node->attributes, (char *)NULL);
}
break;
default:
zabbix_log(LOG_LEVEL_DEBUG, "Unsupported XML node type %d, ignored",
(int)xml_node->type);
zbx_xml_node_free(node);
node = NULL;
break;
}
if (NULL != node)
{
xml_to_vector(xml_node->children, &node->chnodes);
zbx_vector_xml_node_ptr_append(&nodes_local, node);
}
}
zbx_vector_xml_node_ptr_reserve(nodes, (size_t)nodes_local.values_num);
while (0 < nodes_local.values_num)
{
zbx_xml_node_t *first_node, *next_node;
first_node = nodes_local.values[0];
zbx_vector_xml_node_ptr_remove(&nodes_local, 0);
zbx_vector_xml_node_ptr_append(nodes, first_node);
while (FAIL != (index = zbx_vector_xml_node_ptr_search(&nodes_local, first_node,
compare_xml_nodes_by_name)))
{
first_node->is_array = XML_JSON_TRUE;
next_node = nodes_local.values[index];
next_node->is_array = XML_JSON_TRUE;
zbx_vector_xml_node_ptr_remove(&nodes_local, index);
zbx_vector_xml_node_ptr_append(nodes, next_node);
}
}
zbx_vector_xml_node_ptr_clear_ext(&nodes_local, zbx_xml_node_free);
zbx_vector_xml_node_ptr_destroy(&nodes_local);
}
/******************************************************************************
* *
* Purpose: to check if node is leaf node with text content *
* *
* Parameters: node - [IN] node structure *
* *
* Return value: SUCCEED - node has text content *
* FAIL - node has no content *
* *
******************************************************************************/
static int is_data(zbx_xml_node_t *node)
{
if (0 == node->chnodes.values_num &&
(0 == strcmp(XML_TEXT_NAME, node->name) || 0 == strcmp(XML_CDATA_NAME, node->name)))
{
return SUCCEED;
}
return FAIL;
}
/******************************************************************************
* *
* Purpose: to write content of vector into JSON document *
* *
* Parameters: nodes - [IN] vector of nodes *
* json - [IN/OUT] JSON structure *
* text - [OUT] text content for given node *
* *
******************************************************************************/
static void vector_to_json(zbx_vector_xml_node_ptr_t *nodes, struct zbx_json *json, char **text)
{
int i, j, is_object, arr_cnt = 0;
char *tag, *out_text, *arr_name = NULL;
zbx_xml_node_t *node;
*text = NULL;
for (i = 0; i < nodes->values_num; i++)
{
node = nodes->values[i];
if ((XML_JSON_FALSE == node->is_array && 0 != arr_cnt) || (XML_JSON_TRUE == node->is_array &&
NULL != arr_name && 0 != strcmp(arr_name, node->name)))
{
if (FAIL == zbx_json_close(json))
THIS_SHOULD_NEVER_HAPPEN;
arr_name = NULL;
arr_cnt = 0;
}
if (XML_JSON_TRUE == node->is_array)
{
if (0 == arr_cnt)
{
zbx_json_addarray(json, node->name);
arr_name = node->name;
}
arr_cnt++;
}
is_object = XML_JSON_FALSE;
if (0 != node->chnodes.values_num)
{
zbx_xml_node_t *chnode;
/* if first child node is not data node that is enough to recognize current node as object */
chnode = node->chnodes.values[0];
if (FAIL == is_data(chnode))
is_object = XML_JSON_TRUE;
}
if (0 != node->attributes.values_num)
is_object = XML_JSON_TRUE;
if (XML_JSON_TRUE == is_object)
zbx_json_addobject(json, 0 != arr_cnt ? NULL : node->name);
for (j = 0; j < node->attributes.values_num; j += 2)
{
zbx_json_addstring(json, node->attributes.values[j], node->attributes.values[j + 1],
ZBX_JSON_TYPE_STRING);
}
vector_to_json(&node->chnodes, json, &out_text);
*text = node->value;
if (NULL != out_text || (XML_JSON_FALSE == is_object && FAIL == is_data(node)))
{
if (0 != node->attributes.values_num)
tag = XML_TEXT_TAG;
else if (0 != arr_cnt)
tag = NULL;
else
tag = node->name;
zbx_json_addstring(json, tag, out_text, ZBX_JSON_TYPE_STRING);
}
if (XML_JSON_TRUE == is_object && FAIL == zbx_json_close(json))
THIS_SHOULD_NEVER_HAPPEN;
}
if (0 != arr_cnt && FAIL == zbx_json_close(json))
THIS_SHOULD_NEVER_HAPPEN;
}
#endif /* HAVE_LIBXML2 */
#ifdef HAVE_LIBXML2
/******************************************************************************
* *
* Purpose: to create xmlDoc and it's root node for input data *
* *
* Parameters: data - [IN] input data *
* options - [IN] XML options *
* maxerrlen - [IN] the size of error buffer, -1 to ignore *
* xml_doc - [OUT] pointer to xmlDoc structure *
* root_node - [OUT] pointer to xmlNode structure *
* errmsg - [OUTY] error message *
* *
* Return value: SUCCEED - xmlDoc and root node structure created *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_open_xml(char *data, int options, int maxerrlen, void **xml_doc, void **root_node, char **errmsg)
{
const xmlError *pErr;
if (NULL == (*xml_doc = xmlReadMemory(data, strlen(data), "noname.xml", NULL, options)))
{
if (NULL != (pErr = xmlGetLastError()))
{
const char *pmessage;
if (NULL != strstr(pErr->message, "use XML_PARSE_HUGE option"))
pmessage = "Excessive depth in XML document";
else
pmessage = pErr->message;
if (0 > maxerrlen)
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value: %s", pmessage);
else
zbx_snprintf(*errmsg, (size_t)maxerrlen, "Cannot parse XML value: %s", pmessage);
}
else
{
if (0 > maxerrlen)
*errmsg = zbx_strdup(*errmsg, "cannot parse xml value");
else
zbx_snprintf(*errmsg, (size_t)maxerrlen, "Cannot parse XML value");
}
return FAIL;
}
if (NULL == (*root_node = xmlDocGetRootElement((xmlDoc *)*xml_doc)))
{
if (0 > maxerrlen)
*errmsg = zbx_dsprintf(*errmsg, "Cannot parse XML root");
else
zbx_snprintf(*errmsg, (size_t)maxerrlen, "Cannot parse XML root");
return FAIL;
}
return SUCCEED;
}
/******************************************************************************
* *
* Purpose: to check xml memory to be valid *
* *
* Parameters: mem - [IN] pointer to memory *
* maxerrlen - [IN] the size of error buffer, -1 to ignore *
* errmsg - [OUTY] error message *
* *
* Return value: SUCCEED - xml memory is not NULL *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_check_xml_memory(char *mem, int maxerrlen, char **errmsg)
{
const xmlError *pErr;
if (NULL == mem)
{
if (NULL != (pErr = xmlGetLastError()))
if (0 > maxerrlen)
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value: %s", pErr->message);
else
zbx_snprintf(*errmsg, (size_t)maxerrlen, "Cannot save XML: %s", pErr->message);
else
if (0 > maxerrlen)
*errmsg = zbx_strdup(*errmsg, "cannot parse xml value");
else
zbx_snprintf(*errmsg, (size_t)maxerrlen, "Cannot save XML");
return FAIL;
}
return SUCCEED;
}
#endif
/******************************************************************************
* *
* Purpose: convert XML format value to JSON format *
* *
* Parameters: xml_data - [IN] the XML data to process *
* jstr - [OUT] the JSON output *
* errmsg - [OUT] error message *
* *
* Return value: SUCCEED - the value was processed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_xml_to_json(char *xml_data, char **jstr, char **errmsg)
{
#ifndef HAVE_LIBXML2
ZBX_UNUSED(xml_data);
ZBX_UNUSED(jstr);
*errmsg = zbx_dsprintf(*errmsg, "Zabbix was compiled without libxml2 support");
return FAIL;
#else
xmlDoc *doc = NULL;
xmlNode *node;
int ret = FAIL;
if (FAIL == zbx_open_xml(xml_data, XML_PARSE_NOBLANKS, -1, (void **)&doc, (void **)&node, errmsg))
{
if (NULL == doc)
goto exit;
if (NULL == node)
goto clean;
}
ret = zbx_xmlnode_to_json((void *)node, jstr);
clean:
xmlFreeDoc(doc);
exit:
return ret;
#endif /* HAVE_LIBXML2 */
}
/******************************************************************************
* *
* Purpose: convert XML format value to JSON format *
* *
* Parameters: xml_node - [IN] the XML data to process *
* jstr - [OUT] the JSON output *
* *
* Return value: SUCCEED - the value was processed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_xmlnode_to_json(void *xml_node, char **jstr)
{
#ifndef HAVE_LIBXML2
ZBX_UNUSED(xml_node);
ZBX_UNUSED(jstr);
return FAIL;
#else
struct zbx_json json;
zbx_vector_xml_node_ptr_t nodes;
char *out;
xmlNode *node = (xmlNode*)xml_node;
zbx_json_init(&json, ZBX_JSON_STAT_BUF_LEN);
zbx_vector_xml_node_ptr_create(&nodes);
xml_to_vector(node, &nodes);
vector_to_json(&nodes, &json, &out);
*jstr = zbx_strdup(*jstr, json.buffer);
zbx_vector_xml_node_ptr_clear_ext(&nodes, zbx_xml_node_free);
zbx_vector_xml_node_ptr_destroy(&nodes);
zbx_json_free(&json);
return SUCCEED;
#endif /* HAVE_LIBXML2 */
}
#ifdef HAVE_LIBXML2
/******************************************************************************
* *
* Purpose: to write content of JSON document into XML node *
* *
* Parameters: jp - [IN] JSON parse structure *
* arr_name - [IN] name of parent array *
* deep - [IN] node depth level *
* doc - [IN/OUT] xml document structure *
* parent_node - [IN/OUT] parent XML node *
* attr - [OUT] node attribute name *
* attr_val - [OUT] node attribute value *
* text - [OUT] node content *
* *
******************************************************************************/
static void json_to_xmlnode(struct zbx_json_parse *jp, char *arr_name, int deep, xmlDoc *doc, xmlNode *parent_node,
char **attr, char **attr_val, char **text)
{
const char *json_string_ptr = NULL, *json_string_ptr_old = NULL;
char *array_loc, *pname, name[MAX_STRING_LEN], value[MAX_STRING_LEN], *attr_loc = NULL,
*attr_val_loc = NULL, *text_loc = NULL, *pvalue = NULL;
int set_attr, set_text, idx = 0;
zbx_json_type_t type;
xmlNode *node;
struct zbx_json_parse jp_data;
do
{
set_attr = 0;
set_text = 0;
array_loc = NULL;
pname = NULL;
if (NULL != (json_string_ptr = zbx_json_pair_next(jp, json_string_ptr, name, sizeof(name))))
{
pname = name;
if (NULL == zbx_json_decodevalue(json_string_ptr, value, sizeof(value), &type))
type = zbx_json_valuetype(json_string_ptr);
else
pvalue = zbx_xml_escape_dyn(value);
if ('@' == name[0])
set_attr = 1;
else if (0 == strcmp(name, XML_TEXT_TAG))
set_text = 1;
}
else
{
json_string_ptr = json_string_ptr_old;
if (NULL != (json_string_ptr = zbx_json_next_value(jp, json_string_ptr, value, sizeof(value),
&type)))
{
pvalue = zbx_xml_escape_dyn(value);
}
else
{
json_string_ptr = json_string_ptr_old;
if (NULL != (json_string_ptr = zbx_json_next(jp, json_string_ptr)))
type = zbx_json_valuetype(json_string_ptr);
}
}
json_string_ptr_old = json_string_ptr;
if (0 != set_attr)
{
*attr = zbx_strdup(*attr, &name[1]);
if (NULL != pvalue)
*attr_val = zbx_strdup(*attr_val, pvalue);
}
else if (0 != set_text && NULL != pvalue)
{
*text = zbx_strdup(*text, pvalue);
}
else if (NULL != json_string_ptr)
{
pname = (NULL == arr_name) ? pname : arr_name;
node = NULL;
if (0 == deep && 0 < idx)
break;
if (ZBX_JSON_TYPE_ARRAY == type)
{
array_loc = name;
node = parent_node;
}
else if (ZBX_JSON_TYPE_OBJECT == type || ZBX_JSON_TYPE_UNKNOWN == type)
{
node = xmlNewDocNode(doc, NULL, (xmlChar *)pname, NULL);
}
else
node = xmlNewDocNode(doc, NULL, (xmlChar *)pname, (xmlChar *)pvalue);
if (0 == deep)
{
if (NULL != node)
xmlDocSetRootElement(doc, node);
else
break;
}
else
{
if (NULL != node && node != parent_node)
node = xmlAddChild(parent_node, node);
}
if (SUCCEED == zbx_json_brackets_open(json_string_ptr, &jp_data))
{
json_to_xmlnode(&jp_data, array_loc, deep + 1, doc, node, &attr_loc, &attr_val_loc,
&text_loc);
}
}
if (NULL != attr_loc)
xmlNewProp(node, (xmlChar *)attr_loc, (xmlChar *)attr_val_loc);
if (NULL != text_loc)
xmlNodeSetContent(node, (xmlChar *)text_loc);
zbx_free(attr_loc);
zbx_free(attr_val_loc);
zbx_free(text_loc);
zbx_free(pvalue);
idx++;
}
while (NULL != json_string_ptr);
zbx_free(pvalue);
}
#endif /* HAVE_LIBXML2 */
/******************************************************************************
* *
* Purpose: convert JSON format value to XML format *
* *
* Parameters: json_data - [IN] the JSON data to process *
* xstr - [OUT] the XML output *
* errmsg - [OUT] error message *
* *
* Return value: SUCCEED - the value was processed successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_json_to_xml(char *json_data, char **xstr, char **errmsg)
{
#ifndef HAVE_LIBXML2
ZBX_UNUSED(json_data);
ZBX_UNUSED(xstr);
*errmsg = zbx_dsprintf(*errmsg, "Zabbix was compiled without libxml2 support");
return FAIL;
#else
char *attr = NULL, *attr_val = NULL, *text = NULL;
int size, ret = FAIL;
struct zbx_json_parse jp;
xmlDoc *doc = NULL;
const xmlError *pErr;
xmlChar *xmem;
if (NULL == (doc = xmlNewDoc(BAD_CAST XML_DEFAULT_VERSION)))
{
if (NULL != (pErr = xmlGetLastError()))
*errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value: %s", pErr->message);
else
*errmsg = zbx_strdup(*errmsg, "cannot parse xml value");
goto exit;
}
if (SUCCEED != zbx_json_open(json_data, &jp))
{
*errmsg = zbx_strdup(*errmsg, zbx_json_strerror());
goto clean;
}
json_to_xmlnode(&jp, NULL, 0, doc, NULL, &attr, &attr_val, &text);
xmlDocDumpMemory(doc, &xmem, &size);
zbx_free(text);
zbx_free(attr_val);
zbx_free(attr);
if (FAIL == zbx_check_xml_memory((char *)xmem, -1, errmsg))
goto clean;
*xstr = zbx_malloc(*xstr, (size_t)size + 1);
memcpy(*xstr, (const char *)xmem, (size_t)size + 1);
xmlFree(xmem);
ret = SUCCEED;
clean:
xmlFreeDoc(doc);
exit:
return ret;
#endif /* HAVE_LIBXML2 */
}
#ifdef HAVE_LIBXML2
typedef struct
{
char *buf;
size_t len;
}
zbx_libxml_error_t;
/******************************************************************************
* *
* Purpose: libxml2 callback function for error handle *
* *
* Parameters: user_data - [IN/OUT] the user context *
* err - [IN] the libxml2 error message *
* *
******************************************************************************/
#if 21200 > LIBXML_VERSION /* version 2.12.0 */
static void libxml_handle_error_xpath_check(void *user_data, xmlErrorPtr err)
#else
static void libxml_handle_error_xpath_check(void *user_data, const xmlError *err)
#endif
{
zbx_libxml_error_t *err_ctx;
if (NULL == user_data)
return;
err_ctx = (zbx_libxml_error_t *)user_data;
zbx_strlcat(err_ctx->buf, err->message, err_ctx->len);
if (NULL != err->str1)
zbx_strlcat(err_ctx->buf, err->str1, err_ctx->len);
if (NULL != err->str2)
zbx_strlcat(err_ctx->buf, err->str2, err_ctx->len);
if (NULL != err->str3)
zbx_strlcat(err_ctx->buf, err->str3, err_ctx->len);
}
#endif
/******************************************************************************
* *
* Purpose: validate xpath string *
* *
* Parameters: xpath - [IN] the xpath value *
* error - [OUT] the error message buffer *
* errlen - [IN] the size of error message buffer *
* *
* Return value: SUCCEED - the xpath component was parsed successfully *
* FAIL - xpath parsing error *
* *
******************************************************************************/
int zbx_xml_xpath_check(const char *xpath, char *error, size_t errlen)
{
#ifndef HAVE_LIBXML2
ZBX_UNUSED(xpath);
ZBX_UNUSED(error);
ZBX_UNUSED(errlen);
return FAIL;
#else
zbx_libxml_error_t err;
xmlXPathContextPtr ctx;
xmlXPathCompExprPtr p;
err.buf = error;
err.len = errlen;
ctx = xmlXPathNewContext(NULL);
xmlSetStructuredErrorFunc(&err, &libxml_handle_error_xpath_check);
p = xmlXPathCtxtCompile(ctx, (const xmlChar *)xpath);
xmlSetStructuredErrorFunc(NULL, NULL);
if (NULL == p)
{
xmlXPathFreeContext(ctx);
return FAIL;
}
xmlXPathFreeCompExpr(p);
xmlXPathFreeContext(ctx);
return SUCCEED;
#endif
}
#if defined(HAVE_LIBXML2) && defined(HAVE_LIBCURL)
/******************************************************************************
* *
* Purpose: populate array of values from an xml data *
* *
* Parameters: xdoc - [IN] XML document *
* xpath - [IN] XML XPath *
* values - [OUT] list of requested values *
* *
* Return: Upon successful completion the function return SUCCEED. *
* Otherwise, FAIL is returned. *
* *
******************************************************************************/
int zbx_xml_read_values(xmlDoc *xdoc, const char *xpath, zbx_vector_str_t *values)
{
return zbx_xml_node_read_values(xdoc, NULL, xpath, values);
}
/******************************************************************************
* *
* Purpose: populate array of values from an xml data *
* *
* Parameters: xdoc - [IN] XML document *
* node - [IN] the XML node *
* xpath - [IN] XML XPath *
* values - [OUT] list of requested values *
* *
* Return: Upon successful completion the function return SUCCEED. *
* Otherwise, FAIL is returned. *
* *
******************************************************************************/
int zbx_xml_node_read_values(xmlDoc *xdoc, xmlNode *node, const char *xpath, zbx_vector_str_t *values)
{
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNodeSetPtr nodeset;
xmlChar *val;
int i, ret = FAIL;
if (NULL == xdoc)
goto out;
xpathCtx = xmlXPathNewContext(xdoc);
if (NULL != node)
xpathCtx->node = node;
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
goto clean;
if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
goto clean;
nodeset = xpathObj->nodesetval;
for (i = 0; i < nodeset->nodeNr; i++)
{
if (NULL != (val = xmlNodeListGetString(xdoc, nodeset->nodeTab[i]->xmlChildrenNode, 1)))
{
zbx_vector_str_append(values, zbx_strdup(NULL, (const char *)val));
xmlFree(val);
}
}
ret = SUCCEED;
clean:
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
out:
return ret;
}
/*
* XML support
*/
/******************************************************************************
* *
* Purpose: libxml2 callback function for error handle *
* *
* Parameters: user_data - [IN/OUT] the user context *
* err - [IN] the libxml2 error message *
* *
******************************************************************************/
#if 21200 > LIBXML_VERSION /* version 2.12.0 */
static void libxml_handle_error_try_read_value(void *user_data, xmlErrorPtr err)
#else
static void libxml_handle_error_try_read_value(void *user_data, const xmlError *err)
#endif
{
ZBX_UNUSED(user_data);
ZBX_UNUSED(err);
}
/* according to libxml2 changelog XML_PARSE_HUGE option was introduced in version 2.7.0 */
#if 20700 <= LIBXML_VERSION /* version 2.7.0 */
# define ZBX_XML_PARSE_OPTS XML_PARSE_HUGE
#else
# define ZBX_XML_PARSE_OPTS 0
#endif
/******************************************************************************
* *
* Purpose: retrieve a value from xml data and return status of operation *
* *
* Parameters: data - [IN] XML data *
* len - [IN] XML data length (optional) *
* xpath - [IN] XML XPath *
* xdoc - [OUT] parsed xml document *
* value - [OUT] selected xml node value *
* error - [OUT] error of xml or xpath formats *
* *
* Return: SUCCEED - select xpath successfully, result stored in 'value' *
* FAIL - failed select xpath expression *
* *
******************************************************************************/
int zbx_xml_try_read_value(const char *data, size_t len, const char *xpath, xmlDoc **xdoc, char **value,
char **error)
{
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNodeSetPtr nodeset;
xmlChar *val;
int ret = FAIL;
if (NULL == data)
{
if (NULL != error)
*error = zbx_dsprintf(*error, "Received empty response.");
goto out;
}
xmlSetStructuredErrorFunc(NULL, &libxml_handle_error_try_read_value);
#define ZBX_NONAME_XML "noname.xml"
if (NULL == (*xdoc = xmlReadMemory(data, (0 == len ? strlen(data) : len), ZBX_NONAME_XML, NULL,
ZBX_XML_PARSE_OPTS)))
{
if (NULL != error)
*error = zbx_dsprintf(*error, "Received response has no valid XML data.");
xmlSetStructuredErrorFunc(NULL, NULL);
goto out;
}
#undef ZBX_NONAME_XML
xpathCtx = xmlXPathNewContext(*xdoc);
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
{
if (NULL != error)
*error = zbx_dsprintf(*error, "Invalid xpath expression: \"%s\".", xpath);
goto clean;
}
ret = SUCCEED;
if (XPATH_STRING == xpathObj->type)
{
if ('\0' != *xpathObj->stringval)
*value = zbx_strdup(NULL, (const char *)xpathObj->stringval);
goto clean;
}
if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
goto clean;
nodeset = xpathObj->nodesetval;
if (NULL != (val = xmlNodeListGetString(*xdoc, nodeset->nodeTab[0]->xmlChildrenNode, 1)))
{
*value = zbx_strdup(*value, (const char *)val);
xmlFree(val);
}
clean:
xmlXPathFreeObject(xpathObj);
xmlSetStructuredErrorFunc(NULL, NULL);
xmlXPathFreeContext(xpathCtx);
xmlResetLastError();
out:
return ret;
}
/******************************************************************************
* *
* Purpose: retrieves numeric xpath value *
* *
* Parameters: xdoc - [IN] xml document *
* xpath - [IN] xpath *
* num - [OUT] numeric value *
* *
* Return value: SUCCEED - the count was retrieved successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_xml_doc_read_num(xmlDoc *xdoc, const char *xpath, int *num)
{
return zbx_xml_node_read_num(xdoc, NULL, xpath, num);
}
/******************************************************************************
* *
* Purpose: retrieves numeric xpath value *
* *
* Parameters: xdoc - [IN] xml document *
* node - [IN] the XML node *
* xpath - [IN] xpath *
* num - [OUT] numeric value *
* *
* Return value: SUCCEED - the count was retrieved successfully *
* FAIL - otherwise *
* *
******************************************************************************/
int zbx_xml_node_read_num(xmlDoc *xdoc, xmlNode *node, const char *xpath, int *num)
{
int ret = FAIL;
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xpathCtx = xmlXPathNewContext(xdoc);
if (NULL != node)
xpathCtx->node = node;
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
goto out;
if (XPATH_NUMBER == xpathObj->type)
{
*num = (int)xpathObj->floatval;
ret = SUCCEED;
}
xmlXPathFreeObject(xpathObj);
out:
xmlXPathFreeContext(xpathCtx);
return ret;
}
/******************************************************************************
* *
* Purpose: retrieve a value from xml data relative to the specified node *
* *
* Parameters: xdoc - [IN] the XML document *
* node - [IN] the XML node *
* xpath - [IN] the XML XPath *
* *
* Return: The allocated value string or NULL if the xml data does not *
* contain the value specified by xpath. *
* *
******************************************************************************/
char *zbx_xml_node_read_value(xmlDoc *xdoc, xmlNode *node, const char *xpath)
{
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNodeSetPtr nodeset;
xmlChar *val;
char *value = NULL;
xpathCtx = xmlXPathNewContext(xdoc);
xpathCtx->node = node;
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
goto clean;
if (XPATH_STRING == xpathObj->type)
{
value = zbx_strdup(NULL, (const char *)xpathObj->stringval);
goto clean;
}
if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
goto clean;
nodeset = xpathObj->nodesetval;
if (NULL != (val = xmlNodeListGetString(xdoc, nodeset->nodeTab[0]->xmlChildrenNode, 1)))
{
value = zbx_strdup(NULL, (const char *)val);
xmlFree(val);
}
clean:
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
return value;
}
/******************************************************************************
* *
* Purpose: retrieves a property value from XML node *
* *
* Parameters: node - [IN] XML node *
* name - [IN] XML XPath *
* *
* Return: The allocated value string or NULL if the XML data does not *
* contain the value specified by name. *
* *
******************************************************************************/
char *zbx_xml_node_read_prop(xmlNode *node, const char *name)
{
char *value = NULL;
xmlChar *attr_value;
if (NULL == (attr_value = xmlGetProp(node, (const xmlChar *)name)))
return NULL;
value = zbx_strdup(NULL, (const char *)attr_value);
xmlFree(attr_value);
return value;
}
/******************************************************************************
* *
* Purpose: retrieve a value from xml document relative to the root node *
* *
* Parameters: xdoc - [IN] the XML document *
* xpath - [IN] the XML XPath *
* *
* Return: The allocated value string or NULL if the xml data does not *
* contain the value specified by xpath. *
* *
******************************************************************************/
char *zbx_xml_doc_read_value(xmlDoc *xdoc, const char *xpath)
{
xmlNode *root_element;
root_element = xmlDocGetRootElement(xdoc);
return zbx_xml_node_read_value(xdoc, root_element, xpath);
}
/******************************************************************************
* *
* Purpose: retrieve an xmlNode from xml data relative to the specified node *
* *
* Parameters: xdoc - [IN] the XML document *
* node - [IN] the XML node *
* xpath - [IN] the XML XPath *
* *
* Return: The pointer to xmlNode or NULL if the xml data does not *
* contain the value specified by xpath. *
* *
******************************************************************************/
xmlNode *zbx_xml_node_get(xmlDoc *xdoc, xmlNode *node, const char *xpath)
{
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNode *value = NULL;
xpathCtx = xmlXPathNewContext(xdoc);
if (NULL != node)
xpathCtx->node = node;
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
goto clean;
if (XPATH_NODESET != xpathObj->type)
goto clean;
if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
goto clean;
value = xpathObj->nodesetval->nodeTab[0];
clean:
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
return value;
}
/******************************************************************************
* *
* Purpose: retrieve an xmlNode from xml document *
* *
* Parameters: xdoc - [IN] the XML document *
* xpath - [IN] the XML XPath *
* *
* Return: The pointer to xmlNode or NULL if the xml data does not *
* contain the value specified by xpath. *
* *
******************************************************************************/
xmlNode *zbx_xml_doc_get(xmlDoc *xdoc, const char *xpath)
{
return zbx_xml_node_get(xdoc, NULL, xpath);
}
/******************************************************************************
* *
* Purpose: remove a xmlNode from xml data relative to the specified node *
* *
* Parameters: xdoc - [IN] the XML document *
* node - [IN] the XML node *
* xpath - [IN] the XML XPath *
* *
* Return value: SUCCEED - the operation has completed successfully *
* FAIL - the operation has failed *
* *
******************************************************************************/
int zbx_xml_node_remove(xmlDoc *xdoc, xmlNode *node, const char *xpath)
{
xmlXPathContext *xpathCtx;
xmlXPathObject *xpathObj;
xmlNodeSetPtr nodeset;
int i, ret = FAIL;
if (NULL == xdoc)
goto out;
xpathCtx = xmlXPathNewContext(xdoc);
if (NULL != node)
xpathCtx->node = node;
if (NULL == (xpathObj = xmlXPathEvalExpression((const xmlChar *)xpath, xpathCtx)))
goto clean;
if (0 != xmlXPathNodeSetIsEmpty(xpathObj->nodesetval))
goto clean;
nodeset = xpathObj->nodesetval;
for (i = 0; i < nodeset->nodeNr; i++)
{
xmlUnlinkNode(nodeset->nodeTab[i]);
xmlFreeNode(nodeset->nodeTab[i]);
nodeset->nodeTab[i] = NULL;
}
ret = SUCCEED;
clean:
xmlXPathFreeObject(xpathObj);
xmlXPathFreeContext(xpathCtx);
out:
return ret;
}
#endif // HAVE_LIBXML2 && HAVE_LIBCURL