/* ** 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 <https://www.gnu.org/licenses/>. **/ #include "zbxxml.h" #include "zbxalgo.h" #include "zbxjson.h" #include "zbxvariant.h" #include "zbxstr.h" #ifdef HAVE_LIBXML2 # include <libxml/xpath.h> # include <libxml/parser.h> #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 <tag>DATA</tag> * * * * !!! 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; if (NULL == (doc = xmlReadMemory(value->data.str, strlen(value->data.str), "noname.xml", NULL, 0))) { if (NULL != (pErr = xmlGetLastError())) *errmsg = zbx_dsprintf(*errmsg, "cannot parse xml value: %s", pErr->message); else *errmsg = zbx_strdup(*errmsg, "cannot parse xml value"); 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); 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) 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