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