diff --git a/NEWS b/NEWS
index 60f1365e696f2..daa18a857d66b 100644
--- a/NEWS
+++ b/NEWS
@@ -236,5 +236,7 @@ PHP NEWS
. Implement request #64137 (XSLTProcessor::setParameter() should allow both
quotes to be used). (nielsdos)
. Implemented "Improve callbacks in ext/dom and ext/xsl" RFC. (nielsdos)
+ . Added XSLTProcessor::$maxTemplateDepth and XSLTProcessor::$maxTemplateVars.
+ (nielsdos)
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>
diff --git a/UPGRADING b/UPGRADING
index 395e02e15338f..96b99dc393b5e 100644
--- a/UPGRADING
+++ b/UPGRADING
@@ -261,6 +261,8 @@ PHP 8.4 UPGRADE NOTES
quotes.
. It is now possible to pass any callable to registerPhpFunctions().
RFC: https://wiki.php.net/rfc/improve_callbacks_dom_and_xsl
+ . Added XSLTProcessor::$maxTemplateDepth and XSLTProcessor::$maxTemplateVars
+ to control the recursion depth of XSL template evaluation.
========================================
3. Changes in SAPI modules
diff --git a/UPGRADING.INTERNALS b/UPGRADING.INTERNALS
index 6be15a7f9dcf0..4928592134af6 100644
--- a/UPGRADING.INTERNALS
+++ b/UPGRADING.INTERNALS
@@ -192,6 +192,8 @@ PHP 8.4 INTERNALS UPGRADE NOTES
d. ext/libxml
- Added php_libxml_pretend_ctx_error_ex() to emit errors as if they had come
from libxml.
+ - Added php_libxml_error_handler_va() to pass libxml errors, and
+ corresponding php_libxml_error_level enum.
- Removed the "properties" HashTable field from php_libxml_node_object.
- Added a way to attached private data to a php_libxml_ref_obj.
- Added a way to fix a class type onto php_libxml_ref_obj.
diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c
index a4dbc4e3f8c8c..3c67534514c04 100644
--- a/ext/dom/xpath_callbacks.c
+++ b/ext/dom/xpath_callbacks.c
@@ -67,8 +67,9 @@ PHP_DOM_EXPORT void php_dom_xpath_callbacks_clean_argument_stack(xmlXPathParserC
xmlXPathFreeObject(obj);
}
- /* Push sentinel value */
- valuePush(ctxt, xmlXPathNewString((const xmlChar *) ""));
+ /* Don't push a sentinel value here. If this is called from an error situation, then by *not* pushing a sentinel
+ * the execution will halt. If this is called from a regular situation, then it is the caller's responsibility
+ * to ensure the stack remains balanced. */
}
PHP_DOM_EXPORT void php_dom_xpath_callbacks_dtor(php_dom_xpath_callbacks *registry)
diff --git a/ext/libxml/libxml.c b/ext/libxml/libxml.c
index 9c94cb9da59fe..dc63f2d03858c 100644
--- a/ext/libxml/libxml.c
+++ b/ext/libxml/libxml.c
@@ -45,9 +45,6 @@
#include "php_libxml.h"
#define PHP_LIBXML_LOADED_VERSION ((char *)xmlParserVersion)
-#define PHP_LIBXML_ERROR 0
-#define PHP_LIBXML_CTX_ERROR 1
-#define PHP_LIBXML_CTX_WARNING 2
#include "libxml_arginfo.h"
@@ -647,12 +644,12 @@ void php_libxml_issue_error(int level, const char *msg)
}
}
-static void php_libxml_internal_error_handler_ex(int error_type, void *ctx, const char **msg, va_list ap, int line, int column)
+static void php_libxml_internal_error_handler_ex(php_libxml_error_level error_type, void *ctx, const char *msg, va_list ap, int line, int column)
{
char *buf;
int len, len_iter, output = 0;
- len = vspprintf(&buf, 0, *msg, ap);
+ len = vspprintf(&buf, 0, msg, ap);
len_iter = len;
/* remove any trailing \n */
@@ -685,7 +682,7 @@ static void php_libxml_internal_error_handler_ex(int error_type, void *ctx, cons
}
}
-static void php_libxml_internal_error_handler(int error_type, void *ctx, const char **msg, va_list ap)
+PHP_LIBXML_API void php_libxml_error_handler_va(php_libxml_error_level error_type, void *ctx, const char *msg, va_list ap)
{
int line = 0;
int column = 0;
@@ -831,7 +828,7 @@ PHP_LIBXML_API void php_libxml_pretend_ctx_error_ex(const char *file, int line,
{
va_list args;
va_start(args, msg);
- php_libxml_internal_error_handler_ex(PHP_LIBXML_CTX_ERROR, NULL, &msg, args, line, column);
+ php_libxml_internal_error_handler_ex(PHP_LIBXML_CTX_ERROR, NULL, msg, args, line, column);
va_end(args);
/* Propagate back into libxml */
@@ -853,7 +850,7 @@ PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...)
{
va_list args;
va_start(args, msg);
- php_libxml_internal_error_handler(PHP_LIBXML_CTX_ERROR, ctx, &msg, args);
+ php_libxml_error_handler_va(PHP_LIBXML_CTX_ERROR, ctx, msg, args);
va_end(args);
}
@@ -861,7 +858,7 @@ PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...)
{
va_list args;
va_start(args, msg);
- php_libxml_internal_error_handler(PHP_LIBXML_CTX_WARNING, ctx, &msg, args);
+ php_libxml_error_handler_va(PHP_LIBXML_CTX_WARNING, ctx, msg, args);
va_end(args);
}
@@ -878,7 +875,7 @@ PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...)
{
va_list args;
va_start(args, msg);
- php_libxml_internal_error_handler(PHP_LIBXML_ERROR, ctx, &msg, args);
+ php_libxml_error_handler_va(PHP_LIBXML_ERROR, ctx, msg, args);
va_end(args);
}
diff --git a/ext/libxml/php_libxml.h b/ext/libxml/php_libxml.h
index 42bfb2e0fe2b3..b9262f5e96944 100644
--- a/ext/libxml/php_libxml.h
+++ b/ext/libxml/php_libxml.h
@@ -141,6 +141,12 @@ static zend_always_inline void php_libxml_invalidate_node_list_cache_from_doc(xm
typedef void * (*php_libxml_export_node) (zval *object);
+typedef enum {
+ PHP_LIBXML_ERROR = 0,
+ PHP_LIBXML_CTX_ERROR = 1,
+ PHP_LIBXML_CTX_WARNING = 2,
+} php_libxml_error_level;
+
PHP_LIBXML_API int php_libxml_increment_node_ptr(php_libxml_node_object *object, xmlNodePtr node, void *private_data);
PHP_LIBXML_API int php_libxml_decrement_node_ptr(php_libxml_node_object *object);
PHP_LIBXML_API int php_libxml_increment_doc_ref(php_libxml_node_object *object, xmlDocPtr docp);
@@ -157,6 +163,7 @@ PHP_LIBXML_API void php_libxml_error_handler(void *ctx, const char *msg, ...);
PHP_LIBXML_API void php_libxml_ctx_warning(void *ctx, const char *msg, ...);
PHP_LIBXML_API void php_libxml_pretend_ctx_error_ex(const char *file, int line, int column, const char *msg,...);
PHP_LIBXML_API void php_libxml_ctx_error(void *ctx, const char *msg, ...);
+PHP_LIBXML_API void php_libxml_error_handler_va(php_libxml_error_level error_type, void *ctx, const char *msg, va_list args);
PHP_LIBXML_API int php_libxml_xmlCheckUTF8(const unsigned char *s);
PHP_LIBXML_API void php_libxml_switch_context(zval *context, zval *oldcontext);
PHP_LIBXML_API void php_libxml_issue_error(int level, const char *msg);
diff --git a/ext/xsl/php_xsl.c b/ext/xsl/php_xsl.c
index 5beccee2e62ab..605a933c286a1 100644
--- a/ext/xsl/php_xsl.c
+++ b/ext/xsl/php_xsl.c
@@ -118,10 +118,152 @@ zend_object *xsl_objects_new(zend_class_entry *class_type)
intern->parameter = zend_new_array(0);
php_dom_xpath_callbacks_ctor(&intern->xpath_callbacks);
+ /* Default initialize properties that could not be default initialized at the stub because they depend on library
+ * configuration parameters. */
+ ZVAL_LONG(xsl_prop_max_template_depth(&intern->std), xsltMaxDepth);
+ ZVAL_LONG(xsl_prop_max_template_vars(&intern->std), xsltMaxVars);
+
return &intern->std;
}
/* }}} */
+#if ZEND_DEBUG
+# define XSL_DEFINE_PROP_ACCESSOR(c_name, php_name, prop_index) \
+ zval *xsl_prop_##c_name(zend_object *object) \
+ { \
+ zend_string *prop_name = ZSTR_INIT_LITERAL(php_name, false); \
+ const zend_property_info *prop_info = zend_get_property_info(xsl_xsltprocessor_class_entry, prop_name, 0); \
+ zend_string_release_ex(prop_name, false); \
+ ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == prop_index); \
+ return OBJ_PROP_NUM(object, prop_index); \
+ }
+#else
+# define XSL_DEFINE_PROP_ACCESSOR(c_name, php_name, prop_index) \
+ zval *xsl_prop_##c_name(zend_object *object) \
+ { \
+ return OBJ_PROP_NUM(object, prop_index); \
+ }
+#endif
+
+XSL_DEFINE_PROP_ACCESSOR(max_template_depth, "maxTemplateDepth", 2)
+XSL_DEFINE_PROP_ACCESSOR(max_template_vars, "maxTemplateVars", 3)
+
+static zval *xsl_objects_write_property_with_validation(zend_object *object, zend_string *member, zval *value, void **cache_slot, zval *property)
+{
+ /* Read old value so we can restore it if necessary. The value is not refcounted as its type is IS_LONG. */
+ ZEND_ASSERT(Z_TYPE_P(property) == IS_LONG);
+ zend_long old_property_value = Z_LVAL_P(property);
+
+ /* Write new property, which will also potentially perform coercions. */
+ zend_std_write_property(object, member, value, NULL);
+
+ /* Validate value *after* coercions have been performed, and restore the old value if necessary. */
+ if (UNEXPECTED(Z_LVAL_P(property) < 0)) {
+ Z_LVAL_P(property) = old_property_value;
+ zend_value_error("%s::$%s must be greater than or equal to 0", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
+ return &EG(error_zval);
+ }
+
+ return property;
+}
+
+static zval *xsl_objects_write_property(zend_object *object, zend_string *member, zval *value, void **cache_slot)
+{
+ /* Extra validation for maxTemplateDepth and maxTemplateVars */
+ if (zend_string_equals_literal(member, "maxTemplateDepth")) {
+ zval *property = xsl_prop_max_template_depth(object);
+ return xsl_objects_write_property_with_validation(object, member, value, cache_slot, property);
+ } else if (zend_string_equals_literal(member, "maxTemplateVars")) {
+ zval *property = xsl_prop_max_template_vars(object);
+ return xsl_objects_write_property_with_validation(object, member, value, cache_slot, property);
+ } else {
+ return zend_std_write_property(object, member, value, cache_slot);
+ }
+}
+
+static bool xsl_is_validated_property(const zend_string *member)
+{
+ return zend_string_equals_literal(member, "maxTemplateDepth") || zend_string_equals_literal(member, "maxTemplateVars");
+}
+
+static zval *xsl_objects_get_property_ptr_ptr(zend_object *object, zend_string *member, int type, void **cache_slot)
+{
+ if (xsl_is_validated_property(member)) {
+ return NULL;
+ }
+
+ return zend_std_get_property_ptr_ptr(object, member, type, cache_slot);
+}
+
+static zval *xsl_objects_read_property(zend_object *object, zend_string *member, int type, void **cache_slot, zval *rv)
+{
+ /* read handler is being called as a fallback after get_property_ptr_ptr returned NULL */
+ if (type != BP_VAR_IS && type != BP_VAR_R && xsl_is_validated_property(member)) {
+ zend_throw_error(NULL, "Indirect modification of %s::$%s is not allowed", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
+ return &EG(uninitialized_zval);
+ }
+
+ return zend_std_read_property(object, member, type, cache_slot, rv);
+}
+
+static void xsl_objects_unset_property(zend_object *object, zend_string *member, void **cache_slot)
+{
+ if (xsl_is_validated_property(member)) {
+ zend_throw_error(NULL, "Cannot unset %s::$%s", ZSTR_VAL(object->ce->name), ZSTR_VAL(member));
+ return;
+ }
+
+ zend_std_unset_property(object, member, cache_slot);
+}
+
+/* Tries to output an error message where a part was replaced by another string.
+ * Returns true if the search string was found and the error message with replacement was outputted.
+ * Return false otherwise. */
+static bool xsl_try_output_replaced_error_message(
+ void *ctx,
+ const char *msg,
+ va_list args,
+ const char *search,
+ size_t search_len,
+ const char *replace
+)
+{
+ const char *msg_replace_location = strstr(msg, search);
+ if (msg_replace_location != NULL) {
+ php_libxml_ctx_error(ctx, "%.*s%s%s", (int) (msg_replace_location - msg), msg, replace, msg_replace_location + search_len);
+ return true;
+ }
+ return false;
+}
+
+/* Helper macro so the string length doesn't need to be passed separately.
+ * Only allows literal strings for `search` and `replace`. */
+#define XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg, args, search, replace) \
+ xsl_try_output_replaced_error_message(ctx, msg, args, "" search, sizeof("" search) - 1, "" replace)
+
+/* We want to output PHP-tailored error messages for some libxslt error messages, such that
+ * the errors refer to PHP properties instead of libxslt-specific fields. */
+static void xsl_libxslt_error_handler(void *ctx, const char *msg, ...)
+{
+ va_list args;
+ va_start(args, msg);
+
+ if (strcmp(msg, "%s") == 0) {
+ /* Adjust error message to be more descriptive */
+ const char *msg_arg = va_arg(args, const char *);
+ bool output = XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg_arg, args, "xsltMaxDepth (--maxdepth)", "$maxTemplateDepth")
+ || XSL_TRY_OUTPUT_REPLACED_ERROR_MESSAGE(ctx, msg_arg, args, "maxTemplateVars (--maxvars)", "$maxTemplateVars");
+
+ if (!output) {
+ php_libxml_ctx_error(ctx, "%s", msg_arg);
+ }
+ } else {
+ php_libxml_error_handler_va(PHP_LIBXML_ERROR, ctx, msg, args);
+ }
+
+ va_end(args);
+}
+
/* {{{ PHP_MINIT_FUNCTION */
PHP_MINIT_FUNCTION(xsl)
{
@@ -130,6 +272,10 @@ PHP_MINIT_FUNCTION(xsl)
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_object_handlers.write_property = xsl_objects_write_property;
+ xsl_object_handlers.get_property_ptr_ptr = xsl_objects_get_property_ptr_ptr;
+ xsl_object_handlers.read_property = xsl_objects_read_property;
+ xsl_object_handlers.unset_property = xsl_objects_unset_property;
xsl_xsltprocessor_class_entry = register_class_XSLTProcessor();
xsl_xsltprocessor_class_entry->create_object = xsl_objects_new;
@@ -145,7 +291,7 @@ PHP_MINIT_FUNCTION(xsl)
xsltRegisterExtModuleFunction ((const xmlChar *) "function",
(const xmlChar *) "http://php.net/xsl",
xsl_ext_function_object_php);
- xsltSetGenericErrorFunc(NULL, php_libxml_error_handler);
+ xsltSetGenericErrorFunc(NULL, xsl_libxslt_error_handler);
register_php_xsl_symbols(module_number);
diff --git a/ext/xsl/php_xsl.h b/ext/xsl/php_xsl.h
index accdecf46bdb2..8590b7ad92222 100644
--- a/ext/xsl/php_xsl.h
+++ b/ext/xsl/php_xsl.h
@@ -77,6 +77,9 @@ void xsl_objects_free_storage(zend_object *object);
void xsl_ext_function_string_php(xmlXPathParserContextPtr ctxt, int nargs);
void xsl_ext_function_object_php(xmlXPathParserContextPtr ctxt, int nargs);
+zval *xsl_prop_max_template_depth(zend_object *object);
+zval *xsl_prop_max_template_vars(zend_object *object);
+
PHP_MINIT_FUNCTION(xsl);
PHP_MSHUTDOWN_FUNCTION(xsl);
PHP_RINIT_FUNCTION(xsl);
diff --git a/ext/xsl/php_xsl.stub.php b/ext/xsl/php_xsl.stub.php
index fffcc5dfc93c7..c01e218162eef 100644
--- a/ext/xsl/php_xsl.stub.php
+++ b/ext/xsl/php_xsl.stub.php
@@ -75,6 +75,10 @@ class XSLTProcessor
public bool $cloneDocument = false;
+ public int $maxTemplateDepth;
+
+ public int $maxTemplateVars;
+
/**
* @param DOMDocument|DOM\Document|SimpleXMLElement $stylesheet
* @tentative-return-type
diff --git a/ext/xsl/php_xsl_arginfo.h b/ext/xsl/php_xsl_arginfo.h
index 2d8427c95f841..033e1e8e3b21f 100644
--- a/ext/xsl/php_xsl_arginfo.h
+++ b/ext/xsl/php_xsl_arginfo.h
@@ -1,5 +1,5 @@
/* This is a generated file, edit the .stub.php file instead.
- * Stub hash: ae093276a1caf53c34d429076458bc8b802d69e9 */
+ * Stub hash: f7f9951cbb437f1985016dc06490d55f711bc725 */
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)
@@ -131,5 +131,17 @@ static zend_class_entry *register_class_XSLTProcessor(void)
zend_declare_typed_property(class_entry, property_cloneDocument_name, &property_cloneDocument_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_BOOL));
zend_string_release(property_cloneDocument_name);
+ zval property_maxTemplateDepth_default_value;
+ ZVAL_UNDEF(&property_maxTemplateDepth_default_value);
+ zend_string *property_maxTemplateDepth_name = zend_string_init("maxTemplateDepth", sizeof("maxTemplateDepth") - 1, 1);
+ zend_declare_typed_property(class_entry, property_maxTemplateDepth_name, &property_maxTemplateDepth_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
+ zend_string_release(property_maxTemplateDepth_name);
+
+ zval property_maxTemplateVars_default_value;
+ ZVAL_UNDEF(&property_maxTemplateVars_default_value);
+ zend_string *property_maxTemplateVars_name = zend_string_init("maxTemplateVars", sizeof("maxTemplateVars") - 1, 1);
+ zend_declare_typed_property(class_entry, property_maxTemplateVars_name, &property_maxTemplateVars_default_value, ZEND_ACC_PUBLIC, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_LONG));
+ zend_string_release(property_maxTemplateVars_name);
+
return class_entry;
}
diff --git a/ext/xsl/tests/bug71571_a.phpt b/ext/xsl/tests/bug71571_a.phpt
new file mode 100644
index 0000000000000..ee19a91896508
--- /dev/null
+++ b/ext/xsl/tests/bug71571_a.phpt
@@ -0,0 +1,48 @@
+--TEST--
+Request #71571 (XSLT processor should provide option to change maxDepth) - variant A
+--EXTENSIONS--
+xsl
+--INI--
+error_reporting=E_ALL
+--FILE--
+
+
+
+
+
+
+
+
+
+
+EOF;
+
+$xsl = new DOMDocument();
+$xsl->loadXML($myxsl);
+
+$doc = new DOMDocument();
+
+$proc = new XSLTProcessor;
+$proc->maxTemplateDepth = 2;
+$proc->importStyleSheet($xsl);
+$proc->transformToDoc($doc);
+
+?>
+--EXPECTF--
+Warning: XSLTProcessor::transformToDoc(): runtime error: file %s line 8 element call-template in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): xsltApplySequenceConstructor: A potential infinite template recursion was detected.
+You can adjust $maxTemplateDepth in order to raise the maximum number of nested template calls and variables/params (currently set to 2). in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): Templates: in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #0 name recurse in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #1 name recurse in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #2 name / in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): Variables: in %s on line %d
diff --git a/ext/xsl/tests/bug71571_b.phpt b/ext/xsl/tests/bug71571_b.phpt
new file mode 100644
index 0000000000000..b40076bfd368f
--- /dev/null
+++ b/ext/xsl/tests/bug71571_b.phpt
@@ -0,0 +1,61 @@
+--TEST--
+Request #71571 (XSLT processor should provide option to change maxDepth) - variant B
+--EXTENSIONS--
+xsl
+--INI--
+error_reporting=E_ALL
+--FILE--
+
+
+
+
+
+
+
+ 1
+
+
+
+
+
+EOF;
+
+$xsl = new DOMDocument();
+$xsl->loadXML($myxsl);
+
+$doc = new DOMDocument();
+
+$proc = new XSLTProcessor;
+// Set the template depth limit so high that we will certainly hit the variable depth limit first.
+$proc->maxTemplateDepth = 2**30;
+$proc->maxTemplateVars = 2;
+$proc->importStyleSheet($xsl);
+$proc->transformToDoc($doc);
+
+?>
+--EXPECTF--
+Warning: XSLTProcessor::transformToDoc(): runtime error: file %s line 8 element param in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): xsltApplyXSLTTemplate: A potential infinite template recursion was detected.
+You can adjust $maxTemplateVars in order to raise the maximum number of variables/params (currently set to 2). in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): Templates: in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #0 name recurse in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #1 name recurse in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #2 name / in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): Variables: in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #0 in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): COUNT in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): #1 in %s on line %d
+
+Warning: XSLTProcessor::transformToDoc(): param COUNT in %s on line %d
diff --git a/ext/xsl/tests/maxTemplateDepth_errors.phpt b/ext/xsl/tests/maxTemplateDepth_errors.phpt
new file mode 100644
index 0000000000000..ec70927ea9c2d
--- /dev/null
+++ b/ext/xsl/tests/maxTemplateDepth_errors.phpt
@@ -0,0 +1,44 @@
+--TEST--
+XSLTProcessor::$maxTemplateDepth errors
+--EXTENSIONS--
+xsl
+--INI--
+error_reporting=E_ALL & ~E_DEPRECATED
+--FILE--
+maxTemplateDepth;
+
+try {
+ $processor->maxTemplateDepth = -1;
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateDepth === $oldValue);
+
+try {
+ $processor->maxTemplateDepth = -32.1;
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateDepth === $oldValue);
+
+try {
+ $processor->maxTemplateDepth = "-1";
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateDepth === $oldValue);
+
+?>
+--EXPECT--
+XSLTProcessor::$maxTemplateDepth must be greater than or equal to 0
+bool(true)
+XSLTProcessor::$maxTemplateDepth must be greater than or equal to 0
+bool(true)
+XSLTProcessor::$maxTemplateDepth must be greater than or equal to 0
+bool(true)
diff --git a/ext/xsl/tests/maxTemplateDepth_modification_validation_bypass.phpt b/ext/xsl/tests/maxTemplateDepth_modification_validation_bypass.phpt
new file mode 100644
index 0000000000000..48305a512bd8a
--- /dev/null
+++ b/ext/xsl/tests/maxTemplateDepth_modification_validation_bypass.phpt
@@ -0,0 +1,57 @@
+--TEST--
+XSLTProcessor::$maxTemplateDepth modification validation bypass
+--EXTENSIONS--
+xsl
+--FILE--
+maxTemplateDepth = $value;
+ } catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+ }
+}
+
+echo "--- Get reference ---\n";
+
+try {
+ $ref =& $proc->maxTemplateDepth;
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "--- Unset ---\n";
+
+try {
+ unset($proc->maxTemplateDepth);
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "--- Dump ---\n";
+
+var_dump($proc);
+
+?>
+--EXPECT--
+--- Set to 1 ---
+--- Set to -1 ---
+XSLTProcessor::$maxTemplateDepth must be greater than or equal to 0
+--- Get reference ---
+Indirect modification of XSLTProcessor::$maxTemplateDepth is not allowed
+--- Unset ---
+Cannot unset XSLTProcessor::$maxTemplateDepth
+--- Dump ---
+object(XSLTProcessor)#1 (4) {
+ ["doXInclude"]=>
+ bool(false)
+ ["cloneDocument"]=>
+ bool(false)
+ ["maxTemplateDepth"]=>
+ int(1)
+ ["maxTemplateVars"]=>
+ int(15000)
+}
diff --git a/ext/xsl/tests/maxTemplateVars_errors.phpt b/ext/xsl/tests/maxTemplateVars_errors.phpt
new file mode 100644
index 0000000000000..7ad9c7241e74b
--- /dev/null
+++ b/ext/xsl/tests/maxTemplateVars_errors.phpt
@@ -0,0 +1,44 @@
+--TEST--
+XSLTProcessor::$maxTemplateVars errors
+--EXTENSIONS--
+xsl
+--INI--
+error_reporting=E_ALL & ~E_DEPRECATED
+--FILE--
+maxTemplateVars;
+
+try {
+ $processor->maxTemplateVars = -1;
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateVars === $oldValue);
+
+try {
+ $processor->maxTemplateVars = -32.1;
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateVars === $oldValue);
+
+try {
+ $processor->maxTemplateVars = "-1";
+} catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+}
+
+var_dump($processor->maxTemplateVars === $oldValue);
+
+?>
+--EXPECT--
+XSLTProcessor::$maxTemplateVars must be greater than or equal to 0
+bool(true)
+XSLTProcessor::$maxTemplateVars must be greater than or equal to 0
+bool(true)
+XSLTProcessor::$maxTemplateVars must be greater than or equal to 0
+bool(true)
diff --git a/ext/xsl/tests/maxTemplateVars_modification_validation_bypass.phpt b/ext/xsl/tests/maxTemplateVars_modification_validation_bypass.phpt
new file mode 100644
index 0000000000000..d2e52aa6099ba
--- /dev/null
+++ b/ext/xsl/tests/maxTemplateVars_modification_validation_bypass.phpt
@@ -0,0 +1,57 @@
+--TEST--
+XSLTProcessor::$maxTemplateVars modification validation bypass
+--EXTENSIONS--
+xsl
+--FILE--
+maxTemplateVars = $value;
+ } catch (ValueError $e) {
+ echo $e->getMessage(), "\n";
+ }
+}
+
+echo "--- Get reference ---\n";
+
+try {
+ $ref =& $proc->maxTemplateVars;
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "--- Unset ---\n";
+
+try {
+ unset($proc->maxTemplateVars);
+} catch (Error $e) {
+ echo $e->getMessage(), "\n";
+}
+
+echo "--- Dump ---\n";
+
+var_dump($proc);
+
+?>
+--EXPECT--
+--- Set to 1 ---
+--- Set to -1 ---
+XSLTProcessor::$maxTemplateVars must be greater than or equal to 0
+--- Get reference ---
+Indirect modification of XSLTProcessor::$maxTemplateVars is not allowed
+--- Unset ---
+Cannot unset XSLTProcessor::$maxTemplateVars
+--- Dump ---
+object(XSLTProcessor)#1 (4) {
+ ["doXInclude"]=>
+ bool(false)
+ ["cloneDocument"]=>
+ bool(false)
+ ["maxTemplateDepth"]=>
+ int(3000)
+ ["maxTemplateVars"]=>
+ int(1)
+}
diff --git a/ext/xsl/tests/new_without_constructor.phpt b/ext/xsl/tests/new_without_constructor.phpt
new file mode 100644
index 0000000000000..aa1de62758685
--- /dev/null
+++ b/ext/xsl/tests/new_without_constructor.phpt
@@ -0,0 +1,23 @@
+--TEST--
+XSLTProcessor: new instance without constructor
+--EXTENSIONS--
+xsl
+--FILE--
+newInstanceWithoutConstructor();
+var_dump($processor);
+
+?>
+--EXPECTF--
+object(XSLTProcessor)#2 (4) {
+ ["doXInclude"]=>
+ bool(false)
+ ["cloneDocument"]=>
+ bool(false)
+ ["maxTemplateDepth"]=>
+ int(%d)
+ ["maxTemplateVars"]=>
+ int(%d)
+}
diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c
index aa5fbcd0a630a..7d0f9b05546d2 100644
--- a/ext/xsl/xsltprocessor.c
+++ b/ext/xsl/xsltprocessor.c
@@ -359,6 +359,14 @@ static xmlDocPtr php_xsl_apply_stylesheet(zval *id, xsl_object *intern, xsltStyl
ctxt->xinclude = zend_is_true(doXInclude);
zend_string_release_ex(member, 0);
+ zval *max_template_depth = xsl_prop_max_template_depth(Z_OBJ_P(id));
+ ZEND_ASSERT(Z_TYPE_P(max_template_depth) == IS_LONG);
+ ctxt->maxTemplateDepth = Z_LVAL_P(max_template_depth);
+
+ zval *max_template_vars = xsl_prop_max_template_vars(Z_OBJ_P(id));
+ ZEND_ASSERT(Z_TYPE_P(max_template_vars) == IS_LONG);
+ ctxt->maxTemplateVars = Z_LVAL_P(max_template_vars);
+
secPrefsValue = intern->securityPrefs;
/* if securityPrefs is set to NONE, we don't have to do any checks, but otherwise... */