Skip to content

Commit daa94cf

Browse files
committed
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 phpGH-16123.
1 parent f5e81fe commit daa94cf

File tree

5 files changed

+218
-18
lines changed

5 files changed

+218
-18
lines changed

NEWS

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,4 +22,7 @@ PHP NEWS
2222
- XMLWriter:
2323
. Improved performance and reduce memory consumption. (nielsdos)
2424

25+
- XSL:
26+
. Implement request #30622 (make $namespace parameter functional). (nielsdos)
27+
2528
<<< NOTE: Insert NEWS from last stable release here prior to actual release! >>>

UPGRADING

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,14 @@ PHP 8.5 UPGRADE NOTES
3737
2. New Features
3838
========================================
3939

40+
- XSL:
41+
. The $namespace argument of XSLTProcessor::getParameter(),
42+
XSLTProcessor::setParameter() and XSLTProcessor::removeParameter()
43+
now actually works instead of being treated as empty.
44+
This only works if the $name argument does not use Clark notation
45+
and is not a QName because in those cases the namespace is taken
46+
from the namespace href or prefix respectively.
47+
4048
========================================
4149
3. Changes in SAPI modules
4250
========================================

ext/xsl/tests/req30622.phpt

Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
--TEST--
2+
Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI)
3+
--EXTENSIONS--
4+
xsl
5+
--CREDITS--
6+
Based on a test by <ishikawa at arielworks dot com>
7+
--FILE--
8+
<?php
9+
10+
$xmlDom = new DOMDocument();
11+
$xmlDom->loadXML('<root/>');
12+
13+
$xslDom = new DOMDocument();
14+
$xslDom->loadXML(<<<'XML'
15+
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
16+
xmlns:test="http://www.php.net/test">
17+
<xsl:param name="foo" select="'EMPTY'"/>
18+
<xsl:param name="test:foo" select="'EMPTY'"/>
19+
20+
<xsl:template match="/root">
21+
<xsl:text>Namespace "NULL": </xsl:text>
22+
<xsl:value-of select="$foo"/>
23+
<xsl:text>, Namespace "http://www.php.net/test": </xsl:text>
24+
<xsl:value-of select="$test:foo"/>
25+
</xsl:template>
26+
</xsl:stylesheet>
27+
XML);
28+
29+
$proc = new XSLTProcessor();
30+
$proc->importStyleSheet($xslDom);
31+
32+
echo "--- Set both empty and non-empty namespace ---\n";
33+
34+
$proc->setParameter("", "foo", "SET1");
35+
$proc->setParameter("http://www.php.net/test", "foo", "SET2");
36+
var_dump($proc->getParameter("", "foo"));
37+
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
38+
39+
print $proc->transformToXML($xmlDom);
40+
41+
echo "--- Remove empty namespace entry ---\n";
42+
43+
var_dump($proc->removeParameter("", "foo"));
44+
var_dump($proc->getParameter("", "foo"));
45+
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
46+
47+
print $proc->transformToXML($xmlDom);
48+
49+
echo "--- Remove non-empty namespace entry ---\n";
50+
51+
var_dump($proc->removeParameter("http://www.php.net/test", "foo"));
52+
var_dump($proc->getParameter("", "foo"));
53+
var_dump($proc->getParameter("http://www.php.net/test", "foo"));
54+
55+
print $proc->transformToXML($xmlDom);
56+
57+
echo "--- Set via array ---\n";
58+
59+
$proc->setParameter("", ["foo" => "SET1"]);
60+
$proc->setParameter("http://www.php.net/test", ["foo" => "SET2"]);
61+
62+
print $proc->transformToXML($xmlDom);
63+
64+
?>
65+
--EXPECT--
66+
--- Set both empty and non-empty namespace ---
67+
string(4) "SET1"
68+
string(4) "SET2"
69+
<?xml version="1.0"?>
70+
Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2
71+
--- Remove empty namespace entry ---
72+
bool(true)
73+
bool(false)
74+
string(4) "SET2"
75+
<?xml version="1.0"?>
76+
Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": SET2
77+
--- Remove non-empty namespace entry ---
78+
bool(true)
79+
bool(false)
80+
bool(false)
81+
<?xml version="1.0"?>
82+
Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": EMPTY
83+
--- Set via array ---
84+
<?xml version="1.0"?>
85+
Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2

ext/xsl/tests/req30622_errors.phpt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
--TEST--
2+
Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI) - error cases
3+
--EXTENSIONS--
4+
xsl
5+
--CREDITS--
6+
Based on a test by <ishikawa at arielworks dot com>
7+
--FILE--
8+
<?php
9+
10+
$proc = new XSLTProcessor();
11+
12+
try {
13+
$proc->setParameter("urn:x", "{urn:a}x", "");
14+
} catch (ValueError $e) {
15+
echo $e->getMessage(), "\n";
16+
}
17+
18+
try {
19+
$proc->setParameter("urn:x", "a:b", "");
20+
} catch (ValueError $e) {
21+
echo $e->getMessage(), "\n";
22+
}
23+
24+
try {
25+
$proc->getParameter("urn:x", "{urn:a}x");
26+
} catch (ValueError $e) {
27+
echo $e->getMessage(), "\n";
28+
}
29+
30+
try {
31+
$proc->getParameter("urn:x", "a:b");
32+
} catch (ValueError $e) {
33+
echo $e->getMessage(), "\n";
34+
}
35+
36+
try {
37+
$proc->removeParameter("urn:x", "{urn:a}x");
38+
} catch (ValueError $e) {
39+
echo $e->getMessage(), "\n";
40+
}
41+
42+
try {
43+
$proc->removeParameter("urn:x", "a:b");
44+
} catch (ValueError $e) {
45+
echo $e->getMessage(), "\n";
46+
}
47+
48+
// Edge cases, should work
49+
$proc->setParameter("urn:x", ":b", "");
50+
$proc->setParameter("urn:x", ":", "");
51+
52+
?>
53+
--EXPECT--
54+
XSLTProcessor::setParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
55+
XSLTProcessor::setParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty
56+
XSLTProcessor::getParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
57+
XSLTProcessor::getParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty
58+
XSLTProcessor::removeParameter(): Argument #2 ($name) must not use clark notation when argument #1 ($namespace) is not empty
59+
XSLTProcessor::removeParameter(): Argument #2 ($name) must not be a QName when argument #1 ($namespace) is not empty

ext/xsl/xsltprocessor.c

Lines changed: 63 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,32 @@ PHP_METHOD(XSLTProcessor, transformToXml)
549549
}
550550
/* }}} end XSLTProcessor::transformToXml */
551551

552+
static zend_string *xsl_create_parameter_key(uint32_t arg_num, const zend_string *namespace, zend_string *name)
553+
{
554+
if (ZSTR_LEN(namespace) == 0) {
555+
return zend_string_copy(name);
556+
}
557+
558+
/* Clark notation already sets the namespace and we cannot have a double namespace declaration. */
559+
if (ZSTR_VAL(name)[0] == '{') {
560+
zend_argument_value_error(arg_num, "must not use clark notation when argument #1 ($namespace) is not empty");
561+
return NULL;
562+
}
563+
564+
/* Cannot be a QName as that would cause a namespace lookup in the document. */
565+
if (ZSTR_VAL(name)[0] != ':' && strchr(ZSTR_VAL(name), ':')) {
566+
zend_argument_value_error(arg_num, "must not be a QName when argument #1 ($namespace) is not empty");
567+
return NULL;
568+
}
569+
570+
zend_string *clark_str = zend_string_safe_alloc(1, ZSTR_LEN(name), 2 + ZSTR_LEN(namespace), false);
571+
ZSTR_VAL(clark_str)[0] = '{';
572+
memcpy(ZSTR_VAL(clark_str) + 1, ZSTR_VAL(namespace), ZSTR_LEN(namespace));
573+
ZSTR_VAL(clark_str)[ZSTR_LEN(namespace) + 1] = '}';
574+
memcpy(ZSTR_VAL(clark_str) + 2 + ZSTR_LEN(namespace), ZSTR_VAL(name), ZSTR_LEN(name) + 1 /* include '\0' */);
575+
return clark_str;
576+
}
577+
552578
/* {{{ */
553579
PHP_METHOD(XSLTProcessor, setParameter)
554580
{
@@ -557,12 +583,10 @@ PHP_METHOD(XSLTProcessor, setParameter)
557583
zval *entry, new_string;
558584
HashTable *array_value;
559585
xsl_object *intern;
560-
char *namespace;
561-
size_t namespace_len;
562-
zend_string *string_key, *name, *value = NULL;
586+
zend_string *namespace, *string_key, *name, *value = NULL;
563587

564588
ZEND_PARSE_PARAMETERS_START(2, 3)
565-
Z_PARAM_STRING(namespace, namespace_len)
589+
Z_PARAM_PATH_STR(namespace)
566590
Z_PARAM_ARRAY_HT_OR_STR(array_value, name)
567591
Z_PARAM_OPTIONAL
568592
Z_PARAM_PATH_STR_OR_NULL(value)
@@ -590,19 +614,27 @@ PHP_METHOD(XSLTProcessor, setParameter)
590614
RETURN_THROWS();
591615
}
592616

617+
zend_string *ht_key = xsl_create_parameter_key(2, namespace, string_key);
618+
if (!ht_key) {
619+
RETURN_THROWS();
620+
}
621+
593622
str = zval_try_get_string(entry);
594623
if (UNEXPECTED(!str)) {
624+
zend_string_release_ex(ht_key, false);
595625
RETURN_THROWS();
596626
}
597627

598628
if (UNEXPECTED(CHECK_NULL_PATH(ZSTR_VAL(str), ZSTR_LEN(str)))) {
599629
zend_string_release(str);
630+
zend_string_release_ex(ht_key, false);
600631
zend_argument_value_error(3, "must not contain values with any null bytes");
601632
RETURN_THROWS();
602633
}
603634

604635
ZVAL_STR(&tmp, str);
605-
zend_hash_update(intern->parameter, string_key, &tmp);
636+
zend_hash_update(intern->parameter, ht_key, &tmp);
637+
zend_string_release_ex(ht_key, false);
606638
} ZEND_HASH_FOREACH_END();
607639
RETURN_TRUE;
608640
} else {
@@ -616,9 +648,15 @@ PHP_METHOD(XSLTProcessor, setParameter)
616648
RETURN_THROWS();
617649
}
618650

651+
zend_string *key = xsl_create_parameter_key(2, namespace, name);
652+
if (!key) {
653+
RETURN_THROWS();
654+
}
655+
619656
ZVAL_STR_COPY(&new_string, value);
620657

621-
zend_hash_update(intern->parameter, name, &new_string);
658+
zend_hash_update(intern->parameter, key, &new_string);
659+
zend_string_release_ex(key, false);
622660
RETURN_TRUE;
623661
}
624662
}
@@ -628,17 +666,21 @@ PHP_METHOD(XSLTProcessor, setParameter)
628666
PHP_METHOD(XSLTProcessor, getParameter)
629667
{
630668
zval *id = ZEND_THIS;
631-
char *namespace;
632-
size_t namespace_len = 0;
633669
zval *value;
634-
zend_string *name;
670+
zend_string *namespace, *name;
635671
xsl_object *intern;
636672

637-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
673+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "PP", &namespace, &name) == FAILURE) {
674+
RETURN_THROWS();
675+
}
676+
zend_string *key = xsl_create_parameter_key(2, namespace, name);
677+
if (!key) {
638678
RETURN_THROWS();
639679
}
640680
intern = Z_XSL_P(id);
641-
if ((value = zend_hash_find(intern->parameter, name)) != NULL) {
681+
value = zend_hash_find(intern->parameter, key);
682+
zend_string_release_ex(key, false);
683+
if (value != NULL) {
642684
RETURN_STR_COPY(Z_STR_P(value));
643685
} else {
644686
RETURN_FALSE;
@@ -650,20 +692,23 @@ PHP_METHOD(XSLTProcessor, getParameter)
650692
PHP_METHOD(XSLTProcessor, removeParameter)
651693
{
652694
zval *id = ZEND_THIS;
653-
size_t namespace_len = 0;
654-
char *namespace;
655-
zend_string *name;
695+
zend_string *namespace, *name;
656696
xsl_object *intern;
657697

658-
if (zend_parse_parameters(ZEND_NUM_ARGS(), "sS", &namespace, &namespace_len, &name) == FAILURE) {
698+
if (zend_parse_parameters(ZEND_NUM_ARGS(), "PP", &namespace, &name) == FAILURE) {
699+
RETURN_THROWS();
700+
}
701+
zend_string *key = xsl_create_parameter_key(2, namespace, name);
702+
if (!key) {
659703
RETURN_THROWS();
660704
}
661705
intern = Z_XSL_P(id);
662-
if (zend_hash_del(intern->parameter, name) == SUCCESS) {
663-
RETURN_TRUE;
706+
if (zend_hash_del(intern->parameter, key) == SUCCESS) {
707+
RETVAL_TRUE;
664708
} else {
665-
RETURN_FALSE;
709+
RETVAL_FALSE;
666710
}
711+
zend_string_release_ex(key, false);
667712
}
668713
/* }}} end XSLTProcessor::removeParameter */
669714

0 commit comments

Comments
 (0)