xref: /PHP-8.0/ext/dom/xpath.c (revision 8fef83dd)
1 /*
2    +----------------------------------------------------------------------+
3    | Copyright (c) The PHP Group                                          |
4    +----------------------------------------------------------------------+
5    | This source file is subject to version 3.01 of the PHP license,      |
6    | that is bundled with this package in the file LICENSE, and is        |
7    | available through the world-wide-web at the following url:           |
8    | http://www.php.net/license/3_01.txt                                  |
9    | If you did not receive a copy of the PHP license and are unable to   |
10    | obtain it through the world-wide-web, please send a note to          |
11    | license@php.net so we can mail you a copy immediately.               |
12    +----------------------------------------------------------------------+
13    | Authors: Christian Stocker <chregu@php.net>                          |
14    |          Rob Richards <rrichards@php.net>                            |
15    +----------------------------------------------------------------------+
16 */
17 
18 #ifdef HAVE_CONFIG_H
19 #include "config.h"
20 #endif
21 
22 #include "php.h"
23 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
24 #include "php_dom.h"
25 
26 #define PHP_DOM_XPATH_QUERY 0
27 #define PHP_DOM_XPATH_EVALUATE 1
28 
29 /*
30 * class DOMXPath
31 */
32 
33 #ifdef LIBXML_XPATH_ENABLED
34 
dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt,int nargs,int type)35 static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int type) /* {{{ */
36 {
37 	zval retval;
38 	int result, i;
39 	int error = 0;
40 	zend_fcall_info fci;
41 	xmlXPathObjectPtr obj;
42 	char *str;
43 	zend_string *callable = NULL;
44 	dom_xpath_object *intern;
45 
46 
47 	if (! zend_is_executing()) {
48 		xmlGenericError(xmlGenericErrorContext,
49 		"xmlExtFunctionTest: Function called from outside of PHP\n");
50 		error = 1;
51 	} else {
52 		intern = (dom_xpath_object *) ctxt->context->userData;
53 		if (intern == NULL) {
54 			xmlGenericError(xmlGenericErrorContext,
55 			"xmlExtFunctionTest: failed to get the internal object\n");
56 			error = 1;
57 		}
58 		else if (intern->registerPhpFunctions == 0) {
59 			xmlGenericError(xmlGenericErrorContext,
60 			"xmlExtFunctionTest: PHP Object did not register PHP functions\n");
61 			error = 1;
62 		}
63 	}
64 
65 	if (error == 1) {
66 		for (i = nargs - 1; i >= 0; i--) {
67 			obj = valuePop(ctxt);
68 			xmlXPathFreeObject(obj);
69 		}
70 		return;
71 	}
72 
73 	fci.param_count = nargs - 1;
74 	if (fci.param_count > 0) {
75 		fci.params = safe_emalloc(fci.param_count, sizeof(zval), 0);
76 	}
77 	/* Reverse order to pop values off ctxt stack */
78 	for (i = nargs - 2; i >= 0; i--) {
79 		obj = valuePop(ctxt);
80 		switch (obj->type) {
81 			case XPATH_STRING:
82 				ZVAL_STRING(&fci.params[i],  (char *)obj->stringval);
83 				break;
84 			case XPATH_BOOLEAN:
85 				ZVAL_BOOL(&fci.params[i],  obj->boolval);
86 				break;
87 			case XPATH_NUMBER:
88 				ZVAL_DOUBLE(&fci.params[i], obj->floatval);
89 				break;
90 			case XPATH_NODESET:
91 				if (type == 1) {
92 					str = (char *)xmlXPathCastToString(obj);
93 					ZVAL_STRING(&fci.params[i], str);
94 					xmlFree(str);
95 				} else if (type == 2) {
96 					int j;
97 					if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
98 						array_init(&fci.params[i]);
99 						for (j = 0; j < obj->nodesetval->nodeNr; j++) {
100 							xmlNodePtr node = obj->nodesetval->nodeTab[j];
101 							zval child;
102 							/* not sure, if we need this... it's copied from xpath.c */
103 							if (node->type == XML_NAMESPACE_DECL) {
104 								xmlNsPtr curns;
105 								xmlNodePtr nsparent;
106 
107 								nsparent = node->_private;
108 								curns = xmlNewNs(NULL, node->name, NULL);
109 								if (node->children) {
110 									curns->prefix = xmlStrdup((xmlChar *) node->children);
111 								}
112 								if (node->children) {
113 									node = xmlNewDocNode(node->doc, NULL, (xmlChar *) node->children, node->name);
114 								} else {
115 									node = xmlNewDocNode(node->doc, NULL, (xmlChar *) "xmlns", node->name);
116 								}
117 								node->type = XML_NAMESPACE_DECL;
118 								node->parent = nsparent;
119 								node->ns = curns;
120 							}
121 							php_dom_create_object(node, &child, &intern->dom);
122 							add_next_index_zval(&fci.params[i], &child);
123 						}
124 					} else {
125 						ZVAL_EMPTY_ARRAY(&fci.params[i]);
126 					}
127 				}
128 				break;
129 			default:
130 			ZVAL_STRING(&fci.params[i], (char *)xmlXPathCastToString(obj));
131 		}
132 		xmlXPathFreeObject(obj);
133 	}
134 
135 	fci.size = sizeof(fci);
136 
137 	obj = valuePop(ctxt);
138 	if (obj->stringval == NULL) {
139 		zend_type_error("Handler name must be a string");
140 		xmlXPathFreeObject(obj);
141 		goto cleanup;
142 	}
143 	ZVAL_STRING(&fci.function_name, (char *) obj->stringval);
144 	xmlXPathFreeObject(obj);
145 
146 	fci.object = NULL;
147 	fci.named_params = NULL;
148 	fci.retval = &retval;
149 
150 	if (!zend_make_callable(&fci.function_name, &callable)) {
151 		zend_throw_error(NULL, "Unable to call handler %s()", ZSTR_VAL(callable));
152 		goto cleanup;
153 	} else if (intern->registerPhpFunctions == 2 && zend_hash_exists(intern->registered_phpfunctions, callable) == 0) {
154 		zend_throw_error(NULL, "Not allowed to call handler '%s()'.", ZSTR_VAL(callable));
155 		goto cleanup;
156 	} else {
157 		result = zend_call_function(&fci, NULL);
158 		if (result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
159 			if (Z_TYPE(retval) == IS_OBJECT && instanceof_function(Z_OBJCE(retval), dom_node_class_entry)) {
160 				xmlNode *nodep;
161 				dom_object *obj;
162 				if (intern->node_list == NULL) {
163 					intern->node_list = zend_new_array(0);
164 				}
165 				Z_ADDREF(retval);
166 				zend_hash_next_index_insert(intern->node_list, &retval);
167 				obj = Z_DOMOBJ_P(&retval);
168 				nodep = dom_object_get_node(obj);
169 				valuePush(ctxt, xmlXPathNewNodeSet(nodep));
170 			} else if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
171 				valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(retval) == IS_TRUE));
172 			} else if (Z_TYPE(retval) == IS_OBJECT) {
173 				zend_type_error("A PHP Object cannot be converted to a XPath-string");
174 				return;
175 			} else {
176 				zend_string *str = zval_get_string(&retval);
177 				valuePush(ctxt, xmlXPathNewString((xmlChar *) ZSTR_VAL(str)));
178 				zend_string_release_ex(str, 0);
179 			}
180 			zval_ptr_dtor(&retval);
181 		}
182 	}
183 cleanup:
184 	zend_string_release_ex(callable, 0);
185 	zval_ptr_dtor_str(&fci.function_name);
186 	if (fci.param_count > 0) {
187 		for (i = 0; i < nargs - 1; i++) {
188 			zval_ptr_dtor(&fci.params[i]);
189 		}
190 		efree(fci.params);
191 	}
192 }
193 /* }}} */
194 
dom_xpath_ext_function_string_php(xmlXPathParserContextPtr ctxt,int nargs)195 static void dom_xpath_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
196 {
197 	dom_xpath_ext_function_php(ctxt, nargs, 1);
198 }
199 /* }}} */
200 
dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt,int nargs)201 static void dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
202 {
203 	dom_xpath_ext_function_php(ctxt, nargs, 2);
204 }
205 /* }}} */
206 
207 /* {{{ */
PHP_METHOD(DOMXPath,__construct)208 PHP_METHOD(DOMXPath, __construct)
209 {
210 	zval *doc;
211 	zend_bool register_node_ns = 1;
212 	xmlDocPtr docp = NULL;
213 	dom_object *docobj;
214 	dom_xpath_object *intern;
215 	xmlXPathContextPtr ctx, oldctx;
216 
217 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "O|b", &doc, dom_document_class_entry, &register_node_ns) == FAILURE) {
218 		RETURN_THROWS();
219 	}
220 
221 	DOM_GET_OBJ(docp, doc, xmlDocPtr, docobj);
222 
223 	ctx = xmlXPathNewContext(docp);
224 	if (ctx == NULL) {
225 		php_dom_throw_error(INVALID_STATE_ERR, 1);
226 		RETURN_THROWS();
227 	}
228 
229 	intern = Z_XPATHOBJ_P(ZEND_THIS);
230 	if (intern != NULL) {
231 		oldctx = (xmlXPathContextPtr)intern->dom.ptr;
232 		if (oldctx != NULL) {
233 			php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
234 			xmlXPathFreeContext(oldctx);
235 		}
236 
237 		xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "functionString",
238 					   (const xmlChar *) "http://php.net/xpath",
239 					   dom_xpath_ext_function_string_php);
240 		xmlXPathRegisterFuncNS (ctx, (const xmlChar *) "function",
241 					   (const xmlChar *) "http://php.net/xpath",
242 					   dom_xpath_ext_function_object_php);
243 
244 		intern->dom.ptr = ctx;
245 		ctx->userData = (void *)intern;
246 		intern->dom.document = docobj->document;
247 		intern->register_node_ns = register_node_ns;
248 		php_libxml_increment_doc_ref((php_libxml_node_object *) &intern->dom, docp);
249 	}
250 }
251 /* }}} end DOMXPath::__construct */
252 
253 /* {{{ document DOMDocument*/
dom_xpath_document_read(dom_object * obj,zval * retval)254 int dom_xpath_document_read(dom_object *obj, zval *retval)
255 {
256 	xmlDoc *docp = NULL;
257 	xmlXPathContextPtr ctx = (xmlXPathContextPtr) obj->ptr;
258 
259 	if (ctx) {
260 		docp = (xmlDocPtr) ctx->doc;
261 	}
262 
263 	php_dom_create_object((xmlNodePtr) docp, retval, obj);
264 	return SUCCESS;
265 }
266 /* }}} */
267 
268 /* {{{ registerNodeNamespaces bool*/
php_xpath_obj_from_dom_obj(dom_object * obj)269 static inline dom_xpath_object *php_xpath_obj_from_dom_obj(dom_object *obj) {
270 	return (dom_xpath_object*)((char*)(obj) - XtOffsetOf(dom_xpath_object, dom));
271 }
272 
dom_xpath_register_node_ns_read(dom_object * obj,zval * retval)273 int dom_xpath_register_node_ns_read(dom_object *obj, zval *retval)
274 {
275 	ZVAL_BOOL(retval, php_xpath_obj_from_dom_obj(obj)->register_node_ns);
276 
277 	return SUCCESS;
278 }
279 
dom_xpath_register_node_ns_write(dom_object * obj,zval * newval)280 int dom_xpath_register_node_ns_write(dom_object *obj, zval *newval)
281 {
282 	php_xpath_obj_from_dom_obj(obj)->register_node_ns = zend_is_true(newval);
283 
284 	return SUCCESS;
285 }
286 /* }}} */
287 
288 /* {{{ */
PHP_METHOD(DOMXPath,registerNamespace)289 PHP_METHOD(DOMXPath, registerNamespace)
290 {
291 	zval *id;
292 	xmlXPathContextPtr ctxp;
293 	size_t prefix_len, ns_uri_len;
294 	dom_xpath_object *intern;
295 	unsigned char *prefix, *ns_uri;
296 
297 	id = ZEND_THIS;
298 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "ss", &prefix, &prefix_len, &ns_uri, &ns_uri_len) == FAILURE) {
299 		RETURN_THROWS();
300 	}
301 
302 	intern = Z_XPATHOBJ_P(id);
303 
304 	ctxp = (xmlXPathContextPtr) intern->dom.ptr;
305 	if (ctxp == NULL) {
306 		zend_throw_error(NULL, "Invalid XPath Context");
307 		RETURN_THROWS();
308 	}
309 
310 	if (xmlXPathRegisterNs(ctxp, prefix, ns_uri) != 0) {
311 		RETURN_FALSE;
312 	}
313 	RETURN_TRUE;
314 }
315 /* }}} */
316 
dom_xpath_iter(zval * baseobj,dom_object * intern)317 static void dom_xpath_iter(zval *baseobj, dom_object *intern) /* {{{ */
318 {
319 	dom_nnodemap_object *mapptr = (dom_nnodemap_object *) intern->ptr;
320 
321 	ZVAL_COPY_VALUE(&mapptr->baseobj_zv, baseobj);
322 	mapptr->nodetype = DOM_NODESET;
323 }
324 /* }}} */
325 
php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS,int type)326 static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */
327 {
328 	zval *id, retval, *context = NULL;
329 	xmlXPathContextPtr ctxp;
330 	xmlNodePtr nodep = NULL;
331 	xmlXPathObjectPtr xpathobjp;
332 	size_t expr_len, nsnbr = 0, xpath_type;
333 	dom_xpath_object *intern;
334 	dom_object *nodeobj;
335 	char *expr;
336 	xmlDoc *docp = NULL;
337 	xmlNsPtr *ns = NULL;
338 	zend_bool register_node_ns;
339 
340 	id = ZEND_THIS;
341 	intern = Z_XPATHOBJ_P(id);
342 	register_node_ns = intern->register_node_ns;
343 
344 	if (zend_parse_parameters(ZEND_NUM_ARGS(), "s|O!b", &expr, &expr_len, &context, dom_node_class_entry, &register_node_ns) == FAILURE) {
345 		RETURN_THROWS();
346 	}
347 
348 	ctxp = (xmlXPathContextPtr) intern->dom.ptr;
349 	if (ctxp == NULL) {
350 		zend_throw_error(NULL, "Invalid XPath Context");
351 		RETURN_THROWS();
352 	}
353 
354 	docp = (xmlDocPtr) ctxp->doc;
355 	if (docp == NULL) {
356 		php_error_docref(NULL, E_WARNING, "Invalid XPath Document Pointer");
357 		RETURN_FALSE;
358 	}
359 
360 	if (context != NULL) {
361 		DOM_GET_OBJ(nodep, context, xmlNodePtr, nodeobj);
362 	}
363 
364 	if (!nodep) {
365 		nodep = xmlDocGetRootElement(docp);
366 	}
367 
368 	if (nodep && docp != nodep->doc) {
369 		zend_throw_error(NULL, "Node from wrong document");
370 		RETURN_THROWS();
371 	}
372 
373 	ctxp->node = nodep;
374 
375 	if (register_node_ns) {
376 		/* Register namespaces in the node */
377 		ns = xmlGetNsList(docp, nodep);
378 
379 		if (ns != NULL) {
380 			while (ns[nsnbr] != NULL)
381 			nsnbr++;
382 		}
383 	}
384 
385 
386     ctxp->namespaces = ns;
387     ctxp->nsNr = nsnbr;
388 
389 	xpathobjp = xmlXPathEvalExpression((xmlChar *) expr, ctxp);
390 	ctxp->node = NULL;
391 
392 	if (ns != NULL) {
393 		xmlFree(ns);
394 		ctxp->namespaces = NULL;
395 		ctxp->nsNr = 0;
396 	}
397 
398 	if (! xpathobjp) {
399 		/* TODO Add Warning? */
400 		RETURN_FALSE;
401 	}
402 
403 	if (type == PHP_DOM_XPATH_QUERY) {
404 		xpath_type = XPATH_NODESET;
405 	} else {
406 		xpath_type = xpathobjp->type;
407 	}
408 
409 	switch (xpath_type) {
410 
411 		case  XPATH_NODESET:
412 		{
413 			int i;
414 			xmlNodeSetPtr nodesetp;
415 
416 			if (xpathobjp->type == XPATH_NODESET && NULL != (nodesetp = xpathobjp->nodesetval) && nodesetp->nodeNr) {
417 
418 				array_init(&retval);
419 				for (i = 0; i < nodesetp->nodeNr; i++) {
420 					xmlNodePtr node = nodesetp->nodeTab[i];
421 					zval child;
422 
423 					if (node->type == XML_NAMESPACE_DECL) {
424 						xmlNsPtr curns;
425 						xmlNodePtr nsparent;
426 
427 						nsparent = node->_private;
428 						curns = xmlNewNs(NULL, node->name, NULL);
429 						if (node->children) {
430 							curns->prefix = xmlStrdup((xmlChar *) node->children);
431 						}
432 						if (node->children) {
433 							node = xmlNewDocNode(docp, NULL, (xmlChar *) node->children, node->name);
434 						} else {
435 							node = xmlNewDocNode(docp, NULL, (xmlChar *) "xmlns", node->name);
436 						}
437 						node->type = XML_NAMESPACE_DECL;
438 						node->parent = nsparent;
439 						node->ns = curns;
440 					}
441 					php_dom_create_object(node, &child, &intern->dom);
442 					add_next_index_zval(&retval, &child);
443 				}
444 			} else {
445 				ZVAL_EMPTY_ARRAY(&retval);
446 			}
447 			php_dom_create_iterator(return_value, DOM_NODELIST);
448 			nodeobj = Z_DOMOBJ_P(return_value);
449 			dom_xpath_iter(&retval, nodeobj);
450 			break;
451 		}
452 
453 		case XPATH_BOOLEAN:
454 			RETVAL_BOOL(xpathobjp->boolval);
455 			break;
456 
457 		case XPATH_NUMBER:
458 			RETVAL_DOUBLE(xpathobjp->floatval);
459 			break;
460 
461 		case XPATH_STRING:
462 			RETVAL_STRING((char *) xpathobjp->stringval);
463 			break;
464 
465 		default:
466 			RETVAL_NULL();
467 			break;
468 	}
469 
470 	xmlXPathFreeObject(xpathobjp);
471 }
472 /* }}} */
473 
474 /* {{{ */
PHP_METHOD(DOMXPath,query)475 PHP_METHOD(DOMXPath, query)
476 {
477 	php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_QUERY);
478 }
479 /* }}} end dom_xpath_query */
480 
481 /* {{{ */
PHP_METHOD(DOMXPath,evaluate)482 PHP_METHOD(DOMXPath, evaluate)
483 {
484 	php_xpath_eval(INTERNAL_FUNCTION_PARAM_PASSTHRU, PHP_DOM_XPATH_EVALUATE);
485 }
486 /* }}} end dom_xpath_evaluate */
487 
488 /* {{{ */
PHP_METHOD(DOMXPath,registerPhpFunctions)489 PHP_METHOD(DOMXPath, registerPhpFunctions)
490 {
491 	zval *id = ZEND_THIS;
492 	dom_xpath_object *intern = Z_XPATHOBJ_P(id);
493 	zval *entry, new_string;
494 	zend_string *name = NULL;
495 	HashTable *ht = NULL;
496 
497 	ZEND_PARSE_PARAMETERS_START(0, 1)
498 		Z_PARAM_OPTIONAL
499 		Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(ht, name)
500 	ZEND_PARSE_PARAMETERS_END();
501 
502 	if (ht) {
503 		ZEND_HASH_FOREACH_VAL(ht, entry) {
504 			zend_string *str = zval_get_string(entry);
505 			ZVAL_LONG(&new_string, 1);
506 			zend_hash_update(intern->registered_phpfunctions, str, &new_string);
507 			zend_string_release_ex(str, 0);
508 		} ZEND_HASH_FOREACH_END();
509 		intern->registerPhpFunctions = 2;
510 	} else if (name) {
511 		ZVAL_LONG(&new_string, 1);
512 		zend_hash_update(intern->registered_phpfunctions, name, &new_string);
513 		intern->registerPhpFunctions = 2;
514 	} else {
515 		intern->registerPhpFunctions = 1;
516 	}
517 
518 }
519 /* }}} end dom_xpath_register_php_functions */
520 
521 #endif /* LIBXML_XPATH_ENABLED */
522 
523 #endif
524