Skip to content

Commit

Permalink
Implement request #30622: make $namespace parameter functional
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
nielsdos committed Sep 30, 2024
1 parent f5e81fe commit daa94cf
Show file tree
Hide file tree
Showing 5 changed files with 218 additions and 18 deletions.
3 changes: 3 additions & 0 deletions NEWS
Original file line number Diff line number Diff line change
Expand Up @@ -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! >>>
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

0 comments on commit daa94cf

Please sign in to comment.