xref: /PHP-8.4/ext/dom/xpath_callbacks.c (revision 11accb5c)
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    | https://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    |          Niels Dossche <nielsdos@php.net>                            |
16    +----------------------------------------------------------------------+
17  */
18 
19 #ifdef HAVE_CONFIG_H
20 #include <config.h>
21 #endif
22 
23 #include "php.h"
24 #if defined(HAVE_LIBXML) && defined(HAVE_DOM)
25 
26 #include "php_dom.h"
27 #include <libxml/parserInternals.h>
28 
xpath_callbacks_entry_dtor(zval * zv)29 static void xpath_callbacks_entry_dtor(zval *zv)
30 {
31 	zend_fcall_info_cache *fcc = Z_PTR_P(zv);
32 	zend_fcc_dtor(fcc);
33 	efree(fcc);
34 }
35 
php_dom_xpath_callback_ns_ctor(php_dom_xpath_callback_ns * ns)36 PHP_DOM_EXPORT void php_dom_xpath_callback_ns_ctor(php_dom_xpath_callback_ns *ns)
37 {
38 	zend_hash_init(&ns->functions, 0, NULL, xpath_callbacks_entry_dtor, false);
39 	ns->mode = PHP_DOM_REG_FUNC_MODE_NONE;
40 }
41 
php_dom_xpath_callback_ns_dtor(php_dom_xpath_callback_ns * ns)42 PHP_DOM_EXPORT void php_dom_xpath_callback_ns_dtor(php_dom_xpath_callback_ns *ns)
43 {
44 	zend_hash_destroy(&ns->functions);
45 }
46 
php_dom_xpath_callbacks_ctor(php_dom_xpath_callbacks * registry)47 PHP_DOM_EXPORT void php_dom_xpath_callbacks_ctor(php_dom_xpath_callbacks *registry)
48 {
49 	registry->php_ns = NULL;
50 	registry->namespaces = NULL;
51 	registry->node_list = NULL;
52 }
53 
php_dom_xpath_callbacks_clean_node_list(php_dom_xpath_callbacks * registry)54 PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_node_list(php_dom_xpath_callbacks *registry)
55 {
56 	if (registry->node_list) {
57 		zend_hash_destroy(registry->node_list);
58 		FREE_HASHTABLE(registry->node_list);
59 		registry->node_list = NULL;
60 	}
61 }
62 
php_dom_xpath_callbacks_clean_argument_stack(xmlXPathParserContextPtr ctxt,uint32_t num_args)63 PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_argument_stack(xmlXPathParserContextPtr ctxt, uint32_t num_args)
64 {
65 	for (uint32_t i = 0; i < num_args; i++) {
66 		xmlXPathObjectPtr obj = valuePop(ctxt);
67 		xmlXPathFreeObject(obj);
68 	}
69 
70 	/* Don't push a sentinel value here. If this is called from an error situation, then by *not* pushing a sentinel
71 	 * the execution will halt. If this is called from a regular situation, then it is the caller's responsibility
72 	 * to ensure the stack remains balanced. */
73 }
74 
php_dom_xpath_callbacks_dtor(php_dom_xpath_callbacks * registry)75 PHP_DOM_EXPORT void php_dom_xpath_callbacks_dtor(php_dom_xpath_callbacks *registry)
76 {
77 	if (registry->php_ns) {
78 		php_dom_xpath_callback_ns_dtor(registry->php_ns);
79 		efree(registry->php_ns);
80 	}
81 	if (registry->namespaces) {
82 		php_dom_xpath_callback_ns *ns;
83 		ZEND_HASH_MAP_FOREACH_PTR(registry->namespaces, ns) {
84 			php_dom_xpath_callback_ns_dtor(ns);
85 			efree(ns);
86 		} ZEND_HASH_FOREACH_END();
87 
88 		zend_hash_destroy(registry->namespaces);
89 		FREE_HASHTABLE(registry->namespaces);
90 	}
91 	php_dom_xpath_callbacks_clean_node_list(registry);
92 }
93 
php_dom_xpath_callback_ns_get_gc(php_dom_xpath_callback_ns * ns,zend_get_gc_buffer * gc_buffer)94 static void php_dom_xpath_callback_ns_get_gc(php_dom_xpath_callback_ns *ns, zend_get_gc_buffer *gc_buffer)
95 {
96 	zend_fcall_info_cache *entry;
97 	ZEND_HASH_MAP_FOREACH_PTR(&ns->functions, entry) {
98 		zend_get_gc_buffer_add_fcc(gc_buffer, entry);
99 	} ZEND_HASH_FOREACH_END();
100 }
101 
php_dom_xpath_callbacks_get_gc(php_dom_xpath_callbacks * registry,zend_get_gc_buffer * gc_buffer)102 PHP_DOM_EXPORT void php_dom_xpath_callbacks_get_gc(php_dom_xpath_callbacks *registry, zend_get_gc_buffer *gc_buffer)
103 {
104 	if (registry->php_ns) {
105 		php_dom_xpath_callback_ns_get_gc(registry->php_ns, gc_buffer);
106 	}
107 	if (registry->namespaces) {
108 		php_dom_xpath_callback_ns *ns;
109 		ZEND_HASH_MAP_FOREACH_PTR(registry->namespaces, ns) {
110 			php_dom_xpath_callback_ns_get_gc(ns, gc_buffer);
111 		} ZEND_HASH_FOREACH_END();
112 	}
113 }
114 
php_dom_xpath_callbacks_get_gc_for_whole_object(php_dom_xpath_callbacks * registry,zend_object * object,zval ** table,int * n)115 PHP_DOM_EXPORT HashTable *php_dom_xpath_callbacks_get_gc_for_whole_object(php_dom_xpath_callbacks *registry, zend_object *object, zval **table, int *n)
116 {
117 	if (registry->php_ns || registry->namespaces) {
118 		zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
119 		php_dom_xpath_callbacks_get_gc(registry, gc_buffer);
120 		zend_get_gc_buffer_use(gc_buffer, table, n);
121 
122 		if (object->properties == NULL && object->ce->default_properties_count == 0) {
123 			return NULL;
124 		} else {
125 			return zend_std_get_properties(object);
126 		}
127 	} else {
128 		return zend_std_get_gc(object, table, n);
129 	}
130 }
131 
php_dom_xpath_is_callback_name_valid(const zend_string * name,php_dom_xpath_callback_name_validation name_validation)132 static bool php_dom_xpath_is_callback_name_valid(const zend_string *name, php_dom_xpath_callback_name_validation name_validation)
133 {
134 	if (ZSTR_LEN(name) == 0) {
135 		return false;
136 	}
137 
138 	if (name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS || name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME) {
139 		if (zend_str_has_nul_byte(name)) {
140 			return false;
141 		}
142 	}
143 
144 	if (name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME) {
145 		if (xmlValidateNCName(BAD_CAST ZSTR_VAL(name), /* pass 0 to disallow spaces */ 0) != 0) {
146 			return false;
147 		}
148 	}
149 
150 	return true;
151 }
152 
php_dom_xpath_is_callback_name_valid_and_throw(const zend_string * name,php_dom_xpath_callback_name_validation name_validation,bool is_array)153 static bool php_dom_xpath_is_callback_name_valid_and_throw(const zend_string *name, php_dom_xpath_callback_name_validation name_validation, bool is_array)
154 {
155 	if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) {
156 		if (is_array) {
157 			zend_argument_value_error(1, "must be an array containing valid callback names");
158 		} else {
159 			zend_argument_value_error(2, "must be a valid callback name");
160 		}
161 		return false;
162 	}
163 	return true;
164 }
165 
php_dom_xpath_callbacks_delayed_lib_registration(const php_dom_xpath_callbacks * registry,void * ctxt,php_dom_xpath_callbacks_register_func_ctx register_func)166 PHP_DOM_EXPORT void php_dom_xpath_callbacks_delayed_lib_registration(const php_dom_xpath_callbacks* registry, void *ctxt, php_dom_xpath_callbacks_register_func_ctx register_func)
167 {
168 	if (registry->namespaces) {
169 		zend_string *namespace;
170 		php_dom_xpath_callback_ns *ns;
171 		ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(registry->namespaces, namespace, ns) {
172 			zend_string *name;
173 			ZEND_HASH_MAP_FOREACH_STR_KEY(&ns->functions, name) {
174 				register_func(ctxt, namespace, name);
175 			} ZEND_HASH_FOREACH_END();
176 		} ZEND_HASH_FOREACH_END();
177 	}
178 }
179 
php_dom_xpath_callback_ns_update_method_handler(php_dom_xpath_callback_ns * ns,xmlXPathContextPtr ctxt,const zend_string * namespace,zend_string * name,const HashTable * callable_ht,php_dom_xpath_callback_name_validation name_validation,php_dom_xpath_callbacks_register_func_ctx register_func)180 static zend_result php_dom_xpath_callback_ns_update_method_handler(
181 	php_dom_xpath_callback_ns* ns,
182 	xmlXPathContextPtr ctxt,
183 	const zend_string *namespace,
184 	zend_string *name,
185 	const HashTable *callable_ht,
186 	php_dom_xpath_callback_name_validation name_validation,
187 	php_dom_xpath_callbacks_register_func_ctx register_func
188 )
189 {
190 	zval *entry, registered_value;
191 
192 	if (callable_ht) {
193 		zend_string *key;
194 		ZEND_HASH_FOREACH_STR_KEY_VAL(callable_ht, key, entry) {
195 			zend_fcall_info_cache* fcc = emalloc(sizeof(zend_fcall_info));
196 			char *error;
197 			if (!zend_is_callable_ex(entry, NULL, 0, NULL, fcc, &error)) {
198 				zend_argument_type_error(1, "must be an array with valid callbacks as values, %s", error);
199 				efree(fcc);
200 				efree(error);
201 				return FAILURE;
202 			}
203 
204 			zend_fcc_addref(fcc);
205 			ZVAL_PTR(&registered_value, fcc);
206 
207 			if (!key) {
208 				zend_string *str = zval_try_get_string(entry);
209 				if (str && php_dom_xpath_is_callback_name_valid_and_throw(str, name_validation, true)) {
210 					zend_hash_update(&ns->functions, str, &registered_value);
211 					if (register_func) {
212 						register_func(ctxt, namespace, str);
213 					}
214 					zend_string_release_ex(str, false);
215 				} else {
216 					zend_fcc_dtor(fcc);
217 					efree(fcc);
218 					return FAILURE;
219 				}
220 			} else {
221 				if (!php_dom_xpath_is_callback_name_valid_and_throw(key, name_validation, true)) {
222 					zend_fcc_dtor(fcc);
223 					efree(fcc);
224 					return FAILURE;
225 				}
226 				zend_hash_update(&ns->functions, key, &registered_value);
227 				if (register_func) {
228 					register_func(ctxt, namespace, key);
229 				}
230 			}
231 		} ZEND_HASH_FOREACH_END();
232 		ns->mode = PHP_DOM_REG_FUNC_MODE_SET;
233 	} else if (name) {
234 		if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) {
235 			zend_argument_value_error(1, "must be a valid callback name");
236 			return FAILURE;
237 		}
238 		zend_fcall_info_cache* fcc = emalloc(sizeof(zend_fcall_info));
239 		char *error;
240 		zval tmp;
241 		ZVAL_STR(&tmp, name);
242 		if (!zend_is_callable_ex(&tmp, NULL, 0, NULL, fcc, &error)) {
243 			zend_argument_type_error(1, "must be a callable, %s", error);
244 			efree(fcc);
245 			efree(error);
246 			return FAILURE;
247 		}
248 		zend_fcc_addref(fcc);
249 		ZVAL_PTR(&registered_value, fcc);
250 		zend_hash_update(&ns->functions, name, &registered_value);
251 		if (register_func) {
252 			register_func(ctxt, namespace, name);
253 		}
254 		ns->mode = PHP_DOM_REG_FUNC_MODE_SET;
255 	} else {
256 		ns->mode = PHP_DOM_REG_FUNC_MODE_ALL;
257 	}
258 
259 	return SUCCESS;
260 }
261 
php_dom_xpath_callbacks_ensure_ns(php_dom_xpath_callbacks * registry,zend_string * ns)262 static php_dom_xpath_callback_ns *php_dom_xpath_callbacks_ensure_ns(php_dom_xpath_callbacks *registry, zend_string *ns)
263 {
264 	if (ns == NULL) {
265 		if (!registry->php_ns) {
266 			registry->php_ns = emalloc(sizeof(php_dom_xpath_callback_ns));
267 			php_dom_xpath_callback_ns_ctor(registry->php_ns);
268 		}
269 		return registry->php_ns;
270 	} else {
271 		if (!registry->namespaces) {
272 			/* In most cases probably only a single namespace is registered. */
273 			registry->namespaces = zend_new_array(1);
274 		}
275 		php_dom_xpath_callback_ns *namespace = zend_hash_find_ptr(registry->namespaces, ns);
276 		if (namespace == NULL) {
277 			namespace = emalloc(sizeof(php_dom_xpath_callback_ns));
278 			php_dom_xpath_callback_ns_ctor(namespace);
279 			zend_hash_add_new_ptr(registry->namespaces, ns, namespace);
280 		}
281 		return namespace;
282 	}
283 }
284 
php_dom_xpath_callbacks_update_method_handler(php_dom_xpath_callbacks * registry,xmlXPathContextPtr ctxt,zend_string * ns,zend_string * name,const HashTable * callable_ht,php_dom_xpath_callback_name_validation name_validation,php_dom_xpath_callbacks_register_func_ctx register_func)285 PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_update_method_handler(php_dom_xpath_callbacks* registry, xmlXPathContextPtr ctxt, zend_string *ns, zend_string *name, const HashTable *callable_ht, php_dom_xpath_callback_name_validation name_validation, php_dom_xpath_callbacks_register_func_ctx register_func)
286 {
287 	php_dom_xpath_callback_ns *namespace = php_dom_xpath_callbacks_ensure_ns(registry, ns);
288 	return php_dom_xpath_callback_ns_update_method_handler(namespace, ctxt, ns, name, callable_ht, name_validation, register_func);
289 }
290 
php_dom_xpath_callbacks_update_single_method_handler(php_dom_xpath_callbacks * registry,xmlXPathContextPtr ctxt,zend_string * ns,zend_string * name,const zend_fcall_info_cache * fcc,php_dom_xpath_callback_name_validation name_validation,php_dom_xpath_callbacks_register_func_ctx register_func)291 PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_update_single_method_handler(php_dom_xpath_callbacks* registry, xmlXPathContextPtr ctxt, zend_string *ns, zend_string *name, const zend_fcall_info_cache *fcc, php_dom_xpath_callback_name_validation name_validation, php_dom_xpath_callbacks_register_func_ctx register_func)
292 {
293 	if (!php_dom_xpath_is_callback_name_valid_and_throw(name, name_validation, false)) {
294 		return FAILURE;
295 	}
296 
297 	php_dom_xpath_callback_ns *namespace = php_dom_xpath_callbacks_ensure_ns(registry, ns);
298 	zend_fcall_info_cache* allocated_fcc = emalloc(sizeof(zend_fcall_info));
299 	zend_fcc_dup(allocated_fcc, fcc);
300 
301 	zval registered_value;
302 	ZVAL_PTR(&registered_value, allocated_fcc);
303 
304 	zend_hash_update(&namespace->functions, name, &registered_value);
305 	if (register_func) {
306 		register_func(ctxt, ns, name);
307 	}
308 
309 	namespace->mode = PHP_DOM_REG_FUNC_MODE_SET;
310 
311 	return SUCCESS;
312 }
313 
php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt,uint32_t param_count,php_dom_xpath_nodeset_evaluation_mode evaluation_mode,dom_object * intern,php_dom_xpath_callbacks_proxy_factory proxy_factory)314 static zval *php_dom_xpath_callback_fetch_args(xmlXPathParserContextPtr ctxt, uint32_t param_count, php_dom_xpath_nodeset_evaluation_mode evaluation_mode, dom_object *intern, php_dom_xpath_callbacks_proxy_factory proxy_factory)
315 {
316 	if (param_count == 0) {
317 		return NULL;
318 	}
319 
320 	zval *params = safe_emalloc(param_count, sizeof(zval), 0);
321 
322 	/* Reverse order to pop values off ctxt stack */
323 	for (zval *param = params + param_count - 1; param >= params; param--) {
324 		xmlXPathObjectPtr obj = valuePop(ctxt);
325 		ZEND_ASSERT(obj != NULL);
326 		switch (obj->type) {
327 			case XPATH_STRING:
328 				ZVAL_STRING(param, (char *)obj->stringval);
329 				break;
330 			case XPATH_BOOLEAN:
331 				ZVAL_BOOL(param, obj->boolval);
332 				break;
333 			case XPATH_NUMBER:
334 				ZVAL_DOUBLE(param, obj->floatval);
335 				break;
336 			case XPATH_NODESET:
337 				if (evaluation_mode == PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING) {
338 					char *str = (char *)xmlXPathCastToString(obj);
339 					ZVAL_STRING(param, str);
340 					xmlFree(str);
341 				} else if (evaluation_mode == PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET) {
342 					if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
343 						array_init_size(param, obj->nodesetval->nodeNr);
344 						zend_hash_real_init_packed(Z_ARRVAL_P(param));
345 						for (int j = 0; j < obj->nodesetval->nodeNr; j++) {
346 							xmlNodePtr node = obj->nodesetval->nodeTab[j];
347 							zval child;
348 							if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) {
349 								xmlNodePtr nsparent = node->_private;
350 								xmlNsPtr original = (xmlNsPtr) node;
351 
352 								/* Make sure parent dom object exists, so we can take an extra reference. */
353 								zval parent_zval; /* don't destroy me, my lifetime is transfered to the fake namespace decl */
354 								php_dom_create_object(nsparent, &parent_zval, intern);
355 								dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
356 
357 								php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
358 							} else {
359 								proxy_factory(node, &child, intern, ctxt);
360 							}
361 							zend_hash_next_index_insert_new(Z_ARRVAL_P(param), &child);
362 						}
363 					} else {
364 						ZVAL_EMPTY_ARRAY(param);
365 					}
366 				}
367 				break;
368 			default: {
369 				char *str = (char *)xmlXPathCastToString(obj);
370 				ZVAL_STRING(param, str);
371 				xmlFree(str);
372 				break;
373 			}
374 		}
375 		xmlXPathFreeObject(obj);
376 	}
377 
378 	return params;
379 }
380 
php_dom_xpath_callback_cleanup_args(zval * params,uint32_t param_count)381 static void php_dom_xpath_callback_cleanup_args(zval *params, uint32_t param_count)
382 {
383 	if (params) {
384 		for (uint32_t i = 0; i < param_count; i++) {
385 			zval_ptr_dtor(&params[i]);
386 		}
387 		efree(params);
388 	}
389 }
390 
php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks * xpath_callbacks,php_dom_xpath_callback_ns * ns,xmlXPathParserContextPtr ctxt,zval * params,uint32_t param_count,const char * function_name,size_t function_name_length)391 static zend_result php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks *xpath_callbacks, php_dom_xpath_callback_ns *ns, xmlXPathParserContextPtr ctxt, zval *params, uint32_t param_count, const char *function_name, size_t function_name_length)
392 {
393     zval callback_retval;
394 
395 	if (UNEXPECTED(ns == NULL)) {
396 		zend_throw_error(NULL, "No callbacks were registered");
397 		return FAILURE;
398 	}
399 
400 	if (ns->mode == PHP_DOM_REG_FUNC_MODE_ALL) {
401 		zend_fcall_info fci;
402 		fci.size = sizeof(fci);
403 		fci.object = NULL;
404 		fci.retval = &callback_retval;
405 		fci.param_count = param_count;
406 		fci.params = params;
407 		fci.named_params = NULL;
408 		ZVAL_STRINGL(&fci.function_name, function_name, function_name_length);
409 
410 		zend_call_function(&fci, NULL);
411 		zend_string_release_ex(Z_STR(fci.function_name), false);
412 		if (UNEXPECTED(EG(exception))) {
413 			return FAILURE;
414 		}
415 	} else {
416 		ZEND_ASSERT(ns->mode == PHP_DOM_REG_FUNC_MODE_SET);
417 
418 		zval *fcc_wrapper = zend_hash_str_find(&ns->functions, function_name, function_name_length);
419 		if (fcc_wrapper) {
420 			zend_call_known_fcc(Z_PTR_P(fcc_wrapper), &callback_retval, param_count, params, NULL);
421 		} else {
422 			zend_throw_error(NULL, "No callback handler \"%s\" registered", function_name);
423 			return FAILURE;
424 		}
425 	}
426 
427 	if (Z_TYPE(callback_retval) != IS_UNDEF) {
428 		if (Z_TYPE(callback_retval) == IS_OBJECT && instanceof_function(Z_OBJCE(callback_retval), dom_node_class_entry)) {
429 			xmlNode *nodep;
430 			dom_object *obj;
431 			if (xpath_callbacks->node_list == NULL) {
432 				xpath_callbacks->node_list = zend_new_array(0);
433 			}
434 			Z_ADDREF_P(&callback_retval);
435 			zend_hash_next_index_insert_new(xpath_callbacks->node_list, &callback_retval);
436 			obj = Z_DOMOBJ_P(&callback_retval);
437 			nodep = dom_object_get_node(obj);
438 			valuePush(ctxt, xmlXPathNewNodeSet(nodep));
439 		} else if (Z_TYPE(callback_retval) == IS_FALSE || Z_TYPE(callback_retval) == IS_TRUE) {
440 			valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(callback_retval) == IS_TRUE));
441 		} else if (Z_TYPE(callback_retval) == IS_OBJECT) {
442 			zend_type_error("Only objects that are instances of DOMNode can be converted to an XPath expression");
443 			zval_ptr_dtor(&callback_retval);
444 			return FAILURE;
445 		} else {
446 			zend_string *str = zval_get_string(&callback_retval);
447 			valuePush(ctxt, xmlXPathNewString(BAD_CAST ZSTR_VAL(str)));
448 			zend_string_release_ex(str, 0);
449 		}
450 		zval_ptr_dtor(&callback_retval);
451 	}
452 
453 	return SUCCESS;
454 }
455 
php_dom_xpath_callbacks_call_php_ns(php_dom_xpath_callbacks * xpath_callbacks,xmlXPathParserContextPtr ctxt,int num_args,php_dom_xpath_nodeset_evaluation_mode evaluation_mode,dom_object * intern,php_dom_xpath_callbacks_proxy_factory proxy_factory)456 PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_php_ns(php_dom_xpath_callbacks *xpath_callbacks, xmlXPathParserContextPtr ctxt, int num_args, php_dom_xpath_nodeset_evaluation_mode evaluation_mode, dom_object *intern, php_dom_xpath_callbacks_proxy_factory proxy_factory)
457 {
458 	zend_result result = FAILURE;
459 
460 	if (UNEXPECTED(num_args == 0)) {
461 		zend_throw_error(NULL, "Function name must be passed as the first argument");
462 		goto cleanup_no_obj;
463 	}
464 
465 	uint32_t param_count = num_args - 1;
466 	zval *params = php_dom_xpath_callback_fetch_args(ctxt, param_count, evaluation_mode, intern, proxy_factory);
467 
468 	/* Last element of the stack is the function name */
469 	xmlXPathObjectPtr obj = valuePop(ctxt);
470 	if (UNEXPECTED(obj->stringval == NULL)) {
471 		zend_type_error("Handler name must be a string");
472 		goto cleanup;
473 	}
474 
475 	const char *function_name = (const char *) obj->stringval;
476 	size_t function_name_length = strlen(function_name);
477 
478 	result = php_dom_xpath_callback_dispatch(xpath_callbacks, xpath_callbacks->php_ns, ctxt, params, param_count, function_name, function_name_length);
479 
480 cleanup:
481 	xmlXPathFreeObject(obj);
482 	php_dom_xpath_callback_cleanup_args(params, param_count);
483 cleanup_no_obj:
484 	if (UNEXPECTED(result != SUCCESS)) {
485 		/* Push sentinel value */
486 		valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
487 	}
488 
489 	return result;
490 }
491 
php_dom_xpath_callbacks_call_custom_ns(php_dom_xpath_callbacks * xpath_callbacks,xmlXPathParserContextPtr ctxt,int num_args,php_dom_xpath_nodeset_evaluation_mode evaluation_mode,dom_object * intern,php_dom_xpath_callbacks_proxy_factory proxy_factory)492 PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call_custom_ns(php_dom_xpath_callbacks *xpath_callbacks, xmlXPathParserContextPtr ctxt, int num_args, php_dom_xpath_nodeset_evaluation_mode evaluation_mode, dom_object *intern, php_dom_xpath_callbacks_proxy_factory proxy_factory)
493 {
494 	uint32_t param_count = num_args;
495 	zval *params = php_dom_xpath_callback_fetch_args(ctxt, param_count, evaluation_mode, intern, proxy_factory);
496 
497 	const char *namespace = (const char *) ctxt->context->functionURI;
498 	/* Impossible because it wouldn't have been registered inside the context. */
499 	ZEND_ASSERT(xpath_callbacks->namespaces != NULL);
500 
501 	php_dom_xpath_callback_ns *ns = zend_hash_str_find_ptr(xpath_callbacks->namespaces, namespace, strlen(namespace));
502 	/* Impossible because it wouldn't have been registered inside the context. */
503 	ZEND_ASSERT(ns != NULL);
504 
505 	const char *function_name = (const char *) ctxt->context->function;
506 	size_t function_name_length = strlen(function_name);
507 
508 	zend_result result = php_dom_xpath_callback_dispatch(xpath_callbacks, ns, ctxt, params, param_count, function_name, function_name_length);
509 
510 	php_dom_xpath_callback_cleanup_args(params, param_count);
511 	if (UNEXPECTED(result != SUCCESS)) {
512 		/* Push sentinel value */
513 		valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
514 	}
515 
516 	return result;
517 }
518 
519 #endif
520