From 542ae011ef9c14ca827dc405d6924d4635dde4c0 Mon Sep 17 00:00:00 2001 From: Niels Dossche <7771979+nielsdos@users.noreply.github.com> Date: Thu, 30 Nov 2023 23:08:07 +0100 Subject: [PATCH] Implement namespaced functions for DOM --- ext/dom/php_dom.stub.php | 2 + ext/dom/php_dom_arginfo.h | 13 ++- ext/dom/tests/DOMXPath_callables.phpt | 16 ++- ext/dom/tests/registerPhpFunctionsNS.phpt | 91 ++++++++++++++++++ ext/dom/xpath.c | 80 ++++++++++++--- ext/dom/xpath_callbacks.c | 107 ++++++++++++++++++--- ext/dom/xpath_callbacks.h | 12 ++- ext/xsl/tests/XSLTProcessor_callables.phpt | 16 ++- ext/xsl/xsltprocessor.c | 12 ++- 9 files changed, 315 insertions(+), 34 deletions(-) create mode 100644 ext/dom/tests/registerPhpFunctionsNS.phpt diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 2bd600e0c6e39..4d548015d3521 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -932,6 +932,8 @@ public function registerNamespace(string $prefix, string $namespace): bool {} /** @tentative-return-type */ public function registerPhpFunctions(string|array|null $restrict = null): void {} + + public function registerPhpFunctionsNS(string $namespace, string|array $restrict): void {} } #endif diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 703db8fa8184c..2d2c2cb33455c 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 53f161ae504057211c907938819f6e7f1f4fbfa2 */ + * Stub hash: 78a103855033679b654a22cdc6b860bb820722e0 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) @@ -451,6 +451,13 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_DOMXPath_registe ZEND_END_ARG_INFO() #endif +#if defined(LIBXML_XPATH_ENABLED) +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_DOMXPath_registerPhpFunctionsNS, 0, 2, IS_VOID, 0) + ZEND_ARG_TYPE_INFO(0, namespace, IS_STRING, 0) + ZEND_ARG_TYPE_MASK(0, restrict, MAY_BE_STRING|MAY_BE_ARRAY, NULL) +ZEND_END_ARG_INFO() +#endif + ZEND_BEGIN_ARG_INFO_EX(arginfo_class_DOM_Document_createAttribute, 0, 0, 1) ZEND_ARG_TYPE_INFO(0, localName, IS_STRING, 0) ZEND_END_ARG_INFO() @@ -738,6 +745,9 @@ ZEND_METHOD(DOMXPath, registerNamespace); #if defined(LIBXML_XPATH_ENABLED) ZEND_METHOD(DOMXPath, registerPhpFunctions); #endif +#if defined(LIBXML_XPATH_ENABLED) +ZEND_METHOD(DOMXPath, registerPhpFunctionsNS); +#endif ZEND_METHOD(DOM_Document, createAttribute); ZEND_METHOD(DOM_Document, createAttributeNS); ZEND_METHOD(DOM_Document, createCDATASection); @@ -1014,6 +1024,7 @@ static const zend_function_entry class_DOMXPath_methods[] = { ZEND_ME(DOMXPath, query, arginfo_class_DOMXPath_query, ZEND_ACC_PUBLIC) ZEND_ME(DOMXPath, registerNamespace, arginfo_class_DOMXPath_registerNamespace, ZEND_ACC_PUBLIC) ZEND_ME(DOMXPath, registerPhpFunctions, arginfo_class_DOMXPath_registerPhpFunctions, ZEND_ACC_PUBLIC) + ZEND_ME(DOMXPath, registerPhpFunctionsNS, arginfo_class_DOMXPath_registerPhpFunctionsNS, ZEND_ACC_PUBLIC) ZEND_FE_END }; #endif diff --git a/ext/dom/tests/DOMXPath_callables.phpt b/ext/dom/tests/DOMXPath_callables.phpt index aa45de536222a..d43c85158b48f 100644 --- a/ext/dom/tests/DOMXPath_callables.phpt +++ b/ext/dom/tests/DOMXPath_callables.phpt @@ -109,6 +109,18 @@ try { echo $e->getMessage(), "\n"; } +try { + $xpath->registerPhpFunctions(["\0" => var_dump(...)]); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +try { + $xpath->registerPhpFunctions(""); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + ?> --EXPECT-- --- Legit cases: none --- @@ -131,4 +143,6 @@ DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be of type array| Object of class Closure could not be converted to string Object of class Closure could not be converted to string DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array with valid callbacks as values, function "nonexistent" not found or invalid function name -DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) array key must not be empty +DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array containing valid callback names +DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array containing valid callback names +DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be a valid callback name diff --git a/ext/dom/tests/registerPhpFunctionsNS.phpt b/ext/dom/tests/registerPhpFunctionsNS.phpt new file mode 100644 index 0000000000000..073080261b8d5 --- /dev/null +++ b/ext/dom/tests/registerPhpFunctionsNS.phpt @@ -0,0 +1,91 @@ +--TEST-- +registerPhpFunctionsNS() function +--EXTENSIONS-- +dom +--FILE-- +loadHTML('hello'); + +$xpath = new DOMXPath($doc); + +echo "--- Error cases ---\n"; + +try { + $xpath->registerPhpFunctionsNS('http://php.net/xpath', ['strtolower']); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $xpath->registerPhpFunctionsNS('urn:foo', ['x:a' => 'strtolower']); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $xpath->registerPhpFunctionsNS("urn:foo", ["\0" => 'strtolower']); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $xpath->registerPhpFunctionsNS("\0", ['strtolower']); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} + +try { + $xpath->registerPhpFunctionsNS("urn:foo", [var_dump(...)]); +} catch (Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "--- Legit cases: string callable ---\n"; + +$xpath->registerNamespace('foo', 'urn:foo'); +$xpath->registerPhpFunctionsNS('urn:foo', 'strtolower'); +var_dump($xpath->query('//a[foo:strtolower(string(@href)) = "https://php.net"]')); + +echo "--- Legit cases: string callable in array ---\n"; + +$xpath->registerPhpFunctionsNS('urn:foo', ['strtoupper']); +var_dump($xpath->query('//a[foo:strtoupper(string(@href)) = "https://php.net"]')); + +echo "--- Legit cases: callable in array ---\n"; + +$xpath->registerPhpFunctionsNS('urn:foo', ['test' => 'var_dump']); +$xpath->query('//a[foo:test(string(@href))]'); + +echo "--- Legit cases: multiple namespaces ---\n"; + +$xpath->registerNamespace('bar', 'urn:bar'); +$xpath->registerPhpFunctionsNS('urn:bar', ['test' => 'strtolower']); +var_dump($xpath->query('//a[bar:test(string(@href)) = "https://php.net"]')); + +?> +--EXPECT-- +--- Error cases --- +DOMXPath::registerPhpFunctionsNS(): Argument #1 ($namespace) must not be "http://php.net/xpath" because it is reserved for PHP +DOMXPath::registerPhpFunctionsNS(): Argument #1 ($namespace) must be an array containing valid callback names +DOMXPath::registerPhpFunctionsNS(): Argument #1 ($namespace) must be an array containing valid callback names +DOMXPath::registerPhpFunctionsNS(): Argument #1 ($namespace) must not contain any null bytes +Object of class Closure could not be converted to string +--- Legit cases: string callable --- +object(DOMNodeList)#6 (1) { + ["length"]=> + int(1) +} +--- Legit cases: string callable in array --- +object(DOMNodeList)#6 (1) { + ["length"]=> + int(0) +} +--- Legit cases: callable in array --- +string(15) "https://PHP.net" +--- Legit cases: multiple namespaces --- +object(DOMNodeList)#4 (1) { + ["length"]=> + int(1) +} diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c index be17aa1f0eb0f..b49956ac2d323 100644 --- a/ext/dom/xpath.c +++ b/ext/dom/xpath.c @@ -61,28 +61,30 @@ static void dom_xpath_proxy_factory(xmlNodePtr node, zval *child, dom_object *in php_dom_create_object(node, child, intern); } -static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, php_dom_xpath_nodeset_evaluation_mode evaluation_mode) /* {{{ */ +static dom_xpath_object *dom_xpath_ext_fetch_intern(xmlXPathParserContextPtr ctxt) { - bool error = false; - dom_xpath_object *intern; - - if (! zend_is_executing()) { + if (!zend_is_executing()) { xmlGenericError(xmlGenericErrorContext, "xmlExtFunctionTest: Function called from outside of PHP\n"); - error = true; } else { - intern = (dom_xpath_object *) ctxt->context->userData; + dom_xpath_object *intern = (dom_xpath_object *) ctxt->context->userData; if (intern == NULL) { xmlGenericError(xmlGenericErrorContext, "xmlExtFunctionTest: failed to get the internal object\n"); - error = true; + return NULL; } + return intern; } + return NULL; +} - if (error) { +static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, php_dom_xpath_nodeset_evaluation_mode evaluation_mode) /* {{{ */ +{ + dom_xpath_object *intern = dom_xpath_ext_fetch_intern(ctxt); + if (!intern) { php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs); } else { - php_dom_xpath_callbacks_call(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, &intern->dom, dom_xpath_proxy_factory); + php_dom_xpath_callbacks_call_php_ns(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, &intern->dom, dom_xpath_proxy_factory); } } /* }}} */ @@ -99,6 +101,16 @@ static void dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt, int } /* }}} */ +static void dom_xpath_ext_function_trampoline(xmlXPathParserContextPtr ctxt, int nargs) +{ + dom_xpath_object *intern = dom_xpath_ext_fetch_intern(ctxt); + if (!intern) { + php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs); + } else { + php_dom_xpath_callbacks_call_custom_ns(&intern->xpath_callbacks, ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET, &intern->dom, dom_xpath_proxy_factory); + } +} + /* {{{ */ PHP_METHOD(DOMXPath, __construct) { @@ -378,18 +390,60 @@ PHP_METHOD(DOMXPath, registerPhpFunctions) { dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS); - zend_string *name = NULL; + zend_string *callable_name = NULL; HashTable *callable_ht = NULL; ZEND_PARSE_PARAMETERS_START(0, 1) Z_PARAM_OPTIONAL - Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, name) + Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, callable_name) ZEND_PARSE_PARAMETERS_END(); - php_dom_xpath_callbacks_update_method_handler(&intern->xpath_callbacks, NULL, name, callable_ht); + php_dom_xpath_callbacks_update_method_handler( + &intern->xpath_callbacks, + intern->dom.ptr, + NULL, + callable_name, + callable_ht, + PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS, + NULL + ); } /* }}} end dom_xpath_register_php_functions */ +static void dom_xpath_register_func_in_ctx(xmlXPathContextPtr ctxt, const zend_string *ns, const zend_string *name) +{ + xmlXPathRegisterFuncNS(ctxt, (const xmlChar *) ZSTR_VAL(name), (const xmlChar *) ZSTR_VAL(ns), dom_xpath_ext_function_trampoline); +} + +PHP_METHOD(DOMXPath, registerPhpFunctionsNS) +{ + dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS); + + zend_string *namespace; + zend_string *callable_name; + HashTable *callable_ht; + + ZEND_PARSE_PARAMETERS_START(2, 2) + Z_PARAM_PATH_STR(namespace) + Z_PARAM_ARRAY_HT_OR_STR(callable_ht, callable_name) + ZEND_PARSE_PARAMETERS_END(); + + if (zend_string_equals_literal(namespace, "http://php.net/xpath")) { // TODO: this is different for XSL!!! + zend_argument_value_error(1, "must not be \"http://php.net/xpath\" because it is reserved for PHP"); + RETURN_THROWS(); + } + + php_dom_xpath_callbacks_update_method_handler( + &intern->xpath_callbacks, + intern->dom.ptr, + namespace, + callable_name, + callable_ht, + PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME, + dom_xpath_register_func_in_ctx + ); +} + #endif /* LIBXML_XPATH_ENABLED */ #endif diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c index cd37da1c0137d..c9ab377be9a30 100644 --- a/ext/dom/xpath_callbacks.c +++ b/ext/dom/xpath_callbacks.c @@ -126,7 +126,37 @@ PHP_DOM_EXPORT HashTable *php_dom_xpath_callbacks_get_gc_for_whole_object(php_do } } -static void php_dom_xpath_callback_ns_update_method_handler(php_dom_xpath_callback_ns* ns, zend_string *name, const HashTable *callable_ht) +static bool php_dom_xpath_is_callback_name_valid(const zend_string *name, php_dom_xpath_callback_name_validation name_validation) +{ + if (ZSTR_LEN(name) == 0) { + return false; + } + + if (name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS || name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME) { + if (zend_str_has_nul_byte(name)) { + return false; + } + } + + if (name_validation == PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME) { + if (xmlValidateNCName((xmlChar *) ZSTR_VAL(name), /* pass 0 to disallow spaces */ 0) != 0) { + return false; + } + } + + return true; +} + +static bool php_dom_xpath_is_callback_name_valid_and_throw(const zend_string *name, php_dom_xpath_callback_name_validation name_validation) +{ + if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) { + zend_argument_value_error(1, "must be an array containing valid callback names"); + return false; + } + return true; +} + +static zend_result 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) { zval *entry, registered_value; @@ -139,7 +169,7 @@ static void php_dom_xpath_callback_ns_update_method_handler(php_dom_xpath_callba zend_argument_type_error(1, "must be an array with valid callbacks as values, %s", error); efree(fcc); efree(error); - return; + return FAILURE; } zend_fcc_addref(fcc); @@ -147,26 +177,35 @@ static void php_dom_xpath_callback_ns_update_method_handler(php_dom_xpath_callba if (!key) { zend_string *str = zval_try_get_string(entry); - if (str) { + if (str && php_dom_xpath_is_callback_name_valid_and_throw(str, name_validation)) { zend_hash_update(&ns->functions, str, ®istered_value); - zend_string_release_ex(str, 0); + if (register_func) { + register_func(ctxt, namespace, str); + } + zend_string_release_ex(str, false); } else { zend_fcc_dtor(fcc); efree(fcc); - return; + return FAILURE; } } else { - if (ZSTR_LEN(key) == 0) { - zend_argument_value_error(1, "array key must not be empty"); + if (!php_dom_xpath_is_callback_name_valid_and_throw(key, name_validation)) { zend_fcc_dtor(fcc); efree(fcc); - return; + return FAILURE; } zend_hash_update(&ns->functions, key, ®istered_value); + if (register_func) { + register_func(ctxt, namespace, key); + } } } ZEND_HASH_FOREACH_END(); ns->mode = PHP_DOM_REG_FUNC_MODE_SET; } else if (name) { + if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) { + zend_argument_value_error(1, "must be a valid callback name"); + return FAILURE; + } zend_fcall_info_cache* fcc = emalloc(sizeof(zend_fcall_info)); char *error; zval tmp; @@ -175,27 +214,42 @@ static void php_dom_xpath_callback_ns_update_method_handler(php_dom_xpath_callba zend_argument_type_error(1, "must be a callable, %s", error); efree(fcc); efree(error); - return; + return FAILURE; } zend_fcc_addref(fcc); ZVAL_PTR(®istered_value, fcc); zend_hash_update(&ns->functions, name, ®istered_value); + if (register_func) { + register_func(ctxt, namespace, name); + } ns->mode = PHP_DOM_REG_FUNC_MODE_SET; } else { ns->mode = PHP_DOM_REG_FUNC_MODE_ALL; } + + return SUCCESS; } -PHP_DOM_EXPORT void php_dom_xpath_callbacks_update_method_handler(php_dom_xpath_callbacks* registry, zend_string *ns, zend_string *name, const HashTable *callable_ht) +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) { if (ns == NULL) { if (!registry->php_ns) { registry->php_ns = emalloc(sizeof(php_dom_xpath_callback_ns)); php_dom_xpath_callback_ns_ctor(registry->php_ns); } - php_dom_xpath_callback_ns_update_method_handler(registry->php_ns, name, callable_ht); + return php_dom_xpath_callback_ns_update_method_handler(registry->php_ns, ctxt, ns, name, callable_ht, name_validation, register_func); } else { - abort(); // TODO + if (!registry->namespaces) { + /* In most cases probably only a single namespace is registered. */ + registry->namespaces = zend_new_array(1); + } + php_dom_xpath_callback_ns *namespace = zend_hash_find_ptr(registry->namespaces, ns); + if (namespace == NULL) { + namespace = emalloc(sizeof(php_dom_xpath_callback_ns)); + php_dom_xpath_callback_ns_ctor(namespace); + zend_hash_add_new_ptr(registry->namespaces, ns, namespace); + } + return php_dom_xpath_callback_ns_update_method_handler(namespace, ctxt, ns, name, callable_ht, name_validation, register_func); } } @@ -338,7 +392,7 @@ static zend_result php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks *xpat return SUCCESS; } -PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call(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) +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) { zend_result result = FAILURE; @@ -374,4 +428,31 @@ PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call(php_dom_xpath_callbacks return result; } +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) +{ + uint32_t param_count = num_args; + zval *params = php_dom_xpath_callback_fetch_args(ctxt, param_count, evaluation_mode, intern, proxy_factory); + + const char *namespace = (const char *) ctxt->context->functionURI; + /* Impossible because it wouldn't have been registered inside the context. */ + ZEND_ASSERT(xpath_callbacks->namespaces != NULL); + + php_dom_xpath_callback_ns *ns = zend_hash_str_find_ptr(xpath_callbacks->namespaces, namespace, strlen(namespace)); + /* Impossible because it wouldn't have been registered inside the context. */ + ZEND_ASSERT(ns != NULL); + + const char *function_name = (const char *) ctxt->context->function; + size_t function_name_length = strlen(function_name); + + zend_result result = php_dom_xpath_callback_dispatch(xpath_callbacks, ns, ctxt, params, param_count, function_name, function_name_length); + + php_dom_xpath_callback_cleanup_args(params, param_count); + if (UNEXPECTED(result != SUCCESS)) { + /* Push sentinel value */ + valuePush(ctxt, xmlXPathNewString((const xmlChar *) "")); + } + + return result; +} + #endif diff --git a/ext/dom/xpath_callbacks.h b/ext/dom/xpath_callbacks.h index 3a2b1ff79f2cb..d0da046829c0d 100644 --- a/ext/dom/xpath_callbacks.h +++ b/ext/dom/xpath_callbacks.h @@ -34,6 +34,7 @@ typedef enum { } php_dom_xpath_nodeset_evaluation_mode; typedef void (*php_dom_xpath_callbacks_proxy_factory)(xmlNodePtr node, zval *proxy, dom_object *intern, xmlXPathParserContextPtr ctxt); +typedef void (*php_dom_xpath_callbacks_register_func_ctx)(xmlXPathContextPtr ctxt, const zend_string *ns, const zend_string *name); typedef struct { HashTable functions; @@ -46,15 +47,20 @@ typedef struct { HashTable *node_list; } php_dom_xpath_callbacks; +typedef enum { + PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS, + PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME, +} php_dom_xpath_callback_name_validation; + PHP_DOM_EXPORT void php_dom_xpath_callbacks_ctor(php_dom_xpath_callbacks *registry); PHP_DOM_EXPORT void php_dom_xpath_callbacks_dtor(php_dom_xpath_callbacks *registry); PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_node_list(php_dom_xpath_callbacks *registry); PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_argument_stack(xmlXPathParserContextPtr ctxt, uint32_t num_args); PHP_DOM_EXPORT void php_dom_xpath_callbacks_get_gc(php_dom_xpath_callbacks *registry, zend_get_gc_buffer *gc_buffer); 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); -PHP_DOM_EXPORT void php_dom_xpath_callbacks_update_method_handler(php_dom_xpath_callbacks* registry, zend_string *ns, zend_string *name, const HashTable *callable_ht); -PHP_DOM_EXPORT zend_result php_dom_xpath_callbacks_call(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); +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); +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); +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); #endif #endif - diff --git a/ext/xsl/tests/XSLTProcessor_callables.phpt b/ext/xsl/tests/XSLTProcessor_callables.phpt index 1603fd4436451..a845d77feec63 100644 --- a/ext/xsl/tests/XSLTProcessor_callables.phpt +++ b/ext/xsl/tests/XSLTProcessor_callables.phpt @@ -119,6 +119,18 @@ try { echo $e->getMessage(), "\n"; } +try { + $proc->registerPhpFunctions(["\0" => var_dump(...)]); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +try { + $proc->registerPhpFunctions(""); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + ?> --EXPECT-- --- Legit cases: none --- @@ -147,4 +159,6 @@ XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be of type Object of class Closure could not be converted to string Object of class Closure could not be converted to string XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be an array with valid callbacks as values, function "nonexistent" not found or invalid function name -XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) array key must not be empty +XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be an array containing valid callback names +XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be an array containing valid callback names +XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be a valid callback name diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c index 5530f5356a446..21188eabd2c32 100644 --- a/ext/xsl/xsltprocessor.c +++ b/ext/xsl/xsltprocessor.c @@ -93,7 +93,7 @@ static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, php_d if (error) { php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs); } else { - php_dom_xpath_callbacks_call(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, (dom_object *) intern->doc, xsl_proxy_factory); + php_dom_xpath_callbacks_call_php_ns(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, (dom_object *) intern->doc, xsl_proxy_factory); } } /* }}} */ @@ -578,7 +578,15 @@ PHP_METHOD(XSLTProcessor, registerPHPFunctions) Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, name) ZEND_PARSE_PARAMETERS_END(); - php_dom_xpath_callbacks_update_method_handler(&intern->xpath_callbacks, NULL, name, callable_ht); + php_dom_xpath_callbacks_update_method_handler( + &intern->xpath_callbacks, + NULL, + NULL, + name, + callable_ht, + PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NULLS, + NULL + ); } /* }}} end XSLTProcessor::registerPHPFunctions(); */