Skip to content

Implement request #30622: make $namespace parameter functional #16123

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions UPGRADING
Original file line number Diff line number Diff line change
Expand Up @@ -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
========================================
Expand Down
85 changes: 85 additions & 0 deletions ext/xsl/tests/req30622.phpt
Original file line number Diff line number Diff line change
@@ -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
59 changes: 59 additions & 0 deletions ext/xsl/tests/req30622_errors.phpt
Original file line number Diff line number Diff line change
@@ -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
81 changes: 63 additions & 18 deletions ext/xsl/xsltprocessor.c
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand All @@ -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)
Expand Down Expand Up @@ -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 {
Expand All @@ -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;
}
}
Expand All @@ -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;
Expand All @@ -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 */

Expand Down