summaryrefslogtreecommitdiffstats
path: root/lib/common/xpath.c
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 06:53:20 +0000
commite5a812082ae033afb1eed82c0f2df3d0f6bdc93f (patch)
treea6716c9275b4b413f6c9194798b34b91affb3cc7 /lib/common/xpath.c
parentInitial commit. (diff)
downloadpacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.tar.xz
pacemaker-e5a812082ae033afb1eed82c0f2df3d0f6bdc93f.zip
Adding upstream version 2.1.6.upstream/2.1.6
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'lib/common/xpath.c')
-rw-r--r--lib/common/xpath.c378
1 files changed, 378 insertions, 0 deletions
diff --git a/lib/common/xpath.c b/lib/common/xpath.c
new file mode 100644
index 0000000..1f5c0a8
--- /dev/null
+++ b/lib/common/xpath.c
@@ -0,0 +1,378 @@
+/*
+ * Copyright 2004-2022 the Pacemaker project contributors
+ *
+ * The version control history for this file may have further details.
+ *
+ * This source code is licensed under the GNU Lesser General Public License
+ * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
+ */
+
+#include <crm_internal.h>
+#include <stdio.h>
+#include <string.h>
+#include <crm/msg_xml.h>
+#include <crm/common/xml_internal.h>
+#include "crmcommon_private.h"
+
+/*
+ * From xpath2.c
+ *
+ * All the elements returned by an XPath query are pointers to
+ * elements from the tree *except* namespace nodes where the XPath
+ * semantic is different from the implementation in libxml2 tree.
+ * As a result when a returned node set is freed when
+ * xmlXPathFreeObject() is called, that routine must check the
+ * element type. But node from the returned set may have been removed
+ * by xmlNodeSetContent() resulting in access to freed data.
+ *
+ * This can be exercised by running
+ * valgrind xpath2 test3.xml '//discarded' discarded
+ *
+ * There is 2 ways around it:
+ * - make a copy of the pointers to the nodes from the result set
+ * then call xmlXPathFreeObject() and then modify the nodes
+ * or
+ * - remove the references from the node set, if they are not
+ namespace nodes, before calling xmlXPathFreeObject().
+ */
+void
+freeXpathObject(xmlXPathObjectPtr xpathObj)
+{
+ int lpc, max = numXpathResults(xpathObj);
+
+ if (xpathObj == NULL) {
+ return;
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
+ xpathObj->nodesetval->nodeTab[lpc] = NULL;
+ }
+ }
+
+ /* _Now_ it's safe to free it */
+ xmlXPathFreeObject(xpathObj);
+}
+
+xmlNode *
+getXpathResult(xmlXPathObjectPtr xpathObj, int index)
+{
+ xmlNode *match = NULL;
+ int max = numXpathResults(xpathObj);
+
+ CRM_CHECK(index >= 0, return NULL);
+ CRM_CHECK(xpathObj != NULL, return NULL);
+
+ if (index >= max) {
+ crm_err("Requested index %d of only %d items", index, max);
+ return NULL;
+
+ } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
+ /* Previously requested */
+ return NULL;
+ }
+
+ match = xpathObj->nodesetval->nodeTab[index];
+ CRM_CHECK(match != NULL, return NULL);
+
+ if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
+ /* See the comment for freeXpathObject() */
+ xpathObj->nodesetval->nodeTab[index] = NULL;
+ }
+
+ if (match->type == XML_DOCUMENT_NODE) {
+ /* Will happen if section = '/' */
+ match = match->children;
+
+ } else if (match->type != XML_ELEMENT_NODE
+ && match->parent && match->parent->type == XML_ELEMENT_NODE) {
+ /* Return the parent instead */
+ match = match->parent;
+
+ } else if (match->type != XML_ELEMENT_NODE) {
+ /* We only support searching nodes */
+ crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type);
+ match = NULL;
+ }
+ return match;
+}
+
+void
+dedupXpathResults(xmlXPathObjectPtr xpathObj)
+{
+ int lpc, max = numXpathResults(xpathObj);
+
+ if (xpathObj == NULL) {
+ return;
+ }
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *xml = NULL;
+ gboolean dedup = FALSE;
+
+ if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
+ continue;
+ }
+
+ xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
+
+ for (; xml; xml = xml->parent) {
+ int lpc2 = 0;
+
+ for (lpc2 = 0; lpc2 < max; lpc2++) {
+ if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
+ xpathObj->nodesetval->nodeTab[lpc] = NULL;
+ dedup = TRUE;
+ break;
+ }
+ }
+
+ if (dedup) {
+ break;
+ }
+ }
+ }
+}
+
+/* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */
+xmlXPathObjectPtr
+xpath_search(xmlNode * xml_top, const char *path)
+{
+ xmlDocPtr doc = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+ xmlXPathContextPtr xpathCtx = NULL;
+ const xmlChar *xpathExpr = (pcmkXmlStr) path;
+
+ CRM_CHECK(path != NULL, return NULL);
+ CRM_CHECK(xml_top != NULL, return NULL);
+ CRM_CHECK(strlen(path) > 0, return NULL);
+
+ doc = getDocPtr(xml_top);
+
+ xpathCtx = xmlXPathNewContext(doc);
+ CRM_ASSERT(xpathCtx != NULL);
+
+ xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
+ xmlXPathFreeContext(xpathCtx);
+ return xpathObj;
+}
+
+/*!
+ * \brief Run a supplied function for each result of an xpath search
+ *
+ * \param[in,out] xml XML to search
+ * \param[in] xpath XPath search string
+ * \param[in] helper Function to call for each result
+ * \param[in,out] user_data Data to pass to supplied function
+ *
+ * \note The helper function will be passed the XML node of the result,
+ * and the supplied user_data. This function does not otherwise
+ * use user_data.
+ */
+void
+crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
+ void (*helper)(xmlNode*, void*), void *user_data)
+{
+ xmlXPathObjectPtr xpathObj = xpath_search(xml, xpath);
+ int nresults = numXpathResults(xpathObj);
+ int i;
+
+ for (i = 0; i < nresults; i++) {
+ xmlNode *result = getXpathResult(xpathObj, i);
+
+ CRM_LOG_ASSERT(result != NULL);
+ if (result) {
+ (*helper)(result, user_data);
+ }
+ }
+ freeXpathObject(xpathObj);
+}
+
+xmlNode *
+get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level)
+{
+ xmlNode *result = NULL;
+ char *xpath_full = NULL;
+ char *xpath_prefix = NULL;
+
+ if (xml_obj == NULL || xpath == NULL) {
+ return NULL;
+ }
+
+ xpath_prefix = (char *)xmlGetNodePath(xml_obj);
+
+ xpath_full = crm_strdup_printf("%s%s", xpath_prefix, xpath);
+
+ result = get_xpath_object(xpath_full, xml_obj, error_level);
+
+ free(xpath_prefix);
+ free(xpath_full);
+ return result;
+}
+
+xmlNode *
+get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
+{
+ int max;
+ xmlNode *result = NULL;
+ xmlXPathObjectPtr xpathObj = NULL;
+ char *nodePath = NULL;
+ char *matchNodePath = NULL;
+
+ if (xpath == NULL) {
+ return xml_obj; /* or return NULL? */
+ }
+
+ xpathObj = xpath_search(xml_obj, xpath);
+ nodePath = (char *)xmlGetNodePath(xml_obj);
+ max = numXpathResults(xpathObj);
+
+ if (max < 1) {
+ if (error_level < LOG_NEVER) {
+ do_crm_log(error_level, "No match for %s in %s",
+ xpath, pcmk__s(nodePath, "unknown path"));
+ crm_log_xml_explicit(xml_obj, "Unexpected Input");
+ }
+
+ } else if (max > 1) {
+ if (error_level < LOG_NEVER) {
+ int lpc = 0;
+
+ do_crm_log(error_level, "Too many matches for %s in %s",
+ xpath, pcmk__s(nodePath, "unknown path"));
+
+ for (lpc = 0; lpc < max; lpc++) {
+ xmlNode *match = getXpathResult(xpathObj, lpc);
+
+ CRM_LOG_ASSERT(match != NULL);
+ if (match != NULL) {
+ matchNodePath = (char *) xmlGetNodePath(match);
+ do_crm_log(error_level, "%s[%d] = %s",
+ xpath, lpc,
+ pcmk__s(matchNodePath, "unrecognizable match"));
+ free(matchNodePath);
+ }
+ }
+ crm_log_xml_explicit(xml_obj, "Bad Input");
+ }
+
+ } else {
+ result = getXpathResult(xpathObj, 0);
+ }
+
+ freeXpathObject(xpathObj);
+ free(nodePath);
+
+ return result;
+}
+
+/*!
+ * \internal
+ * \brief Get an XPath string that matches an XML element as closely as possible
+ *
+ * \param[in] xml The XML element for which to build an XPath string
+ *
+ * \return A \p GString that matches \p xml, or \p NULL if \p xml is \p NULL.
+ *
+ * \note The caller is responsible for freeing the string using
+ * \p g_string_free().
+ */
+GString *
+pcmk__element_xpath(const xmlNode *xml)
+{
+ const xmlNode *parent = NULL;
+ GString *xpath = NULL;
+ const char *id = NULL;
+
+ if (xml == NULL) {
+ return NULL;
+ }
+
+ parent = xml->parent;
+ xpath = pcmk__element_xpath(parent);
+ if (xpath == NULL) {
+ xpath = g_string_sized_new(256);
+ }
+
+ // Build xpath like "/" -> "/cib" -> "/cib/configuration"
+ if (parent == NULL) {
+ g_string_append_c(xpath, '/');
+ } else if (parent->parent == NULL) {
+ g_string_append(xpath, TYPE(xml));
+ } else {
+ pcmk__g_strcat(xpath, "/", TYPE(xml), NULL);
+ }
+
+ id = ID(xml);
+ if (id != NULL) {
+ pcmk__g_strcat(xpath, "[@" XML_ATTR_ID "='", id, "']", NULL);
+ }
+
+ return xpath;
+}
+
+char *
+pcmk__xpath_node_id(const char *xpath, const char *node)
+{
+ char *retval = NULL;
+ char *patt = NULL;
+ char *start = NULL;
+ char *end = NULL;
+
+ if (node == NULL || xpath == NULL) {
+ return retval;
+ }
+
+ patt = crm_strdup_printf("/%s[@" XML_ATTR_ID "=", node);
+ start = strstr(xpath, patt);
+
+ if (!start) {
+ free(patt);
+ return retval;
+ }
+
+ start += strlen(patt);
+ start++;
+
+ end = strstr(start, "\'");
+ CRM_ASSERT(end);
+ retval = strndup(start, end-start);
+
+ free(patt);
+ return retval;
+}
+
+// Deprecated functions kept only for backward API compatibility
+// LCOV_EXCL_START
+
+#include <crm/common/xml_compat.h>
+
+/*!
+ * \deprecated This function will be removed in a future release
+ * \brief Get an XPath string that matches an XML element as closely as possible
+ *
+ * \param[in] xml The XML element for which to build an XPath string
+ *
+ * \return A string that matches \p xml, or \p NULL if \p xml is \p NULL.
+ *
+ * \note The caller is responsible for freeing the string using free().
+ */
+char *
+xml_get_path(const xmlNode *xml)
+{
+ char *path = NULL;
+ GString *g_path = pcmk__element_xpath(xml);
+
+ if (g_path == NULL) {
+ return NULL;
+ }
+
+ path = strdup((const char *) g_path->str);
+ CRM_ASSERT(path != NULL);
+
+ g_string_free(g_path, TRUE);
+ return path;
+}
+
+// LCOV_EXCL_STOP
+// End deprecated API