Line data Source code
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 0 : freeXpathObject(xmlXPathObjectPtr xpathObj)
40 : {
41 0 : int lpc, max = numXpathResults(xpathObj);
42 :
43 0 : if (xpathObj == NULL) {
44 0 : return;
45 : }
46 :
47 0 : for (lpc = 0; lpc < max; lpc++) {
48 0 : if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
49 0 : xpathObj->nodesetval->nodeTab[lpc] = NULL;
50 : }
51 : }
52 :
53 : /* _Now_ it's safe to free it */
54 0 : xmlXPathFreeObject(xpathObj);
55 : }
56 :
57 : xmlNode *
58 0 : getXpathResult(xmlXPathObjectPtr xpathObj, int index)
59 : {
60 0 : xmlNode *match = NULL;
61 0 : int max = numXpathResults(xpathObj);
62 :
63 0 : CRM_CHECK(index >= 0, return NULL);
64 0 : CRM_CHECK(xpathObj != NULL, return NULL);
65 :
66 0 : if (index >= max) {
67 0 : crm_err("Requested index %d of only %d items", index, max);
68 0 : return NULL;
69 :
70 0 : } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
71 : /* Previously requested */
72 0 : return NULL;
73 : }
74 :
75 0 : match = xpathObj->nodesetval->nodeTab[index];
76 0 : CRM_CHECK(match != NULL, return NULL);
77 :
78 0 : if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
79 : /* See the comment for freeXpathObject() */
80 0 : xpathObj->nodesetval->nodeTab[index] = NULL;
81 : }
82 :
83 0 : if (match->type == XML_DOCUMENT_NODE) {
84 : /* Will happen if section = '/' */
85 0 : match = match->children;
86 :
87 0 : } else if (match->type != XML_ELEMENT_NODE
88 0 : && match->parent && match->parent->type == XML_ELEMENT_NODE) {
89 : /* Return the parent instead */
90 0 : match = match->parent;
91 :
92 0 : } else if (match->type != XML_ELEMENT_NODE) {
93 : /* We only support searching nodes */
94 0 : crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type);
95 0 : match = NULL;
96 : }
97 0 : return match;
98 : }
99 :
100 : void
101 0 : dedupXpathResults(xmlXPathObjectPtr xpathObj)
102 : {
103 0 : int lpc, max = numXpathResults(xpathObj);
104 :
105 0 : if (xpathObj == NULL) {
106 0 : return;
107 : }
108 :
109 0 : for (lpc = 0; lpc < max; lpc++) {
110 0 : xmlNode *xml = NULL;
111 0 : gboolean dedup = FALSE;
112 :
113 0 : if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
114 0 : continue;
115 : }
116 :
117 0 : xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
118 :
119 0 : for (; xml; xml = xml->parent) {
120 0 : int lpc2 = 0;
121 :
122 0 : for (lpc2 = 0; lpc2 < max; lpc2++) {
123 0 : if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
124 0 : xpathObj->nodesetval->nodeTab[lpc] = NULL;
125 0 : dedup = TRUE;
126 0 : break;
127 : }
128 : }
129 :
130 0 : if (dedup) {
131 0 : break;
132 : }
133 : }
134 : }
135 : }
136 :
137 : /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */
138 : xmlXPathObjectPtr
139 0 : xpath_search(const xmlNode *xml_top, const char *path)
140 : {
141 0 : xmlXPathObjectPtr xpathObj = NULL;
142 0 : xmlXPathContextPtr xpathCtx = NULL;
143 0 : const xmlChar *xpathExpr = (pcmkXmlStr) path;
144 :
145 0 : CRM_CHECK(path != NULL, return NULL);
146 0 : CRM_CHECK(xml_top != NULL, return NULL);
147 0 : CRM_CHECK(strlen(path) > 0, return NULL);
148 :
149 0 : xpathCtx = xmlXPathNewContext(xml_top->doc);
150 0 : pcmk__mem_assert(xpathCtx);
151 :
152 0 : xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
153 0 : xmlXPathFreeContext(xpathCtx);
154 0 : 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 0 : crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
171 : void (*helper)(xmlNode*, void*), void *user_data)
172 : {
173 0 : xmlXPathObjectPtr xpathObj = xpath_search(xml, xpath);
174 0 : int nresults = numXpathResults(xpathObj);
175 : int i;
176 :
177 0 : for (i = 0; i < nresults; i++) {
178 0 : xmlNode *result = getXpathResult(xpathObj, i);
179 :
180 0 : CRM_LOG_ASSERT(result != NULL);
181 0 : if (result) {
182 0 : (*helper)(result, user_data);
183 : }
184 : }
185 0 : freeXpathObject(xpathObj);
186 0 : }
187 :
188 : xmlNode *
189 0 : get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
190 : {
191 : int max;
192 0 : xmlNode *result = NULL;
193 0 : xmlXPathObjectPtr xpathObj = NULL;
194 0 : char *nodePath = NULL;
195 0 : char *matchNodePath = NULL;
196 :
197 0 : if (xpath == NULL) {
198 0 : return xml_obj; /* or return NULL? */
199 : }
200 :
201 0 : xpathObj = xpath_search(xml_obj, xpath);
202 0 : nodePath = (char *)xmlGetNodePath(xml_obj);
203 0 : max = numXpathResults(xpathObj);
204 :
205 0 : if (max < 1) {
206 0 : if (error_level < LOG_NEVER) {
207 0 : do_crm_log(error_level, "No match for %s in %s",
208 : xpath, pcmk__s(nodePath, "unknown path"));
209 0 : crm_log_xml_explicit(xml_obj, "Unexpected Input");
210 : }
211 :
212 0 : } else if (max > 1) {
213 0 : if (error_level < LOG_NEVER) {
214 0 : int lpc = 0;
215 :
216 0 : do_crm_log(error_level, "Too many matches for %s in %s",
217 : xpath, pcmk__s(nodePath, "unknown path"));
218 :
219 0 : for (lpc = 0; lpc < max; lpc++) {
220 0 : xmlNode *match = getXpathResult(xpathObj, lpc);
221 :
222 0 : CRM_LOG_ASSERT(match != NULL);
223 0 : if (match != NULL) {
224 0 : matchNodePath = (char *) xmlGetNodePath(match);
225 0 : do_crm_log(error_level, "%s[%d] = %s",
226 : xpath, lpc,
227 : pcmk__s(matchNodePath, "unrecognizable match"));
228 0 : free(matchNodePath);
229 : }
230 : }
231 0 : crm_log_xml_explicit(xml_obj, "Bad Input");
232 : }
233 :
234 : } else {
235 0 : result = getXpathResult(xpathObj, 0);
236 : }
237 :
238 0 : freeXpathObject(xpathObj);
239 0 : free(nodePath);
240 :
241 0 : 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 0 : pcmk__element_xpath(const xmlNode *xml)
257 : {
258 0 : const xmlNode *parent = NULL;
259 0 : GString *xpath = NULL;
260 0 : const char *id = NULL;
261 :
262 0 : if (xml == NULL) {
263 0 : return NULL;
264 : }
265 :
266 0 : parent = xml->parent;
267 0 : xpath = pcmk__element_xpath(parent);
268 0 : if (xpath == NULL) {
269 0 : xpath = g_string_sized_new(256);
270 : }
271 :
272 : // Build xpath like "/" -> "/cib" -> "/cib/configuration"
273 0 : if (parent == NULL) {
274 : g_string_append_c(xpath, '/');
275 0 : } else if (parent->parent == NULL) {
276 0 : g_string_append(xpath, (const gchar *) xml->name);
277 : } else {
278 0 : pcmk__g_strcat(xpath, "/", (const char *) xml->name, NULL);
279 : }
280 :
281 0 : id = pcmk__xe_id(xml);
282 0 : if (id != NULL) {
283 0 : pcmk__g_strcat(xpath, "[@" PCMK_XA_ID "='", id, "']", NULL);
284 : }
285 :
286 0 : return xpath;
287 : }
288 :
289 : char *
290 15 : pcmk__xpath_node_id(const char *xpath, const char *node)
291 : {
292 15 : char *retval = NULL;
293 15 : char *patt = NULL;
294 15 : char *start = NULL;
295 15 : char *end = NULL;
296 :
297 15 : if (node == NULL || xpath == NULL) {
298 6 : return retval;
299 : }
300 :
301 9 : patt = crm_strdup_printf("/%s[@" PCMK_XA_ID "=", node);
302 9 : start = strstr(xpath, patt);
303 :
304 9 : if (!start) {
305 6 : free(patt);
306 6 : return retval;
307 : }
308 :
309 3 : start += strlen(patt);
310 3 : start++;
311 :
312 3 : end = strstr(start, "\'");
313 3 : CRM_ASSERT(end);
314 2 : retval = strndup(start, end-start);
315 :
316 2 : free(patt);
317 2 : return retval;
318 : }
319 :
320 : static int
321 0 : output_attr_child(xmlNode *child, void *userdata)
322 : {
323 0 : pcmk__output_t *out = userdata;
324 :
325 0 : 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 0 : return pcmk_rc_ok;
329 : }
330 :
331 : void
332 0 : pcmk__warn_multiple_name_matches(pcmk__output_t *out, xmlNode *search,
333 : const char *name)
334 : {
335 0 : if (out == NULL || name == NULL || search == NULL ||
336 0 : search->children == NULL) {
337 0 : return;
338 : }
339 :
340 0 : out->info(out, "Multiple attributes match " PCMK_XA_NAME "=%s", name);
341 0 : 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)
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)
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
|