root/lib/common/xpath.c

/* [previous][next][first][last][top][bottom][index][help] */

DEFINITIONS

This source file includes following definitions.
  1. freeXpathObject
  2. getXpathResult
  3. dedupXpathResults
  4. xpath_search
  5. crm_foreach_xpath_result
  6. get_xpath_object
  7. pcmk__element_xpath
  8. pcmk__xpath_node_id
  9. output_attr_child
  10. pcmk__warn_multiple_name_matches
  11. xml_get_path
  12. get_xpath_object_relative

   1 /*
   2  * Copyright 2004-2024 the Pacemaker project contributors
   3  *
   4  * The version control history for this file may have further details.
   5  *
   6  * This source code is licensed under the GNU Lesser General Public License
   7  * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY.
   8  */
   9 
  10 #include <crm_internal.h>
  11 #include <stdio.h>
  12 #include <string.h>
  13 #include <crm/common/xml.h>
  14 #include <crm/common/xml_internal.h>
  15 #include "crmcommon_private.h"
  16 
  17 /*
  18  * From xpath2.c
  19  *
  20  * All the elements returned by an XPath query are pointers to
  21  * elements from the tree *except* namespace nodes where the XPath
  22  * semantic is different from the implementation in libxml2 tree.
  23  * As a result when a returned node set is freed when
  24  * xmlXPathFreeObject() is called, that routine must check the
  25  * element type. But node from the returned set may have been removed
  26  * by xmlNodeSetContent() resulting in access to freed data.
  27  *
  28  * This can be exercised by running
  29  *       valgrind xpath2 test3.xml '//discarded' discarded
  30  *
  31  * There is 2 ways around it:
  32  *   - make a copy of the pointers to the nodes from the result set
  33  *     then call xmlXPathFreeObject() and then modify the nodes
  34  * or
  35  * - remove the references from the node set, if they are not
  36        namespace nodes, before calling xmlXPathFreeObject().
  37  */
  38 void
  39 freeXpathObject(xmlXPathObjectPtr xpathObj)
     /* [previous][next][first][last][top][bottom][index][help] */
  40 {
  41     int lpc, max = numXpathResults(xpathObj);
  42 
  43     if (xpathObj == NULL) {
  44         return;
  45     }
  46 
  47     for (lpc = 0; lpc < max; lpc++) {
  48         if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
  49             xpathObj->nodesetval->nodeTab[lpc] = NULL;
  50         }
  51     }
  52 
  53     /* _Now_ it's safe to free it */
  54     xmlXPathFreeObject(xpathObj);
  55 }
  56 
  57 xmlNode *
  58 getXpathResult(xmlXPathObjectPtr xpathObj, int index)
     /* [previous][next][first][last][top][bottom][index][help] */
  59 {
  60     xmlNode *match = NULL;
  61     int max = numXpathResults(xpathObj);
  62 
  63     CRM_CHECK(index >= 0, return NULL);
  64     CRM_CHECK(xpathObj != NULL, return NULL);
  65 
  66     if (index >= max) {
  67         crm_err("Requested index %d of only %d items", index, max);
  68         return NULL;
  69 
  70     } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
  71         /* Previously requested */
  72         return NULL;
  73     }
  74 
  75     match = xpathObj->nodesetval->nodeTab[index];
  76     CRM_CHECK(match != NULL, return NULL);
  77 
  78     if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
  79         /* See the comment for freeXpathObject() */
  80         xpathObj->nodesetval->nodeTab[index] = NULL;
  81     }
  82 
  83     if (match->type == XML_DOCUMENT_NODE) {
  84         /* Will happen if section = '/' */
  85         match = match->children;
  86 
  87     } else if (match->type != XML_ELEMENT_NODE
  88                && match->parent && match->parent->type == XML_ELEMENT_NODE) {
  89         /* Return the parent instead */
  90         match = match->parent;
  91 
  92     } else if (match->type != XML_ELEMENT_NODE) {
  93         /* We only support searching nodes */
  94         crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type);
  95         match = NULL;
  96     }
  97     return match;
  98 }
  99 
 100 void
 101 dedupXpathResults(xmlXPathObjectPtr xpathObj)
     /* [previous][next][first][last][top][bottom][index][help] */
 102 {
 103     int lpc, max = numXpathResults(xpathObj);
 104 
 105     if (xpathObj == NULL) {
 106         return;
 107     }
 108 
 109     for (lpc = 0; lpc < max; lpc++) {
 110         xmlNode *xml = NULL;
 111         gboolean dedup = FALSE;
 112 
 113         if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
 114             continue;
 115         }
 116 
 117         xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
 118 
 119         for (; xml; xml = xml->parent) {
 120             int lpc2 = 0;
 121 
 122             for (lpc2 = 0; lpc2 < max; lpc2++) {
 123                 if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
 124                     xpathObj->nodesetval->nodeTab[lpc] = NULL;
 125                     dedup = TRUE;
 126                     break;
 127                 }
 128             }
 129 
 130             if (dedup) {
 131                 break;
 132             }
 133         }
 134     }
 135 }
 136 
 137 /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */
 138 xmlXPathObjectPtr
 139 xpath_search(const xmlNode *xml_top, const char *path)
     /* [previous][next][first][last][top][bottom][index][help] */
 140 {
 141     xmlXPathObjectPtr xpathObj = NULL;
 142     xmlXPathContextPtr xpathCtx = NULL;
 143     const xmlChar *xpathExpr = (pcmkXmlStr) path;
 144 
 145     CRM_CHECK(path != NULL, return NULL);
 146     CRM_CHECK(xml_top != NULL, return NULL);
 147     CRM_CHECK(strlen(path) > 0, return NULL);
 148 
 149     xpathCtx = xmlXPathNewContext(xml_top->doc);
 150     pcmk__mem_assert(xpathCtx);
 151 
 152     xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
 153     xmlXPathFreeContext(xpathCtx);
 154     return xpathObj;
 155 }
 156 
 157 /*!
 158  * \brief Run a supplied function for each result of an xpath search
 159  *
 160  * \param[in,out] xml        XML to search
 161  * \param[in]     xpath      XPath search string
 162  * \param[in]     helper     Function to call for each result
 163  * \param[in,out] user_data  Data to pass to supplied function
 164  *
 165  * \note The helper function will be passed the XML node of the result,
 166  *       and the supplied user_data. This function does not otherwise
 167  *       use user_data.
 168  */
 169 void
 170 crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
     /* [previous][next][first][last][top][bottom][index][help] */
 171                          void (*helper)(xmlNode*, void*), void *user_data)
 172 {
 173     xmlXPathObjectPtr xpathObj = xpath_search(xml, xpath);
 174     int nresults = numXpathResults(xpathObj);
 175     int i;
 176 
 177     for (i = 0; i < nresults; i++) {
 178         xmlNode *result = getXpathResult(xpathObj, i);
 179 
 180         CRM_LOG_ASSERT(result != NULL);
 181         if (result) {
 182             (*helper)(result, user_data);
 183         }
 184     }
 185     freeXpathObject(xpathObj);
 186 }
 187 
 188 xmlNode *
 189 get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
     /* [previous][next][first][last][top][bottom][index][help] */
 190 {
 191     int max;
 192     xmlNode *result = NULL;
 193     xmlXPathObjectPtr xpathObj = NULL;
 194     char *nodePath = NULL;
 195     char *matchNodePath = NULL;
 196 
 197     if (xpath == NULL) {
 198         return xml_obj;         /* or return NULL? */
 199     }
 200 
 201     xpathObj = xpath_search(xml_obj, xpath);
 202     nodePath = (char *)xmlGetNodePath(xml_obj);
 203     max = numXpathResults(xpathObj);
 204 
 205     if (max < 1) {
 206         if (error_level < LOG_NEVER) {
 207             do_crm_log(error_level, "No match for %s in %s",
 208                        xpath, pcmk__s(nodePath, "unknown path"));
 209             crm_log_xml_explicit(xml_obj, "Unexpected Input");
 210         }
 211 
 212     } else if (max > 1) {
 213         if (error_level < LOG_NEVER) {
 214             int lpc = 0;
 215 
 216             do_crm_log(error_level, "Too many matches for %s in %s",
 217                        xpath, pcmk__s(nodePath, "unknown path"));
 218 
 219             for (lpc = 0; lpc < max; lpc++) {
 220                 xmlNode *match = getXpathResult(xpathObj, lpc);
 221 
 222                 CRM_LOG_ASSERT(match != NULL);
 223                 if (match != NULL) {
 224                     matchNodePath = (char *) xmlGetNodePath(match);
 225                     do_crm_log(error_level, "%s[%d] = %s",
 226                                xpath, lpc,
 227                                pcmk__s(matchNodePath, "unrecognizable match"));
 228                     free(matchNodePath);
 229                 }
 230             }
 231             crm_log_xml_explicit(xml_obj, "Bad Input");
 232         }
 233 
 234     } else {
 235         result = getXpathResult(xpathObj, 0);
 236     }
 237 
 238     freeXpathObject(xpathObj);
 239     free(nodePath);
 240 
 241     return result;
 242 }
 243 
 244 /*!
 245  * \internal
 246  * \brief Get an XPath string that matches an XML element as closely as possible
 247  *
 248  * \param[in] xml  The XML element for which to build an XPath string
 249  *
 250  * \return A \p GString that matches \p xml, or \p NULL if \p xml is \p NULL.
 251  *
 252  * \note The caller is responsible for freeing the string using
 253  *       \p g_string_free().
 254  */
 255 GString *
 256 pcmk__element_xpath(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 257 {
 258     const xmlNode *parent = NULL;
 259     GString *xpath = NULL;
 260     const char *id = NULL;
 261 
 262     if (xml == NULL) {
 263         return NULL;
 264     }
 265 
 266     parent = xml->parent;
 267     xpath = pcmk__element_xpath(parent);
 268     if (xpath == NULL) {
 269         xpath = g_string_sized_new(256);
 270     }
 271 
 272     // Build xpath like "/" -> "/cib" -> "/cib/configuration"
 273     if (parent == NULL) {
 274         g_string_append_c(xpath, '/');
 275     } else if (parent->parent == NULL) {
 276         g_string_append(xpath, (const gchar *) xml->name);
 277     } else {
 278         pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL);
 279     }
 280 
 281     id = pcmk__xe_id(xml);
 282     if (id != NULL) {
 283         pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL);
 284     }
 285 
 286     return xpath;
 287 }
 288 
 289 char *
 290 pcmk__xpath_node_id(const char *xpath, const char *node)
     /* [previous][next][first][last][top][bottom][index][help] */
 291 {
 292     char *retval = NULL;
 293     char *patt = NULL;
 294     char *start = NULL;
 295     char *end = NULL;
 296 
 297     if (node == NULL || xpath == NULL) {
 298         return retval;
 299     }
 300 
 301     patt = crm_strdup_printf("/%s[@" PCMK_XA_ID "=", node);
 302     start = strstr(xpath, patt);
 303 
 304     if (!start) {
 305         free(patt);
 306         return retval;
 307     }
 308 
 309     start += strlen(patt);
 310     start++;
 311 
 312     end = strstr(start, "\'");
 313     CRM_ASSERT(end);
 314     retval = strndup(start, end-start);
 315 
 316     free(patt);
 317     return retval;
 318 }
 319 
 320 static int
 321 output_attr_child(xmlNode *child, void *userdata)
     /* [previous][next][first][last][top][bottom][index][help] */
 322 {
 323     pcmk__output_t *out = userdata;
 324 
 325     out->info(out, "  Value: %s \t(id=%s)",
 326               crm_element_value(child, PCMK_XA_VALUE),
 327               pcmk__s(pcmk__xe_id(child), "<none>"));
 328     return pcmk_rc_ok;
 329 }
 330 
 331 void
 332 pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search,
     /* [previous][next][first][last][top][bottom][index][help] */
 333                                  const char *name)
 334 {
 335     if (out == NULL || name == NULL || search == NULL ||
 336         search->children == NULL) {
 337         return;
 338     }
 339 
 340     out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name);
 341     pcmk__xe_foreach_child(search, NULL, output_attr_child, out);
 342 }
 343 
 344 // Deprecated functions kept only for backward API compatibility
 345 // LCOV_EXCL_START
 346 
 347 #include <crm/common/xml_compat.h>
 348 
 349 /*!
 350  * \deprecated This function will be removed in a future release
 351  * \brief Get an XPath string that matches an XML element as closely as possible
 352  *
 353  * \param[in] xml  The XML element for which to build an XPath string
 354  *
 355  * \return A string that matches \p xml, or \p NULL if \p xml is \p NULL.
 356  *
 357  * \note The caller is responsible for freeing the string using free().
 358  */
 359 char *
 360 xml_get_path(const xmlNode *xml)
     /* [previous][next][first][last][top][bottom][index][help] */
 361 {
 362     char *path = NULL;
 363     GString *g_path = pcmk__element_xpath(xml);
 364 
 365     if (g_path == NULL) {
 366         return NULL;
 367     }
 368     path = pcmk__str_copy(g_path->str);
 369     g_string_free(g_path, TRUE);
 370     return path;
 371 }
 372 
 373 xmlNode *
 374 get_xpath_object_relative(const char *xpath, xmlNode *xml_obj, int error_level)
     /* [previous][next][first][last][top][bottom][index][help] */
 375 {
 376     xmlNode *result = NULL;
 377     char *xpath_full = NULL;
 378     char *xpath_prefix = NULL;
 379 
 380     if (xml_obj == NULL || xpath == NULL) {
 381         return NULL;
 382     }
 383 
 384     xpath_prefix = (char *)xmlGetNodePath(xml_obj);
 385 
 386     xpath_full = crm_strdup_printf("%s%s", xpath_prefix, xpath);
 387 
 388     result = get_xpath_object(xpath_full, xml_obj, error_level);
 389 
 390     free(xpath_prefix);
 391     free(xpath_full);
 392     return result;
 393 }
 394 
 395 // LCOV_EXCL_STOP
 396 // End deprecated API

/* [previous][next][first][last][top][bottom][index][help] */