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:
2
NEWS
2
NEWS
@@ -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! >>>
|
||||
|
||||
13
UPGRADING
13
UPGRADING
@@ -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
|
||||
========================================
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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) /* {{{ */
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
14
ext/dom/php_dom_arginfo.h
generated
14
ext/dom/php_dom_arginfo.h
generated
@@ -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
|
||||
|
||||
88
ext/dom/tests/DOMXPath_callables.phpt
Normal file
88
ext/dom/tests/DOMXPath_callables.phpt
Normal 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"
|
||||
69
ext/dom/tests/DOMXPath_callables_errors.phpt
Normal file
69
ext/dom/tests/DOMXPath_callables_errors.phpt
Normal 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
|
||||
@@ -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
|
||||
|
||||
106
ext/dom/tests/registerPhpFunctionNS.phpt
Normal file
106
ext/dom/tests/registerPhpFunctionNS.phpt
Normal 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)
|
||||
}
|
||||
42
ext/dom/tests/registerPhpFunctionNS_errors.phpt
Normal file
42
ext/dom/tests/registerPhpFunctionNS_errors.phpt
Normal 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
|
||||
284
ext/dom/xpath.c
284
ext/dom/xpath.c
@@ -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
512
ext/dom/xpath_callbacks.c
Normal 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(®istered_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, ®istered_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, ®istered_value);
|
||||
if (register_func) {
|
||||
register_func(ctxt, namespace, key);
|
||||
}
|
||||
}
|
||||
} ZEND_HASH_FOREACH_END();
|
||||
ns->mode = PHP_DOM_REG_FUNC_MODE_SET;
|
||||
} else if (name) {
|
||||
if (!php_dom_xpath_is_callback_name_valid(name, name_validation)) {
|
||||
zend_argument_value_error(1, "must be a valid callback name");
|
||||
return FAILURE;
|
||||
}
|
||||
zend_fcall_info_cache* fcc = emalloc(sizeof(zend_fcall_info));
|
||||
char *error;
|
||||
zval tmp;
|
||||
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(®istered_value, fcc);
|
||||
zend_hash_update(&ns->functions, name, ®istered_value);
|
||||
if (register_func) {
|
||||
register_func(ctxt, namespace, name);
|
||||
}
|
||||
ns->mode = PHP_DOM_REG_FUNC_MODE_SET;
|
||||
} else {
|
||||
ns->mode = PHP_DOM_REG_FUNC_MODE_ALL;
|
||||
}
|
||||
|
||||
return SUCCESS;
|
||||
}
|
||||
|
||||
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(®istered_value, allocated_fcc);
|
||||
|
||||
zend_hash_update(&namespace->functions, name, ®istered_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(¶ms[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
68
ext/dom/xpath_callbacks.h
Normal 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
|
||||
@@ -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;
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
10
ext/xsl/php_xsl_arginfo.h
generated
10
ext/xsl/php_xsl_arginfo.h
generated
@@ -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)
|
||||
|
||||
73
ext/xsl/tests/XSLTProcessor_callables.phpt
Normal file
73
ext/xsl/tests/XSLTProcessor_callables.phpt
Normal 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
|
||||
"
|
||||
68
ext/xsl/tests/XSLTProcessor_callables_errors.phpt
Normal file
68
ext/xsl/tests/XSLTProcessor_callables_errors.phpt
Normal 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
|
||||
@@ -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
|
||||
|
||||
126
ext/xsl/tests/registerPHPFunctionNS.phpt
Normal file
126
ext/xsl/tests/registerPHPFunctionNS.phpt
Normal 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
|
||||
"
|
||||
39
ext/xsl/tests/registerPHPFunctionNS_errors.phpt
Normal file
39
ext/xsl/tests/registerPHPFunctionNS_errors.phpt
Normal 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
|
||||
@@ -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===
|
||||
|
||||
34
ext/xsl/tests/xpath_callables.inc
Normal file
34
ext/xsl/tests/xpath_callables.inc
Normal 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;
|
||||
}
|
||||
@@ -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 -
|
||||
|
||||
@@ -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>
|
||||
|
||||
31
ext/xsl/tests/xslt_non_dom_node.phpt
Normal file
31
ext/xsl/tests/xslt_non_dom_node.phpt
Normal 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
|
||||
9
ext/xsl/tests/xslt_non_dom_node.xsl
Normal file
9
ext/xsl/tests/xslt_non_dom_node.xsl
Normal 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>
|
||||
62
ext/xsl/tests/xsltprocessor_exsl_registerPhpFunctionNs.phpt
Normal file
62
ext/xsl/tests/xsltprocessor_exsl_registerPhpFunctionNs.phpt
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user