Skip to content

Commit 5274a1c

Browse files
committed
Improve callbacks in ext/dom and ext/xsl
1 parent 16f39ec commit 5274a1c

23 files changed

+859
-518
lines changed

Diff for: ext/dom/config.m4

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ if test "$PHP_DOM" != "no"; then
3535
nodelist.c text.c comment.c \
3636
entityreference.c \
3737
notation.c xpath.c dom_iterators.c \
38-
namednodemap.c \
38+
namednodemap.c xpath_callbacks.c \
3939
$LEXBOR_SOURCES],
4040
$ext_shared,,$PHP_LEXBOR_CFLAGS)
4141
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ports/posix/lexbor/core)
@@ -49,7 +49,7 @@ if test "$PHP_DOM" != "no"; then
4949
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ns)
5050
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/tag)
5151
PHP_SUBST(DOM_SHARED_LIBADD)
52-
PHP_INSTALL_HEADERS([ext/dom/xml_common.h])
52+
PHP_INSTALL_HEADERS([ext/dom/xml_common.h ext/dom/xpath_callbacks.h])
5353
PHP_ADD_EXTENSION_DEP(dom, libxml)
5454
])
5555
fi

Diff for: ext/dom/config.w32

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ if (PHP_DOM == "yes") {
1515
entity.c nodelist.c text.c comment.c \
1616
entityreference.c \
1717
notation.c xpath.c dom_iterators.c \
18-
namednodemap.c", null, "-Iext/dom/lexbor");
18+
namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor");
1919

2020
ADD_SOURCES("ext/dom/lexbor/lexbor/ports/windows_nt/lexbor/core", "memory.c", "dom");
2121
ADD_SOURCES("ext/dom/lexbor/lexbor/core", "array_obj.c array.c avl.c bst.c diyfp.c conv.c dobject.c dtoa.c hash.c mem.c mraw.c print.c serialize.c shs.c str.c strtod.c", "dom");
@@ -41,7 +41,7 @@ if (PHP_DOM == "yes") {
4141
WARNING("dom support can't be enabled, libxml is not found")
4242
}
4343
}
44-
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h");
44+
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h xpath_callbacks.h");
4545
} else {
4646
WARNING("dom support can't be enabled, libxml is not enabled")
4747
PHP_DOM = "no"

Diff for: ext/dom/php_dom.c

+6-27
Original file line numberDiff line numberDiff line change
@@ -596,8 +596,10 @@ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int che
596596
static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
597597
static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
598598
static zend_object *dom_objects_store_clone_obj(zend_object *zobject);
599+
599600
#ifdef LIBXML_XPATH_ENABLED
600601
void dom_xpath_objects_free_storage(zend_object *object);
602+
HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
601603
#endif
602604

603605
static void *dom_malloc(size_t size) {
@@ -889,6 +891,7 @@ PHP_MINIT_FUNCTION(dom)
889891
memcpy(&dom_xpath_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers));
890892
dom_xpath_object_handlers.offset = XtOffsetOf(dom_xpath_object, dom) + XtOffsetOf(dom_object, std);
891893
dom_xpath_object_handlers.free_obj = dom_xpath_objects_free_storage;
894+
dom_xpath_object_handlers.get_gc = dom_xpath_get_gc;
892895

893896
dom_xpath_class_entry = register_class_DOMXPath();
894897
dom_xpath_class_entry->create_object = dom_xpath_objects_new;
@@ -1001,32 +1004,6 @@ void node_list_unlink(xmlNodePtr node)
10011004
}
10021005
/* }}} end node_list_unlink */
10031006

1004-
#ifdef LIBXML_XPATH_ENABLED
1005-
/* {{{ dom_xpath_objects_free_storage */
1006-
void dom_xpath_objects_free_storage(zend_object *object)
1007-
{
1008-
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
1009-
1010-
zend_object_std_dtor(&intern->dom.std);
1011-
1012-
if (intern->dom.ptr != NULL) {
1013-
xmlXPathFreeContext((xmlXPathContextPtr) intern->dom.ptr);
1014-
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
1015-
}
1016-
1017-
if (intern->registered_phpfunctions) {
1018-
zend_hash_destroy(intern->registered_phpfunctions);
1019-
FREE_HASHTABLE(intern->registered_phpfunctions);
1020-
}
1021-
1022-
if (intern->node_list) {
1023-
zend_hash_destroy(intern->node_list);
1024-
FREE_HASHTABLE(intern->node_list);
1025-
}
1026-
}
1027-
/* }}} */
1028-
#endif
1029-
10301007
/* {{{ dom_objects_free_storage */
10311008
void dom_objects_free_storage(zend_object *object)
10321009
{
@@ -1133,12 +1110,13 @@ static void dom_object_namespace_node_free_storage(zend_object *object)
11331110
}
11341111

11351112
#ifdef LIBXML_XPATH_ENABLED
1113+
11361114
/* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
11371115
zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
11381116
{
11391117
dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);
11401118

1141-
intern->registered_phpfunctions = zend_new_array(0);
1119+
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
11421120
intern->register_node_ns = 1;
11431121

11441122
intern->dom.prop_handler = &dom_xpath_prop_handlers;
@@ -1149,6 +1127,7 @@ zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
11491127
return &intern->dom.std;
11501128
}
11511129
/* }}} */
1130+
11521131
#endif
11531132

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

Diff for: 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

Diff for: 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

Diff for: 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)