1
0
mirror of https://github.com/php/php-src.git synced 2026-03-24 00:02:20 +01:00

[RFC] Improve callbacks in ext/dom and ext/xsl (#12627)

This commit is contained in:
Niels Dossche
2024-01-13 00:00:26 +01:00
committed by GitHub
parent 2b30f18708
commit 90785dd865
38 changed files with 1682 additions and 535 deletions

2
NEWS
View File

@@ -22,6 +22,7 @@ DOM:
. Implement DOM HTML5 parsing and serialization RFC. (nielsdos)
. Fix DOMElement->prefix with empty string creates bogus prefix. (nielsdos)
. Handle OOM more consistently. (nielsdos)
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
FPM:
. Implement GH-12385 (flush headers without body when calling flush()).
@@ -147,5 +148,6 @@ XML:
XSL:
. Implement request #64137 (XSLTProcessor::setParameter() should allow both
quotes to be used). (nielsdos)
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

View File

@@ -121,6 +121,9 @@ PHP 8.4 UPGRADE NOTES
. XSLTProcessor::setParameter() will now throw a ValueError when its arguments
contain null bytes. This never actually worked correctly in the first place,
which is why it throws an exception nowadays.
. Failure to call a PHP function callback during evaluation now throws
instead of emitting a warning.
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
========================================
2. New Features
@@ -145,6 +148,8 @@ PHP 8.4 UPGRADE NOTES
These classes provide a cleaner API to handle HTML and XML documents.
Furthermore, the DOM\HTMLDocument class implements spec-compliant HTML5
parsing and serialization.
. It is now possible to pass any callable to registerPhpFunctions().
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
- FPM:
. Flushing headers without a body will now succeed. See GH-12785.
@@ -195,6 +200,8 @@ PDO_SQLITE:
- XSL:
. It is now possible to use parameters that contain both single and double
quotes.
. It is now possible to pass any callable to registerPhpFunctions().
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
========================================
3. Changes in SAPI modules
@@ -318,6 +325,8 @@ PDO_SQLITE:
- DOM:
. Added DOMNode::compareDocumentPosition().
. Added DOMXPath::registerPhpFunctionNS().
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
- MBString:
. Added mb_trim, mb_ltrim and mb_rtrim functions.
@@ -334,6 +343,10 @@ PDO_SQLITE:
. sodium_crypto_aead_aes256gcm_*() functions are now enabled on aarch64 CPUs
with the ARM cryptographic extensions.
- XSL:
. Added XSLTProcessor::registerPhpFunctionNS().
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
========================================
7. New Classes and Interfaces
========================================

View File

@@ -50,6 +50,8 @@ PHP 8.4 INTERNALS UPGRADE NOTES
- dom_read_t and dom_write_t now expect the function to return zend_result
instead of int.
- The macros DOM_NO_ARGS() and DOM_NOT_IMPLEMENTED() have been removed.
- New public APIs are available to handle callbacks from XPath, see
xpath_callbacks.h.
b. ext/random
- The macro RAND_RANGE_BADSCALING() has been removed. The implementation

View File

@@ -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)
@@ -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

View File

@@ -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");
@@ -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"

View File

@@ -595,8 +595,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) {
@@ -888,6 +890,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;
@@ -1000,32 +1003,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)
{
@@ -1132,12 +1109,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;
@@ -1148,6 +1126,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) /* {{{ */

View File

@@ -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
@@ -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;

View File

@@ -932,6 +932,8 @@ namespace
/** @tentative-return-type */
public function registerPhpFunctions(string|array|null $restrict = null): void {}
public function registerPhpFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
}
#endif

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 5512165ddaad08287561abac2a325e2aab3c6188 */
* Stub hash: 184308dfd1a133145d170c467e7600a12b14e327 */
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,14 @@ 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_registerPhpFunctionNS, 0, 3, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, namespaceURI, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, callable, IS_CALLABLE, 0)
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 +746,9 @@ ZEND_METHOD(DOMXPath, registerNamespace);
#if defined(LIBXML_XPATH_ENABLED)
ZEND_METHOD(DOMXPath, registerPhpFunctions);
#endif
#if defined(LIBXML_XPATH_ENABLED)
ZEND_METHOD(DOMXPath, registerPhpFunctionNS);
#endif
ZEND_METHOD(DOM_Document, createAttribute);
ZEND_METHOD(DOM_Document, createAttributeNS);
ZEND_METHOD(DOM_Document, createCDATASection);
@@ -1014,6 +1025,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, registerPhpFunctionNS, arginfo_class_DOMXPath_registerPhpFunctionNS, ZEND_ACC_PUBLIC)
ZEND_FE_END
};
#endif

View File

@@ -0,0 +1,88 @@
--TEST--
registerPHPFunctions() with callables - legit cases
--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 "--- Legit cases: reset to null ---\n";
$xpath->registerPhpFunctions(null);
$xpath->evaluate("//a[php:function('var_dump', string(@href))]");
?>
--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
--- Legit cases: reset to null ---
string(15) "https://php.net"

View File

@@ -0,0 +1,69 @@
--TEST--
registerPHPFunctions() with callables - error cases
--EXTENSIONS--
dom
--FILE--
<?php
$doc = new DOMDocument();
$doc->loadHTML('<a href="https://php.net">hello</a>');
$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";
}
try {
$xpath->registerPhpFunctions(["\0" => var_dump(...)]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
try {
$xpath->registerPhpFunctions("");
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
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) 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

View File

@@ -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

View File

@@ -0,0 +1,106 @@
--TEST--
registerPhpFunctionNS() function - legit cases
--EXTENSIONS--
dom
--FILE--
<?php
class TrampolineClass {
public static function __callStatic(string $name, array $arguments): mixed {
var_dump($name, $arguments);
return strtoupper($arguments[0]);
}
}
class StatefulClass {
public array $state = [];
public function __call(string $name, array $arguments): mixed {
$this->state[] = [$name, $arguments[0]];
return $arguments[0];
}
}
$doc = new DOMDocument();
$doc->loadHTML('<a href="https://PHP.net">hello</a>');
$xpath = new DOMXPath($doc);
$xpath->registerNamespace('foo', 'urn:foo');
echo "--- Legit cases: global function callable ---\n";
$xpath->registerPhpFunctionNS('urn:foo', 'strtolower', strtolower(...));
var_dump($xpath->query('//a[foo:strtolower(string(@href)) = "https://php.net"]'));
echo "--- Legit cases: string callable ---\n";
$xpath->registerPhpFunctionNS('urn:foo', 'strtolower', 'strtolower');
var_dump($xpath->query('//a[foo:strtolower(string(@href)) = "https://php.net"]'));
echo "--- Legit cases: trampoline callable ---\n";
$xpath->registerPhpFunctionNS('urn:foo', 'test', TrampolineClass::test(...));
var_dump($xpath->query('//a[foo:test(string(@href)) = "https://php.net"]'));
echo "--- Legit cases: instance class method callable ---\n";
$state = new StatefulClass;
$xpath->registerPhpFunctionNS('urn:foo', 'test', $state->test(...));
var_dump($xpath->query('//a[foo:test(string(@href))]'));
var_dump($state->state);
echo "--- Legit cases: global function callable that returns nothing ---\n";
$xpath->registerPhpFunctionNS('urn:foo', 'test', var_dump(...));
$xpath->query('//a[foo:test(string(@href))]');
echo "--- Legit cases: multiple namespaces ---\n";
$xpath->registerNamespace('bar', 'urn:bar');
$xpath->registerPhpFunctionNS('urn:bar', 'test', 'strtolower');
var_dump($xpath->query('//a[bar:test(string(@href)) = "https://php.net"]'));
?>
--EXPECT--
--- Legit cases: global function callable ---
object(DOMNodeList)#5 (1) {
["length"]=>
int(1)
}
--- Legit cases: string callable ---
object(DOMNodeList)#5 (1) {
["length"]=>
int(1)
}
--- Legit cases: trampoline callable ---
string(4) "test"
array(1) {
[0]=>
string(15) "https://PHP.net"
}
object(DOMNodeList)#3 (1) {
["length"]=>
int(0)
}
--- Legit cases: instance class method callable ---
object(DOMNodeList)#6 (1) {
["length"]=>
int(1)
}
array(1) {
[0]=>
array(2) {
[0]=>
string(4) "test"
[1]=>
string(15) "https://PHP.net"
}
}
--- Legit cases: global function callable that returns nothing ---
string(15) "https://PHP.net"
--- Legit cases: multiple namespaces ---
object(DOMNodeList)#5 (1) {
["length"]=>
int(1)
}

View File

@@ -0,0 +1,42 @@
--TEST--
registerPhpFunctionNS() function - error cases
--EXTENSIONS--
dom
--FILE--
<?php
$doc = new DOMDocument();
$doc->loadHTML('<a href="https://PHP.net">hello</a>');
$xpath = new DOMXPath($doc);
try {
$xpath->registerPhpFunctionNS('http://php.net/xpath', 'strtolower', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$xpath->registerPhpFunctionNS('urn:foo', 'x:a', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$xpath->registerPhpFunctionNS("urn:foo", "\0", strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$xpath->registerPhpFunctionNS("\0", 'strtolower', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
DOMXPath::registerPhpFunctionNS(): Argument #1 ($namespaceURI) must not be "http://php.net/xpath" because it is reserved by PHP
DOMXPath::registerPhpFunctionNS(): Argument #2 ($name) must be a valid callback name
DOMXPath::registerPhpFunctionNS(): Argument #2 ($name) must not contain any null bytes
DOMXPath::registerPhpFunctionNS(): Argument #1 ($namespaceURI) must not contain any null bytes

View File

@@ -32,179 +32,86 @@
#ifdef LIBXML_XPATH_ENABLED
static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int type) /* {{{ */
void dom_xpath_objects_free_storage(zend_object *object)
{
zval retval;
int result, i;
int error = 0;
zend_fcall_info fci;
xmlXPathObjectPtr obj;
char *str;
zend_string *callable = NULL;
dom_xpath_object *intern;
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
zend_object_std_dtor(&intern->dom.std);
if (! zend_is_executing()) {
if (intern->dom.ptr != NULL) {
xmlXPathFreeContext((xmlXPathContextPtr) intern->dom.ptr);
php_libxml_decrement_doc_ref((php_libxml_node_object *) &intern->dom);
}
php_dom_xpath_callbacks_dtor(&intern->xpath_callbacks);
}
HashTable *dom_xpath_get_gc(zend_object *object, zval **table, int *n)
{
dom_xpath_object *intern = php_xpath_obj_from_obj(object);
return php_dom_xpath_callbacks_get_gc_for_whole_object(&intern->xpath_callbacks, object, table, n);
}
static void dom_xpath_proxy_factory(xmlNodePtr node, zval *child, dom_object *intern, xmlXPathParserContextPtr ctxt)
{
ZEND_IGNORE_VALUE(ctxt);
ZEND_ASSERT(node->type != XML_NAMESPACE_DECL);
php_dom_create_object(node, child, intern);
}
static dom_xpath_object *dom_xpath_ext_fetch_intern(xmlXPathParserContextPtr ctxt)
{
if (UNEXPECTED(!zend_is_executing())) {
xmlGenericError(xmlGenericErrorContext,
"xmlExtFunctionTest: Function called from outside of PHP\n");
error = 1;
return NULL;
}
dom_xpath_object *intern = (dom_xpath_object *) ctxt->context->userData;
if (UNEXPECTED(intern == NULL)) {
xmlGenericError(xmlGenericErrorContext,
"xmlExtFunctionTest: failed to get the internal object\n");
return NULL;
}
return intern;
}
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 {
intern = (dom_xpath_object *) ctxt->context->userData;
if (intern == NULL) {
xmlGenericError(xmlGenericErrorContext,
"xmlExtFunctionTest: failed to get the internal object\n");
error = 1;
}
else if (intern->registerPhpFunctions == 0) {
xmlGenericError(xmlGenericErrorContext,
"xmlExtFunctionTest: PHP Object did not register PHP functions\n");
error = 1;
}
}
if (error == 1) {
for (i = nargs - 1; i >= 0; i--) {
obj = valuePop(ctxt);
xmlXPathFreeObject(obj);
}
return;
}
if (UNEXPECTED(nargs == 0)) {
zend_throw_error(NULL, "Function name must be passed as the first argument");
return;
}
fci.param_count = nargs - 1;
if (fci.param_count > 0) {
fci.params = safe_emalloc(fci.param_count, sizeof(zval), 0);
}
/* Reverse order to pop values off ctxt stack */
for (i = fci.param_count - 1; i >= 0; i--) {
obj = valuePop(ctxt);
switch (obj->type) {
case XPATH_STRING:
ZVAL_STRING(&fci.params[i], (char *)obj->stringval);
break;
case XPATH_BOOLEAN:
ZVAL_BOOL(&fci.params[i], obj->boolval);
break;
case XPATH_NUMBER:
ZVAL_DOUBLE(&fci.params[i], obj->floatval);
break;
case XPATH_NODESET:
if (type == 1) {
str = (char *)xmlXPathCastToString(obj);
ZVAL_STRING(&fci.params[i], str);
xmlFree(str);
} else if (type == 2) {
int j;
if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
array_init_size(&fci.params[i], obj->nodesetval->nodeNr);
zend_hash_real_init_packed(Z_ARRVAL_P(&fci.params[i]));
for (j = 0; j < obj->nodesetval->nodeNr; j++) {
xmlNodePtr node = obj->nodesetval->nodeTab[j];
zval child;
if (node->type == XML_NAMESPACE_DECL) {
xmlNodePtr nsparent = node->_private;
xmlNsPtr original = (xmlNsPtr) node;
/* Make sure parent dom object exists, so we can take an extra reference. */
zval parent_zval; /* don't destroy me, my lifetime is transfered to the fake namespace decl */
php_dom_create_object(nsparent, &parent_zval, &intern->dom);
dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
} else {
php_dom_create_object(node, &child, &intern->dom);
}
add_next_index_zval(&fci.params[i], &child);
}
} else {
ZVAL_EMPTY_ARRAY(&fci.params[i]);
}
}
break;
default:
ZVAL_STRING(&fci.params[i], (char *)xmlXPathCastToString(obj));
}
xmlXPathFreeObject(obj);
}
fci.size = sizeof(fci);
/* Last element of the stack is the function name */
obj = valuePop(ctxt);
if (obj->stringval == NULL) {
zend_type_error("Handler name must be a string");
xmlXPathFreeObject(obj);
goto cleanup_no_callable;
}
ZVAL_STRING(&fci.function_name, (char *) obj->stringval);
xmlXPathFreeObject(obj);
fci.object = NULL;
fci.named_params = NULL;
fci.retval = &retval;
if (!zend_make_callable(&fci.function_name, &callable)) {
zend_throw_error(NULL, "Unable to call handler %s()", ZSTR_VAL(callable));
goto cleanup;
} else if (intern->registerPhpFunctions == 2 && zend_hash_exists(intern->registered_phpfunctions, callable) == 0) {
zend_throw_error(NULL, "Not allowed to call handler '%s()'.", ZSTR_VAL(callable));
goto cleanup;
} else {
result = zend_call_function(&fci, NULL);
if (result == SUCCESS && Z_TYPE(retval) != IS_UNDEF) {
if (Z_TYPE(retval) == IS_OBJECT && instanceof_function(Z_OBJCE(retval), dom_node_class_entry)) {
xmlNode *nodep;
dom_object *obj;
if (intern->node_list == NULL) {
intern->node_list = zend_new_array(0);
}
Z_ADDREF(retval);
zend_hash_next_index_insert(intern->node_list, &retval);
obj = Z_DOMOBJ_P(&retval);
nodep = dom_object_get_node(obj);
valuePush(ctxt, xmlXPathNewNodeSet(nodep));
} else if (Z_TYPE(retval) == IS_FALSE || Z_TYPE(retval) == IS_TRUE) {
valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(retval) == IS_TRUE));
} else if (Z_TYPE(retval) == IS_OBJECT) {
zend_type_error("A PHP Object cannot be converted to a XPath-string");
return;
} else {
zend_string *str = zval_get_string(&retval);
valuePush(ctxt, xmlXPathNewString((xmlChar *) ZSTR_VAL(str)));
zend_string_release_ex(str, 0);
}
zval_ptr_dtor(&retval);
}
}
cleanup:
zend_string_release_ex(callable, 0);
zval_ptr_dtor_nogc(&fci.function_name);
cleanup_no_callable:
if (fci.param_count > 0) {
for (i = 0; i < nargs - 1; i++) {
zval_ptr_dtor(&fci.params[i]);
}
efree(fci.params);
php_dom_xpath_callbacks_call_php_ns(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, &intern->dom, dom_xpath_proxy_factory);
}
}
/* }}} */
static void dom_xpath_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
{
dom_xpath_ext_function_php(ctxt, nargs, 1);
dom_xpath_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING);
}
/* }}} */
static void dom_xpath_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
{
dom_xpath_ext_function_php(ctxt, nargs, 2);
dom_xpath_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET);
}
/* }}} */
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)
{
@@ -482,36 +389,63 @@ PHP_METHOD(DOMXPath, evaluate)
/* {{{ */
PHP_METHOD(DOMXPath, registerPhpFunctions)
{
zval *id = ZEND_THIS;
dom_xpath_object *intern = Z_XPATHOBJ_P(id);
zval *entry, new_string;
zend_string *name = NULL;
HashTable *ht = NULL;
dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
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(ht, name)
Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, callable_name)
ZEND_PARSE_PARAMETERS_END();
if (ht) {
ZEND_HASH_FOREACH_VAL(ht, entry) {
zend_string *str = zval_get_string(entry);
ZVAL_LONG(&new_string, 1);
zend_hash_update(intern->registered_phpfunctions, str, &new_string);
zend_string_release_ex(str, 0);
} ZEND_HASH_FOREACH_END();
intern->registerPhpFunctions = 2;
} else if (name) {
ZVAL_LONG(&new_string, 1);
zend_hash_update(intern->registered_phpfunctions, name, &new_string);
intern->registerPhpFunctions = 2;
} else {
intern->registerPhpFunctions = 1;
}
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(void *ctxt, const zend_string *ns, const zend_string *name)
{
xmlXPathRegisterFuncNS((xmlXPathContextPtr) ctxt, (const xmlChar *) ZSTR_VAL(name), (const xmlChar *) ZSTR_VAL(ns), dom_xpath_ext_function_trampoline);
}
PHP_METHOD(DOMXPath, registerPhpFunctionNS)
{
dom_xpath_object *intern = Z_XPATHOBJ_P(ZEND_THIS);
zend_string *namespace, *name;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_PATH_STR(namespace)
Z_PARAM_PATH_STR(name)
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
ZEND_PARSE_PARAMETERS_END();
if (zend_string_equals_literal(namespace, "http://php.net/xpath")) {
zend_argument_value_error(1, "must not be \"http://php.net/xpath\" because it is reserved by PHP");
RETURN_THROWS();
}
php_dom_xpath_callbacks_update_single_method_handler(
&intern->xpath_callbacks,
intern->dom.ptr,
namespace,
name,
&fcc,
PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME,
dom_xpath_register_func_in_ctx
);
}
#endif /* LIBXML_XPATH_ENABLED */
#endif

512
ext/dom/xpath_callbacks.c Normal file
View File

@@ -0,0 +1,512 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Christian Stocker <chregu@php.net> |
| Rob Richards <rrichards@php.net> |
| Niels Dossche <nielsdos@php.net> |
+----------------------------------------------------------------------+
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include "php_dom.h"
#include <libxml/parserInternals.h>
static void xpath_callbacks_entry_dtor(zval *zv)
{
zend_fcall_info_cache *fcc = Z_PTR_P(zv);
zend_fcc_dtor(fcc);
efree(fcc);
}
PHP_DOM_EXPORT void php_dom_xpath_callback_ns_ctor(php_dom_xpath_callback_ns *ns)
{
zend_hash_init(&ns->functions, 0, NULL, xpath_callbacks_entry_dtor, false);
ns->mode = PHP_DOM_REG_FUNC_MODE_NONE;
}
PHP_DOM_EXPORT void php_dom_xpath_callback_ns_dtor(php_dom_xpath_callback_ns *ns)
{
zend_hash_destroy(&ns->functions);
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_ctor(php_dom_xpath_callbacks *registry)
{
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_node_list(php_dom_xpath_callbacks *registry)
{
if (registry->node_list) {
zend_hash_destroy(registry->node_list);
FREE_HASHTABLE(registry->node_list);
registry->node_list = NULL;
}
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_argument_stack(xmlXPathParserContextPtr ctxt, uint32_t num_args)
{
for (uint32_t i = 0; i < num_args; i++) {
xmlXPathObjectPtr obj = valuePop(ctxt);
xmlXPathFreeObject(obj);
}
/* Push sentinel value */
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_dtor(php_dom_xpath_callbacks *registry)
{
if (registry->php_ns) {
php_dom_xpath_callback_ns_dtor(registry->php_ns);
efree(registry->php_ns);
}
if (registry->namespaces) {
php_dom_xpath_callback_ns *ns;
ZEND_HASH_MAP_FOREACH_PTR(registry->namespaces, ns) {
php_dom_xpath_callback_ns_dtor(ns);
efree(ns);
} ZEND_HASH_FOREACH_END();
zend_hash_destroy(registry->namespaces);
FREE_HASHTABLE(registry->namespaces);
}
php_dom_xpath_callbacks_clean_node_list(registry);
}
static void php_dom_xpath_callback_ns_get_gc(php_dom_xpath_callback_ns *ns, zend_get_gc_buffer *gc_buffer)
{
zend_fcall_info_cache *entry;
ZEND_HASH_MAP_FOREACH_PTR(&ns->functions, entry) {
zend_get_gc_buffer_add_fcc(gc_buffer, entry);
} ZEND_HASH_FOREACH_END();
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_get_gc(php_dom_xpath_callbacks *registry, zend_get_gc_buffer *gc_buffer)
{
if (registry->php_ns) {
php_dom_xpath_callback_ns_get_gc(registry->php_ns, gc_buffer);
}
if (registry->namespaces) {
php_dom_xpath_callback_ns *ns;
ZEND_HASH_MAP_FOREACH_PTR(registry->namespaces, ns) {
php_dom_xpath_callback_ns_get_gc(ns, gc_buffer);
} ZEND_HASH_FOREACH_END();
}
}
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)
{
if (registry->php_ns || registry->namespaces) {
zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create();
php_dom_xpath_callbacks_get_gc(registry, gc_buffer);
zend_get_gc_buffer_use(gc_buffer, table, n);
if (object->properties == NULL && object->ce->default_properties_count == 0) {
return NULL;
} else {
return zend_std_get_properties(object);
}
} else {
return zend_std_get_gc(object, table, n);
}
}
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, bool is_array)
{
if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) {
if (is_array) {
zend_argument_value_error(1, "must be an array containing valid callback names");
} else {
zend_argument_value_error(2, "must be a valid callback name");
}
return false;
}
return true;
}
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)
{
if (registry->namespaces) {
zend_string *namespace;
php_dom_xpath_callback_ns *ns;
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(registry->namespaces, namespace, ns) {
zend_string *name;
ZEND_HASH_MAP_FOREACH_STR_KEY(&ns->functions, name) {
register_func(ctxt, namespace, name);
} ZEND_HASH_FOREACH_END();
} ZEND_HASH_FOREACH_END();
}
}
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;
if (callable_ht) {
zend_string *key;
ZEND_HASH_FOREACH_STR_KEY_VAL(callable_ht, key, entry) {
zend_fcall_info_cache* fcc = emalloc(sizeof(zend_fcall_info));
char *error;
if (!zend_is_callable_ex(entry, NULL, 0, NULL, fcc, &error)) {
zend_argument_type_error(1, "must be an array with valid callbacks as values, %s", error);
efree(fcc);
efree(error);
return FAILURE;
}
zend_fcc_addref(fcc);
ZVAL_PTR(&registered_value, fcc);
if (!key) {
zend_string *str = zval_try_get_string(entry);
if (str && php_dom_xpath_is_callback_name_valid_and_throw(str, name_validation, true)) {
zend_hash_update(&ns->functions, str, &registered_value);
if (register_func) {
register_func(ctxt, namespace, str);
}
zend_string_release_ex(str, false);
} else {
zend_fcc_dtor(fcc);
efree(fcc);
return FAILURE;
}
} else {
if (!php_dom_xpath_is_callback_name_valid_and_throw(key, name_validation, true)) {
zend_fcc_dtor(fcc);
efree(fcc);
return FAILURE;
}
zend_hash_update(&ns->functions, key, &registered_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;
ZVAL_STR(&tmp, name);
if (!zend_is_callable_ex(&tmp, NULL, 0, NULL, fcc, &error)) {
zend_argument_type_error(1, "must be a callable, %s", error);
efree(fcc);
efree(error);
return FAILURE;
}
zend_fcc_addref(fcc);
ZVAL_PTR(&registered_value, fcc);
zend_hash_update(&ns->functions, name, &registered_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;
}
static php_dom_xpath_callback_ns *php_dom_xpath_callbacks_ensure_ns(php_dom_xpath_callbacks *registry, zend_string *ns)
{
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);
}
return registry->php_ns;
} else {
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 namespace;
}
}
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_xpath_callback_ns *namespace = php_dom_xpath_callbacks_ensure_ns(registry, ns);
return php_dom_xpath_callback_ns_update_method_handler(namespace, ctxt, ns, name, callable_ht, name_validation, register_func);
}
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)
{
if (!php_dom_xpath_is_callback_name_valid_and_throw(name, name_validation, false)) {
return FAILURE;
}
php_dom_xpath_callback_ns *namespace = php_dom_xpath_callbacks_ensure_ns(registry, ns);
zend_fcall_info_cache* allocated_fcc = emalloc(sizeof(zend_fcall_info));
zend_fcc_dup(allocated_fcc, fcc);
zval registered_value;
ZVAL_PTR(&registered_value, allocated_fcc);
zend_hash_update(&namespace->functions, name, &registered_value);
if (register_func) {
register_func(ctxt, ns, name);
}
namespace->mode = PHP_DOM_REG_FUNC_MODE_SET;
return SUCCESS;
}
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)
{
if (param_count == 0) {
return NULL;
}
zval *params = safe_emalloc(param_count, sizeof(zval), 0);
/* Reverse order to pop values off ctxt stack */
for (zval *param = params + param_count - 1; param >= params; param--) {
xmlXPathObjectPtr obj = valuePop(ctxt);
ZEND_ASSERT(obj != NULL);
switch (obj->type) {
case XPATH_STRING:
ZVAL_STRING(param, (char *)obj->stringval);
break;
case XPATH_BOOLEAN:
ZVAL_BOOL(param, obj->boolval);
break;
case XPATH_NUMBER:
ZVAL_DOUBLE(param, obj->floatval);
break;
case XPATH_NODESET:
if (evaluation_mode == PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING) {
char *str = (char *)xmlXPathCastToString(obj);
ZVAL_STRING(param, str);
xmlFree(str);
} else if (evaluation_mode == PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET) {
if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
array_init_size(param, obj->nodesetval->nodeNr);
zend_hash_real_init_packed(Z_ARRVAL_P(param));
for (int j = 0; j < obj->nodesetval->nodeNr; j++) {
xmlNodePtr node = obj->nodesetval->nodeTab[j];
zval child;
if (UNEXPECTED(node->type == XML_NAMESPACE_DECL)) {
xmlNodePtr nsparent = node->_private;
xmlNsPtr original = (xmlNsPtr) node;
/* Make sure parent dom object exists, so we can take an extra reference. */
zval parent_zval; /* don't destroy me, my lifetime is transfered to the fake namespace decl */
php_dom_create_object(nsparent, &parent_zval, intern);
dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval);
php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern);
} else {
proxy_factory(node, &child, intern, ctxt);
}
zend_hash_next_index_insert_new(Z_ARRVAL_P(param), &child);
}
} else {
ZVAL_EMPTY_ARRAY(param);
}
}
break;
default:
ZVAL_STRING(param, (char *)xmlXPathCastToString(obj));
break;
}
xmlXPathFreeObject(obj);
}
return params;
}
static void php_dom_xpath_callback_cleanup_args(zval *params, uint32_t param_count)
{
if (params) {
for (uint32_t i = 0; i < param_count; i++) {
zval_ptr_dtor(&params[i]);
}
efree(params);
}
}
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)
{
zval callback_retval;
if (UNEXPECTED(ns == NULL)) {
zend_throw_error(NULL, "No callbacks were registered");
return FAILURE;
}
if (ns->mode == PHP_DOM_REG_FUNC_MODE_ALL) {
zend_fcall_info fci;
fci.size = sizeof(fci);
fci.object = NULL;
fci.retval = &callback_retval;
fci.param_count = param_count;
fci.params = params;
fci.named_params = NULL;
ZVAL_STRINGL(&fci.function_name, function_name, function_name_length);
zend_call_function(&fci, NULL);
zend_string_release_ex(Z_STR(fci.function_name), false);
if (UNEXPECTED(EG(exception))) {
return FAILURE;
}
} else {
ZEND_ASSERT(ns->mode == PHP_DOM_REG_FUNC_MODE_SET);
zval *fcc_wrapper = zend_hash_str_find(&ns->functions, function_name, function_name_length);
if (fcc_wrapper) {
zend_call_known_fcc(Z_PTR_P(fcc_wrapper), &callback_retval, param_count, params, NULL);
} else {
zend_throw_error(NULL, "No callback handler \"%s\" registered", function_name);
return FAILURE;
}
}
if (Z_TYPE(callback_retval) != IS_UNDEF) {
if (Z_TYPE(callback_retval) == IS_OBJECT && instanceof_function(Z_OBJCE(callback_retval), dom_node_class_entry)) {
xmlNode *nodep;
dom_object *obj;
if (xpath_callbacks->node_list == NULL) {
xpath_callbacks->node_list = zend_new_array(0);
}
Z_ADDREF_P(&callback_retval);
zend_hash_next_index_insert_new(xpath_callbacks->node_list, &callback_retval);
obj = Z_DOMOBJ_P(&callback_retval);
nodep = dom_object_get_node(obj);
valuePush(ctxt, xmlXPathNewNodeSet(nodep));
} else if (Z_TYPE(callback_retval) == IS_FALSE || Z_TYPE(callback_retval) == IS_TRUE) {
valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(callback_retval) == IS_TRUE));
} else if (Z_TYPE(callback_retval) == IS_OBJECT) {
zend_type_error("Only objects that are instances of DOMNode can be converted to an XPath expression");
zval_ptr_dtor(&callback_retval);
return FAILURE;
} else {
zend_string *str = zval_get_string(&callback_retval);
valuePush(ctxt, xmlXPathNewString((xmlChar *) ZSTR_VAL(str)));
zend_string_release_ex(str, 0);
}
zval_ptr_dtor(&callback_retval);
}
return SUCCESS;
}
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;
if (UNEXPECTED(num_args == 0)) {
zend_throw_error(NULL, "Function name must be passed as the first argument");
goto cleanup_no_obj;
}
uint32_t param_count = num_args - 1;
zval *params = php_dom_xpath_callback_fetch_args(ctxt, param_count, evaluation_mode, intern, proxy_factory);
/* Last element of the stack is the function name */
xmlXPathObjectPtr obj = valuePop(ctxt);
if (UNEXPECTED(obj->stringval == NULL)) {
zend_type_error("Handler name must be a string");
goto cleanup;
}
const char *function_name = (const char *) obj->stringval;
size_t function_name_length = strlen(function_name);
result = php_dom_xpath_callback_dispatch(xpath_callbacks, xpath_callbacks->php_ns, ctxt, params, param_count, function_name, function_name_length);
cleanup:
xmlXPathFreeObject(obj);
php_dom_xpath_callback_cleanup_args(params, param_count);
cleanup_no_obj:
if (UNEXPECTED(result != SUCCESS)) {
/* Push sentinel value */
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
}
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

68
ext/dom/xpath_callbacks.h Normal file
View File

@@ -0,0 +1,68 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Authors: Niels Dossche <nielsdos@php.net> |
+----------------------------------------------------------------------+
*/
#ifndef PHP_DOM_XPATH_CALLBACKS_H
#define PHP_DOM_XPATH_CALLBACKS_H
#if defined(HAVE_LIBXML) && defined(HAVE_DOM)
#include <libxml/xpathInternals.h>
#include "xml_common.h"
typedef enum {
PHP_DOM_REG_FUNC_MODE_NONE = 0,
PHP_DOM_REG_FUNC_MODE_ALL,
PHP_DOM_REG_FUNC_MODE_SET,
} php_dom_register_functions_mode;
typedef enum {
PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING,
PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET,
} 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)(void *ctxt, const zend_string *ns, const zend_string *name);
typedef struct {
HashTable functions;
php_dom_register_functions_mode mode;
} php_dom_xpath_callback_ns;
typedef struct {
php_dom_xpath_callback_ns *php_ns;
HashTable *namespaces;
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_delayed_lib_registration(const php_dom_xpath_callbacks* registry, void *ctxt, php_dom_xpath_callbacks_register_func_ctx register_func);
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_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);
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

View File

@@ -53,6 +53,12 @@ zend_module_entry xsl_module_entry = {
ZEND_GET_MODULE(xsl)
#endif
static HashTable *xsl_objects_get_gc(zend_object *object, zval **table, int *n)
{
xsl_object *intern = php_xsl_fetch_object(object);
return php_dom_xpath_callbacks_get_gc_for_whole_object(&intern->xpath_callbacks, object, table, n);
}
/* {{{ xsl_objects_free_storage */
void xsl_objects_free_storage(zend_object *object)
{
@@ -65,15 +71,7 @@ void xsl_objects_free_storage(zend_object *object)
FREE_HASHTABLE(intern->parameter);
}
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);
}
php_dom_xpath_callbacks_dtor(&intern->xpath_callbacks);
if (intern->doc) {
php_libxml_decrement_doc_ref(intern->doc);
@@ -106,7 +104,7 @@ zend_object *xsl_objects_new(zend_class_entry *class_type)
zend_object_std_init(&intern->std, class_type);
object_properties_init(&intern->std, class_type);
intern->parameter = zend_new_array(0);
intern->registered_phpfunctions = zend_new_array(0);
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
return &intern->std;
}
@@ -119,6 +117,7 @@ PHP_MINIT_FUNCTION(xsl)
xsl_object_handlers.offset = XtOffsetOf(xsl_object, std);
xsl_object_handlers.clone_obj = NULL;
xsl_object_handlers.free_obj = xsl_objects_free_storage;
xsl_object_handlers.get_gc = xsl_objects_get_gc;
xsl_xsltprocessor_class_entry = register_class_XSLTProcessor();
xsl_xsltprocessor_class_entry->create_object = xsl_objects_new;

View File

@@ -38,6 +38,7 @@ extern zend_module_entry xsl_module_entry;
#endif
#include "../dom/xml_common.h"
#include "../dom/xpath_callbacks.h"
#include <libxslt/extensions.h>
#include <libxml/xpathInternals.h>
@@ -53,18 +54,14 @@ extern zend_module_entry xsl_module_entry;
typedef struct _xsl_object {
void *ptr;
HashTable *prop_handler;
zval handle;
HashTable *parameter;
int hasKeys;
int registerPhpFunctions;
HashTable *registered_phpfunctions;
HashTable *node_list;
int securityPrefsSet;
zend_long securityPrefs;
php_dom_xpath_callbacks xpath_callbacks;
php_libxml_node_object *doc;
char *profiling;
zend_long securityPrefs;
int securityPrefsSet;
zend_object std;
zend_object std;
} xsl_object;
static inline xsl_object *php_xsl_fetch_object(zend_object *obj) {

View File

@@ -114,6 +114,8 @@ class XSLTProcessor
/** @tentative-return-type */
public function registerPHPFunctions(array|string|null $functions = null): void {}
public function registerPHPFunctionNS(string $namespaceURI, string $name, callable $callable): void {}
/** @return true */
public function setProfiling(?string $filename) {} // TODO make return type void

View File

@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
* Stub hash: 87ea452722956b6cfe46458e7fcd97f0bcfb767b */
* Stub hash: 0d12e04d92a3f0cc70179814aab0491d1d3fd2f7 */
ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_XSLTProcessor_importStylesheet, 0, 1, _IS_BOOL, 0)
ZEND_ARG_TYPE_INFO(0, stylesheet, IS_OBJECT, 0)
@@ -42,6 +42,12 @@ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_XSLTProcessor_re
ZEND_ARG_TYPE_MASK(0, functions, MAY_BE_ARRAY|MAY_BE_STRING|MAY_BE_NULL, "null")
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class_XSLTProcessor_registerPHPFunctionNS, 0, 3, IS_VOID, 0)
ZEND_ARG_TYPE_INFO(0, namespaceURI, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, name, IS_STRING, 0)
ZEND_ARG_TYPE_INFO(0, callable, IS_CALLABLE, 0)
ZEND_END_ARG_INFO()
ZEND_BEGIN_ARG_INFO_EX(arginfo_class_XSLTProcessor_setProfiling, 0, 0, 1)
ZEND_ARG_TYPE_INFO(0, filename, IS_STRING, 1)
ZEND_END_ARG_INFO()
@@ -63,6 +69,7 @@ ZEND_METHOD(XSLTProcessor, getParameter);
ZEND_METHOD(XSLTProcessor, removeParameter);
ZEND_METHOD(XSLTProcessor, hasExsltSupport);
ZEND_METHOD(XSLTProcessor, registerPHPFunctions);
ZEND_METHOD(XSLTProcessor, registerPHPFunctionNS);
ZEND_METHOD(XSLTProcessor, setProfiling);
ZEND_METHOD(XSLTProcessor, setSecurityPrefs);
ZEND_METHOD(XSLTProcessor, getSecurityPrefs);
@@ -78,6 +85,7 @@ static const zend_function_entry class_XSLTProcessor_methods[] = {
ZEND_ME(XSLTProcessor, removeParameter, arginfo_class_XSLTProcessor_removeParameter, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, hasExsltSupport, arginfo_class_XSLTProcessor_hasExsltSupport, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, registerPHPFunctions, arginfo_class_XSLTProcessor_registerPHPFunctions, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, registerPHPFunctionNS, arginfo_class_XSLTProcessor_registerPHPFunctionNS, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, setProfiling, arginfo_class_XSLTProcessor_setProfiling, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, setSecurityPrefs, arginfo_class_XSLTProcessor_setSecurityPrefs, ZEND_ACC_PUBLIC)
ZEND_ME(XSLTProcessor, getSecurityPrefs, arginfo_class_XSLTProcessor_getSecurityPrefs, ZEND_ACC_PUBLIC)

View File

@@ -0,0 +1,73 @@
--TEST--
registerPhpFunctions() with callables - legit cases
--EXTENSIONS--
xsl
--FILE--
<?php
require __DIR__ . '/xpath_callables.inc';
$inputdom = new DomDocument();
$inputdom->loadXML('<?xml version="1.0" encoding="iso-8859-1"?><a href="https://php.net">hello</a>');
echo "--- Legit cases: none ---\n";
$proc = createProcessor(["'var_dump', string(@href)"]);
try {
$proc->transformToXml($inputdom);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
echo "--- Legit cases: all ---\n";
$proc = createProcessor(["'var_dump', string(@href)", "'MyClass::dump', string(@href)"]);
$proc->registerPHPFunctions();
var_dump($proc->transformToXml($inputdom));
echo "--- Legit cases: set ---\n";
$proc = createProcessor(["'mydump', string(@href)", "'xyz', string(@href)", "'var_dump', string(@href)"]);
$proc->registerPhpFunctions([]);
$proc->registerPHPFunctions(["xyz" => MyClass::dump(...), "mydump" => function (string $x) {
var_dump($x);
}]);
$proc->registerPhpFunctions(str_repeat("var_dump", mt_rand(1, 1) /* defeat SCCP */));
var_dump($proc->transformToXml($inputdom));
$proc = createProcessor(["'notinset', string(@href)"]);
$proc->registerPhpFunctions([]);
try {
var_dump($proc->transformToXml($inputdom));
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
echo "--- Legit cases: set with cycle ---\n";
$proc = createProcessor(["'cycle', string(@href)"], 'MyXSLTProcessor');
$proc->registerCycle();
var_dump($proc->transformToXml($inputdom));
?>
--EXPECT--
--- Legit cases: none ---
No callbacks were registered
--- Legit cases: all ---
string(15) "https://php.net"
string(15) "https://php.net"
string(44) "<?xml version="1.0"?>
dump: https://php.net
"
--- Legit cases: set ---
string(15) "https://php.net"
string(15) "https://php.net"
string(15) "https://php.net"
string(44) "<?xml version="1.0"?>
dump: https://php.net
"
No callback handler "notinset" registered
--- Legit cases: set with cycle ---
string(45) "<?xml version="1.0"?>
dummy: https://php.net
"

View File

@@ -0,0 +1,68 @@
--TEST--
registerPhpFunctions() with callables - error cases
--EXTENSIONS--
xsl
--FILE--
<?php
require __DIR__ . '/xpath_callables.inc';
$proc = createProcessor([]);
try {
$proc->registerPhpFunctions("nonexistent");
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->registerPhpFunctions(function () {});
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->registerPhpFunctions([function () {}]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->registerPhpFunctions([var_dump(...)]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->registerPhpFunctions(["nonexistent"]);
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->registerPhpFunctions(["" => var_dump(...)]);
} catch (Throwable $e) {
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--
XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) must be a callable, function "nonexistent" not found or invalid function name
XSLTProcessor::registerPHPFunctions(): Argument #1 ($functions) 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
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) 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

View File

@@ -22,12 +22,8 @@ function test($input) {
$proc = new XsltProcessor();
$proc->registerPhpFunctions();
$xsl = $proc->importStylesheet($xsl);
try {
$proc->transformToDoc($inputdom);
} catch (Exception $e) {
echo $e->getMessage(), "\n";
}
$proc->importStylesheet($xsl);
$proc->transformToDoc($inputdom);
}
try {
@@ -36,10 +32,13 @@ try {
echo $e->getMessage(), "\n";
}
test("3");
try {
test("3");
} catch (TypeError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
--EXPECT--
Function name must be passed as the first argument
Warning: XSLTProcessor::transformToDoc(): Handler name must be a string in %s on line %d
Handler name must be a string

View File

@@ -0,0 +1,126 @@
--TEST--
registerPHPFunctionNS() function - legit cases
--EXTENSIONS--
xsl
--FILE--
<?php
class TrampolineClass {
public static function __callStatic(string $name, array $arguments): mixed {
var_dump($name, $arguments);
return "foo";
}
}
class StatefulClass {
public array $state = [];
public function __call(string $name, array $arguments): mixed {
$this->state[] = [$name, $arguments[0]];
return $arguments[0];
}
}
function createProcessor($inputs) {
$xsl = new DomDocument();
$xsl->loadXML('<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:foo="urn:foo"
xmlns:bar="urn:bar">
<xsl:template match="//a">'
. implode('', array_map(fn($input) => '<xsl:value-of select="' . $input . '" />', $inputs)) .
'</xsl:template>
</xsl:stylesheet>');
$proc = new XSLTProcessor();
$proc->importStylesheet($xsl);
return $proc;
}
$inputdom = new DomDocument();
$inputdom->loadXML('<?xml version="1.0" encoding="iso-8859-1"?><a href="https://php.net">hello</a>');
echo "--- Legit cases: none ---\n";
$proc = createProcessor(["foo:var_dump(string(@href))"]);
try {
$proc->transformToXml($inputdom);
} catch (Error $e) {
echo $e->getMessage(), "\n";
}
echo "--- Legit cases: global function callable ---\n";
$proc = createProcessor(["foo:var_dump(string(@href))"]);
$proc->registerPHPFunctionNS('urn:foo', 'var_dump', var_dump(...));
$proc->transformToXml($inputdom);
echo "--- Legit cases: global string callable ---\n";
$proc = createProcessor(["foo:var_dump(string(@href))"]);
$proc->registerPHPFunctionNS('urn:foo', 'var_dump', 'var_dump');
$proc->transformToXml($inputdom);
echo "--- Legit cases: trampoline callable ---\n";
$proc = createProcessor(["foo:trampoline(string(@href))"]);
$proc->registerPHPFunctionNS('urn:foo', 'trampoline', TrampolineClass::test(...));
var_dump($proc->transformToXml($inputdom));
echo "--- Legit cases: instance class method callable ---\n";
$state = new StatefulClass;
$proc = createProcessor(["foo:test(string(@href))"]);
$proc->registerPHPFunctionNS('urn:foo', 'test', $state->test(...));
var_dump($proc->transformToXml($inputdom));
var_dump($state->state);
echo "--- Legit cases: multiple namespaces ---\n";
$proc = createProcessor(["foo:test(string(@href))", "bar:test(string(@href))"]);
$proc->registerPHPFunctionNS('urn:foo', 'test', strrev(...));
$proc->registerPHPFunctionNS('urn:bar', 'test', strtoupper(...));
var_dump($proc->transformToXml($inputdom));
?>
--EXPECTF--
--- Legit cases: none ---
Warning: XSLTProcessor::transformToXml(): xmlXPathCompOpEval: function var_dump not found in %s on line %d
Warning: XSLTProcessor::transformToXml(): Unregistered function in %s on line %d
Warning: XSLTProcessor::transformToXml(): runtime error: file %s line 6 element value-of in %s on line %d
Warning: XSLTProcessor::transformToXml(): XPath evaluation returned no result. in %s on line %d
--- Legit cases: global function callable ---
string(15) "https://php.net"
--- Legit cases: global string callable ---
string(15) "https://php.net"
--- Legit cases: trampoline callable ---
string(4) "test"
array(1) {
[0]=>
string(15) "https://php.net"
}
string(26) "<?xml version="1.0"?>
foo
"
--- Legit cases: instance class method callable ---
string(38) "<?xml version="1.0"?>
https://php.net
"
array(1) {
[0]=>
array(2) {
[0]=>
string(4) "test"
[1]=>
string(15) "https://php.net"
}
}
--- Legit cases: multiple namespaces ---
string(53) "<?xml version="1.0"?>
ten.php//:sptthHTTPS://PHP.NET
"

View File

@@ -0,0 +1,39 @@
--TEST--
registerPHPFunctionNS() function - error cases
--EXTENSIONS--
xsl
--FILE--
<?php
require __DIR__ . '/xpath_callables.inc';
try {
createProcessor([])->registerPhpFunctionNS('http://php.net/xsl', 'strtolower', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
createProcessor([])->registerPhpFunctionNS('urn:foo', 'x:a', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
createProcessor([])->registerPhpFunctionNS("urn:foo", "\0", strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
createProcessor([])->registerPhpFunctionNS("\0", 'strtolower', strtolower(...));
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECT--
XSLTProcessor::registerPHPFunctionNS(): Argument #1 ($namespaceURI) must not be "http://php.net/xsl" because it is reserved by PHP
XSLTProcessor::registerPHPFunctionNS(): Argument #2 ($name) must be a valid callback name
XSLTProcessor::registerPHPFunctionNS(): Argument #2 ($name) must not contain any null bytes
XSLTProcessor::registerPHPFunctionNS(): Argument #1 ($namespaceURI) must not contain any null bytes

View File

@@ -28,12 +28,14 @@ $proc->registerPhpFunctions();
$xsl = $proc->importStylesheet($xsl);
try {
$newdom = $proc->transformToDoc($inputdom);
} catch (Exception $e) {
} catch (Error $e) {
echo $e->getMessage(), "\n";
echo $e->getPrevious()->getMessage(), "\n";
}
?>
===DONE===
--EXPECT--
string(4) "TeSt"
Invalid callback TeSt::dateLang, class "TeSt" not found
Autoload exception
===DONE===

View File

@@ -0,0 +1,34 @@
<?php
class MyClass {
public static function dump(string $var) {
var_dump($var);
return "dump: $var";
}
}
class MyXSLTProcessor extends XSLTProcessor {
public function registerCycle() {
$this->registerPhpFunctions(["cycle" => array($this, "dummy")]);
}
public function dummy(string $var) {
return "dummy: $var";
}
}
function createProcessor($inputs, $class = "XSLTProcessor") {
$xsl = new DomDocument();
$xsl->loadXML('<?xml version="1.0" encoding="iso-8859-1" ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl">
<xsl:template match="//a">'
. implode('', array_map(fn($input) => '<xsl:value-of select="php:function(' . $input . ')" />', $inputs)) .
'</xsl:template>
</xsl:stylesheet>');
$proc = new $class();
$proc->importStylesheet($xsl);
return $proc;
}

View File

@@ -5,51 +5,42 @@ xsl
--FILE--
<?php
print "Test 11: php:function Support\n";
Class foo {
function __construct() {}
function __toString() { return "not a DomNode object";}
}
$dom = new domDocument();
$dom->load(__DIR__."/xslt011.xsl");
$proc = new xsltprocessor;
$xsl = $proc->importStylesheet($dom);
$dom->load(__DIR__."/xslt011.xsl");
$proc = new xsltprocessor;
$xsl = $proc->importStylesheet($dom);
$xml = new DomDocument();
$xml->load(__DIR__."/xslt011.xml");
$proc->registerPHPFunctions();
print $proc->transformToXml($xml);
$xml = new DomDocument();
$xml->load(__DIR__."/xslt011.xml");
$proc->registerPHPFunctions();
print $proc->transformToXml($xml);
function foobar($id, $secondArg = "" ) {
if (is_array($id)) {
return $id[0]->value . " - " . $secondArg;
function foobar($id, $secondArg = "" ) {
if (is_array($id)) {
return $id[0]->value . " - " . $secondArg;
} else {
return $id . " - " . $secondArg;
}
}
function nodeSet($id = null) {
if ($id and is_array($id)) {
return $id[0];
} else {
return $id . " - " . $secondArg;
$dom = new domdocument;
$dom->loadXML("<root>this is from an external DomDocument</root>");
return $dom->documentElement;
}
}
function nodeSet($id = null) {
if ($id and is_array($id)) {
return $id[0];
} else {
$dom = new domdocument;
$dom->loadXML("<root>this is from an external DomDocument</root>");
return $dom->documentElement;
}
}
function nonDomNode() {
return new foo();
}
}
class aClass {
static function aStaticFunction($id) {
return $id;
}
class aClass {
static function aStaticFunction($id) {
return $id;
}
}
?>
--EXPECTF--
Test 11: php:function Support
Warning: XSLTProcessor::transformToXml(): A PHP Object cannot be converted to a XPath-string in %s on line 16
<?xml version="1.0"?>
foobar - secondArg
foobar -

View File

@@ -19,7 +19,5 @@
<xsl:value-of select="php:function('aClass::aStaticFunction','static')"/>
<xsl:text>
</xsl:text>
<xsl:value-of select="php:function('nonDomNode')"/>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,31 @@
--TEST--
php:function Support - non-DOMNode
--EXTENSIONS--
xsl
--FILE--
<?php
Class foo {
function __construct() {}
function __toString() { return "not a DomNode object";}
}
function nonDomNode() {
return new foo();
}
$dom = new domDocument();
$dom->load(__DIR__."/xslt_non_dom_node.xsl");
$proc = new xsltprocessor;
$xsl = $proc->importStylesheet($dom);
$xml = new DomDocument();
$xml->load(__DIR__."/xslt011.xml");
$proc->registerPHPFunctions();
try {
$proc->transformToXml($xml);
} catch (TypeError $e) {
echo $e->getMessage();
}
?>
--EXPECT--
Only objects that are instances of DOMNode can be converted to an XPath expression

View File

@@ -0,0 +1,9 @@
<?xml version='1.0'?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:php="http://php.net/xsl"
xsl:extension-element-prefixes="php"
version='1.0'>
<xsl:template match="/">
<xsl:value-of select="php:function('nonDomNode')"/>
</xsl:template>
</xsl:stylesheet>

View File

@@ -0,0 +1,62 @@
--TEST--
Overriding an EXSLT builtin
--EXTENSIONS--
xsl
--SKIPIF--
<?php
$proc = new xsltprocessor;
if (!$proc->hasExsltSupport()) die('skip EXSLT support not available');
if (LIBXSLT_VERSION < 10130) die('skip too old libxsl');
?>
--FILE--
<?php
function dummy_year($input) {
var_dump($input[0]->textContent);
return 'dummy value';
}
function dummy_exit($input) {
var_dump($input);
exit("dummy exit");
}
$xsl = <<<XML
<?xml version="1.0" encoding="ISO-8859-1"?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:date="http://exslt.org/dates-and-times"
extension-element-prefixes="date">
<xsl:output method="text"/>
<xsl:template match="date"><xsl:value-of select="date:year(@date)"/></xsl:template>
</xsl:stylesheet>
XML;
$xml = <<<XML
<?xml version="1.0"?>
<page><date date="2007-12-31"/></page>
XML;
$xsldoc = new DOMDocument();
$xsldoc->loadXML($xsl);
$xmldoc = new DOMDocument();
$xmldoc->loadXML($xml);
$proc = new XSLTProcessor();
$proc->importStylesheet($xsldoc);
// Should override the builtin function
$proc->registerPHPFunctionNS('http://exslt.org/dates-and-times', 'year', dummy_year(...));
echo $proc->transformToXML($xmldoc), "\n";
// Should not exit
$proc->registerPHPFunctionNS('http://www.w3.org/1999/XSL/Transform', 'value-of', dummy_exit(...));
echo $proc->transformToXML($xmldoc), "\n";
?>
--EXPECT--
string(10) "2007-12-31"
dummy value
string(10) "2007-12-31"
dummy value

View File

@@ -13,13 +13,15 @@ if(!$phpfuncxsl) {
}
$proc->importStylesheet($phpfuncxsl);
var_dump($proc->registerPHPFunctions(array()));
var_dump($proc->transformToXml($dom));
try {
var_dump($proc->transformToXml($dom));
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
NULL
Warning: XSLTProcessor::transformToXml(): Not allowed to call handler 'ucwords()' in %s on line %d
--EXPECT--
NULL
No callback handler "ucwords" registered
--CREDITS--
Christian Weiske, cweiske@php.net
PHP Testfest Berlin 2009-05-09

View File

@@ -16,13 +16,15 @@ if(!$phpfuncxsl) {
}
$proc->importStylesheet($phpfuncxsl);
var_dump($proc->registerPHPFunctions());
var_dump($proc->transformToXml($dom));
try {
var_dump($proc->transformToXml($dom));
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
NULL
Warning: XSLTProcessor::transformToXml(): Handler name must be a string in %s on line %d
--EXPECT--
NULL
Handler name must be a string
--CREDITS--
Christian Weiske, cweiske@php.net
PHP Testfest Berlin 2009-05-09

View File

@@ -15,13 +15,15 @@ if(!$phpfuncxsl) {
}
$proc->importStylesheet($phpfuncxsl);
var_dump($proc->registerPHPFunctions());
var_dump($proc->transformToXml($dom));
try {
var_dump($proc->transformToXml($dom));
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
NULL
Warning: XSLTProcessor::transformToXml(): Unable to call handler undefinedfunc() in %s on line %d
--EXPECT--
NULL
Invalid callback undefinedfunc, function "undefinedfunc" not found or invalid function name
--CREDITS--
Christian Weiske, cweiske@php.net
PHP Testfest Berlin 2009-05-09

View File

@@ -13,13 +13,15 @@ if(!$phpfuncxsl) {
}
$proc->importStylesheet($phpfuncxsl);
var_dump($proc->registerPHPFunctions('strpos'));
var_dump($proc->transformToXml($dom));
try {
var_dump($proc->transformToXml($dom));
} catch (Throwable $e) {
echo $e->getMessage(), "\n";
}
?>
--EXPECTF--
NULL
Warning: XSLTProcessor::transformToXml(): Not allowed to call handler 'ucwords()' in %s on line %d
--EXPECT--
NULL
No callback handler "ucwords" registered
--CREDITS--
Christian Weiske, cweiske@php.net
PHP Testfest Berlin 2009-05-09

View File

@@ -45,231 +45,83 @@ static zend_result php_xsl_xslt_apply_params(xsltTransformContextPtr ctxt, HashT
return SUCCESS;
}
static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, int type) /* {{{ */
static void xsl_proxy_factory(xmlNodePtr node, zval *child, dom_object *intern, xmlXPathParserContextPtr ctxt)
{
xsltTransformContextPtr tctxt;
zval *args = NULL;
zval retval;
int i;
int error = 0;
zend_fcall_info fci;
zval handler;
xmlXPathObjectPtr obj;
char *str;
xsl_object *intern;
zend_string *callable = NULL;
ZEND_ASSERT(node->type != XML_NAMESPACE_DECL);
/**
* Upon freeing libxslt's context, every document that is not the *main* document will be freed by libxslt.
* If a node of a document that is *not the main* document gets returned to userland, we'd free the node twice:
* first by the cleanup of the xslt context, and then by our own refcounting mechanism.
* To prevent this, we'll take a copy if the node is not from the main document.
* It is important that we do not copy the node unconditionally, because that means that:
* - modifications to the node will only modify the copy, and not the original
* - accesses to the parent, path, ... will not work
*/
xsltTransformContextPtr transform_ctxt = (xsltTransformContextPtr) ctxt->context->extra;
if (node->doc != transform_ctxt->document->doc) {
node = xmlDocCopyNode(node, intern->document->ptr, 1);
}
php_dom_create_object(node, child, intern);
}
if (! zend_is_executing()) {
static xsl_object *xsl_ext_fetch_intern(xmlXPathParserContextPtr ctxt)
{
if (UNEXPECTED(!zend_is_executing())) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: Function called from outside of PHP\n");
error = 1;
return NULL;
}
xsltTransformContextPtr tctxt = xsltXPathGetTransformContext(ctxt);
if (UNEXPECTED(tctxt == NULL)) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get the transformation context\n");
return NULL;
}
xsl_object *intern = (xsl_object *) tctxt->_private;
if (UNEXPECTED(intern == NULL)) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get the internal object\n");
return NULL;
}
return intern;
}
static void xsl_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, php_dom_xpath_nodeset_evaluation_mode evaluation_mode) /* {{{ */
{
xsl_object *intern = xsl_ext_fetch_intern(ctxt);
if (!intern) {
php_dom_xpath_callbacks_clean_argument_stack(ctxt, nargs);
} else {
tctxt = xsltXPathGetTransformContext(ctxt);
if (tctxt == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get the transformation context\n");
error = 1;
} else {
intern = (xsl_object*)tctxt->_private;
if (intern == NULL) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: failed to get the internal object\n");
error = 1;
}
else if (intern->registerPhpFunctions == 0) {
xsltGenericError(xsltGenericErrorContext,
"xsltExtFunctionTest: PHP Object did not register PHP functions\n");
error = 1;
}
}
}
if (error == 1) {
for (i = nargs - 1; i >= 0; i--) {
obj = valuePop(ctxt);
if (obj) {
xmlXPathFreeObject(obj);
}
}
return;
}
if (UNEXPECTED(nargs == 0)) {
zend_throw_error(NULL, "Function name must be passed as the first argument");
return;
}
fci.param_count = nargs - 1;
if (fci.param_count > 0) {
args = safe_emalloc(fci.param_count, sizeof(zval), 0);
}
/* Reverse order to pop values off ctxt stack */
for (i = fci.param_count - 1; i >= 0; i--) {
obj = valuePop(ctxt);
if (obj == NULL) {
ZVAL_NULL(&args[i]);
continue;
}
switch (obj->type) {
case XPATH_STRING:
ZVAL_STRING(&args[i], (char *)obj->stringval);
break;
case XPATH_BOOLEAN:
ZVAL_BOOL(&args[i], obj->boolval);
break;
case XPATH_NUMBER:
ZVAL_DOUBLE(&args[i], obj->floatval);
break;
case XPATH_NODESET:
if (type == 1) {
str = (char*)xmlXPathCastToString(obj);
ZVAL_STRING(&args[i], str);
xmlFree(str);
} else if (type == 2) {
int j;
dom_object *domintern = (dom_object *)intern->doc;
if (obj->nodesetval && obj->nodesetval->nodeNr > 0) {
array_init(&args[i]);
for (j = 0; j < obj->nodesetval->nodeNr; j++) {
xmlNodePtr node = obj->nodesetval->nodeTab[j];
zval child;
/* not sure, if we need this... it's copied from xpath.c */
if (node->type == XML_NAMESPACE_DECL) {
xmlNsPtr curns;
xmlNodePtr nsparent;
nsparent = node->_private;
curns = xmlNewNs(NULL, node->name, NULL);
if (node->children) {
curns->prefix = xmlStrdup((xmlChar *)node->children);
}
if (node->children) {
node = xmlNewDocNode(node->doc, NULL, (xmlChar *) node->children, node->name);
} else {
node = xmlNewDocNode(node->doc, NULL, (const xmlChar *) "xmlns", node->name);
}
node->type = XML_NAMESPACE_DECL;
node->parent = nsparent;
node->ns = curns;
} else {
/**
* Upon freeing libxslt's context, every document which is not the *main* document will be freed by libxslt.
* If a node of a document which is *not the main* document gets returned to userland, we'd free the node twice:
* first by the cleanup of the xslt context, and then by our own refcounting mechanism.
* To prevent this, we'll take a copy if the node is not from the main document.
* It is important that we do not copy the node unconditionally, because that means that:
* - modifications to the node will only modify the copy, and not the original
* - accesses to the parent, path, ... will not work
*/
xsltTransformContextPtr transform_ctxt = (xsltTransformContextPtr) ctxt->context->extra;
if (node->doc != transform_ctxt->document->doc) {
node = xmlDocCopyNode(node, domintern->document->ptr, 1);
}
}
php_dom_create_object(node, &child, domintern);
add_next_index_zval(&args[i], &child);
}
} else {
ZVAL_EMPTY_ARRAY(&args[i]);
}
}
break;
default:
str = (char *) xmlXPathCastToString(obj);
ZVAL_STRING(&args[i], str);
xmlFree(str);
}
xmlXPathFreeObject(obj);
}
fci.size = sizeof(fci);
fci.named_params = NULL;
if (fci.param_count > 0) {
fci.params = args;
} else {
fci.params = NULL;
}
/* Last element of the stack is the function name */
obj = valuePop(ctxt);
if (obj == NULL || obj->stringval == NULL) {
php_error_docref(NULL, E_WARNING, "Handler name must be a string");
xmlXPathFreeObject(obj);
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
if (fci.param_count > 0) {
for (i = 0; i < nargs - 1; i++) {
zval_ptr_dtor(&args[i]);
}
efree(args);
}
return;
}
ZVAL_STRING(&handler, (char *) obj->stringval);
xmlXPathFreeObject(obj);
ZVAL_COPY_VALUE(&fci.function_name, &handler);
fci.object = NULL;
fci.retval = &retval;
if (!zend_make_callable(&handler, &callable)) {
if (!EG(exception)) {
php_error_docref(NULL, E_WARNING, "Unable to call handler %s()", ZSTR_VAL(callable));
}
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
} else if ( intern->registerPhpFunctions == 2 && zend_hash_exists(intern->registered_phpfunctions, callable) == 0) {
php_error_docref(NULL, E_WARNING, "Not allowed to call handler '%s()'", ZSTR_VAL(callable));
/* Push an empty string, so that we at least have an xslt result... */
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
} else {
zend_call_function(&fci, NULL);
if (Z_ISUNDEF(retval)) {
/* Exception thrown, don't do anything further. */
} else if (Z_TYPE(retval) == IS_OBJECT && instanceof_function(Z_OBJCE(retval), dom_node_class_entry)) {
xmlNode *nodep;
dom_object *obj;
if (intern->node_list == NULL) {
intern->node_list = zend_new_array(0);
}
Z_ADDREF(retval);
zend_hash_next_index_insert(intern->node_list, &retval);
obj = Z_DOMOBJ_P(&retval);
nodep = dom_object_get_node(obj);
valuePush(ctxt, xmlXPathNewNodeSet(nodep));
} else if (Z_TYPE(retval) == IS_TRUE || Z_TYPE(retval) == IS_FALSE) {
valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(retval) == IS_TRUE));
} else if (Z_TYPE(retval) == IS_OBJECT) {
php_error_docref(NULL, E_WARNING, "A PHP Object cannot be converted to a XPath-string");
valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
} else {
convert_to_string(&retval);
valuePush(ctxt, xmlXPathNewString((xmlChar *) Z_STRVAL(retval)));
}
zval_ptr_dtor(&retval);
}
zend_string_release_ex(callable, 0);
zval_ptr_dtor_nogc(&handler);
if (fci.param_count > 0) {
for (i = 0; i < nargs - 1; i++) {
zval_ptr_dtor(&args[i]);
}
efree(args);
php_dom_xpath_callbacks_call_php_ns(&intern->xpath_callbacks, ctxt, nargs, evaluation_mode, (dom_object *) intern->doc, xsl_proxy_factory);
}
}
/* }}} */
void xsl_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
{
xsl_ext_function_php(ctxt, nargs, 1);
xsl_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_STRING);
}
/* }}} */
void xsl_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs) /* {{{ */
{
xsl_ext_function_php(ctxt, nargs, 2);
xsl_ext_function_php(ctxt, nargs, PHP_DOM_XPATH_EVALUATE_NODESET_TO_NODESET);
}
/* }}} */
static void xsl_ext_function_trampoline(xmlXPathParserContextPtr ctxt, int nargs)
{
xsl_object *intern = xsl_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, (dom_object *) intern->doc, xsl_proxy_factory);
}
}
/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#
Since:
*/
@@ -353,6 +205,12 @@ PHP_METHOD(XSLTProcessor, importStylesheet)
}
/* }}} end XSLTProcessor::importStylesheet */
static void php_xsl_delayed_lib_registration(void *ctxt, const zend_string *ns, const zend_string *name)
{
xsltTransformContextPtr xsl = (xsltTransformContextPtr) ctxt;
xsltRegisterExtFunction(xsl, (const xmlChar *) ZSTR_VAL(name), (const xmlChar *) ZSTR_VAL(ns), xsl_ext_function_trampoline);
}
static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStylesheetPtr style, zval *docp) /* {{{ */
{
xmlDocPtr newdocp = NULL;
@@ -459,6 +317,8 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
}
}
php_dom_xpath_callbacks_delayed_lib_registration(&intern->xpath_callbacks, ctxt, php_xsl_delayed_lib_registration);
if (secPrefsError == 1) {
php_error_docref(NULL, E_WARNING, "Can't set libxslt security properties, not doing transformation for security reasons");
} else {
@@ -475,11 +335,7 @@ out:
xsltFreeSecurityPrefs(secPrefs);
}
if (intern->node_list != NULL) {
zend_hash_destroy(intern->node_list);
FREE_HASHTABLE(intern->node_list);
intern->node_list = NULL;
}
php_dom_xpath_callbacks_clean_node_list(&intern->xpath_callbacks);
php_libxml_decrement_doc_ref(intern->doc);
efree(intern->doc);
@@ -734,41 +590,58 @@ PHP_METHOD(XSLTProcessor, removeParameter)
/* {{{ */
PHP_METHOD(XSLTProcessor, registerPHPFunctions)
{
zval *id = ZEND_THIS;
xsl_object *intern;
zval *entry, new_string;
zend_string *restrict_str = NULL;
HashTable *restrict_ht = NULL;
xsl_object *intern = Z_XSL_P(ZEND_THIS);
zend_string *name = NULL;
HashTable *callable_ht = NULL;
ZEND_PARSE_PARAMETERS_START(0, 1)
Z_PARAM_OPTIONAL
Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(restrict_ht, restrict_str)
Z_PARAM_ARRAY_HT_OR_STR_OR_NULL(callable_ht, name)
ZEND_PARSE_PARAMETERS_END();
intern = Z_XSL_P(id);
if (restrict_ht) {
ZEND_HASH_FOREACH_VAL(restrict_ht, entry) {
zend_string *str = zval_try_get_string(entry);
if (UNEXPECTED(!str)) {
return;
}
ZVAL_LONG(&new_string, 1);
zend_hash_update(intern->registered_phpfunctions, str, &new_string);
zend_string_release(str);
} ZEND_HASH_FOREACH_END();
intern->registerPhpFunctions = 2;
} else if (restrict_str) {
ZVAL_LONG(&new_string, 1);
zend_hash_update(intern->registered_phpfunctions, restrict_str, &new_string);
intern->registerPhpFunctions = 2;
} else {
intern->registerPhpFunctions = 1;
}
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(); */
PHP_METHOD(XSLTProcessor, registerPHPFunctionNS)
{
xsl_object *intern = Z_XSL_P(ZEND_THIS);
zend_string *namespace, *name;
zend_fcall_info fci;
zend_fcall_info_cache fcc;
ZEND_PARSE_PARAMETERS_START(3, 3)
Z_PARAM_PATH_STR(namespace)
Z_PARAM_PATH_STR(name)
Z_PARAM_FUNC_NO_TRAMPOLINE_FREE(fci, fcc)
ZEND_PARSE_PARAMETERS_END();
if (zend_string_equals_literal(namespace, "http://php.net/xsl")) {
zend_argument_value_error(1, "must not be \"http://php.net/xsl\" because it is reserved by PHP");
RETURN_THROWS();
}
php_dom_xpath_callbacks_update_single_method_handler(
&intern->xpath_callbacks,
NULL,
namespace,
name,
&fcc,
PHP_DOM_XPATH_CALLBACK_NAME_VALIDATE_NCNAME,
NULL
);
}
/* {{{ */
PHP_METHOD(XSLTProcessor, setProfiling)
{