Skip to content

Commit

Permalink
Improve callbacks in ext/dom and ext/xsl
Browse files Browse the repository at this point in the history
  • Loading branch information
nielsdos committed Nov 30, 2023
1 parent 4d51d58 commit cd5c8d0
Show file tree
Hide file tree
Showing 23 changed files with 860 additions and 518 deletions.
4 changes: 2 additions & 2 deletions ext/dom/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ if test "$PHP_DOM" != "no"; then
nodelist.c text.c comment.c \
entityreference.c \
notation.c xpath.c dom_iterators.c \
namednodemap.c \
namednodemap.c xpath_callbacks.c \
$LEXBOR_SOURCES],
$ext_shared,,$PHP_LEXBOR_CFLAGS)
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ports/posix/lexbor/core)
Expand All @@ -49,7 +49,7 @@ if test "$PHP_DOM" != "no"; then
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/ns)
PHP_ADD_BUILD_DIR($ext_builddir/$LEXBOR_DIR/tag)
PHP_SUBST(DOM_SHARED_LIBADD)
PHP_INSTALL_HEADERS([ext/dom/xml_common.h])
PHP_INSTALL_HEADERS([ext/dom/xml_common.h ext/dom/xpath_callbacks.h])
PHP_ADD_EXTENSION_DEP(dom, libxml)
])
fi
4 changes: 2 additions & 2 deletions ext/dom/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ if (PHP_DOM == "yes") {
entity.c nodelist.c text.c comment.c \
entityreference.c \
notation.c xpath.c dom_iterators.c \
namednodemap.c", null, "-Iext/dom/lexbor");
namednodemap.c xpath_callbacks.c", null, "-Iext/dom/lexbor");

ADD_SOURCES("ext/dom/lexbor/lexbor/ports/windows_nt/lexbor/core", "memory.c", "dom");
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");
Expand All @@ -41,7 +41,7 @@ if (PHP_DOM == "yes") {
WARNING("dom support can't be enabled, libxml is not found")
}
}
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h");
PHP_INSTALL_HEADERS("ext/dom", "xml_common.h xpath_callbacks.h");
} else {
WARNING("dom support can't be enabled, libxml is not enabled")
PHP_DOM = "no"
Expand Down
33 changes: 6 additions & 27 deletions ext/dom/php_dom.c
Original file line number Diff line number Diff line change
Expand Up @@ -596,8 +596,10 @@ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int che
static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv);
static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty);
static zend_object *dom_objects_store_clone_obj(zend_object *zobject);

#ifdef LIBXML_XPATH_ENABLED
void dom_xpath_objects_free_storage(zend_object *object);
HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n);
#endif

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

dom_xpath_class_entry = register_class_DOMXPath();
dom_xpath_class_entry->create_object = dom_xpath_objects_new;
Expand Down Expand Up @@ -1001,32 +1004,6 @@ void node_list_unlink(xmlNodePtr node)
}
/* }}} end node_list_unlink */

#ifdef LIBXML_XPATH_ENABLED
/* {{{ dom_xpath_objects_free_storage */
void dom_xpath_objects_free_storage(zend_object *object)
{
dom_xpath_object *intern = php_xpath_obj_from_obj(object);

zend_object_std_dtor(&intern->dom.std);

if (intern->dom.ptr != NULL) {
xmlXPathFreeContext((xmlXPathContextPtr) intern->dom.ptr);
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
}

if (intern->registered_phpfunctions) {
zend_hash_destroy(intern->registered_phpfunctions);
FREE_HASHTABLE(intern->registered_phpfunctions);
}

if (intern->node_list) {
zend_hash_destroy(intern->node_list);
FREE_HASHTABLE(intern->node_list);
}
}
/* }}} */
#endif

/* {{{ dom_objects_free_storage */
void dom_objects_free_storage(zend_object *object)
{
Expand Down Expand Up @@ -1133,12 +1110,13 @@ static void dom_object_namespace_node_free_storage(zend_object *object)
}

#ifdef LIBXML_XPATH_ENABLED

/* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */
zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
{
dom_xpath_object *intern = zend_object_alloc(sizeof(dom_xpath_object), class_type);

intern->registered_phpfunctions = zend_new_array(0);
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
intern->register_node_ns = 1;

intern->dom.prop_handler = &dom_xpath_prop_handlers;
Expand All @@ -1149,6 +1127,7 @@ zend_object *dom_xpath_objects_new(zend_class_entry *class_type)
return &intern->dom.std;
}
/* }}} */

#endif

void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */
Expand Down
5 changes: 2 additions & 3 deletions ext/dom/php_dom.h
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ extern zend_module_entry dom_module_entry;

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

typedef struct _dom_xpath_object {
int registerPhpFunctions;
php_dom_xpath_callbacks xpath_callbacks;
int register_node_ns;
HashTable *registered_phpfunctions;
HashTable *node_list;
dom_object dom;
} dom_xpath_object;

Expand Down
127 changes: 127 additions & 0 deletions ext/dom/tests/DOMXPath_callables.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
--TEST--
registerPHPFunctions() with callables
--EXTENSIONS--
dom
--FILE--
<?php

class MyClass {
public static function dump(string $var) {
var_dump($var);
}
}

class MyDOMXPath extends DOMXPath {
public function registerCycle() {
$this->registerPhpFunctions(["cycle" => array($this, "dummy")]);
}

public function dummy(string $var) {
echo "dummy: $var\n";
}
}

$doc = new DOMDocument();
$doc->loadHTML('<a href="https://php.net">hello</a>');

echo "--- Legit cases: none ---\n";

$xpath = new DOMXPath($doc);
$xpath->registerNamespace("php", "http://php.net/xpath");
try {
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
} catch (Error $e) {
echo $e->getMessage(), "\n";
}

echo "--- Legit cases: all ---\n";

$xpath->registerPHPFunctions(null);
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
$xpath->evaluate("//a[php:function('MyClass::dump', string(@href))]");

echo "--- Legit cases: set ---\n";

$xpath = new DOMXPath($doc);
$xpath->registerNamespace("php", "http://php.net/xpath");
$xpath->registerPhpFunctions([]);
$xpath->registerPHPFunctions(["xyz" => MyClass::dump(...), "mydump" => function (string $x) {
var_dump($x);
}]);
$xpath->registerPhpFunctions(str_repeat("var_dump", mt_rand(1, 1) /* defeat SCCP */));
$xpath->evaluate("//a[php:function('mydump', string(@href))]");
$xpath->evaluate("//a[php:function('xyz', string(@href))]");
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
try {
$xpath->evaluate("//a[php:function('notinset', string(@href))]");
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}

echo "--- Legit cases: set with cycle ---\n";

$xpath = new MyDOMXPath($doc);
$xpath->registerNamespace("php", "http://php.net/xpath");
$xpath->registerCycle();
$xpath->evaluate("//a[php:function('cycle', string(@href))]");

echo "--- Error cases ---\n";

$xpath = new DOMXPath($doc);
try {
$xpath->registerPhpFunctions("nonexistent");
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

try {
$xpath->registerPhpFunctions(function () {});
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}

try {
$xpath->registerPhpFunctions([function () {}]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}

try {
$xpath->registerPhpFunctions([var_dump(...)]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}

try {
$xpath->registerPhpFunctions(["nonexistent"]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}

try {
$xpath->registerPhpFunctions(["" => var_dump(...)]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}

?>
--EXPECT--
--- Legit cases: none ---
No callbacks were registered
--- Legit cases: all ---
string(15) "https://php.net"
string(15) "https://php.net"
--- Legit cases: set ---
string(15) "https://php.net"
string(15) "https://php.net"
string(15) "https://php.net"
No callback handler "notinset" registered
--- Legit cases: set with cycle ---
dummy: https://php.net
--- Error cases ---
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be a callable, function "nonexistent" not found or invalid function name
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be of type array|string|null, Closure given
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
4 changes: 2 additions & 2 deletions ext/dom/tests/domxpath.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -69,5 +69,5 @@ myval
float(1)
bool(true)
float(4)
Unable to call handler non_existent()
Unable to call handler non_existent()
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be a callable, function "non_existent" not found or invalid function name
DOMXPath::registerPhpFunctions(): Argument #1 ($restrict) must be an array with valid callbacks as values, function "non_existant" not found or invalid function name
Loading

0 comments on commit cd5c8d0

Please sign in to comment.