1 /*
2  * Copyright (C) 2004 Andrew Beekhof <andrew@beekhof.net>
3  *
4  * This library is free software; you can redistribute it and/or
5  * modify it under the terms of the GNU Lesser General Public
6  * License as published by the Free Software Foundation; either
7  * version 2.1 of the License, or (at your option) any later version.
8  *
9  * This library is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
12  * Lesser General Public License for more details.
13  *
14  * You should have received a copy of the GNU Lesser General Public
15  * License along with this library; if not, write to the Free Software
16  * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
17  */
18 
19 #include <crm_internal.h>
20 #include <stdio.h>
21 #include <string.h>
22 
23 /*
24  * From xpath2.c
25  *
26  * All the elements returned by an XPath query are pointers to
27  * elements from the tree *except* namespace nodes where the XPath
28  * semantic is different from the implementation in libxml2 tree.
29  * As a result when a returned node set is freed when
30  * xmlXPathFreeObject() is called, that routine must check the
31  * element type. But node from the returned set may have been removed
32  * by xmlNodeSetContent() resulting in access to freed data.
33  *
34  * This can be exercised by running
35  *       valgrind xpath2 test3.xml '//discarded' discarded
36  *
37  * There is 2 ways around it:
38  *   - make a copy of the pointers to the nodes from the result set
39  *     then call xmlXPathFreeObject() and then modify the nodes
40  * or
41  * - remove the references from the node set, if they are not
42        namespace nodes, before calling xmlXPathFreeObject().
43  */
44 void
freeXpathObject(xmlXPathObjectPtr xpathObj)45 freeXpathObject(xmlXPathObjectPtr xpathObj)
46 {
47     int lpc, max = numXpathResults(xpathObj);
48 
49     if (xpathObj == NULL) {
50         return;
51     }
52 
53     for (lpc = 0; lpc < max; lpc++) {
54         if (xpathObj->nodesetval->nodeTab[lpc] && xpathObj->nodesetval->nodeTab[lpc]->type != XML_NAMESPACE_DECL) {
55             xpathObj->nodesetval->nodeTab[lpc] = NULL;
56         }
57     }
58 
59     /* _Now_ it's safe to free it */
60     xmlXPathFreeObject(xpathObj);
61 }
62 
63 xmlNode *
getXpathResult(xmlXPathObjectPtr xpathObj,int index)64 getXpathResult(xmlXPathObjectPtr xpathObj, int index)
65 {
66     xmlNode *match = NULL;
67     int max = numXpathResults(xpathObj);
68 
69     CRM_CHECK(index >= 0, return NULL);
70     CRM_CHECK(xpathObj != NULL, return NULL);
71 
72     if (index >= max) {
73         crm_err("Requested index %d of only %d items", index, max);
74         return NULL;
75 
76     } else if(xpathObj->nodesetval->nodeTab[index] == NULL) {
77         /* Previously requested */
78         return NULL;
79     }
80 
81     match = xpathObj->nodesetval->nodeTab[index];
82     CRM_CHECK(match != NULL, return NULL);
83 
84     if (xpathObj->nodesetval->nodeTab[index]->type != XML_NAMESPACE_DECL) {
85         /* See the comment for freeXpathObject() */
86         xpathObj->nodesetval->nodeTab[index] = NULL;
87     }
88 
89     if (match->type == XML_DOCUMENT_NODE) {
90         /* Will happen if section = '/' */
91         match = match->children;
92 
93     } else if (match->type != XML_ELEMENT_NODE
94                && match->parent && match->parent->type == XML_ELEMENT_NODE) {
95         /* Return the parent instead */
96         match = match->parent;
97 
98     } else if (match->type != XML_ELEMENT_NODE) {
99         /* We only support searching nodes */
100         crm_err("We only support %d not %d", XML_ELEMENT_NODE, match->type);
101         match = NULL;
102     }
103     return match;
104 }
105 
106 void
dedupXpathResults(xmlXPathObjectPtr xpathObj)107 dedupXpathResults(xmlXPathObjectPtr xpathObj)
108 {
109     int lpc, max = numXpathResults(xpathObj);
110 
111     if (xpathObj == NULL) {
112         return;
113     }
114 
115     for (lpc = 0; lpc < max; lpc++) {
116         xmlNode *xml = NULL;
117         gboolean dedup = FALSE;
118 
119         if (xpathObj->nodesetval->nodeTab[lpc] == NULL) {
120             continue;
121         }
122 
123         xml = xpathObj->nodesetval->nodeTab[lpc]->parent;
124 
125         for (; xml; xml = xml->parent) {
126             int lpc2 = 0;
127 
128             for (lpc2 = 0; lpc2 < max; lpc2++) {
129                 if (xpathObj->nodesetval->nodeTab[lpc2] == xml) {
130                     xpathObj->nodesetval->nodeTab[lpc] = NULL;
131                     dedup = TRUE;
132                     break;
133                 }
134             }
135 
136             if (dedup) {
137                 break;
138             }
139         }
140     }
141 }
142 
143 /* the caller needs to check if the result contains a xmlDocPtr or xmlNodePtr */
144 xmlXPathObjectPtr
xpath_search(xmlNode * xml_top,const char * path)145 xpath_search(xmlNode * xml_top, const char *path)
146 {
147     xmlDocPtr doc = NULL;
148     xmlXPathObjectPtr xpathObj = NULL;
149     xmlXPathContextPtr xpathCtx = NULL;
150     const xmlChar *xpathExpr = (const xmlChar *)path;
151 
152     CRM_CHECK(path != NULL, return NULL);
153     CRM_CHECK(xml_top != NULL, return NULL);
154     CRM_CHECK(strlen(path) > 0, return NULL);
155 
156     doc = getDocPtr(xml_top);
157 
158     xpathCtx = xmlXPathNewContext(doc);
159     CRM_ASSERT(xpathCtx != NULL);
160 
161     xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
162     xmlXPathFreeContext(xpathCtx);
163     return xpathObj;
164 }
165 
166 /*!
167  * \brief Run a supplied function for each result of an xpath search
168  *
169  * \param[in] xml            XML to search
170  * \param[in] xpath          XPath search string
171  * \param[in] helper         Function to call for each result
172  * \param[in,out] user_data  Data to pass to supplied function
173  *
174  * \note The helper function will be passed the XML node of the result,
175  *       and the supplied user_data. This function does not otherwise
176  *       use user_data.
177  */
178 void
crm_foreach_xpath_result(xmlNode * xml,const char * xpath,void (* helper)(xmlNode *,void *),void * user_data)179 crm_foreach_xpath_result(xmlNode *xml, const char *xpath,
180                          void (*helper)(xmlNode*, void*), void *user_data)
181 {
182     xmlXPathObjectPtr xpathObj = xpath_search(xml, xpath);
183     int nresults = numXpathResults(xpathObj);
184     int i;
185 
186     for (i = 0; i < nresults; i++) {
187         xmlNode *result = getXpathResult(xpathObj, i);
188 
189         CRM_LOG_ASSERT(result != NULL);
190         if (result) {
191             (*helper)(result, user_data);
192         }
193     }
194     freeXpathObject(xpathObj);
195 }
196 
197 xmlNode *
get_xpath_object_relative(const char * xpath,xmlNode * xml_obj,int error_level)198 get_xpath_object_relative(const char *xpath, xmlNode * xml_obj, int error_level)
199 {
200     int len = 0;
201     xmlNode *result = NULL;
202     char *xpath_full = NULL;
203     char *xpath_prefix = NULL;
204 
205     if (xml_obj == NULL || xpath == NULL) {
206         return NULL;
207     }
208 
209     xpath_prefix = (char *)xmlGetNodePath(xml_obj);
210 
211     len = strlen(xpath_prefix) + strlen(xpath) + 1;
212     xpath_full = malloc(len);
213     strcpy(xpath_full, xpath_prefix);
214     strcat(xpath_full, xpath);
215 
216     result = get_xpath_object(xpath_full, xml_obj, error_level);
217 
218     free(xpath_prefix);
219     free(xpath_full);
220     return result;
221 }
222 
223 xmlNode *
get_xpath_object(const char * xpath,xmlNode * xml_obj,int error_level)224 get_xpath_object(const char *xpath, xmlNode * xml_obj, int error_level)
225 {
226     int max;
227     xmlNode *result = NULL;
228     xmlXPathObjectPtr xpathObj = NULL;
229     char *nodePath = NULL;
230     char *matchNodePath = NULL;
231 
232     if (xpath == NULL) {
233         return xml_obj;         /* or return NULL? */
234     }
235 
236     xpathObj = xpath_search(xml_obj, xpath);
237     nodePath = (char *)xmlGetNodePath(xml_obj);
238     max = numXpathResults(xpathObj);
239 
240     if (max < 1) {
241         do_crm_log(error_level, "No match for %s in %s", xpath, crm_str(nodePath));
242         crm_log_xml_explicit(xml_obj, "Unexpected Input");
243 
244     } else if (max > 1) {
245         int lpc = 0;
246 
247         do_crm_log(error_level, "Too many matches for %s in %s", xpath, crm_str(nodePath));
248 
249         for (lpc = 0; lpc < max; lpc++) {
250             xmlNode *match = getXpathResult(xpathObj, lpc);
251 
252             CRM_LOG_ASSERT(match != NULL);
253             if(match != NULL) {
254                 matchNodePath = (char *)xmlGetNodePath(match);
255                 do_crm_log(error_level, "%s[%d] = %s", xpath, lpc, crm_str(matchNodePath));
256                 free(matchNodePath);
257             }
258         }
259         crm_log_xml_explicit(xml_obj, "Bad Input");
260 
261     } else {
262         result = getXpathResult(xpathObj, 0);
263     }
264 
265     freeXpathObject(xpathObj);
266     free(nodePath);
267 
268     return result;
269 }
270