Skip to content

Commit 14499b1

Browse files
committed
Improve callbacks in ext/dom and ext/xsl
1 parent 61c251d commit 14499b1

23 files changed

+859
-530
lines changed

ext/dom/config.m4

+2-2
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,10 @@ if test "$PHP_DOM" != "no"; then
2121
nodelist.c text.c comment.c \
2222
entityreference.c \
2323
notation.c xpath.c dom_iterators.c \
24-
namednodemap.c],
24+
namednodemap.c xpath_callbacks.c],
2525
$ext_shared)
2626
PHP_SUBST(DOM_SHARED_LIBADD)
27-
PHP_INSTALL_HEADERS([ext/dom/xml_common.h])
27+
PHP_INSTALL_HEADERS([ext/dom/xml_common.h ext/dom/xpath_callbacks.h])
2828
PHP_ADD_EXTENSION_DEP(dom, libxml)
2929
])
3030
fi

ext/dom/config.w32

+2-2
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ if (PHP_DOM == "yes") {
1414
entity.c nodelist.c text.c comment.c \
1515
entityreference.c \
1616
notation.c xpath.c dom_iterators.c \
17-
namednodemap.c");
17+
namednodemap.c xpath_callbacks.c");
1818

1919
AC_DEFINE("HAVE_DOM", 1, "DOM support");
2020

@@ -25,7 +25,7 @@ if (PHP_DOM == "yes") {
2525
WARNING("dom support can't be enabled, libxml is not found")
2626
}
2727
}
28-
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h");
28+
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h xpath_callbacks.h");
2929
} else {
3030
WARNING("dom support can't be enabled, libxml is not enabled")
3131
PHP_DOM = "no"

ext/dom/php_dom.c

+6-27
Original file line numberDiff line numberDiff line change
@@ -582,8 +582,10 @@ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int che
582582
static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
583583
static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
584584
static zend_object *dom_objects_store_clone_obj(zend_object *zobject);
585+
585586
#ifdef LIBXML_XPATH_ENABLED
586587
void dom_xpath_objects_free_storage(zend_object *object);
588+
HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
587589
#endif
588590

589591
/* {{{ PHP_MINIT_FUNCTION(dom) */
@@ -830,6 +832,7 @@ PHP_MINIT_FUNCTION(dom)
830832
memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
831833
dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std);
832834
dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage;
835+
dom_xpath_object_handlers.get_gc = dom_xpath_get_gc;
833836

834837
dom_xpath_class_entry = register_class_DOMXPath();
835838
dom_xpath_class_entry->create_object = dom_xpath_objects_new;
@@ -938,32 +941,6 @@ void node_list_unlink(xmlNodePtr node)
938941
}
939942
/* }}} end node_list_unlink */
940943

941-
#ifdef LIBXML_XPATH_ENABLED
942-
/* {{{ dom_xpath_objects_free_storage */
943-
void dom_xpath_objects_free_storage(zend_object *object)
944-
{
945-
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
946-
947-
zend_object_std_dtor(&intern->dom.std);
948-
949-
if (intern->dom.ptr != NULL) {
950-
xmlXPathFreeContext((xmlXPathContextPtr) intern->dom.ptr);
951-
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
952-
}
953-
954-
if (intern->registered_phpfunctions) {
955-
zend_hash_destroy(intern->registered_phpfunctions);
956-
FREE_HASHTABLE(intern->registered_phpfunctions);
957-
}
958-
959-
if (intern->node_list) {
960-
zend_hash_destroy(intern->node_list);
961-
FREE_HASHTABLE(intern->node_list);
962-
}
963-
}
964-
/* }}} */
965-
#endif
966-
967944
/* {{{ dom_objects_free_storage */
968945
void dom_objects_free_storage(zend_object *object)
969946
{
@@ -1070,12 +1047,13 @@ static void dom_object_namespace_node_free_storage(zend_object *object)
10701047
}
10711048

10721049
#ifdef LIBXML_XPATH_ENABLED
1050+
10731051
/* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
10741052
zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
10751053
{
10761054
dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);
10771055

1078-
intern->registered_phpfunctions = zend_new_array(0);
1056+
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
10791057
intern->register_node_ns = 1;
10801058

10811059
intern->dom.prop_handler = &dom_xpath_prop_handlers;
@@ -1086,6 +1064,7 @@ zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
10861064
return &intern->dom.std;
10871065
}
10881066
/* }}} */
1067+
10891068
#endif
10901069

10911070
void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */

ext/dom/php_dom.h

+2-3
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ extern zend_module_entry dom_module_entry;
5353

5454
#include "xml_common.h"
5555
#include "ext/libxml/php_libxml.h"
56+
#include "xpath_callbacks.h"
5657
#include "zend_exceptions.h"
5758
#include "dom_ce.h"
5859
/* DOM API_VERSION, please bump it up, if you change anything in the API
@@ -64,10 +65,8 @@ extern zend_module_entry dom_module_entry;
6465
#define DOM_NODESET XML_XINCLUDE_START
6566

6667
typedef struct _dom_xpath_object {
67-
int registerPhpFunctions;
68+
php_dom_xpath_callbacks xpath_callbacks;
6869
int register_node_ns;
69-
HashTable *registered_phpfunctions;
70-
HashTable *node_list;
7170
dom_object dom;
7271
} dom_xpath_object;
7372

ext/dom/tests/DOMXPath_callables.phpt

+127
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,127 @@
1+
--TEST--
2+
registerPHPFunctions() with callables
3+
--EXTENSIONS--
4+
dom
5+
--FILE--
6+
<?php
7+
8+
class MyClass {
9+
public static function dump(string $var) {
10+
var_dump($var);
11+
}
12+
}
13+
14+
class MyDOMXPath extends DOMXPath {
15+
public function registerCycle() {
16+
$this->registerPhpFunctions(["cycle" => array($this, "dummy")]);
17+
}
18+
19+
public function dummy(string $var) {
20+
echo "dummy: $var\n";
21+
}
22+
}
23+
24+
$doc = new DOMDocument();
25+
$doc->loadHTML('<a href="https://php.net">hello</a>');
26+
27+
echo "--- Legit cases: none ---\n";
28+
29+
$xpath = new DOMXPath($doc);
30+
$xpath->registerNamespace("php", "http://php.net/xpath");
31+
try {
32+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
33+
} catch (Error $e) {
34+
echo $e->getMessage(), "\n";
35+
}
36+
37+
echo "--- Legit cases: all ---\n";
38+
39+
$xpath->registerPHPFunctions(null);
40+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
41+
$xpath->evaluate("//a[php:function('MyClass::dump', string(@href))]");
42+
43+
echo "--- Legit cases: set ---\n";
44+
45+
$xpath = new DOMXPath($doc);
46+
$xpath->registerNamespace("php", "http://php.net/xpath");
47+
$xpath->registerPhpFunctions([]);
48+
$xpath->registerPHPFunctions(["xyz" => MyClass::dump(...), "mydump" => function (string $x) {
49+
var_dump($x);
50+
}]);
51+
$xpath->registerPhpFunctions(str_repeat("var_dump", mt_rand(1, 1) /* defeat SCCP */));
52+
$xpath->evaluate("//a[php:function('mydump', string(@href))]");
53+
$xpath->evaluate("//a[php:function('xyz', string(@href))]");
54+
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
55+
try {
56+
$xpath->evaluate("//a[php:function('notinset', string(@href))]");
57+
} catch (Throwable $e) {
58+
echo $e->getMessage(), "\n";
59+
}
60+
61+
echo "--- Legit cases: set with cycle ---\n";
62+
63+
$xpath = new MyDOMXPath($doc);
64+
$xpath->registerNamespace("php", "http://php.net/xpath");
65+
$xpath->registerCycle();
66+
$xpath->evaluate("//a[php:function('cycle', string(@href))]");
67+
68+
echo "--- Error cases ---\n";
69+
70+
$xpath = new DOMXPath($doc);
71+
try {
72+
$xpath->registerPhpFunctions("nonexistent");
73+
} catch (TypeError $e) {
74+
echo $e->getMessage(), "\n";
75+
}
76+
77+
try {
78+
$xpath->registerPhpFunctions(function () {});
79+
} catch (TypeError $e) {
80+
echo $e->getMessage(), "\n";
81+
}
82+
83+
try {
84+
$xpath->registerPhpFunctions([function () {}]);
85+
} catch (Throwable $e) {
86+
echo $e->getMessage(), "\n";
87+
}
88+
89+
try {
90+
$xpath->registerPhpFunctions([var_dump(...)]);
91+
} catch (Throwable $e) {
92+
echo $e->getMessage(), "\n";
93+
}
94+
95+
try {
96+
$xpath->registerPhpFunctions(["nonexistent"]);
97+
} catch (Throwable $e) {
98+
echo $e->getMessage(), "\n";
99+
}
100+
101+
try {
102+
$xpath->registerPhpFunctions(["" => var_dump(...)]);
103+
} catch (Throwable $e) {
104+
echo $e->getMessage(), "\n";
105+
}
106+
107+
?>
108+
--EXPECT--
109+
--- Legit cases: none ---
110+
No callbacks were registered
111+
--- Legit cases: all ---
112+
string(15) "https://php.net"
113+
string(15) "https://php.net"
114+
--- Legit cases: set ---
115+
string(15) "https://php.net"
116+
string(15) "https://php.net"
117+
string(15) "https://php.net"
118+
No callback handler "notinset" registered
119+
--- Legit cases: set with cycle ---
120+
dummy: https://php.net
121+
--- Error cases ---
122+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be a callable, function "nonexistent" not found or invalid function name
123+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be of type array|string|null, Closure given
124+
Object of class Closure could not be converted to string
125+
Object of class Closure could not be converted to string
126+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array with valid callbacks as values, function "nonexistent" not found or invalid function name
127+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) array key must not be empty

ext/dom/tests/domxpath.phpt

+2-2
Original file line numberDiff line numberDiff line change
@@ -69,5 +69,5 @@ myval
6969
float(1)
7070
bool(true)
7171
float(4)
72-
Unable to call handler non_existent()
73-
Unable to call handler non_existent()
72+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be a callable, function "non_existent" not found or invalid function name
73+
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array with valid callbacks as values, function "non_existant" not found or invalid function name

0 commit comments

Comments
 (0)