Implement request #30622: make $namespace parameter functional

This parameter never actually did anything and was forgotten about.
We solve this by detecting when we have a $namespace argument
(that won't conflict with the name argument) and creating a Clark
notation name out of it.

Closes GH-16123.
This commit is contained in:
Niels Dossche 2024-09-29 21:45:55 +02:00
parent f5e81fe182
commit daa94cf279
No known key found for this signature in database
GPG Key ID: B8A8AD166DF0E2E5
5 changed files with 218 additions and 18 deletions

3
NEWS
View File

@ -22,4 +22,7 @@ PHP NEWS
- XMLWriter:
. Improved performance and reduce memory consumption. (nielsdos)
- XSL:
. Implement request #30622 (make $namespace parameter functional). (nielsdos)
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

View File

@ -37,6 +37,14 @@ PHP 8.5 UPGRADE NOTES
2. New Features
========================================
- XSL:
. The $namespace argument of XSLTProcessor::getParameter(),
XSLTProcessor::setParameter() and XSLTProcessor::removeParameter()
now actually works instead of being treated as empty.
This only works if the $name argument does not use Clark notation
and is not a QName because in those cases the namespace is taken
from the namespace href or prefix respectively.
========================================
3. Changes in SAPI modules
========================================

View File

@ -0,0 +1,85 @@
--TEST--
Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI)
--EXTENSIONS--
xsl
--CREDITS--
Based on a test by <ishikawa at arielworks dot com>
--FILE--
<?php
$xmlDom = new DOMDocument();
$xmlDom->loadXML('<root/>');
$xslDom = new DOMDocument();
$xslDom->loadXML(<<<'XML'
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:test="http://www.php.net/test">
<xsl:param name="foo" select="'EMPTY'"/>
<xsl:param name="test:foo" select="'EMPTY'"/>
<xsl:template match="/root">
<xsl:text>Namespace "NULL": </xsl:text>
<xsl:value-of select="$foo"/>
<xsl:text>, Namespace "http://www.php.net/test": </xsl:text>
<xsl:value-of select="$test:foo"/>
</xsl:template>
</xsl:stylesheet>
XML);
$proc = new XSLTProcessor();
$proc->importStyleSheet($xslDom);
echo "--- Set both empty and non-empty namespace ---\n";
$proc->setParameter("", "foo", "SET1");
$proc->setParameter("http://www.php.net/test", "foo", "SET2");
var_dump($proc->getParameter("", "foo"));
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
print $proc->transformToXML($xmlDom);
echo "--- Remove empty namespace entry ---\n";
var_dump($proc->removeParameter("", "foo"));
var_dump($proc->getParameter("", "foo"));
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
print $proc->transformToXML($xmlDom);
echo "--- Remove non-empty namespace entry ---\n";
var_dump($proc->removeParameter("http://www.php.net/test", "foo"));
var_dump($proc->getParameter("", "foo"));
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
print $proc->transformToXML($xmlDom);
echo "--- Set via array ---\n";
$proc->setParameter("", ["foo" => "SET1"]);
$proc->setParameter("http://www.php.net/test", ["foo" => "SET2"]);
print $proc->transformToXML($xmlDom);
?>
--EXPECT--
--- Set both empty and non-empty namespace ---
string(4) "SET1"
string(4) "SET2"
<?xml version="1.0"?>
Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2
--- Remove empty namespace entry ---
bool(true)
bool(false)
string(4) "SET2"
<?xml version="1.0"?>
Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": SET2
--- Remove non-empty namespace entry ---
bool(true)
bool(false)
bool(false)
<?xml version="1.0"?>
Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": EMPTY
--- Set via array ---
<?xml version="1.0"?>
Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2

View File

@ -0,0 +1,59 @@
--TEST--
Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI) - error cases
--EXTENSIONS--
xsl
--CREDITS--
Based on a test by <ishikawa at arielworks dot com>
--FILE--
<?php
$proc = new XSLTProcessor();
try {
$proc->setParameter("urn:x", "{urn:a}x", "");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->setParameter("urn:x", "a:b", "");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->getParameter("urn:x", "{urn:a}x");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->getParameter("urn:x", "a:b");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->removeParameter("urn:x", "{urn:a}x");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
try {
$proc->removeParameter("urn:x", "a:b");
} catch (ValueError $e) {
echo $e->getMessage(), "\n";
}
// Edge cases, should work
$proc->setParameter("urn:x", ":b", "");
$proc->setParameter("urn:x", ":", "");
?>
--EXPECT--
XSLTProcessor::setParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
XSLTProcessor::setParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty
XSLTProcessor::getParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
XSLTProcessor::getParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty
XSLTProcessor::removeParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
XSLTProcessor::removeParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty

View File

@ -549,6 +549,32 @@ PHP_METHOD(XSLTProcessor, transformToXml)
}
/* }}} end XSLTProcessor::transformToXml */
static zend_string *xsl_create_parameter_key(uint32_t arg_num, const zend_string *namespace, zend_string *name)
{
if (ZSTR_LEN(namespace) == 0) {
return zend_string_copy(name);
}
/* Clark notation already sets the namespace and we cannot have a double namespace declaration. */
if (ZSTR_VAL(name)[0] == '{') {
zend_argument_value_error(arg_num, "must not use clark notation when argument #1 ($namespace) is not empty");
return NULL;
}
/* Cannot be a QName as that would cause a namespace lookup in the document. */
if (ZSTR_VAL(name)[0] != ':' && strchr(ZSTR_VAL(name), ':')) {
zend_argument_value_error(arg_num, "must not be a QName when argument #1 ($namespace) is not empty");
return NULL;
}
zend_string *clark_str = zend_string_safe_alloc(1, ZSTR_LEN(name), 2 + ZSTR_LEN(namespace), false);
ZSTR_VAL(clark_str)[0] = '{';
memcpy(ZSTR_VAL(clark_str) + 1, ZSTR_VAL(namespace), ZSTR_LEN(namespace));
ZSTR_VAL(clark_str)[ZSTR_LEN(namespace) + 1] = '}';
memcpy(ZSTR_VAL(clark_str) + 2 + ZSTR_LEN(namespace), ZSTR_VAL(name), ZSTR_LEN(name) + 1 /* include '\0' */);
return clark_str;
}
/* {{{ */
PHP_METHOD(XSLTProcessor, setParameter)
{
@ -557,12 +583,10 @@ PHP_METHOD(XSLTProcessor, setParameter)
zval *entry, new_string;
HashTable *array_value;
xsl_object *intern;
char *namespace;
size_t namespace_len;
zend_string *string_key, *name, *value = NULL;
zend_string *namespace, *string_key, *name, *value = NULL;
ZEND_PARSE_PARAMETERS_START(2, 3)
Z_PARAM_STRING(namespace, namespace_len)
Z_PARAM_PATH_STR(namespace)
Z_PARAM_ARRAY_HT_OR_STR(array_value, name)
Z_PARAM_OPTIONAL
Z_PARAM_PATH_STR_OR_NULL(value)
@ -590,19 +614,27 @@ PHP_METHOD(XSLTProcessor, setParameter)
RETURN_THROWS();
}
zend_string *ht_key = xsl_create_parameter_key(2, namespace, string_key);
if (!ht_key) {
RETURN_THROWS();
}
str = zval_try_get_string(entry);
if (UNEXPECTED(!str)) {
zend_string_release_ex(ht_key, false);
RETURN_THROWS();
}
if (UNEXPECTED(CHECK_NULL_PATH(ZSTR_VAL(str), ZSTR_LEN(str)))) {
zend_string_release(str);
zend_string_release_ex(ht_key, false);
zend_argument_value_error(3, "must not contain values with any null bytes");
RETURN_THROWS();
}
ZVAL_STR(&tmp, str);
zend_hash_update(intern->parameter, string_key, &tmp);
zend_hash_update(intern->parameter, ht_key, &tmp);
zend_string_release_ex(ht_key, false);
} ZEND_HASH_FOREACH_END();
RETURN_TRUE;
} else {
@ -616,9 +648,15 @@ PHP_METHOD(XSLTProcessor, setParameter)
RETURN_THROWS();
}
zend_string *key = xsl_create_parameter_key(2, namespace, name);
if (!key) {
RETURN_THROWS();
}
ZVAL_STR_COPY(&new_string, value);
zend_hash_update(intern->parameter, name, &new_string);
zend_hash_update(intern->parameter, key, &new_string);
zend_string_release_ex(key, false);
RETURN_TRUE;
}
}
@ -628,17 +666,21 @@ PHP_METHOD(XSLTProcessor, setParameter)
PHP_METHOD(XSLTProcessor, getParameter)
{
zval *id = ZEND_THIS;
char *namespace;
size_t namespace_len = 0;
zval *value;
zend_string *name;
zend_string *namespace, *name;
xsl_object *intern;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "PP", &namespace, &name) == FAILURE) {
RETURN_THROWS();
}
zend_string *key = xsl_create_parameter_key(2, namespace, name);
if (!key) {
RETURN_THROWS();
}
intern = Z_XSL_P(id);
if ((value = zend_hash_find(intern->parameter, name)) != NULL) {
value = zend_hash_find(intern->parameter, key);
zend_string_release_ex(key, false);
if (value != NULL) {
RETURN_STR_COPY(Z_STR_P(value));
} else {
RETURN_FALSE;
@ -650,20 +692,23 @@ PHP_METHOD(XSLTProcessor, getParameter)
PHP_METHOD(XSLTProcessor, removeParameter)
{
zval *id = ZEND_THIS;
size_t namespace_len = 0;
char *namespace;
zend_string *name;
zend_string *namespace, *name;
xsl_object *intern;
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
if (zend_parse_parameters(ZEND_NUM_ARGS(), "PP", &namespace, &name) == FAILURE) {
RETURN_THROWS();
}
zend_string *key = xsl_create_parameter_key(2, namespace, name);
if (!key) {
RETURN_THROWS();
}
intern = Z_XSL_P(id);
if (zend_hash_del(intern->parameter, name) == SUCCESS) {
RETURN_TRUE;
if (zend_hash_del(intern->parameter, key) == SUCCESS) {
RETVAL_TRUE;
} else {
RETURN_FALSE;
RETVAL_FALSE;
}
zend_string_release_ex(key, false);
}
/* }}} end XSLTProcessor::removeParameter */