diff --git a/NEWS b/NEWS index 76505f350fdf9..afff740696b83 100644 --- a/NEWS +++ b/NEWS @@ -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! >>> diff --git a/UPGRADING b/UPGRADING index 649c252492c5f..c356d37fc511e 100644 --- a/UPGRADING +++ b/UPGRADING @@ -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 ======================================== diff --git a/ext/xsl/tests/req30622.phpt b/ext/xsl/tests/req30622.phpt new file mode 100644 index 0000000000000..e5e0244fc38fd --- /dev/null +++ b/ext/xsl/tests/req30622.phpt @@ -0,0 +1,85 @@ +--TEST-- +Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI) +--EXTENSIONS-- +xsl +--CREDITS-- +Based on a test by +--FILE-- +loadXML(''); + +$xslDom = new DOMDocument(); +$xslDom->loadXML(<<<'XML' + + + + + + Namespace "NULL": + + , Namespace "http://www.php.net/test": + + + +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" + +Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2 +--- Remove empty namespace entry --- +bool(true) +bool(false) +string(4) "SET2" + +Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": SET2 +--- Remove non-empty namespace entry --- +bool(true) +bool(false) +bool(false) + +Namespace "NULL": EMPTY, Namespace "http://www.php.net/test": EMPTY +--- Set via array --- + +Namespace "NULL": SET1, Namespace "http://www.php.net/test": SET2 diff --git a/ext/xsl/tests/req30622_errors.phpt b/ext/xsl/tests/req30622_errors.phpt new file mode 100644 index 0000000000000..75ec592305255 --- /dev/null +++ b/ext/xsl/tests/req30622_errors.phpt @@ -0,0 +1,59 @@ +--TEST-- +Request #30622 (XSLT: xsltProcessor->setParameter() cannot set namespace URI) - error cases +--EXTENSIONS-- +xsl +--CREDITS-- +Based on a test by +--FILE-- +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 diff --git a/ext/xsl/xsltprocessor.c b/ext/xsl/xsltprocessor.c index eaefdb2f8ef7c..ea0f9232aced4 100644 --- a/ext/xsl/xsltprocessor.c +++ b/ext/xsl/xsltprocessor.c @@ -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 */