From b5fe9a162c79395d86d6d92e6ea48aa9bbd53e05 Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Tue, 6 Jan 2026 23:39:48 +0100 Subject: [PATCH 1/2] Add AES-SIV support with optional AAD setting Closes GH-20853 --- NEWS | 1 + ext/openssl/openssl.c | 4 +- ext/openssl/openssl.stub.php | 4 +- ext/openssl/openssl_arginfo.h | 6 +-- ext/openssl/openssl_backend_common.c | 17 ++++++- ext/openssl/php_openssl_backend.h | 1 + ext/openssl/tests/cipher_tests.inc | 53 ++++++++++++++++++++++ ext/openssl/tests/openssl_decrypt_siv.phpt | 43 ++++++++++++++++++ ext/openssl/tests/openssl_encrypt_siv.phpt | 51 +++++++++++++++++++++ 9 files changed, 171 insertions(+), 9 deletions(-) create mode 100644 ext/openssl/tests/openssl_decrypt_siv.phpt create mode 100644 ext/openssl/tests/openssl_encrypt_siv.phpt diff --git a/NEWS b/NEWS index d26148877670..a5393d50bfcc 100644 --- a/NEWS +++ b/NEWS @@ -88,6 +88,7 @@ PHP NEWS preloading). (Arnaud, welcomycozyhom) - OpenSSL: + . Added AES-SIV support. (jordikroon) . Implemented GH-20310 (No critical extension indication in openssl_x509_parse() output). (StephenWall) diff --git a/ext/openssl/openssl.c b/ext/openssl/openssl.c index 381a369d5718..de52191c441a 100644 --- a/ext/openssl/openssl.c +++ b/ext/openssl/openssl.c @@ -4571,7 +4571,7 @@ PHP_FUNCTION(openssl_encrypt) zend_string *ret; zval *tag = NULL; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lszsl", &data, &data_len, &method, &method_len, + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lszs!l", &data, &data_len, &method, &method_len, &password, &password_len, &options, &iv, &iv_len, &tag, &aad, &aad_len, &tag_len) == FAILURE) { RETURN_THROWS(); } @@ -4593,7 +4593,7 @@ PHP_FUNCTION(openssl_decrypt) size_t data_len, method_len, password_len, iv_len = 0, tag_len = 0, aad_len = 0; zend_string *ret; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lss!s", &data, &data_len, &method, &method_len, + if (zend_parse_parameters(ZEND_NUM_ARGS(), "sss|lss!s!", &data, &data_len, &method, &method_len, &password, &password_len, &options, &iv, &iv_len, &tag, &tag_len, &aad, &aad_len) == FAILURE) { RETURN_THROWS(); } diff --git a/ext/openssl/openssl.stub.php b/ext/openssl/openssl.stub.php index 94902a4acf0d..0111cc0cc7bc 100644 --- a/ext/openssl/openssl.stub.php +++ b/ext/openssl/openssl.stub.php @@ -662,9 +662,9 @@ function openssl_digest(string $data, string $digest_algo, bool $binary = false) /** * @param string $tag */ -function openssl_encrypt(#[\SensitiveParameter] string $data, string $cipher_algo, #[\SensitiveParameter] string $passphrase, int $options = 0, string $iv = "", &$tag = null, string $aad = "", int $tag_length = 16): string|false {} +function openssl_encrypt(#[\SensitiveParameter] string $data, string $cipher_algo, #[\SensitiveParameter] string $passphrase, int $options = 0, string $iv = "", &$tag = null, ?string $aad = "", int $tag_length = 16): string|false {} -function openssl_decrypt(string $data, string $cipher_algo, #[\SensitiveParameter] string $passphrase, int $options = 0, string $iv = "", ?string $tag = null, string $aad = ""): string|false {} +function openssl_decrypt(string $data, string $cipher_algo, #[\SensitiveParameter] string $passphrase, int $options = 0, string $iv = "", ?string $tag = null, ?string $aad = ""): string|false {} function openssl_cipher_iv_length(string $cipher_algo): int|false {} diff --git a/ext/openssl/openssl_arginfo.h b/ext/openssl/openssl_arginfo.h index bae435e90241..32002cd81d5a 100644 --- a/ext/openssl/openssl_arginfo.h +++ b/ext/openssl/openssl_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit openssl.stub.php instead. - * Stub hash: 8233a8abc8ab7145d905d0fa51478edfe1e55a06 */ + * Stub hash: a571945d38a3460de017405454b61609811fe1b1 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_openssl_x509_export_to_file, 0, 2, _IS_BOOL, 0) ZEND_ARG_OBJ_TYPE_MASK(0, certificate, OpenSSLCertificate, MAY_BE_STRING, NULL) @@ -337,7 +337,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_encrypt, 0, 3, MAY_BE_ST ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, iv, IS_STRING, 0, "\"\"") ZEND_ARG_INFO_WITH_DEFAULT_VALUE(1, tag, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, aad, IS_STRING, 0, "\"\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, aad, IS_STRING, 1, "\"\"") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, tag_length, IS_LONG, 0, "16") ZEND_END_ARG_INFO() @@ -348,7 +348,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_decrypt, 0, 3, MAY_BE_ST ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_LONG, 0, "0") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, iv, IS_STRING, 0, "\"\"") ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, tag, IS_STRING, 1, "null") - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, aad, IS_STRING, 0, "\"\"") + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, aad, IS_STRING, 1, "\"\"") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_openssl_cipher_iv_length, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) diff --git a/ext/openssl/openssl_backend_common.c b/ext/openssl/openssl_backend_common.c index 6eae715d4321..99193615bec2 100644 --- a/ext/openssl/openssl_backend_common.c +++ b/ext/openssl/openssl_backend_common.c @@ -1650,10 +1650,14 @@ void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EV { int cipher_mode = EVP_CIPHER_mode(cipher_type); memset(mode, 0, sizeof(struct php_openssl_cipher_mode)); + switch (cipher_mode) { case EVP_CIPH_GCM_MODE: case EVP_CIPH_CCM_MODE: - /* We check for EVP_CIPH_OCB_MODE, because LibreSSL does not support it. */ + /* We check for EVP_CIPH_SIV_MODE and EVP_CIPH_SIV_MODE, because LibreSSL does not support it. */ +#ifdef EVP_CIPH_SIV_MODE + case EVP_CIPH_SIV_MODE: +#endif #ifdef EVP_CIPH_OCB_MODE case EVP_CIPH_OCB_MODE: /* For OCB mode, explicitly set the tag length even when decrypting, @@ -1663,6 +1667,7 @@ void php_openssl_load_cipher_mode(struct php_openssl_cipher_mode *mode, const EV php_openssl_set_aead_flags(mode); mode->set_tag_length_when_encrypting = cipher_mode == EVP_CIPH_CCM_MODE; mode->is_single_run_aead = cipher_mode == EVP_CIPH_CCM_MODE; + mode->aad_supports_vector = cipher_mode == EVP_CIPH_SIV_MODE; break; #ifdef NID_chacha20_poly1305 default: @@ -1804,13 +1809,21 @@ zend_result php_openssl_cipher_update(const EVP_CIPHER *cipher_type, { int i = 0; + /* For AEAD modes that do not support vector AAD, treat NULL AAD as zero-length AAD */ + if (!mode->aad_supports_vector && aad == NULL) { + aad_len = 0; + aad = ""; + } + if (mode->is_single_run_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, NULL, (int)data_len)) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Setting of data length failed"); return FAILURE; } - if (mode->is_aead && !EVP_CipherUpdate(cipher_ctx, NULL, &i, (const unsigned char *) aad, (int) aad_len)) { + /* Only pass AAD to OpenSSL if caller provided it. + This makes NULL mean zero AAD items, while "" with len 0 means one empty AAD item. */ + if (mode->is_aead && aad != NULL && !EVP_CipherUpdate(cipher_ctx, NULL, &i, (const unsigned char *)aad, (int)aad_len)) { php_openssl_store_errors(); php_error_docref(NULL, E_WARNING, "Setting of additional application data failed"); return FAILURE; diff --git a/ext/openssl/php_openssl_backend.h b/ext/openssl/php_openssl_backend.h index 79082f4eca22..2d37e594ea57 100644 --- a/ext/openssl/php_openssl_backend.h +++ b/ext/openssl/php_openssl_backend.h @@ -346,6 +346,7 @@ struct php_openssl_cipher_mode { bool is_single_run_aead; bool set_tag_length_always; bool set_tag_length_when_encrypting; + bool aad_supports_vector; int aead_get_tag_flag; int aead_set_tag_flag; int aead_ivlen_flag; diff --git a/ext/openssl/tests/cipher_tests.inc b/ext/openssl/tests/cipher_tests.inc index f543a42c9f45..9d17e847bfa2 100644 --- a/ext/openssl/tests/cipher_tests.inc +++ b/ext/openssl/tests/cipher_tests.inc @@ -160,6 +160,59 @@ $php_openssl_cipher_tests = array( 'ct' => '1792A4E31E0755FB03E31B22116E6C2DDF9EFD6E33D536F1A0124B0A55BAE884ED93481529C76B6A', ), ), + 'aes-128-siv' => array( + array( + 'key' => 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0' . + '0f0e0d0c0b0a09080706050403020100', + 'iv' => '', + 'aad' => '', + 'tag' => 'baba5b99dfc42fa9810fb2eb71ac2e9c', + 'pt' => 'b1677d933fa706f7ef349f9dd569c028' . + '279a5e2219728e77cfe916d5db979942' . + '5d8fb93b0e26dbc85ed14c050dc9f054' . + 'd9153c2be1e9b99ae7a109aba1e5a7f1' . + 'f2131786da90fe998d3571c144d066c3', + 'ct' => '91416054151e844965ad20a2057e2baa' . + '0e785269b152ba9d4dc834777e0d5376' . + 'db611856ae0d5d826f446c8eef47acb4' . + '83dccb37da9481648a4907fd3d65335b' . + 'd9585361c0c1834ac2b975f3238ea7c6', + ), + array( + 'key' => 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0' . + '0f0e0d0c0b0a09080706050403020100', + 'iv' => '', + 'aad' => null, + 'tag' => '606ac96568128a278b02e3e04de97b7e', + 'pt' => 'ea597a2f9fb0b5c4d5a6f215047b58a3' . + '3d2c885bf67cbb09239239f5aecafd6f' . + 'd2401391154b024b05cd938b40fdc749' . + 'ebccb3f48a3156c0bad69cfc5035360d' . + '21ad626dc866cc539f2d0e34b6824fc3', + 'ct' => '9c75fa0345b35e2d6cbcc91ed3fc7feb' . + '84fea50c35766db0c847fb627385107b' . + '4f257548d8b80ccd04261fa651fb89cc' . + 'e6815ecf0c8c4586ce68544ddce4c3af' . + '01e9587282256569194b1dca788fd987', + ), + array( + 'key' => 'fffefdfcfbfaf9f8f7f6f5f4f3f2f1f0' . + '0f0e0d0c0b0a09080706050403020100', + 'iv' => '', + 'aad' => 'c0ef488e684e6fc95e0bd1da59861259', + 'tag' => 'a24cd6dcc0791bd7719a7f4fcb16de81', + 'pt' => 'b1677d933fa706f7ef349f9dd569c028' . + '279a5e2219728e77cfe916d5db979942' . + '5d8fb93b0e26dbc85ed14c050dc9f054' . + 'd9153c2be1e9b99ae7a109aba1e5a7f1' . + 'f2131786da90fe998d3571c144d066c3', + 'ct' => 'ea597a2f9fb0b5c4d5a6f215047b58a3' . + '3d2c885bf67cbb09239239f5aecafd6f' . + 'd2401391154b024b05cd938b40fdc749' . + 'ebccb3f48a3156c0bad69cfc5035360d' . + '21ad626dc866cc539f2d0e34b6824fc3', + ), + ), 'chacha20-poly1305' => array( array( 'key' => '808182838485868788898a8b8c8d8e8f' . diff --git a/ext/openssl/tests/openssl_decrypt_siv.phpt b/ext/openssl/tests/openssl_decrypt_siv.phpt new file mode 100644 index 000000000000..6fc299546e20 --- /dev/null +++ b/ext/openssl/tests/openssl_decrypt_siv.phpt @@ -0,0 +1,43 @@ +--TEST-- +openssl_decrypt() with SIV cipher algorithm tests +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + $test) { + echo "TEST $idx\n"; + $pt = openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $test['tag'], $test['aad']); + var_dump($test['pt'] === $pt); +} + +// failed because no AAD +echo "TEST AAD\n"; +var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $test['tag'])); +// failed because wrong tag +echo "TEST WRONGTAG\n"; +var_dump(openssl_decrypt($test['ct'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], str_repeat('x', 16), $test['aad'])); + +?> +--EXPECTF-- +TEST 0 +bool(true) +TEST 1 +bool(true) +TEST 2 +bool(true) +TEST AAD +bool(false) +TEST WRONGTAG +bool(false) diff --git a/ext/openssl/tests/openssl_encrypt_siv.phpt b/ext/openssl/tests/openssl_encrypt_siv.phpt new file mode 100644 index 000000000000..43a5c5b34b5a --- /dev/null +++ b/ext/openssl/tests/openssl_encrypt_siv.phpt @@ -0,0 +1,51 @@ +--TEST-- +openssl_encrypt() with SIV cipher algorithm tests +--EXTENSIONS-- +openssl +--SKIPIF-- + +--FILE-- + $test) { + echo "TEST $idx\n"; + $ct = openssl_encrypt($test['pt'], $method, $test['key'], OPENSSL_RAW_DATA, + $test['iv'], $tag, $test['aad'], strlen($test['tag'])); + var_dump($test['ct'] === $ct); + var_dump($test['tag'] === $tag); +} + +// Empty tag should not be equivalent to null tag +echo "TEST AAD\n"; +var_dump(openssl_encrypt('data', $method, 'password', 0, '', $tag, '') !== openssl_encrypt('data', $method, 'password', 0, '', $tag, null)); + +// Failing to retrieve tag (max is 16 bytes) +var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 32), $tag, '', 20)); + +// Failing when no tag supplied +var_dump(openssl_encrypt('data', $method, 'password', 0, str_repeat('x', 32))); +?> +--EXPECTF-- +TEST 0 +bool(true) +bool(true) +TEST 1 +bool(true) +bool(true) +TEST 2 +bool(true) +bool(true) +TEST AAD +bool(true) + +Warning: openssl_encrypt(): Retrieving verification tag failed in %s on line %d +bool(false) + +Warning: openssl_encrypt(): A tag should be provided when using AEAD mode in %s on line %d +bool(false) From 9498bc3ee131616344370f59d4f1a6bb46375750 Mon Sep 17 00:00:00 2001 From: Jordi Kroon Date: Fri, 24 Apr 2026 20:37:54 +0200 Subject: [PATCH 2/2] fix Dom\Notation nodes missing tree connection Fixes an existing TODO by @ndossche. Notation nodes returned from $doctype->notations were not linked to their owning document or parent DocumentType. This caused several incorrect behaviour; including: ownerDocument was missing parentNode was NULL isConnected returned false baseURI fell back to "about:blank" textContent returned an empty string instead of NULL The last point is a violation of the DOM specification. Since Notation is not an Element, CharacterData, Attr, or DocumentFragment, its textContent must return NULL. Spec reference: https://dom.spec.whatwg.org/#dom-node-textcontent close GH-21868 --- NEWS | 4 + ext/dom/dom_iterators.c | 15 ++- ext/dom/obj_map.c | 6 +- ext/dom/php_dom.c | 8 +- ext/dom/php_dom.h | 3 +- ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt | 13 +-- .../xml/XMLDocument_node_notation_wiring.phpt | 101 ++++++++++++++++++ 7 files changed, 139 insertions(+), 11 deletions(-) create mode 100644 ext/dom/tests/modern/xml/XMLDocument_node_notation_wiring.phpt diff --git a/NEWS b/NEWS index a5393d50bfcc..5be5ffd18ac4 100644 --- a/NEWS +++ b/NEWS @@ -33,6 +33,10 @@ PHP NEWS correctly, and external writes raise "Cannot modify private(set) property" instead of the previous readonly modification error. (David Carlier) + . Fixed Dom\Notation nodes missing tree connection, so that + ownerDocument, parentNode, isConnected and baseURI now return correct + values, and textContent returns NULL per the DOM specification. + (jordikroon) - Fileinfo: . Fixed bug GH-20679 (finfo_file() doesn't work on remote resources). diff --git a/ext/dom/dom_iterators.c b/ext/dom/dom_iterators.c index b71d188dceee..f97a9ec825d5 100644 --- a/ext/dom/dom_iterators.c +++ b/ext/dom/dom_iterators.c @@ -54,7 +54,7 @@ static dom_nnodemap_object *php_dom_iterator_get_nnmap(const php_dom_iterator *i return nnmap->ptr; } -xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID) /* {{{ */ +xmlNodePtr create_notation(xmlDtdPtr parent_dtd, const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID) /* {{{ */ { xmlEntityPtr ret = xmlMalloc(sizeof(xmlEntity)); memset(ret, 0, sizeof(xmlEntity)); @@ -62,10 +62,23 @@ xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const ret->name = xmlStrdup(name); ret->ExternalID = xmlStrdup(ExternalID); ret->SystemID = xmlStrdup(SystemID); + if (parent_dtd != NULL) { + ret->parent = parent_dtd; + ret->doc = parent_dtd->doc; + } return (xmlNodePtr) ret; } /* }}} */ +void dom_free_notation(xmlEntityPtr entity) /* {{{ */ +{ + xmlFree((xmlChar *) entity->name); + xmlFree((xmlChar *) entity->ExternalID); + xmlFree((xmlChar *) entity->SystemID); + xmlFree(entity); +} +/* }}} */ + xmlNodePtr php_dom_libxml_hash_iter(xmlHashTable *ht, int index) { int htsize; diff --git a/ext/dom/obj_map.c b/ext/dom/obj_map.c index 60f8b28e6cad..eeef8345fc63 100644 --- a/ext/dom/obj_map.c +++ b/ext/dom/obj_map.c @@ -176,7 +176,8 @@ static void dom_map_get_notation_item(dom_nnodemap_object *map, zend_long index, xmlNodePtr node = map->ht ? php_dom_libxml_hash_iter(map->ht, index) : NULL; if (node) { xmlNotation *notation = (xmlNotation *) node; - node = create_notation(notation->name, notation->PublicID, notation->SystemID); + xmlDtdPtr dtd = (xmlDtdPtr) dom_object_get_node(map->baseobj); + node = create_notation(dtd, notation->name, notation->PublicID, notation->SystemID); } dom_ret_node_to_zobj(map, node, return_value); } @@ -504,7 +505,8 @@ static xmlNodePtr dom_map_get_ns_named_item_notation(dom_nnodemap_object *map, c { xmlNotationPtr notation = xmlHashLookup(map->ht, BAD_CAST ZSTR_VAL(named)); if (notation) { - return create_notation(notation->name, notation->PublicID, notation->SystemID); + xmlDtdPtr dtd = (xmlDtdPtr) dom_object_get_node(map->baseobj); + return create_notation(dtd, notation->name, notation->PublicID, notation->SystemID); } return NULL; } diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index b188dea6eb49..ec1a2ea1c6cf 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -1486,7 +1486,13 @@ void dom_objects_free_storage(zend_object *object) if (ptr != NULL && ptr->node != NULL) { xmlNodePtr node = ptr->node; - if (node->type != XML_DOCUMENT_NODE && node->type != XML_HTML_DOCUMENT_NODE) { + if (node->type == XML_NOTATION_NODE) { + unsigned int refcount = php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); + php_libxml_decrement_doc_ref((php_libxml_node_object *) intern); + if (refcount == 0) { + dom_free_notation((xmlEntityPtr) node); + } + } else if (node->type != XML_DOCUMENT_NODE && node->type != XML_HTML_DOCUMENT_NODE) { php_libxml_node_decrement_resource((php_libxml_node_object *) intern); } else { php_libxml_decrement_node_ptr((php_libxml_node_object *) intern); diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index bc414adaa2f9..d424b26cc694 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -125,7 +125,8 @@ int dom_hierarchy(xmlNodePtr parent, xmlNodePtr child); bool dom_has_feature(zend_string *feature, zend_string *version); bool dom_node_is_read_only(const xmlNode *node); bool dom_node_children_valid(const xmlNode *node); -xmlNodePtr create_notation(const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID); +xmlNodePtr create_notation(xmlDtdPtr parent_dtd, const xmlChar *name, const xmlChar *ExternalID, const xmlChar *SystemID); +void dom_free_notation(xmlEntityPtr entity); xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index); zend_object_iterator *php_dom_get_iterator(zend_class_entry *ce, zval *object, int by_ref); void dom_set_doc_classmap(php_libxml_ref_obj *document, zend_class_entry *basece, zend_class_entry *ce); diff --git a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt index f9bb1f7a996e..4ac15a029e37 100644 --- a/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt +++ b/ext/dom/tests/modern/xml/DTDNamedNodeMap.phpt @@ -21,7 +21,6 @@ var_dump($doctype); var_dump($doctype->entities["test"]); var_dump($doctype->entities["myimage"]); -// TODO: isConnected returning false is a bug var_dump($doctype->notations["GIF"]); ?> @@ -142,17 +141,19 @@ object(Dom\Entity)#3 (17) { ["textContent"]=> NULL } -object(Dom\Notation)#4 (13) { +object(Dom\Notation)#4 (14) { ["nodeType"]=> int(12) ["nodeName"]=> string(3) "GIF" ["baseURI"]=> - string(11) "about:blank" + string(%d) "%s" ["isConnected"]=> - bool(false) + bool(true) + ["ownerDocument"]=> + string(22) "(object value omitted)" ["parentNode"]=> - NULL + string(22) "(object value omitted)" ["parentElement"]=> NULL ["childNodes"]=> @@ -168,5 +169,5 @@ object(Dom\Notation)#4 (13) { ["nodeValue"]=> NULL ["textContent"]=> - string(0) "" + NULL } diff --git a/ext/dom/tests/modern/xml/XMLDocument_node_notation_wiring.phpt b/ext/dom/tests/modern/xml/XMLDocument_node_notation_wiring.phpt new file mode 100644 index 000000000000..dd74a2737594 --- /dev/null +++ b/ext/dom/tests/modern/xml/XMLDocument_node_notation_wiring.phpt @@ -0,0 +1,101 @@ +--TEST-- +Dom\XMLDocument: Dom\Notation nodes are connected to their document and doctype +--EXTENSIONS-- +dom +--FILE-- + '', + 'JPEG' => '', + 'HTML' => '', +]; + +foreach ($cases as $name => $declaration) { + $xml = << + +XML; + + $dom = Dom\XMLDocument::createFromString($xml); + $doctype = $dom->doctype; + $notations = $doctype->notations; + + echo "=== $name ===\n"; + + $namedNotation = $notations->getNamedItem($name); + foreach ($notations as $iteratedNotation) { + // getNamedItem + var_dump($namedNotation->nodeName); + var_dump($namedNotation->textContent); + var_dump($namedNotation->nodeValue); + var_dump($namedNotation->isConnected); + var_dump($namedNotation->ownerDocument === $dom); + var_dump($namedNotation->parentNode === $doctype); + var_dump($namedNotation->parentElement); + + // iteration + var_dump($iteratedNotation->nodeName); + var_dump($iteratedNotation->textContent); + var_dump($iteratedNotation->nodeValue); + var_dump($iteratedNotation->isConnected); + var_dump($iteratedNotation->ownerDocument === $dom); + var_dump($iteratedNotation->parentNode === $doctype); + var_dump($iteratedNotation->parentElement); + + // wiring + // getNamedItem and iteration each allocate a fresh Notation instance + var_dump($namedNotation !== $iteratedNotation); + } +} +?> +--EXPECT-- +=== GIF === +string(3) "GIF" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +string(3) "GIF" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +bool(true) +=== JPEG === +string(4) "JPEG" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +string(4) "JPEG" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +bool(true) +=== HTML === +string(4) "HTML" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +string(4) "HTML" +NULL +NULL +bool(true) +bool(true) +bool(true) +NULL +bool(true)