diff --git a/.github/actions/setup-x64/action.yml b/.github/actions/setup-x64/action.yml index bb014bfe9de11..6cec51d4c8079 100644 --- a/.github/actions/setup-x64/action.yml +++ b/.github/actions/setup-x64/action.yml @@ -17,7 +17,7 @@ runs: docker exec sql1 /opt/mssql-tools/bin/sqlcmd -S 127.0.0.1 -U SA -P "" -Q "create login pdo_test with password='password', check_policy=off; create user pdo_test for login pdo_test; grant alter, control to pdo_test;" sudo locale-gen de_DE - ./.github/scripts/setup-slapd.sh &>/dev/null + ./.github/scripts/setup-slapd.sh sudo cp ext/snmp/tests/snmpd.conf /etc/snmp sudo cp ext/snmp/tests/bigtest /etc/snmp diff --git a/NEWS b/NEWS index 14ac06359aef7..d6dca61c18b0b 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,82 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.2.7 +06 Jul 2023, PHP 8.2.8 + +- CLI: + . Fixed bug GH-11246 (cli/get_set_process_title fails on MacOS). + (James Lucas) + +- Core: + . Fixed build for the riscv64 architecture/GCC 12. (Daniil Gentili) + +- Curl: + . Fixed bug GH-11433 (Unable to set CURLOPT_ACCEPT_ENCODING to NULL). + (nielsdos) + +- Date: + . Fixed bug GH-11455 (Segmentation fault with custom object date properties). + (nielsdos) + +- DOM: + . Fixed bugs GH-11288 and GH-11289 and GH-11290 and GH-9142 (DOMExceptions + and segfaults with replaceWith). (nielsdos) + . Fixed bug GH-10234 (Setting DOMAttr::textContent results in an empty + attribute value). (nielsdos) + . Fix return value in stub file for DOMNodeList::item. (divinity76) + . Fix spec compliance error with '*' namespace for + DOMDocument::getElementsByTagNameNS. (nielsdos) + . Fix DOMElement::append() and DOMElement::prepend() hierarchy checks. + (nielsdos) + . Fixed bug GH-11347 (Memory leak when calling a static method inside an + xpath query). (nielsdos) + . Fixed bug #67440 (append_node of a DOMDocumentFragment does not reconcile + namespaces). (nielsdos) + . Fixed bug #81642 (DOMChildNode::replaceWith() bug when replacing a node + with itself). (nielsdos) + . Fixed bug #77686 (Removed elements are still returned by getElementById). + (nielsdos) + . Fixed bug #70359 (print_r() on DOMAttr causes Segfault in + php_libxml_node_free_list()). (nielsdos) + . Fixed bug #78577 (Crash in DOMNameSpace debug info handlers). (nielsdos) + . Fix lifetime issue with getAttributeNodeNS(). (nielsdos) + . Fix "invalid state error" with cloned namespace declarations. (nielsdos) + . Fixed bug #55294 and #47530 and #47847 (various namespace reconciliation + issues). (nielsdos) + . Fixed bug #80332 (Completely broken array access functionality with + DOMNamedNodeMap). (nielsdos) + +- Opcache: + . Fix allocation loop in zend_shared_alloc_startup(). (nielsdos) + . Access violation on smm_shared_globals with ALLOC_FALLBACK. (KoudelkaB) + . Fixed bug GH-11336 (php still tries to unlock the shared memory ZendSem + with opcache.file_cache_only=1 but it was never locked). (nielsdos) + +- OpenSSL: + . Fixed bug GH-9356 Incomplete validation of IPv6 Address fields in + subjectAltNames (James Lucas, Jakub Zelenka). + +- PCRE: + . Fix preg_replace_callback_array() pattern validation. (ilutov) + +- PGSQL: + . Fixed intermittent segfault with pg_trace. (David Carlier) + +- Phar: + . Fix cross-compilation check in phar generation for FreeBSD. (peter279k) + +- SPL: + . Fixed bug GH-11338 (SplFileInfo empty getBasename with more than one + slash). (nielsdos) + +- Standard: + . Fix access on NULL pointer in array_merge_recursive(). (ilutov) + . Fix exception handling in array_multisort(). (ilutov) + +- SQLite3: + . Fixed bug GH-11451 (Invalid associative array containing duplicate + keys). (nielsdos) + +08 Jun 2023, PHP 8.2.7 - Core: . Fixed bug GH-11152 (Unable to alias namespaces containing reserved class @@ -58,6 +134,8 @@ PHP NEWS done). (peter279k) - Soap: + . Fixed bug GHSA-76gg-c692-v2mw (Missing error check and insufficient random + bytes in HTTP Digest authentication for SOAP). (nielsdos, timwolla) . Fixed bug GH-8426 (make test fail while soap extension build). (nielsdos) - SPL: diff --git a/Zend/tests/array_merge_recursive_next_key_overflow.phpt b/Zend/tests/array_merge_recursive_next_key_overflow.phpt new file mode 100644 index 0000000000000..f7d2872957837 --- /dev/null +++ b/Zend/tests/array_merge_recursive_next_key_overflow.phpt @@ -0,0 +1,25 @@ +--TEST-- +Access on NULL pointer in array_merge_recursive() +--FILE-- + [PHP_INT_MAX => null]], + ['' => [null]], + ); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} + +try { + array_merge_recursive( + ['foo' => [PHP_INT_MAX => null]], + ['foo' => str_repeat('a', 2)], + ); +} catch (Throwable $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Cannot add element to the array as the next element is already occupied +Cannot add element to the array as the next element is already occupied diff --git a/Zend/tests/array_multisort_exception.phpt b/Zend/tests/array_multisort_exception.phpt new file mode 100644 index 0000000000000..8ee6007745e03 --- /dev/null +++ b/Zend/tests/array_multisort_exception.phpt @@ -0,0 +1,13 @@ +--TEST-- +Exception handling in array_multisort() +--FILE-- + new DateTime(), 0 => new DateTime()]; +array_multisort($array, SORT_STRING); +?> +--EXPECTF-- +Fatal error: Uncaught Error: Object of class DateTime could not be converted to string in %s:%d +Stack trace: +#0 %s(%d): array_multisort(Array, 2) +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index cd10119651ba8..5c6d42b42eb85 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.2.7-dev" +#define ZEND_VERSION "4.2.8" #define ZEND_ENGINE_3 diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 6aca553c62617..89610f84b5efc 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -2194,7 +2194,7 @@ static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_use_scalar_as_array(v zend_throw_error(NULL, "Cannot use a scalar value as an array"); } -static zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void) +ZEND_API zend_never_inline ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void) { zend_throw_error(NULL, "Cannot add element to the array as the next element is already occupied"); } diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index 70449f5bf62bd..2a61f4bff711e 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -76,6 +76,8 @@ ZEND_API ZEND_COLD void zend_wrong_string_offset_error(void); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_modification_error(zend_property_info *info); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_readonly_property_indirect_modification_error(zend_property_info *info); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_cannot_add_element(void); + ZEND_API bool zend_verify_scalar_type_hint(uint32_t type_mask, zval *arg, bool strict, bool is_internal_arg); ZEND_API ZEND_COLD void zend_verify_arg_error( const zend_function *zf, const zend_arg_info *arg_info, uint32_t arg_num, zval *value); diff --git a/Zend/zend_observer.c b/Zend/zend_observer.c index 79929bfdd80e5..2cb4db914758a 100644 --- a/Zend/zend_observer.c +++ b/Zend/zend_observer.c @@ -158,9 +158,8 @@ static bool zend_observer_remove_handler(void **first_handler, void *old_handler } else { if (cur_handler != last_handler) { memmove(cur_handler, cur_handler + 1, sizeof(cur_handler) * (last_handler - cur_handler)); - } else { - *last_handler = NULL; } + *last_handler = NULL; } return true; } @@ -196,7 +195,7 @@ ZEND_API void zend_observer_add_end_handler(zend_function *function, zend_observ if (*end_handler != ZEND_OBSERVER_NOT_OBSERVED) { // there's no space for new handlers, then it's forbidden to call this function ZEND_ASSERT(end_handler[registered_observers - 1] == NULL); - memmove(end_handler + 1, end_handler, registered_observers - 1); + memmove(end_handler + 1, end_handler, sizeof(end_handler) * (registered_observers - 1)); } *end_handler = end; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index a634bc7431f33..07d24170ae71c 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4541,6 +4541,8 @@ ZEND_VM_HANDLER(161, ZEND_GENERATOR_RETURN, CONST|TMP|VAR|CV, ANY, SPEC(OBSERVER ZEND_OBSERVER_FCALL_END(generator->execute_data, &generator->retval); + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); @@ -7899,6 +7901,7 @@ ZEND_VM_HELPER(zend_dispatch_try_catch_finally_helper, ANY, ANY, uint32_t try_ca cleanup_live_vars(execute_data, op_num, 0); if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) { zend_generator *generator = zend_get_running_generator(EXECUTE_DATA_C); + EG(current_execute_data) = EX(prev_execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { @@ -7992,6 +7995,7 @@ ZEND_VM_HANDLER(150, ZEND_USER_OPCODE, ANY, ANY) case ZEND_USER_OPCODE_RETURN: if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) { zend_generator *generator = zend_get_running_generator(EXECUTE_DATA_C); + EG(current_execute_data) = EX(prev_execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index a3faccf985d39..b2ab2013a2401 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -3151,6 +3151,7 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_dispatch_try cleanup_live_vars(execute_data, op_num, 0); if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) { zend_generator *generator = zend_get_running_generator(EXECUTE_DATA_C); + EG(current_execute_data) = EX(prev_execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { @@ -3244,6 +3245,7 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_USER_OPCODE_SPEC_HANDLER(ZEND_ case ZEND_USER_OPCODE_RETURN: if (UNEXPECTED((EX_CALL_INFO() & ZEND_CALL_GENERATOR) != 0)) { zend_generator *generator = zend_get_running_generator(EXECUTE_DATA_C); + EG(current_execute_data) = EX(prev_execute_data); zend_generator_close(generator, 1); ZEND_VM_RETURN(); } else { @@ -4573,6 +4575,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_CONST_HA } } + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); @@ -4618,6 +4622,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_OBSERVER zend_observer_fcall_end(generator->execute_data, &generator->retval); + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); @@ -19071,6 +19077,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_TMP_HAND } } + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); @@ -21725,6 +21733,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_VAR_HAND } } + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); @@ -38692,6 +38702,8 @@ static ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_GENERATOR_RETURN_SPEC_CV_HANDL } } + EG(current_execute_data) = EX(prev_execute_data); + /* Close the generator to free up resources */ zend_generator_close(generator, 1); diff --git a/configure.ac b/configure.ac index e95a37fd428e0..b4bb044fae741 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.2.7-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.2.8],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER @@ -371,6 +371,16 @@ if test "$ac_cv_func_dlopen" = "yes"; then fi AC_CHECK_LIB(m, sin) +case $host_alias in + riscv64*) + AC_CHECK_LIB(atomic, __atomic_exchange_1, [ + PHP_ADD_LIBRARY(atomic) + ], [ + AC_MSG_ERROR([Problem with enabling atomic. Please check config.log for details.]) + ]) + ;; +esac + dnl Check for inet_aton in -lc, -lbind and -lresolv. PHP_CHECK_FUNC(inet_aton, resolv, bind) diff --git a/ext/curl/interface.c b/ext/curl/interface.c index 2dd4a7bf657c1..707f4e0a6fc4e 100644 --- a/ext/curl/interface.c +++ b/ext/curl/interface.c @@ -1835,7 +1835,6 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue case CURLOPT_TLSAUTH_TYPE: case CURLOPT_TLSAUTH_PASSWORD: case CURLOPT_TLSAUTH_USERNAME: - case CURLOPT_ACCEPT_ENCODING: case CURLOPT_TRANSFER_ENCODING: case CURLOPT_DNS_SERVERS: case CURLOPT_MAIL_AUTH: @@ -1910,6 +1909,7 @@ static zend_result _php_curl_setopt(php_curl *ch, zend_long option, zval *zvalue case CURLOPT_RANGE: case CURLOPT_FTP_ACCOUNT: case CURLOPT_RTSP_SESSION_ID: + case CURLOPT_ACCEPT_ENCODING: #if LIBCURL_VERSION_NUM >= 0x072100 /* Available since 7.33.0 */ case CURLOPT_DNS_INTERFACE: case CURLOPT_DNS_LOCAL_IP4: diff --git a/ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt b/ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt new file mode 100644 index 0000000000000..c170308c2e981 --- /dev/null +++ b/ext/curl/tests/curl_setopt_CURLOPT_ACCEPT_ENCODING.phpt @@ -0,0 +1,38 @@ +--TEST-- +Test curl_setopt() with CURLOPT_ACCEPT_ENCODING +--EXTENSIONS-- +curl +--FILE-- + +--EXPECTF-- +GET /get.inc?test= HTTP/1.1 +Host: %s +Accept: */* +Accept-Encoding: gzip + +GET /get.inc?test= HTTP/1.1 +Host: %s +Accept: */* diff --git a/ext/date/php_date.c b/ext/date/php_date.c index 2c622f66904a0..91420a94bd8ab 100644 --- a/ext/date/php_date.c +++ b/ext/date/php_date.c @@ -2305,7 +2305,9 @@ static void add_common_properties(HashTable *myht, zend_object *zobj) common = zend_std_get_properties(zobj); ZEND_HASH_MAP_FOREACH_STR_KEY_VAL_IND(common, name, prop) { - zend_hash_add(myht, name, prop); + if (zend_hash_add(myht, name, prop) != NULL) { + Z_TRY_ADDREF_P(prop); + } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/date/tests/gh11455.phpt b/ext/date/tests/gh11455.phpt new file mode 100644 index 0000000000000..cb6784f0b5a71 --- /dev/null +++ b/ext/date/tests/gh11455.phpt @@ -0,0 +1,39 @@ +--TEST-- +Bug GH-11455 (PHP 8.2 Segmentation fault on nesbot/carbon) +--FILE-- +myProperty->field = str_repeat("hello", 3); +$serialized = serialize($datetime); +var_dump($datetime->myProperty); +$unserialized = unserialize($serialized); +var_dump($unserialized); +?> +--EXPECT-- +object(stdClass)#2 (1) { + ["field"]=> + string(15) "hellohellohello" +} +object(MyDateTimeImmutable)#3 (4) { + ["myProperty"]=> + object(stdClass)#4 (1) { + ["field"]=> + string(15) "hellohellohello" + } + ["date"]=> + string(26) "2022-12-22 11:26:00.000000" + ["timezone_type"]=> + int(2) + ["timezone"]=> + string(1) "Z" +} diff --git a/ext/dom/document.c b/ext/dom/document.c index c60198a3be110..93091df83a04f 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1008,6 +1008,19 @@ PHP_METHOD(DOMDocument, getElementsByTagNameNS) } /* }}} end dom_document_get_elements_by_tag_name_ns */ +static bool php_dom_is_node_attached(const xmlNode *node) +{ + ZEND_ASSERT(node != NULL); + node = node->parent; + while (node != NULL) { + if (node->type == XML_DOCUMENT_NODE || node->type == XML_HTML_DOCUMENT_NODE) { + return true; + } + node = node->parent; + } + return false; +} + /* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-getElBId Since: DOM Level 2 */ @@ -1030,7 +1043,13 @@ PHP_METHOD(DOMDocument, getElementById) attrp = xmlGetID(docp, (xmlChar *) idname); - if (attrp && attrp->parent) { + /* From the moment an ID is created, libxml2's behaviour is to cache that element, even + * if that element is not yet attached to the document. Similarly, only upon destruction of + * the element the ID is actually removed by libxml2. Since libxml2 has such behaviour deeply + * ingrained in the library, and uses the cache for various purposes, it seems like a bad + * idea and lost cause to fight it. Instead, we'll simply walk the tree upwards to check + * if the node is attached to the document. */ + if (attrp && attrp->parent && php_dom_is_node_attached(attrp->parent)) { DOM_RET_OBJ((xmlNodePtr) attrp->parent, &ret, intern); } else { RETVAL_NULL(); diff --git a/ext/dom/element.c b/ext/dom/element.c index 19cef5834657a..44c576a07363f 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -150,6 +150,7 @@ int dom_element_schema_type_info_read(dom_object *obj, zval *retval) /* }}} */ +/* Note: the object returned is not necessarily a node, but can be an attribute or a namespace declaration. */ static xmlNodePtr dom_get_dom1_attribute(xmlNodePtr elem, xmlChar *name) /* {{{ */ { int len; @@ -376,25 +377,13 @@ PHP_METHOD(DOMElement, getAttributeNode) } if (attrp->type == XML_NAMESPACE_DECL) { - xmlNsPtr curns; - xmlNodePtr nsparent; - - nsparent = attrp->_private; - curns = xmlNewNs(NULL, attrp->name, NULL); - if (attrp->children) { - curns->prefix = xmlStrdup((xmlChar *) attrp->children); - } - if (attrp->children) { - attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *) attrp->children, attrp->name); - } else { - attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *)"xmlns", attrp->name); - } - attrp->type = XML_NAMESPACE_DECL; - attrp->parent = nsparent; - attrp->ns = curns; + xmlNsPtr original = (xmlNsPtr) attrp; + /* Keep parent alive, because we're a fake child. */ + GC_ADDREF(&intern->std); + (void) php_dom_create_fake_namespace_decl(nodep, original, return_value, intern); + } else { + DOM_RET_OBJ((xmlNodePtr) attrp, &ret, intern); } - - DOM_RET_OBJ((xmlNodePtr) attrp, &ret, intern); } /* }}} end dom_element_get_attribute_node */ @@ -798,7 +787,7 @@ Since: DOM Level 2 PHP_METHOD(DOMElement, getAttributeNodeNS) { zval *id; - xmlNodePtr elemp, fakeAttrp; + xmlNodePtr elemp; xmlAttrPtr attrp; dom_object *intern; size_t uri_len, name_len; @@ -819,21 +808,9 @@ PHP_METHOD(DOMElement, getAttributeNodeNS) xmlNsPtr nsptr; nsptr = dom_get_nsdecl(elemp, (xmlChar *)name); if (nsptr != NULL) { - xmlNsPtr curns; - curns = xmlNewNs(NULL, nsptr->href, NULL); - if (nsptr->prefix) { - curns->prefix = xmlStrdup((xmlChar *) nsptr->prefix); - } - if (nsptr->prefix) { - fakeAttrp = xmlNewDocNode(elemp->doc, NULL, (xmlChar *) nsptr->prefix, nsptr->href); - } else { - fakeAttrp = xmlNewDocNode(elemp->doc, NULL, (xmlChar *)"xmlns", nsptr->href); - } - fakeAttrp->type = XML_NAMESPACE_DECL; - fakeAttrp->parent = elemp; - fakeAttrp->ns = curns; - - DOM_RET_OBJ(fakeAttrp, &ret, intern); + /* Keep parent alive, because we're a fake child. */ + GC_ADDREF(&intern->std); + (void) php_dom_create_fake_namespace_decl(elemp, nsptr, return_value, intern); } else { RETURN_NULL(); } @@ -1234,7 +1211,7 @@ PHP_METHOD(DOMElement, prepend) } /* }}} end DOMElement::prepend */ -/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-prepend +/* {{{ URL: https://dom.spec.whatwg.org/#dom-parentnode-replacechildren Since: DOM Living Standard (DOM4) */ PHP_METHOD(DOMElement, replaceWith) @@ -1251,8 +1228,7 @@ PHP_METHOD(DOMElement, replaceWith) id = ZEND_THIS; DOM_GET_OBJ(context, id, xmlNodePtr, intern); - dom_parent_node_after(intern, args, argc); - dom_child_node_remove(intern); + dom_child_replace_with(intern, args, argc); } /* }}} end DOMElement::prepend */ diff --git a/ext/dom/namednodemap.c b/ext/dom/namednodemap.c index 99103ce30b7ad..31b915243c5f7 100644 --- a/ext/dom/namednodemap.c +++ b/ext/dom/namednodemap.c @@ -31,7 +31,7 @@ * Since: */ -static int get_namednodemap_length(dom_object *obj) +int php_dom_get_namednodemap_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; if (!objmap) { @@ -65,95 +65,74 @@ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core- */ int dom_namednodemap_length_read(dom_object *obj, zval *retval) { - ZVAL_LONG(retval, get_namednodemap_length(obj)); + ZVAL_LONG(retval, php_dom_get_namednodemap_length(obj)); return SUCCESS; } /* }}} */ -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1074577549 -Since: -*/ -PHP_METHOD(DOMNamedNodeMap, getNamedItem) +xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const char *named, bool may_transform) { - zval *id; - int ret; - size_t namedlen=0; - dom_object *intern; xmlNodePtr itemnode = NULL; - char *named; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep; - xmlNotation *notep = NULL; - - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &named, &namedlen) == FAILURE) { - RETURN_THROWS(); - } - - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; - if (objmap != NULL) { if ((objmap->nodetype == XML_NOTATION_NODE) || objmap->nodetype == XML_ENTITY_NODE) { if (objmap->ht) { if (objmap->nodetype == XML_ENTITY_NODE) { - itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, (xmlChar *) named); + itemnode = (xmlNodePtr)xmlHashLookup(objmap->ht, (const xmlChar *) named); } else { - notep = (xmlNotation *)xmlHashLookup(objmap->ht, (xmlChar *) named); + xmlNotationPtr notep = xmlHashLookup(objmap->ht, (const xmlChar *) named); if (notep) { - itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID); + if (may_transform) { + itemnode = create_notation(notep->name, notep->PublicID, notep->SystemID); + } else { + itemnode = (xmlNodePtr) notep; + } } } } } else { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { - itemnode = (xmlNodePtr)xmlHasProp(nodep, (xmlChar *) named); + itemnode = (xmlNodePtr)xmlHasProp(nodep, (const xmlChar *) named); } } } + return itemnode; +} +void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const char *named, zval *return_value) +{ + int ret; + xmlNodePtr itemnode = php_dom_named_node_map_get_named_item(objmap, named, true); if (itemnode) { DOM_RET_OBJ(itemnode, &ret, objmap->baseobj); - return; } else { - RETVAL_NULL(); + RETURN_NULL(); } } -/* }}} end dom_namednodemap_get_named_item */ -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9 +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-1074577549 Since: */ -PHP_METHOD(DOMNamedNodeMap, item) +PHP_METHOD(DOMNamedNodeMap, getNamedItem) { - zval *id; - zend_long index; - int ret; - dom_object *intern; - xmlNodePtr itemnode = NULL; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode; - int count; + size_t namedlen; + char *named; - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } - if (index < 0 || ZEND_LONG_INT_OVFL(index)) { - zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + if (zend_parse_parameters(ZEND_NUM_ARGS(), "s", &named, &namedlen) == FAILURE) { RETURN_THROWS(); } - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; + zval *id = ZEND_THIS; + dom_nnodemap_object *objmap = Z_DOMOBJ_P(id)->ptr; + php_dom_named_node_map_get_named_item_into_zval(objmap, named, return_value); +} +/* }}} end dom_namednodemap_get_named_item */ +xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index) +{ + xmlNodePtr itemnode = NULL; if (objmap != NULL) { if ((objmap->nodetype == XML_NOTATION_NODE) || objmap->nodetype == XML_ENTITY_NODE) { @@ -165,10 +144,10 @@ PHP_METHOD(DOMNamedNodeMap, item) } } } else { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { - curnode = (xmlNodePtr)nodep->properties; - count = 0; + xmlNodePtr curnode = (xmlNodePtr)nodep->properties; + zend_long count = 0; while (count < index && curnode != NULL) { count++; curnode = (xmlNodePtr)curnode->next; @@ -177,13 +156,38 @@ PHP_METHOD(DOMNamedNodeMap, item) } } } + return itemnode; +} +void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value) +{ + int ret; + xmlNodePtr itemnode = php_dom_named_node_map_get_item(objmap, index); if (itemnode) { DOM_RET_OBJ(itemnode, &ret, objmap->baseobj); - return; + } else { + RETURN_NULL(); + } +} + +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#core-ID-349467F9 +Since: +*/ +PHP_METHOD(DOMNamedNodeMap, item) +{ + zend_long index; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + RETURN_THROWS(); + } + if (index < 0 || ZEND_LONG_INT_OVFL(index)) { + zend_argument_value_error(1, "must be between 0 and %d", INT_MAX); + RETURN_THROWS(); } - RETVAL_NULL(); + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_named_node_map_get_item_into_zval(objmap, index, return_value); } /* }}} end dom_namednodemap_item */ @@ -254,7 +258,7 @@ PHP_METHOD(DOMNamedNodeMap, count) } intern = Z_DOMOBJ_P(id); - RETURN_LONG(get_namednodemap_length(intern)); + RETURN_LONG(php_dom_get_namednodemap_length(intern)); } /* }}} end dom_namednodemap_count */ diff --git a/ext/dom/node.c b/ext/dom/node.c index d55ac99efeff2..227cc35efd906 100644 --- a/ext/dom/node.c +++ b/ext/dom/node.c @@ -769,17 +769,28 @@ int dom_node_text_content_write(dom_object *obj, zval *newval) return FAILURE; } - if (nodep->type == XML_ELEMENT_NODE || nodep->type == XML_ATTRIBUTE_NODE) { + const xmlChar *xmlChars = (const xmlChar *) ZSTR_VAL(str); + int type = nodep->type; + + /* We can't directly call xmlNodeSetContent, because it might encode the string through + * xmlStringLenGetNodeList for types XML_DOCUMENT_FRAG_NODE, XML_ELEMENT_NODE, XML_ATTRIBUTE_NODE. + * See tree.c:xmlNodeSetContent in libxml. + * In these cases we need to use a text node to avoid the encoding. + * For the other cases, we *can* rely on xmlNodeSetContent because it is either a no-op, or handles + * the content without encoding. */ + if (type == XML_DOCUMENT_FRAG_NODE || type == XML_ELEMENT_NODE || type == XML_ATTRIBUTE_NODE) { if (nodep->children) { node_list_unlink(nodep->children); php_libxml_node_free_list((xmlNodePtr) nodep->children); nodep->children = NULL; } + + xmlNode *textNode = xmlNewText(xmlChars); + xmlAddChild(nodep, textNode); + } else { + xmlNodeSetContent(nodep, xmlChars); } - /* we have to use xmlNodeAddContent() to get the same behavior as with xmlNewText() */ - xmlNodeSetContent(nodep, (xmlChar *) ""); - xmlNodeAddContent(nodep, (xmlChar *) ZSTR_VAL(str)); zend_string_release_ex(str, 0); return SUCCESS; @@ -932,12 +943,20 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, refp->prev, refp, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddPrevSibling(refp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } else { if (child->parent != NULL){ @@ -974,23 +993,28 @@ PHP_METHOD(DOMNode, insertBefore) return; } } + new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(parentp, parentp->last, NULL, child, intern, childobj); - } - if (new_child == NULL) { + dom_reconcile_ns_list(parentp->doc, new_child, last); + } else { new_child = xmlAddChild(parentp, child); + if (UNEXPECTED(NULL == new_child)) { + goto cannot_add; + } + dom_reconcile_ns(parentp->doc, new_child); } } - if (NULL == new_child) { - zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); - RETURN_THROWS(); - } - - dom_reconcile_ns(parentp->doc, new_child); - DOM_RET_OBJ(new_child, &ret, intern); - + return; +cannot_add: + zend_throw_error(NULL, "Cannot add newnode as the previous sibling of refnode"); + RETURN_THROWS(); } /* }}} end dom_node_insert_before */ @@ -1055,9 +1079,10 @@ PHP_METHOD(DOMNode, replaceChild) xmlUnlinkNode(oldchild); + xmlNodePtr last = newchild->last; newchild = _php_dom_insert_fragment(nodep, prevsib, nextsib, newchild, intern, newchildobj); if (newchild) { - dom_reconcile_ns(nodep->doc, newchild); + dom_reconcile_ns_list(nodep->doc, newchild, last); } } else if (oldchild != newchild) { xmlDtdPtr intSubset = xmlGetIntSubset(nodep->doc); @@ -1204,22 +1229,28 @@ PHP_METHOD(DOMNode, appendChild) php_libxml_node_free_resource((xmlNodePtr) lastattr); } } + new_child = xmlAddChild(nodep, child); + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; + } } else if (child->type == XML_DOCUMENT_FRAG_NODE) { + xmlNodePtr last = child->last; new_child = _php_dom_insert_fragment(nodep, nodep->last, NULL, child, intern, childobj); - } - - if (new_child == NULL) { + dom_reconcile_ns_list(nodep->doc, new_child, last); + } else { new_child = xmlAddChild(nodep, child); - if (new_child == NULL) { - // TODO Convert to Error? - php_error_docref(NULL, E_WARNING, "Couldn't append node"); - RETURN_FALSE; + if (UNEXPECTED(new_child == NULL)) { + goto cannot_add; } + dom_reconcile_ns(nodep->doc, new_child); } - dom_reconcile_ns(nodep->doc, new_child); - DOM_RET_OBJ(new_child, &ret, intern); + return; +cannot_add: + // TODO Convert to Error? + php_error_docref(NULL, E_WARNING, "Couldn't append node"); + RETURN_FALSE; } /* }}} end dom_node_append_child */ diff --git a/ext/dom/nodelist.c b/ext/dom/nodelist.c index b03ebe1acd90a..20f6320a54607 100644 --- a/ext/dom/nodelist.c +++ b/ext/dom/nodelist.c @@ -31,7 +31,7 @@ * Since: */ -static int get_nodelist_length(dom_object *obj) +int php_dom_get_nodelist_length(dom_object *obj) { dom_nnodemap_object *objmap = (dom_nnodemap_object *) obj->ptr; if (!objmap) { @@ -82,7 +82,7 @@ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-20 */ int dom_nodelist_length_read(dom_object *obj, zval *retval) { - ZVAL_LONG(retval, get_nodelist_length(obj)); + ZVAL_LONG(retval, php_dom_get_nodelist_length(obj)); return SUCCESS; } @@ -99,36 +99,15 @@ PHP_METHOD(DOMNodeList, count) } intern = Z_DOMOBJ_P(id); - RETURN_LONG(get_nodelist_length(intern)); + RETURN_LONG(php_dom_get_nodelist_length(intern)); } /* }}} end dom_nodelist_count */ -/* }}} */ - -/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-844377136 -Since: -*/ -PHP_METHOD(DOMNodeList, item) +void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value) { - zval *id; - zend_long index; int ret; - dom_object *intern; xmlNodePtr itemnode = NULL; - - dom_nnodemap_object *objmap; - xmlNodePtr nodep, curnode; - int count = 0; - - id = ZEND_THIS; - if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { - RETURN_THROWS(); - } - if (index >= 0) { - intern = Z_DOMOBJ_P(id); - - objmap = (dom_nnodemap_object *)intern->ptr; if (objmap != NULL) { if (objmap->ht) { if (objmap->nodetype == XML_ENTITY_NODE) { @@ -145,10 +124,11 @@ PHP_METHOD(DOMNodeList, item) return; } } else if (objmap->baseobj) { - nodep = dom_object_get_node(objmap->baseobj); + xmlNodePtr nodep = dom_object_get_node(objmap->baseobj); if (nodep) { + int count = 0; if (objmap->nodetype == XML_ATTRIBUTE_NODE || objmap->nodetype == XML_ELEMENT_NODE) { - curnode = nodep->children; + xmlNodePtr curnode = nodep->children; while (count < index && curnode != NULL) { count++; curnode = curnode->next; @@ -175,6 +155,22 @@ PHP_METHOD(DOMNodeList, item) RETVAL_NULL(); } + +/* {{{ URL: http://www.w3.org/TR/2003/WD-DOM-Level-3-Core-20030226/DOM3-Core.html#ID-844377136 +Since: +*/ +PHP_METHOD(DOMNodeList, item) +{ + zend_long index; + if (zend_parse_parameters(ZEND_NUM_ARGS(), "l", &index) == FAILURE) { + RETURN_THROWS(); + } + + zval *id = ZEND_THIS; + dom_object *intern = Z_DOMOBJ_P(id); + dom_nnodemap_object *objmap = intern->ptr; + php_dom_nodelist_get_item_into_zval(objmap, index, return_value); +} /* }}} end dom_nodelist_item */ ZEND_METHOD(DOMNodeList, getIterator) diff --git a/ext/dom/parentnode.c b/ext/dom/parentnode.c index cf823057d22ae..a9dfda59622b7 100644 --- a/ext/dom/parentnode.c +++ b/ext/dom/parentnode.c @@ -124,6 +124,23 @@ int dom_parent_node_child_element_count(dom_object *obj, zval *retval) } /* }}} */ +static bool dom_is_node_in_list(const zval *nodes, int nodesc, const xmlNodePtr node_to_find) +{ + for (int i = 0; i < nodesc; i++) { + if (Z_TYPE(nodes[i]) == IS_OBJECT) { + const zend_class_entry *ce = Z_OBJCE(nodes[i]); + + if (instanceof_function(ce, dom_node_class_entry)) { + if (dom_object_get_node(Z_DOMOBJ_P(nodes + i)) == node_to_find) { + return true; + } + } + } + } + + return false; +} + xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNode, zval *nodes, int nodesc) { int i; @@ -177,17 +194,16 @@ xmlNode* dom_zvals_to_fragment(php_libxml_ref_obj *document, xmlNode *contextNod goto hierarchy_request_err; } - /* - * xmlNewDocText function will always returns same address to the second parameter if the parameters are greater than or equal to three. - * If it's text, that's fine, but if it's an object, it can cause invalid pointer because many new nodes point to the same memory address. - * So we must copy the new node to avoid this situation. - */ - if (nodesc > 1) { + /* Citing from the docs (https://gnome.pages.gitlab.gnome.org/libxml2/devhelp/libxml2-tree.html#xmlAddChild): + * "Add a new node to @parent, at the end of the child (or property) list merging adjacent TEXT nodes (in which case @cur is freed)". + * So we must take a copy if this situation arises to prevent a use-after-free. */ + bool will_free = newNode->type == XML_TEXT_NODE && fragment->last && fragment->last->type == XML_TEXT_NODE; + if (will_free) { newNode = xmlCopyNode(newNode, 1); } if (!xmlAddChild(fragment, newNode)) { - if (nodesc > 1) { + if (will_free) { xmlFreeNode(newNode); } goto hierarchy_request_err; @@ -239,10 +255,33 @@ static void dom_fragment_assign_parent_node(xmlNodePtr parentNode, xmlNodePtr fr fragment->last = NULL; } +static zend_result dom_hierarchy_node_list(xmlNodePtr parentNode, zval *nodes, int nodesc) +{ + for (int i = 0; i < nodesc; i++) { + if (Z_TYPE(nodes[i]) == IS_OBJECT) { + const zend_class_entry *ce = Z_OBJCE(nodes[i]); + + if (instanceof_function(ce, dom_node_class_entry)) { + if (dom_hierarchy(parentNode, dom_object_get_node(Z_DOMOBJ_P(nodes + i))) != SUCCESS) { + return FAILURE; + } + } + } + } + + return SUCCESS; +} + void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc) { xmlNode *parentNode = dom_object_get_node(context); xmlNodePtr newchild, prevsib; + + if (UNEXPECTED(dom_hierarchy_node_list(parentNode, nodes, nodesc) != SUCCESS)) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document)); + return; + } + xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -259,13 +298,14 @@ void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc) parentNode->children = newchild; } - parentNode->last = fragment->last; + xmlNodePtr last = fragment->last; + parentNode->last = last; newchild->prev = prevsib; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); @@ -280,6 +320,11 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc) return; } + if (UNEXPECTED(dom_hierarchy_node_list(parentNode, nodes, nodesc) != SUCCESS)) { + php_dom_throw_error(HIERARCHY_REQUEST_ERR, dom_get_strict_error(context->document)); + return; + } + xmlNodePtr newchild, nextsib; xmlNode *fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); @@ -291,37 +336,77 @@ void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc) nextsib = parentNode->children; if (newchild) { + xmlNodePtr last = fragment->last; parentNode->children = newchild; fragment->last->next = nextsib; - nextsib->prev = fragment->last; + nextsib->prev = last; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(parentNode->doc, newchild); + dom_reconcile_ns_list(parentNode->doc, newchild, last); } xmlFree(fragment); } +static void dom_pre_insert(xmlNodePtr insertion_point, xmlNodePtr parentNode, xmlNodePtr newchild, xmlNodePtr fragment) +{ + if (!insertion_point) { + /* Place it as last node */ + if (parentNode->children) { + /* There are children */ + fragment->last->prev = parentNode->last; + newchild->prev = parentNode->last->prev; + parentNode->last->next = newchild; + } else { + /* No children, because they moved out when they became a fragment */ + parentNode->children = newchild; + parentNode->last = newchild; + } + } else { + /* Insert fragment before insertion_point */ + fragment->last->next = insertion_point; + if (insertion_point->prev) { + insertion_point->prev->next = newchild; + newchild->prev = insertion_point->prev; + } + insertion_point->prev = newchild; + if (parentNode->children == insertion_point) { + parentNode->children = newchild; + } + } +} + void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) { + /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-after */ + xmlNode *prevsib = dom_object_get_node(context); xmlNodePtr newchild, parentNode; - xmlNode *fragment, *nextsib; + xmlNode *fragment; xmlDoc *doc; - bool afterlastchild; - int stricterror = dom_get_strict_error(context->document); - - if (!prevsib->parent) { - php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror); + /* Spec step 1 */ + parentNode = prevsib->parent; + /* Spec step 2 */ + if (!parentNode) { + int stricterror = dom_get_strict_error(context->document); + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); return; } + /* Spec step 3: find first following child not in nodes; otherwise null */ + xmlNodePtr viable_next_sibling = prevsib->next; + while (viable_next_sibling) { + if (!dom_is_node_in_list(nodes, nodesc, viable_next_sibling)) { + break; + } + viable_next_sibling = viable_next_sibling->next; + } + doc = prevsib->doc; - parentNode = prevsib->parent; - nextsib = prevsib->next; - afterlastchild = (nextsib == NULL); + + /* Spec step 4: convert nodes into fragment */ fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -331,42 +416,13 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { - /* first node and last node are both both parameters to DOMElement::after() method so nextsib and prevsib are null. */ - if (!parentNode->children) { - prevsib = nextsib = NULL; - } else if (afterlastchild) { - /* - * The new node will be inserted after last node, prevsib is last node. - * The first node is the parameter to DOMElement::after() if parentNode->children == prevsib is true - * and prevsib does not change, otherwise prevsib is parentNode->last (first node). - */ - prevsib = parentNode->children == prevsib ? prevsib : parentNode->last; - } else { - /* - * The new node will be inserted after first node, prevsib is first node. - * The first node is not the parameter to DOMElement::after() if parentNode->children == prevsib is true - * and prevsib does not change otherwise prevsib is null to mean that parentNode->children is the new node. - */ - prevsib = parentNode->children == prevsib ? prevsib : NULL; - } + xmlNodePtr last = fragment->last; - if (prevsib) { - fragment->last->next = prevsib->next; - if (prevsib->next) { - prevsib->next->prev = fragment->last; - } - prevsib->next = newchild; - } else { - parentNode->children = newchild; - if (nextsib) { - fragment->last->next = nextsib; - nextsib->prev = fragment->last; - } - } + /* Step 5: place fragment into the parent before viable_next_sibling */ + dom_pre_insert(viable_next_sibling, parentNode, newchild, fragment); - newchild->prev = prevsib; dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); @@ -374,17 +430,34 @@ void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc) void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) { + /* Spec link: https://dom.spec.whatwg.org/#dom-childnode-before */ + xmlNode *nextsib = dom_object_get_node(context); - xmlNodePtr newchild, prevsib, parentNode; - xmlNode *fragment, *afternextsib; + xmlNodePtr newchild, parentNode; + xmlNode *fragment; xmlDoc *doc; - bool beforefirstchild; - doc = nextsib->doc; - prevsib = nextsib->prev; - afternextsib = nextsib->next; + /* Spec step 1 */ parentNode = nextsib->parent; - beforefirstchild = !prevsib; + /* Spec step 2 */ + if (!parentNode) { + int stricterror = dom_get_strict_error(context->document); + php_dom_throw_error(HIERARCHY_REQUEST_ERR, stricterror); + return; + } + + /* Spec step 3: find first following child not in nodes; otherwise null */ + xmlNodePtr viable_previous_sibling = nextsib->prev; + while (viable_previous_sibling) { + if (!dom_is_node_in_list(nodes, nodesc, viable_previous_sibling)) { + break; + } + viable_previous_sibling = viable_previous_sibling->prev; + } + + doc = nextsib->doc; + + /* Spec step 4: convert nodes into fragment */ fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); if (fragment == NULL) { @@ -394,74 +467,63 @@ void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc) newchild = fragment->children; if (newchild) { - /* first node and last node are both both parameters to DOMElement::before() method so nextsib is null. */ - if (!parentNode->children) { - nextsib = NULL; - } else if (beforefirstchild) { - /* - * The new node will be inserted before first node, nextsib is first node and afternextsib is last node. - * The first node is not the parameter to DOMElement::before() if parentNode->children == nextsib is true - * and nextsib does not change, otherwise nextsib is the last node. - */ - nextsib = parentNode->children == nextsib ? nextsib : afternextsib; - } else { - /* - * The new node will be inserted before last node, prevsib is first node and nestsib is last node. - * The first node is not the parameter to DOMElement::before() if parentNode->children == prevsib is true - * but last node may be, so use prevsib->next to determine the value of nextsib, otherwise nextsib does not change. - */ - nextsib = parentNode->children == prevsib ? prevsib->next : nextsib; - } + xmlNodePtr last = fragment->last; - if (parentNode->children == nextsib) { - parentNode->children = newchild; + /* Step 5: if viable_previous_sibling is null, set it to the parent's first child, otherwise viable_previous_sibling's next sibling */ + if (!viable_previous_sibling) { + viable_previous_sibling = parentNode->children; } else { - prevsib->next = newchild; - } - - fragment->last->next = nextsib; - if (nextsib) { - nextsib->prev = fragment->last; + viable_previous_sibling = viable_previous_sibling->next; } - - newchild->prev = prevsib; + /* Step 6: place fragment into the parent after viable_previous_sibling */ + dom_pre_insert(viable_previous_sibling, parentNode, newchild, fragment); dom_fragment_assign_parent_node(parentNode, fragment); - dom_reconcile_ns(doc, newchild); + dom_reconcile_ns_list(doc, newchild, last); } xmlFree(fragment); } -void dom_child_node_remove(dom_object *context) +static zend_result dom_child_removal_preconditions(const xmlNodePtr child, int stricterror) { - xmlNode *child = dom_object_get_node(context); - xmlNodePtr children; - int stricterror; - - stricterror = dom_get_strict_error(context->document); - if (dom_node_is_read_only(child) == SUCCESS || (child->parent != NULL && dom_node_is_read_only(child->parent) == SUCCESS)) { php_dom_throw_error(NO_MODIFICATION_ALLOWED_ERR, stricterror); - return; + return FAILURE; } if (!child->parent) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); - return; + return FAILURE; } if (dom_node_children_valid(child->parent) == FAILURE) { - return; + return FAILURE; } - children = child->parent->children; + xmlNodePtr children = child->parent->children; if (!children) { php_dom_throw_error(NOT_FOUND_ERR, stricterror); + return FAILURE; + } + + return SUCCESS; +} + +void dom_child_node_remove(dom_object *context) +{ + xmlNode *child = dom_object_get_node(context); + xmlNodePtr children; + int stricterror; + + stricterror = dom_get_strict_error(context->document); + + if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) { return; } + children = child->parent->children; while (children) { if (children == child) { xmlUnlinkNode(child); @@ -473,4 +535,41 @@ void dom_child_node_remove(dom_object *context) php_dom_throw_error(NOT_FOUND_ERR, stricterror); } +void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc) +{ + xmlNodePtr child = dom_object_get_node(context); + xmlNodePtr parentNode = child->parent; + + int stricterror = dom_get_strict_error(context->document); + if (UNEXPECTED(dom_child_removal_preconditions(child, stricterror) != SUCCESS)) { + return; + } + + xmlNodePtr insertion_point = child->next; + + xmlNodePtr fragment = dom_zvals_to_fragment(context->document, parentNode, nodes, nodesc); + if (UNEXPECTED(fragment == NULL)) { + return; + } + + xmlNodePtr newchild = fragment->children; + xmlDocPtr doc = parentNode->doc; + + if (newchild) { + xmlNodePtr last = fragment->last; + + /* Unlink and free it unless it became a part of the fragment. */ + if (child->parent != fragment) { + xmlUnlinkNode(child); + } + + dom_pre_insert(insertion_point, parentNode, newchild, fragment); + + dom_fragment_assign_parent_node(parentNode, fragment); + dom_reconcile_ns_list(doc, newchild, last); + } + + xmlFree(fragment); +} + #endif diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 18b0bce90d3b8..55c6ee3c93d03 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -61,6 +61,8 @@ PHP_DOM_EXPORT zend_class_entry *dom_namespace_node_class_entry; zend_object_handlers dom_object_handlers; zend_object_handlers dom_nnodemap_object_handlers; +zend_object_handlers dom_nodelist_object_handlers; +zend_object_handlers dom_object_namespace_node_handlers; #ifdef LIBXML_XPATH_ENABLED zend_object_handlers dom_xpath_object_handlers; #endif @@ -86,6 +88,11 @@ static HashTable dom_xpath_prop_handlers; #endif /* }}} */ +static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type); +static void dom_object_namespace_node_free_storage(zend_object *object); +static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original); +static zend_object *dom_nodelist_objects_new(zend_class_entry *class_type); + typedef int (*dom_read_t)(dom_object *obj, zval *retval); typedef int (*dom_write_t)(dom_object *obj, zval *newval); @@ -473,6 +480,19 @@ PHP_FUNCTION(dom_import_simplexml) static dom_object* dom_objects_set_class(zend_class_entry *class_type); +static void dom_update_refcount_after_clone(dom_object *original, xmlNodePtr original_node, dom_object *clone, xmlNodePtr cloned_node) +{ + /* If we cloned a document then we must create new doc proxy */ + if (cloned_node->doc == original_node->doc) { + clone->document = original->document; + } + php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc); + php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone); + if (original->document != clone->document) { + dom_copy_doc_props(original->document, clone->document); + } +} + static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ { dom_object *intern = php_dom_obj_from_obj(zobject); @@ -485,15 +505,7 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ if (node != NULL) { xmlNodePtr cloned_node = xmlDocCopyNode(node, node->doc, 1); if (cloned_node != NULL) { - /* If we cloned a document then we must create new doc proxy */ - if (cloned_node->doc == node->doc) { - clone->document = intern->document; - } - php_libxml_increment_doc_ref((php_libxml_node_object *)clone, cloned_node->doc); - php_libxml_increment_node_ptr((php_libxml_node_object *)clone, cloned_node, (void *)clone); - if (intern->document != clone->document) { - dom_copy_doc_props(intern->document, clone->document); - } + dom_update_refcount_after_clone(intern, node, clone, cloned_node); } } @@ -505,6 +517,26 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ } /* }}} */ +static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject) +{ + dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject); + zend_object *clone = dom_objects_namespace_node_new(intern->dom.std.ce); + dom_object_namespace_node *clone_intern = php_dom_namespace_node_obj_from_obj(clone); + + xmlNodePtr original_node = dom_object_get_node(&intern->dom); + ZEND_ASSERT(original_node->type == XML_NAMESPACE_DECL); + xmlNodePtr cloned_node = php_dom_create_fake_namespace_decl_node_ptr(original_node->parent, original_node->ns); + + if (intern->parent_intern) { + clone_intern->parent_intern = intern->parent_intern; + GC_ADDREF(&clone_intern->parent_intern->std); + } + dom_update_refcount_after_clone(&intern->dom, original_node, &clone_intern->dom, cloned_node); + + zend_objects_clone_members(clone, &intern->dom.std); + return clone; +} + static void dom_copy_prop_handler(zval *zv) /* {{{ */ { dom_prop_handler *hnd = Z_PTR_P(zv); @@ -547,6 +579,8 @@ void dom_objects_free_storage(zend_object *object); void dom_nnodemap_objects_free_storage(zend_object *object); static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv); static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty); +static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv); +static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty); static zend_object *dom_objects_store_clone_obj(zend_object *zobject); #ifdef LIBXML_XPATH_ENABLED void dom_xpath_objects_free_storage(zend_object *object); @@ -567,8 +601,17 @@ PHP_MINIT_FUNCTION(dom) memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage; - dom_nnodemap_object_handlers.read_dimension = dom_nodelist_read_dimension; - dom_nnodemap_object_handlers.has_dimension = dom_nodelist_has_dimension; + dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension; + dom_nnodemap_object_handlers.has_dimension = dom_nodemap_has_dimension; + + memcpy(&dom_nodelist_object_handlers, &dom_nnodemap_object_handlers, sizeof(zend_object_handlers)); + dom_nodelist_object_handlers.read_dimension = dom_nodelist_read_dimension; + dom_nodelist_object_handlers.has_dimension = dom_nodelist_has_dimension; + + memcpy(&dom_object_namespace_node_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); + dom_object_namespace_node_handlers.offset = XtOffsetOf(dom_object_namespace_node, dom.std); + dom_object_namespace_node_handlers.free_obj = dom_object_namespace_node_free_storage; + dom_object_namespace_node_handlers.clone_obj = dom_object_namespace_node_clone_obj; zend_hash_init(&classes, 0, NULL, NULL, 1); @@ -604,7 +647,7 @@ PHP_MINIT_FUNCTION(dom) zend_hash_add_ptr(&classes, dom_node_class_entry->name, &dom_node_prop_handlers); dom_namespace_node_class_entry = register_class_DOMNameSpaceNode(); - dom_namespace_node_class_entry->create_object = dom_objects_new; + dom_namespace_node_class_entry->create_object = dom_objects_namespace_node_new; zend_hash_init(&dom_namespace_node_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1); dom_register_prop_handler(&dom_namespace_node_prop_handlers, "nodeName", sizeof("nodeName")-1, dom_node_node_name_read, NULL); @@ -659,7 +702,7 @@ PHP_MINIT_FUNCTION(dom) zend_hash_add_ptr(&classes, dom_document_class_entry->name, &dom_document_prop_handlers); dom_nodelist_class_entry = register_class_DOMNodeList(zend_ce_aggregate, zend_ce_countable); - dom_nodelist_class_entry->create_object = dom_nnodemap_objects_new; + dom_nodelist_class_entry->create_object = dom_nodelist_objects_new; dom_nodelist_class_entry->get_iterator = php_dom_get_iterator; zend_hash_init(&dom_nodelist_prop_handlers, 0, NULL, dom_dtor_prop_handler, 1); @@ -952,10 +995,8 @@ void dom_namednode_iter(dom_object *basenode, int ntype, dom_object *intern, xml } /* }}} */ -static dom_object* dom_objects_set_class(zend_class_entry *class_type) /* {{{ */ +static void dom_objects_set_class_ex(zend_class_entry *class_type, dom_object *intern) { - dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type); - zend_class_entry *base_class = class_type; while ((base_class->type != ZEND_INTERNAL_CLASS || base_class->info.internal.module->module_number != dom_module_entry.module_number) && base_class->parent != NULL) { base_class = base_class->parent; @@ -965,10 +1006,14 @@ static dom_object* dom_objects_set_class(zend_class_entry *class_type) /* {{{ */ zend_object_std_init(&intern->std, class_type); object_properties_init(&intern->std, class_type); +} +static dom_object* dom_objects_set_class(zend_class_entry *class_type) +{ + dom_object *intern = zend_object_alloc(sizeof(dom_object), class_type); + dom_objects_set_class_ex(class_type, intern); return intern; } -/* }}} */ /* {{{ dom_objects_new */ zend_object *dom_objects_new(zend_class_entry *class_type) @@ -979,6 +1024,25 @@ zend_object *dom_objects_new(zend_class_entry *class_type) } /* }}} */ +static zend_object *dom_objects_namespace_node_new(zend_class_entry *class_type) +{ + dom_object_namespace_node *intern = zend_object_alloc(sizeof(dom_object_namespace_node), class_type); + dom_objects_set_class_ex(class_type, &intern->dom); + intern->dom.std.handlers = &dom_object_namespace_node_handlers; + return &intern->dom.std; +} + +static void dom_object_namespace_node_free_storage(zend_object *object) +{ + dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(object); + if (intern->parent_intern != NULL) { + zval tmp; + ZVAL_OBJ(&tmp, &intern->parent_intern->std); + zval_ptr_dtor(&tmp); + } + dom_objects_free_storage(object); +} + #ifdef LIBXML_XPATH_ENABLED /* {{{ zend_object dom_xpath_objects_new(zend_class_entry *class_type) */ zend_object *dom_xpath_objects_new(zend_class_entry *class_type) @@ -1024,7 +1088,7 @@ void dom_nnodemap_objects_free_storage(zend_object *object) /* {{{ */ } /* }}} */ -zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ +static zend_object *dom_nodemap_or_nodelist_objects_new(zend_class_entry *class_type, const zend_object_handlers *handlers) { dom_object *intern; dom_nnodemap_object *objmap; @@ -1039,12 +1103,22 @@ zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ objmap->local = NULL; objmap->ns = NULL; - intern->std.handlers = &dom_nnodemap_object_handlers; + intern->std.handlers = handlers; return &intern->std; } + +zend_object *dom_nnodemap_objects_new(zend_class_entry *class_type) /* {{{ */ +{ + return dom_nodemap_or_nodelist_objects_new(class_type, &dom_nnodemap_object_handlers); +} /* }}} */ +zend_object *dom_nodelist_objects_new(zend_class_entry *class_type) +{ + return dom_nodemap_or_nodelist_objects_new(class_type, &dom_nodelist_object_handlers); +} + void php_dom_create_iterator(zval *return_value, int ce_type) /* {{{ */ { zend_class_entry *ce; @@ -1221,10 +1295,15 @@ xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *l { xmlNodePtr ret = NULL; + /* Note: The spec says that ns == '' must be transformed to ns == NULL. In other words, they are equivalent. + * PHP however does not do this and internally uses the empty string everywhere when the user provides ns == NULL. + * This is because for PHP ns == NULL has another meaning: "match every namespace" instead of "match the empty namespace". */ + bool ns_match_any = ns == NULL || (ns[0] == '*' && ns[1] == '\0'); + while (nodep != NULL && (*cur <= index || index == -1)) { if (nodep->type == XML_ELEMENT_NODE) { if (xmlStrEqual(nodep->name, (xmlChar *)local) || xmlStrEqual((xmlChar *)"*", (xmlChar *)local)) { - if (ns == NULL || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && (xmlStrEqual(nodep->ns->href, (xmlChar *)ns) || xmlStrEqual((xmlChar *)"*", (xmlChar *)ns)))) { + if (ns_match_any || (!strcmp(ns, "") && nodep->ns == NULL) || (nodep->ns != NULL && xmlStrEqual(nodep->ns->href, (xmlChar *)ns))) { if (*cur == index) { ret = nodep; break; @@ -1331,38 +1410,86 @@ void dom_set_old_ns(xmlDoc *doc, xmlNs *ns) { } /* }}} end dom_set_old_ns */ -void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +static void dom_reconcile_ns_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr search_parent) { xmlNsPtr nsptr, nsdftptr, curns, prevns = NULL; - if (nodep->type == XML_ELEMENT_NODE) { - /* Following if block primarily used for inserting nodes created via createElementNS */ - if (nodep->nsDef != NULL) { - curns = nodep->nsDef; - while (curns) { - nsdftptr = curns->next; - if (curns->href != NULL) { - if((nsptr = xmlSearchNsByHref(doc, nodep->parent, curns->href)) && - (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { - curns->next = NULL; - if (prevns == NULL) { - nodep->nsDef = nsdftptr; - } else { - prevns->next = nsdftptr; - } - dom_set_old_ns(doc, curns); - curns = prevns; + /* Following if block primarily used for inserting nodes created via createElementNS */ + if (nodep->nsDef != NULL) { + curns = nodep->nsDef; + while (curns) { + nsdftptr = curns->next; + if (curns->href != NULL) { + if((nsptr = xmlSearchNsByHref(doc, search_parent, curns->href)) && + (curns->prefix == NULL || xmlStrEqual(nsptr->prefix, curns->prefix))) { + curns->next = NULL; + if (prevns == NULL) { + nodep->nsDef = nsdftptr; + } else { + prevns->next = nsdftptr; } + dom_set_old_ns(doc, curns); + curns = prevns; } - prevns = curns; - curns = nsdftptr; } + prevns = curns; + curns = nsdftptr; } - xmlReconciliateNs(doc, nodep); + } +} + +static void dom_libxml_reconcile_ensure_namespaces_are_declared(xmlNodePtr nodep) +{ + /* Put on stack to avoid allocation. + * Although libxml2 currently does not use this for the reconciliation, it still + * makes sense to do this just in case libxml2's internal change in the future. */ + xmlDOMWrapCtxt dummy_ctxt = {0}; + xmlDOMWrapReconcileNamespaces(&dummy_ctxt, nodep, /* options */ 0); +} + +void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep) /* {{{ */ +{ + /* Although the node type will be checked by the libxml2 API, + * we still want to do the internal reconciliation conditionally. */ + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep, nodep->parent); + dom_libxml_reconcile_ensure_namespaces_are_declared(nodep); } } /* }}} */ +static void dom_reconcile_ns_list_internal(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last, xmlNodePtr search_parent) +{ + ZEND_ASSERT(nodep != NULL); + while (true) { + if (nodep->type == XML_ELEMENT_NODE) { + dom_reconcile_ns_internal(doc, nodep, search_parent); + if (nodep->children) { + dom_reconcile_ns_list_internal(doc, nodep->children, nodep->last /* process the whole children list */, search_parent); + } + } + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last) +{ + dom_reconcile_ns_list_internal(doc, nodep, last, nodep->parent); + /* The loop is outside of the recursion in the above call because + * dom_libxml_reconcile_ensure_namespaces_are_declared() performs its own recursion. */ + while (true) { + /* The internal libxml2 call will already check the node type, no need for us to do it here. */ + dom_libxml_reconcile_ensure_namespaces_are_declared(nodep); + if (nodep == last) { + break; + } + nodep = nodep->next; + } +} + /* http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/core.html#ID-DocCrElNS @@ -1461,34 +1588,111 @@ xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName) { } /* }}} end dom_get_nsdecl */ -static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +static xmlNodePtr php_dom_create_fake_namespace_decl_node_ptr(xmlNodePtr nodep, xmlNsPtr original) { - zval offset_copy; + xmlNodePtr attrp; + xmlNsPtr curns = xmlNewNs(NULL, original->href, NULL); + if (original->prefix) { + curns->prefix = xmlStrdup(original->prefix); + attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *) original->prefix, original->href); + } else { + attrp = xmlNewDocNode(nodep->doc, NULL, (xmlChar *)"xmlns", original->href); + } + attrp->type = XML_NAMESPACE_DECL; + attrp->parent = nodep; + attrp->ns = curns; + return attrp; +} - if (!offset) { - zend_throw_error(NULL, "Cannot access node list without offset"); - return NULL; +/* Note: Assumes the additional lifetime was already added in the caller. */ +xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern) +{ + xmlNodePtr attrp = php_dom_create_fake_namespace_decl_node_ptr(nodep, original); + php_dom_create_object(attrp, return_value, parent_intern); + /* This object must exist, because we just created an object for it via php_dom_create_object(). */ + php_dom_namespace_node_obj_from_obj(Z_OBJ_P(return_value))->parent_intern = parent_intern; + return attrp; +} + +static bool dom_nodemap_or_nodelist_process_offset_as_named(zval *offset, zend_long *lval) +{ + if (Z_TYPE_P(offset) == IS_STRING) { + /* See zval_get_long_func() */ + double dval; + zend_uchar is_numeric_string_type; + if (0 == (is_numeric_string_type = is_numeric_string(Z_STRVAL_P(offset), Z_STRLEN_P(offset), lval, &dval, true))) { + return true; + } else if (is_numeric_string_type == IS_DOUBLE) { + *lval = zend_dval_to_lval_cap(dval); + } + } else { + *lval = zval_get_long(offset); } + return false; +} - ZVAL_LONG(&offset_copy, zval_get_long(offset)); +static zval *dom_nodelist_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot access DOMNodeList without offset"); + return NULL; + } - zend_call_method_with_1_params(object, object->ce, NULL, "item", rv, &offset_copy); + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { + /* does not support named lookup */ + ZVAL_NULL(rv); + return rv; + } + php_dom_nodelist_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); return rv; } /* }}} end dom_nodelist_read_dimension */ static int dom_nodelist_has_dimension(zend_object *object, zval *member, int check_empty) { - zend_long offset = zval_get_long(member); - zval rv; - - if (offset < 0) { + zend_long offset; + if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + /* does not support named lookup */ return 0; - } else { - zval *length = zend_read_property( - object->ce, object, "length", sizeof("length") - 1, 0, &rv); - return length && offset < Z_LVAL_P(length); } + + return offset >= 0 && offset < php_dom_get_nodelist_length(php_dom_obj_from_obj(object)); } /* }}} end dom_nodelist_has_dimension */ +static zval *dom_nodemap_read_dimension(zend_object *object, zval *offset, int type, zval *rv) /* {{{ */ +{ + if (UNEXPECTED(!offset)) { + zend_throw_error(NULL, "Cannot access DOMNamedNodeMap without offset"); + return NULL; + } + + zend_long lval; + if (dom_nodemap_or_nodelist_process_offset_as_named(offset, &lval)) { + /* exceptional case, switch to named lookup */ + php_dom_named_node_map_get_named_item_into_zval(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(offset), rv); + return rv; + } + + /* see PHP_METHOD(DOMNamedNodeMap, item) */ + if (UNEXPECTED(lval < 0 || ZEND_LONG_INT_OVFL(lval))) { + zend_value_error("must be between 0 and %d", INT_MAX); + return NULL; + } + + php_dom_named_node_map_get_item_into_zval(php_dom_obj_from_obj(object)->ptr, lval, rv); + return rv; +} /* }}} end dom_nodemap_read_dimension */ + +static int dom_nodemap_has_dimension(zend_object *object, zval *member, int check_empty) +{ + zend_long offset; + if (dom_nodemap_or_nodelist_process_offset_as_named(member, &offset)) { + /* exceptional case, switch to named lookup */ + return php_dom_named_node_map_get_named_item(php_dom_obj_from_obj(object)->ptr, Z_STRVAL_P(member), false) != NULL; + } + + return offset >= 0 && offset < php_dom_get_namednodemap_length(php_dom_obj_from_obj(object)); +} /* }}} end dom_nodemap_has_dimension */ + #endif /* HAVE_DOM */ diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index fdfdd4e7a31ca..9a57996729dbf 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -93,6 +93,18 @@ typedef struct { HashPosition pos; } php_dom_iterator; +typedef struct { + /* This may be a fake object that isn't actually in the children list of the parent. + * This is because some namespace declaration nodes aren't stored on the parent in libxml2, so we have to fake it. + * We could use a zval for this, but since this is always going to be an object let's save space... */ + dom_object *parent_intern; + dom_object dom; +} dom_object_namespace_node; + +static inline dom_object_namespace_node *php_dom_namespace_node_obj_from_obj(zend_object *obj) { + return (dom_object_namespace_node*)((char*)(obj) - XtOffsetOf(dom_object_namespace_node, dom.std)); +} + #include "domexception.h" dom_object *dom_object_get_data(xmlNodePtr obj); @@ -110,6 +122,7 @@ int dom_check_qname(char *qname, char **localname, char **prefix, int uri_len, i xmlNsPtr dom_get_ns(xmlNodePtr node, char *uri, int *errorcode, char *prefix); void dom_set_old_ns(xmlDoc *doc, xmlNs *ns); void dom_reconcile_ns(xmlDocPtr doc, xmlNodePtr nodep); +void dom_reconcile_ns_list(xmlDocPtr doc, xmlNodePtr nodep, xmlNodePtr last); xmlNsPtr dom_get_nsdecl(xmlNode *node, xmlChar *localName); void dom_normalize (xmlNodePtr nodep); xmlNode *dom_get_elements_by_tag_name_ns_raw(xmlNodePtr nodep, char *ns, char *local, int *cur, int index); @@ -125,12 +138,23 @@ xmlNode *php_dom_libxml_hash_iter(xmlHashTable *ht, int index); xmlNode *php_dom_libxml_notation_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); +xmlNodePtr php_dom_create_fake_namespace_decl(xmlNodePtr nodep, xmlNsPtr original, zval *return_value, dom_object *parent_intern); void dom_parent_node_prepend(dom_object *context, zval *nodes, int nodesc); void dom_parent_node_append(dom_object *context, zval *nodes, int nodesc); void dom_parent_node_after(dom_object *context, zval *nodes, int nodesc); void dom_parent_node_before(dom_object *context, zval *nodes, int nodesc); void dom_child_node_remove(dom_object *context); +void dom_child_replace_with(dom_object *context, zval *nodes, int nodesc); + +/* nodemap and nodelist APIs */ +xmlNodePtr php_dom_named_node_map_get_named_item(dom_nnodemap_object *objmap, const char *named, bool may_transform); +void php_dom_named_node_map_get_named_item_into_zval(dom_nnodemap_object *objmap, const char *named, zval *return_value); +xmlNodePtr php_dom_named_node_map_get_item(dom_nnodemap_object *objmap, zend_long index); +void php_dom_named_node_map_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); +void php_dom_nodelist_get_item_into_zval(dom_nnodemap_object *objmap, zend_long index, zval *return_value); +int php_dom_get_namednodemap_length(dom_object *obj); +int php_dom_get_nodelist_length(dom_object *obj); #define DOM_GET_OBJ(__ptr, __id, __prtype, __intern) { \ __intern = Z_DOMOBJ_P(__id); \ diff --git a/ext/dom/php_dom.stub.php b/ext/dom/php_dom.stub.php index 472deb3df3d7a..2f26067d1eafa 100644 --- a/ext/dom/php_dom.stub.php +++ b/ext/dom/php_dom.stub.php @@ -467,7 +467,7 @@ public function count(): int {} public function getIterator(): Iterator {} - /** @return DOMNode|DOMNameSpaceNode|null */ + /** @return DOMElement|DOMNode|DOMNameSpaceNode|null */ public function item(int $index) {} } diff --git a/ext/dom/php_dom_arginfo.h b/ext/dom/php_dom_arginfo.h index 4edb4cf2bb15c..d7c6c4496e90d 100644 --- a/ext/dom/php_dom_arginfo.h +++ b/ext/dom/php_dom_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 8365be2868e4932ea74f8eb2d4ce840117d48deb */ + * Stub hash: 4570a3d2e6a74946b0f12353b1136922a2e77072 */ ZEND_BEGIN_ARG_WITH_RETURN_OBJ_INFO_EX(arginfo_dom_import_simplexml, 0, 1, DOMElement, 0) ZEND_ARG_TYPE_INFO(0, node, IS_OBJECT, 0) diff --git a/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt b/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt new file mode 100644 index 0000000000000..888d1ef9b8057 --- /dev/null +++ b/ext/dom/tests/DOMDocument_getElementsByTagNameNS_match_any_namespace.phpt @@ -0,0 +1,82 @@ +--TEST-- +DOMDocument::getElementsByTagNameNS() match any namespace +--EXTENSIONS-- +dom +--FILE-- + + +Books of the other guy.. + + + + xinclude: book.xml not found + + + + This is another namespace + + + +EOD; +$dom = new DOMDocument; + +// load the XML string defined above +$dom->loadXML($xml); + +function test($namespace, $local) { + global $dom; + $namespace_str = $namespace !== NULL ? "'$namespace'" : "null"; + echo "-- getElementsByTagNameNS($namespace_str, '$local') --\n"; + foreach ($dom->getElementsByTagNameNS($namespace, $local) as $element) { + echo 'local name: \'', $element->localName, '\', prefix: \'', $element->prefix, "'\n"; + } +} + +// Should *also* include objects even without a namespace +test(null, '*'); +// Should *also* include objects even without a namespace +test('*', '*'); +// Should *only* include objects without a namespace +test('', '*'); +// Should *only* include objects with the specified namespace +test('http://www.w3.org/2001/XInclude', '*'); +// Should not give any output +test('', 'fallback'); +// Should not give any output, because the null namespace is the same as the empty namespace +test(null, 'fallback'); +// Should only output the include from the empty namespace +test(null, 'include'); + +?> +--EXPECT-- +-- getElementsByTagNameNS(null, '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('*', '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'include', prefix: 'xi' +local name: 'fallback', prefix: 'xi' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('', '*') -- +local name: 'chapter', prefix: '' +local name: 'title', prefix: '' +local name: 'para', prefix: '' +local name: 'error', prefix: '' +local name: 'include', prefix: '' +-- getElementsByTagNameNS('http://www.w3.org/2001/XInclude', '*') -- +local name: 'include', prefix: 'xi' +local name: 'fallback', prefix: 'xi' +-- getElementsByTagNameNS('', 'fallback') -- +-- getElementsByTagNameNS(null, 'fallback') -- +-- getElementsByTagNameNS(null, 'include') -- +local name: 'include', prefix: '' diff --git a/ext/dom/tests/DOMElement_append_hierarchy_test.phpt b/ext/dom/tests/DOMElement_append_hierarchy_test.phpt new file mode 100644 index 0000000000000..2d70b10fe9f70 --- /dev/null +++ b/ext/dom/tests/DOMElement_append_hierarchy_test.phpt @@ -0,0 +1,89 @@ +--TEST-- +DOMElement::append() with hierarchy changes and errors +--EXTENSIONS-- +dom +--FILE-- +loadXML('

helloworld

'); + +echo "-- Append hello with world --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->append($b_world); +var_dump($dom->saveHTML()); + +echo "-- Append hello with world's child --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->append($b_world->firstChild); +var_dump($dom->saveHTML()); + +echo "-- Append world's child with hello --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_world->firstChild->append($b_hello); +var_dump($dom->saveHTML()); + +echo "-- Append hello with itself --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +try { + $b_hello->append($b_hello); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append world's i tag with the parent --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +try { + $b_world->firstChild->append($b_world); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append from another document --\n"; +$dom = clone $dom_original; +$dom2 = new DOMDocument; +$dom2->loadXML('

other

'); +try { + $dom->firstChild->firstChild->prepend($dom2->firstChild); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom2->saveHTML()); +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +-- Append hello with world -- +string(39) "

helloworld

+" +-- Append hello with world's child -- +string(39) "

helloworld

+" +-- Append world's child with hello -- +string(39) "

worldhello

+" +-- Append hello with itself -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append world's i tag with the parent -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append from another document -- +Wrong Document Error +string(13) "

other

+" +string(39) "

helloworld

+" diff --git a/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt b/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt new file mode 100644 index 0000000000000..4d9cf24a61828 --- /dev/null +++ b/ext/dom/tests/DOMElement_prepend_hierarchy_test.phpt @@ -0,0 +1,89 @@ +--TEST-- +DOMElement::prepend() with hierarchy changes and errors +--EXTENSIONS-- +dom +--FILE-- +loadXML('

helloworld

'); + +echo "-- Prepend hello with world --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->prepend($b_world); +var_dump($dom->saveHTML()); + +echo "-- Prepend hello with world's child --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_hello->prepend($b_world->firstChild); +var_dump($dom->saveHTML()); + +echo "-- Prepend world's child with hello --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +$b_world->firstChild->prepend($b_hello); +var_dump($dom->saveHTML()); + +echo "-- Prepend hello with itself --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +try { + $b_hello->prepend($b_hello); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Prepend world's i tag with the parent --\n"; +$dom = clone $dom_original; +$b_hello = $dom->firstChild->firstChild; +$b_world = $b_hello->nextSibling; +try { + $b_world->firstChild->prepend($b_world); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom->saveHTML()); + +echo "-- Append from another document --\n"; +$dom = clone $dom_original; +$dom2 = new DOMDocument; +$dom2->loadXML('

other

'); +try { + $dom->firstChild->firstChild->prepend($dom2->firstChild); +} catch (\DOMException $e) { + echo $e->getMessage(), "\n"; +} +var_dump($dom2->saveHTML()); +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +-- Prepend hello with world -- +string(39) "

worldhello

+" +-- Prepend hello with world's child -- +string(39) "

worldhello

+" +-- Prepend world's child with hello -- +string(39) "

helloworld

+" +-- Prepend hello with itself -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Prepend world's i tag with the parent -- +Hierarchy Request Error +string(39) "

helloworld

+" +-- Append from another document -- +Wrong Document Error +string(13) "

other

+" +string(39) "

helloworld

+" diff --git a/ext/dom/tests/bug47530.phpt b/ext/dom/tests/bug47530.phpt new file mode 100644 index 0000000000000..0fb990e0e7bff --- /dev/null +++ b/ext/dom/tests/bug47530.phpt @@ -0,0 +1,152 @@ +--TEST-- +Bug #47530 (Importing objects into document fragments creates bogus "default" namespace) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + $root = $doc->documentElement; + $frag = $doc->createDocumentFragment(); + $frag->appendChild($doc->importNode($root->firstChild)); + $root->appendChild($frag); + echo $doc->saveXML(); +} + +function test_document_fragment_without_import() { + $doc = new DOMDocument; + $doc->loadXML(''); + $frag = $doc->createDocumentFragment(); + $frag->appendChild($doc->createElementNS('https://php.net/bar', 'bar')); + $frag->appendChild($doc->createElementNS('', 'bar')); + $element = $doc->documentElement->firstChild; + $element->appendChild($frag); + unset($frag); // Free fragment, should not break getting the namespaceURI below + echo $doc->saveXML(); + unset($doc); + var_dump($element->firstChild->tagName); + var_dump($element->firstChild->namespaceURI); +} + +function test_document_import() { + $xml = << + +
+

Test-Text

+
+
+XML; + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $dom2 = new DOMDocument(); + $importedNode = $dom2->importNode($dom->documentElement, true); + $dom2->appendChild($importedNode); + + echo $dom2->saveXML(); +} + +function test_partial_document_import() { + $xml = << + +
+

Test-Text

+ More test text + Even more test text +
+
+XML; + + $dom = new DOMDocument(); + $dom->loadXML($xml); + + $dom2 = new DOMDocument(); + $dom2->loadXML(''); + $importedNode = $dom2->importNode($dom->documentElement, true); + $dom2->documentElement->appendChild($importedNode); + + // Freeing the original document shouldn't break the other document + unset($importedNode); + unset($dom); + + echo $dom2->saveXML(); +} + +function test_document_import_with_attributes() { + $dom = new DOMDocument(); + $dom->loadXML('

'); + $dom2 = new DOMDocument(); + $dom2->loadXML('
'); + $dom2->documentElement->appendChild($dom2->importNode($dom->documentElement->firstChild)); + echo $dom2->saveXML(), "\n"; + + $dom2->documentElement->firstChild->appendChild($dom2->importNode($dom->documentElement->firstChild->nextSibling)); + echo $dom2->saveXML(), "\n"; +} + +function test_appendChild_with_shadowing() { + $dom = new DOMDocument(); + $dom->loadXML(''); + + $a = $dom->documentElement->firstElementChild; + $b = $a->nextSibling; + $b->remove(); + $a->appendChild($b); + + echo $dom->saveXML(), "\n"; +} + +echo "-- Test document fragment with import --\n"; +test_document_fragment_with_import(); +echo "-- Test document fragment without import --\n"; +test_document_fragment_without_import(); +echo "-- Test document import --\n"; +test_document_import(); +echo "-- Test partial document import --\n"; +test_partial_document_import(); +echo "-- Test document import with attributes --\n"; +test_document_import_with_attributes(); +echo "-- Test appendChild with shadowing --\n"; +test_appendChild_with_shadowing(); + +?> +--EXPECT-- +-- Test document fragment with import -- + + +-- Test document fragment without import -- + + +string(7) "foo:bar" +string(19) "https://php.net/bar" +-- Test document import -- + + +
+

Test-Text

+
+
+-- Test partial document import -- + + +
+

Test-Text

+ More test text + Even more test text +
+
+-- Test document import with attributes -- + +

+ + +

+ +-- Test appendChild with shadowing -- + +
diff --git a/ext/dom/tests/bug47847.phpt b/ext/dom/tests/bug47847.phpt new file mode 100644 index 0000000000000..324bf9508d5ce --- /dev/null +++ b/ext/dom/tests/bug47847.phpt @@ -0,0 +1,27 @@ +--TEST-- +Bug #47847 (importNode loses the namespace of an XML element) +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + + + + +XML); + +$aDOM = new DOMDocument(); +$imported = $aDOM->importNode($fromdom->documentElement->firstElementChild, true); +$aDOM->appendChild($imported); + +echo $aDOM->saveXML(); +?> +--EXPECT-- + + + + diff --git a/ext/dom/tests/bug55294.phpt b/ext/dom/tests/bug55294.phpt new file mode 100644 index 0000000000000..19534955029bc --- /dev/null +++ b/ext/dom/tests/bug55294.phpt @@ -0,0 +1,29 @@ +--TEST-- +Bug #55294 (DOMDocument::importNode shifts namespaces when "default" namespace exists) +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + + + +EOXML +); + +$bDOM = new DOMDocument(); +$node = $bDOM->importNode($aDOM->getElementsByTagNameNS('http://example.com/A', 'B')->item(0), true); +$bDOM->appendChild($node); + +echo $bDOM->saveXML(), "\n"; + +?> +--EXPECT-- + + + + diff --git a/ext/dom/tests/bug67440.phpt b/ext/dom/tests/bug67440.phpt new file mode 100644 index 0000000000000..3e30f69b9ae4d --- /dev/null +++ b/ext/dom/tests/bug67440.phpt @@ -0,0 +1,151 @@ +--TEST-- +Bug #67440 (append_node of a DOMDocumentFragment does not reconcile namespaces) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + $fragment = $document->createDocumentFragment(); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '1')); + $fragment->appendChild($document->createTextNode("\n")); + $fragment->appendChild($document->createElementNS('http://example/ns', 'myns:childNode', '2')); + $fragment->appendChild($document->createTextNode("\n")); + return array($document, $fragment); +} + +function case1($method) { + list($document, $fragment) = createDocument(); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case2($method) { + list($document, $fragment) = createDocument(); + $childNodes = iterator_to_array($fragment->childNodes); + foreach ($childNodes as $childNode) { + $document->documentElement->{$method}($childNode); + } + echo $document->saveXML(); +} + +function case3($method) { + list($document, $fragment) = createDocument(); + $fragment->removeChild($fragment->firstChild); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +function case4($method) { + list($document, $fragment) = createDocument(); + $fragment->childNodes[1]->appendChild($document->createElementNS('http://example/ns2', 'myns2:childNode', '3')); + $document->documentElement->{$method}($fragment); + echo $document->saveXML(); +} + +echo "== appendChild ==\n"; +echo "-- fragment to document element --\n"; case1('appendChild'); echo "\n"; +echo "-- children manually document element --\n"; case2('appendChild'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('appendChild'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('appendChild'); echo "\n"; + +echo "== insertBefore ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +echo "== insertAfter ==\n"; +echo "-- fragment to document element --\n"; case1('insertBefore'); echo "\n"; +echo "-- children manually document element --\n"; case2('insertBefore'); echo "\n"; +echo "-- fragment to document where first element is not a text node --\n"; case3('insertBefore'); echo "\n"; +echo "-- fragment with namespace declarations in children --\n"; case4('insertBefore'); echo "\n"; + +?> +--EXPECT-- +== appendChild == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertBefore == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + + +== insertAfter == +-- fragment to document element -- + + +1 +2 + + +-- children manually document element -- + + +1 +2 + + +-- fragment to document where first element is not a text node -- + +1 +2 + + +-- fragment with namespace declarations in children -- + + +13 +2 + diff --git a/ext/dom/tests/bug67949.phpt b/ext/dom/tests/bug67949.phpt index 394c9888bd352..270beb02b494c 100644 --- a/ext/dom/tests/bug67949.phpt +++ b/ext/dom/tests/bug67949.phpt @@ -86,7 +86,7 @@ bool(true) string(4) "data" string(4) "test" testing read_dimension with null offset -Cannot access node list without offset +Cannot access DOMNodeList without offset testing attribute access string(4) "href" ==DONE== diff --git a/ext/dom/tests/bug70359.phpt b/ext/dom/tests/bug70359.phpt new file mode 100644 index 0000000000000..b0a5ae57a3232 --- /dev/null +++ b/ext/dom/tests/bug70359.phpt @@ -0,0 +1,83 @@ +--TEST-- +Bug #70359 (print_r() on DOMAttr causes Segfault in php_libxml_node_free_list()) +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + +XML); +$spaceNode = $dom->documentElement->getAttributeNode('xmlns'); +print_r($spaceNode); + +echo "-- Test with parent and non-ns attribute --\n"; + +$dom = new DOMDocument(); +$dom->loadXML(<< + + + +XML); +$spaceNode = $dom->documentElement->firstElementChild->getAttributeNode('myattrib'); +var_dump($spaceNode->nodeType); +var_dump($spaceNode->nodeValue); + +$dom->documentElement->firstElementChild->remove(); +try { + print_r($spaceNode->parentNode); +} catch (\Error $e) { + echo $e->getMessage(), "\n"; +} + +echo "-- Test with parent and ns attribute --\n"; + +$dom = new DOMDocument(); +$dom->loadXML(<< + + + +XML); +$spaceNode = $dom->documentElement->firstElementChild->getAttributeNode('xmlns:xsi'); +print_r($spaceNode); + +$dom->documentElement->firstElementChild->remove(); +var_dump($spaceNode->parentNode->nodeName); // Shouldn't crash + +?> +--EXPECT-- +-- Test without parent -- +DOMNameSpaceNode Object +( + [nodeName] => xmlns + [nodeValue] => http://www.sitemaps.org/schemas/sitemap/0.9 + [nodeType] => 18 + [prefix] => + [localName] => xmlns + [namespaceURI] => http://www.sitemaps.org/schemas/sitemap/0.9 + [ownerDocument] => (object value omitted) + [parentNode] => (object value omitted) +) +-- Test with parent and non-ns attribute -- +int(2) +string(3) "bar" +Couldn't fetch DOMAttr. Node no longer exists +-- Test with parent and ns attribute -- +DOMNameSpaceNode Object +( + [nodeName] => xmlns:xsi + [nodeValue] => fooooooooooooooooooooo + [nodeType] => 18 + [prefix] => xsi + [localName] => xsi + [namespaceURI] => fooooooooooooooooooooo + [ownerDocument] => (object value omitted) + [parentNode] => (object value omitted) +) +string(3) "url" diff --git a/ext/dom/tests/bug77686.phpt b/ext/dom/tests/bug77686.phpt new file mode 100644 index 0000000000000..ddd7c3364786c --- /dev/null +++ b/ext/dom/tests/bug77686.phpt @@ -0,0 +1,40 @@ +--TEST-- +Bug #77686 (Removed elements are still returned by getElementById) +--EXTENSIONS-- +dom +--FILE-- +loadHTML('before
hello
after'); +$body = $doc->getElementById('x'); +$div = $doc->getElementById('y'); +var_dump($doc->getElementById('y')->textContent); + +// Detached from document, should not find it anymore +$body->removeChild($div); +var_dump($doc->getElementById('y')); + +// Added again, should find it +$body->appendChild($div); +var_dump($doc->getElementById('y')->textContent); + +// Should find root element without a problem +var_dump($doc->getElementById('htmlelement')->textContent); + +// Created element but not yet attached, should not find it before it is added +$new_element = $doc->createElement('p'); +$new_element->textContent = 'my new text'; +$new_element->setAttribute('id', 'myp'); +var_dump($doc->getElementById('myp')); +$body->appendChild($new_element); +var_dump($doc->getElementById('myp')->textContent); + +?> +--EXPECT-- +string(5) "hello" +NULL +string(5) "hello" +string(16) "beforeafterhello" +NULL +string(11) "my new text" diff --git a/ext/dom/tests/bug78577.phpt b/ext/dom/tests/bug78577.phpt new file mode 100644 index 0000000000000..2631efc1e206c --- /dev/null +++ b/ext/dom/tests/bug78577.phpt @@ -0,0 +1,33 @@ +--TEST-- +Bug #78577 (Crash in DOMNameSpace debug info handlers) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +$attr = $doc->documentElement->getAttributeNode('xmlns'); +var_dump($attr); + +?> +--EXPECT-- +object(DOMNameSpaceNode)#3 (8) { + ["nodeName"]=> + string(5) "xmlns" + ["nodeValue"]=> + string(19) "http://php.net/test" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(0) "" + ["localName"]=> + string(5) "xmlns" + ["namespaceURI"]=> + string(19) "http://php.net/test" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" +} diff --git a/ext/dom/tests/bug80332_1.phpt b/ext/dom/tests/bug80332_1.phpt new file mode 100644 index 0000000000000..f0484399781e2 --- /dev/null +++ b/ext/dom/tests/bug80332_1.phpt @@ -0,0 +1,84 @@ +--TEST-- +Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNamedNodeMap variation +--EXTENSIONS-- +dom +--FILE-- +loadHTML(''); + +$x = new DOMXPath($doc); +$span = $x->query('//span')[0]; + +print "Node name: {$span->nodeName}\n"; + +function test($span, $key) { + $key_formatted = match ($key) { + false => 'false', + true => 'true', + null => 'null', + default => is_string($key) ? "'$key'" : $key, + }; + echo "Attribute [{$key_formatted}] name: ", $span->attributes[$key]->nodeName ?? '/', "\n"; + echo "Attribute [{$key_formatted}] value: ", $span->attributes[$key]->nodeValue ?? '/', "\n"; + echo "Attribute [{$key_formatted}] isset: ", isset($span->attributes[$key]) ? "yes" : "no", "\n"; + echo "\n"; +} + +test($span, 0); +test($span, false); +test($span, true); +test($span, null); +test($span, 'attr2'); +// This one should fail because there is no 'hi' attribute +test($span, 'hi'); +test($span, '0'); +test($span, '0.5'); +test($span, '1'); +// This one should fail because it's out of bounds +test($span, '2147483647'); + +?> +--EXPECT-- +Node name: span +Attribute [0] name: attr1 +Attribute [0] value: value1 +Attribute [0] isset: yes + +Attribute [false] name: attr1 +Attribute [false] value: value1 +Attribute [false] isset: yes + +Attribute [true] name: attr2 +Attribute [true] value: value2 +Attribute [true] isset: yes + +Attribute [null] name: attr1 +Attribute [null] value: value1 +Attribute [null] isset: yes + +Attribute ['attr2'] name: attr2 +Attribute ['attr2'] value: value2 +Attribute ['attr2'] isset: yes + +Attribute ['hi'] name: / +Attribute ['hi'] value: / +Attribute ['hi'] isset: no + +Attribute ['0'] name: attr1 +Attribute ['0'] value: value1 +Attribute ['0'] isset: yes + +Attribute ['0.5'] name: attr1 +Attribute ['0.5'] value: value1 +Attribute ['0.5'] isset: yes + +Attribute ['1'] name: attr2 +Attribute ['1'] value: value2 +Attribute ['1'] isset: yes + +Attribute ['2147483647'] name: / +Attribute ['2147483647'] value: / +Attribute ['2147483647'] isset: no diff --git a/ext/dom/tests/bug80332_2.phpt b/ext/dom/tests/bug80332_2.phpt new file mode 100644 index 0000000000000..f1793e43b5931 --- /dev/null +++ b/ext/dom/tests/bug80332_2.phpt @@ -0,0 +1,47 @@ +--TEST-- +Bug #80332 (Completely broken array access functionality with DOMNamedNodeMap) - DOMNodeList variation +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +$list = $doc->getElementsByTagName('strong'); + +function test($list, $key) { + $key_formatted = match ($key) { + false => 'false', + true => 'true', + null => 'null', + default => is_string($key) ? "'$key'" : $key, + }; + echo "list[$key_formatted] id attribute: ", $list[$key] ? $list[$key]->getAttribute('id') : '/', "\n"; +} + +test($list, 0); +test($list, false); +test($list, true); +test($list, null); +test($list, '0'); +test($list, '0.5'); +test($list, '1'); +// These two should fail because there's no named lookup possible here +test($list, 'attr2'); +test($list, 'hi'); +// This one should fail because it's out of bounds +test($list, '2147483647'); + +?> +--EXPECT-- +list[0] id attribute: 1 +list[false] id attribute: 1 +list[true] id attribute: 2 +list[null] id attribute: 1 +list['0'] id attribute: 1 +list['0.5'] id attribute: 1 +list['1'] id attribute: 2 +list['attr2'] id attribute: / +list['hi'] id attribute: / +list['2147483647'] id attribute: / diff --git a/ext/dom/tests/bug80602.phpt b/ext/dom/tests/bug80602.phpt index 9f041f686f516..844d829cb08d0 100644 --- a/ext/dom/tests/bug80602.phpt +++ b/ext/dom/tests/bug80602.phpt @@ -8,84 +8,84 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($doc->documentElement->lastChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target, $doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -93,19 +93,19 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -113,21 +113,21 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before('bar', $target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, 'bar', $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->before($target, $doc->documentElement->lastChild, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -136,43 +136,43 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before('bar', $doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, 'bar', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->before($doc->documentElement->firstChild, $target, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL; ?> --EXPECTF-- -foo -foo -foo -foo -foo -foo -foo -foo -barbazfoo -foobarbaz -foobarbaz -barfoobaz -barbazfoo -foobarbaz -foobarbaz -foobarbaz -barfoo -foobar -foobar -barfoo -foobar -foobar +1 foo +2 foo +3 foo +4 foo +5 foo +6 foo +7 foo +8 foo +9 barbazfoo +10 foobarbaz +11 foobarbaz +12 barfoobaz +13 barbazfoo +14 foobarbaz +15 foobarbaz +16 foobarbaz +17 barfoo +18 foobar +19 foobar +20 barfoo +21 foobar +22 foobar diff --git a/ext/dom/tests/bug80602_2.phpt b/ext/dom/tests/bug80602_2.phpt index 1151417c0f845..7c5070f51424c 100644 --- a/ext/dom/tests/bug80602_2.phpt +++ b/ext/dom/tests/bug80602_2.phpt @@ -8,84 +8,84 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "1 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "2 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "3 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "4 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "5 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($doc->documentElement->lastChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "6 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target, $doc->documentElement->firstChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "7 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "8 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "9 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "10 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "11 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "12 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "13 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -93,19 +93,19 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($target, 'bar','baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "14 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', $target, 'baz'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "15 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', 'baz', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "16 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -113,21 +113,21 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after('bar', $target, $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "17 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, 'bar', $doc->documentElement->lastChild); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "18 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->firstChild; $target->after($target, $doc->documentElement->lastChild, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "19 ", $doc->saveXML($doc->documentElement).PHP_EOL; @@ -136,43 +136,43 @@ $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after('bar', $doc->documentElement->firstChild, $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "20 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, 'bar', $target); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "21 ", $doc->saveXML($doc->documentElement).PHP_EOL; $doc = new \DOMDocument(); $doc->loadXML('foo'); $target = $doc->documentElement->lastChild; $target->after($doc->documentElement->firstChild, $target, 'bar'); -echo $doc->saveXML($doc->documentElement).PHP_EOL; +echo "22 ", $doc->saveXML($doc->documentElement).PHP_EOL; ?> --EXPECTF-- -foo -foo -foo -foo -foo -foo -foo -foo -foobarbaz -foobarbaz -foobarbaz -barfoobaz -barbazfoo -foobarbaz -foobarbaz -foobarbaz -barfoo -foobar -foobar -barfoo -foobar -foobar +1 foo +2 foo +3 foo +4 foo +5 foo +6 foo +7 foo +8 foo +9 foobarbaz +10 foobarbaz +11 foobarbaz +12 barfoobaz +13 barbazfoo +14 foobarbaz +15 foobarbaz +16 foobarbaz +17 barfoo +18 foobar +19 foobar +20 barfoo +21 foobar +22 foobar diff --git a/ext/dom/tests/bug80602_3.phpt b/ext/dom/tests/bug80602_3.phpt new file mode 100644 index 0000000000000..f9bf67e778da5 --- /dev/null +++ b/ext/dom/tests/bug80602_3.phpt @@ -0,0 +1,120 @@ +--TEST-- +Bug #80602 (Segfault when using DOMChildNode::before()) - use-after-free variation +--FILE-- +loadXML('foo'); +$target = $doc->documentElement->lastChild; +$target->before('bar', $doc->documentElement->firstChild, 'baz'); +echo $doc->saveXML($doc->documentElement), "\n"; +var_dump($target); + +$doc = new \DOMDocument(); +$doc->loadXML('foo'); +$target = $doc->documentElement->lastChild; +// Note: after instead of before +$target->after('bar', $doc->documentElement->firstChild, 'baz'); +echo $doc->saveXML($doc->documentElement), "\n"; +var_dump($target); + +?> +--EXPECTF-- +barfoobaz +object(DOMElement)#3 (23) { + ["schemaTypeInfo"]=> + NULL + ["tagName"]=> + string(4) "last" + ["firstElementChild"]=> + NULL + ["lastElementChild"]=> + NULL + ["childElementCount"]=> + int(0) + ["previousElementSibling"]=> + NULL + ["nextElementSibling"]=> + NULL + ["nodeName"]=> + string(4) "last" + ["nodeValue"]=> + string(0) "" + ["nodeType"]=> + int(1) + ["parentNode"]=> + string(22) "(object value omitted)" + ["childNodes"]=> + string(22) "(object value omitted)" + ["firstChild"]=> + NULL + ["lastChild"]=> + NULL + ["previousSibling"]=> + string(22) "(object value omitted)" + ["nextSibling"]=> + NULL + ["attributes"]=> + string(22) "(object value omitted)" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["namespaceURI"]=> + NULL + ["prefix"]=> + string(0) "" + ["localName"]=> + string(4) "last" + ["baseURI"]=> + string(%d) %s + ["textContent"]=> + string(0) "" +} +barfoobaz +object(DOMElement)#2 (23) { + ["schemaTypeInfo"]=> + NULL + ["tagName"]=> + string(4) "last" + ["firstElementChild"]=> + NULL + ["lastElementChild"]=> + NULL + ["childElementCount"]=> + int(0) + ["previousElementSibling"]=> + NULL + ["nextElementSibling"]=> + NULL + ["nodeName"]=> + string(4) "last" + ["nodeValue"]=> + string(0) "" + ["nodeType"]=> + int(1) + ["parentNode"]=> + string(22) "(object value omitted)" + ["childNodes"]=> + string(22) "(object value omitted)" + ["firstChild"]=> + NULL + ["lastChild"]=> + NULL + ["previousSibling"]=> + NULL + ["nextSibling"]=> + string(22) "(object value omitted)" + ["attributes"]=> + string(22) "(object value omitted)" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["namespaceURI"]=> + NULL + ["prefix"]=> + string(0) "" + ["localName"]=> + string(4) "last" + ["baseURI"]=> + string(%d) %s + ["textContent"]=> + string(0) "" +} diff --git a/ext/dom/tests/bug80602_4.phpt b/ext/dom/tests/bug80602_4.phpt new file mode 100644 index 0000000000000..a1df8d10caa31 --- /dev/null +++ b/ext/dom/tests/bug80602_4.phpt @@ -0,0 +1,33 @@ +--TEST-- +Bug #80602 (Segfault when using DOMChildNode::before()) - after text merge variation +--FILE-- +loadXML('foobar'); +$foo = $doc->firstChild->firstChild; +$bar = $doc->firstChild->lastChild; + +$foo->after($bar); + +var_dump($doc->saveXML()); + +$foo->nodeValue = "x"; + +var_dump($doc->saveXML()); + +$bar->nodeValue = "y"; + +var_dump($doc->saveXML()); + +?> +--EXPECT-- +string(43) " +foobar +" +string(41) " +xbar +" +string(39) " +xy +" diff --git a/ext/dom/tests/bug81642.phpt b/ext/dom/tests/bug81642.phpt new file mode 100644 index 0000000000000..7bf3dde50588e --- /dev/null +++ b/ext/dom/tests/bug81642.phpt @@ -0,0 +1,49 @@ +--TEST-- +Bug #81642 (DOMChildNode::replaceWith() bug when replacing a node with itself) +--EXTENSIONS-- +dom +--FILE-- +appendChild($target = $doc->createElement('test')); +$target->replaceWith($target); +var_dump($doc->saveXML()); + +// Replace with itself + another element +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith($target, $doc->createElement('foo')); +var_dump($doc->saveXML()); + +// Replace with text node +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith($target, 'foo'); +var_dump($doc->saveXML()); + +// Replace with text node variant 2 +$doc = new DOMDocument(); +$doc->appendChild($target = $doc->createElement('test')); +$target->replaceWith('bar', $target, 'foo'); +var_dump($doc->saveXML()); + +?> +--EXPECT-- +string(30) " + +" +string(37) " + + +" +string(34) " + +foo +" +string(38) " +bar + +foo +" diff --git a/ext/dom/tests/bug_lifetime_parentNode_getAttributeNodeNS.phpt b/ext/dom/tests/bug_lifetime_parentNode_getAttributeNodeNS.phpt new file mode 100644 index 0000000000000..3c53e08d4db76 --- /dev/null +++ b/ext/dom/tests/bug_lifetime_parentNode_getAttributeNodeNS.phpt @@ -0,0 +1,20 @@ +--TEST-- +Lifetime issue with parentNode on getAttributeNodeNS() +--EXTENSIONS-- +dom +--FILE-- + + + +'; + +$xml=new DOMDocument(); +$xml->loadXML($xmlString); +$ns2 = $xml->documentElement->getAttributeNodeNS("http://www.w3.org/2000/xmlns/", "ns2"); +$ns2->parentNode->remove(); +var_dump($ns2->parentNode->localName); + +?> +--EXPECT-- +string(4) "root" diff --git a/ext/dom/tests/clone_nodes.phpt b/ext/dom/tests/clone_nodes.phpt new file mode 100644 index 0000000000000..1841c702caf8d --- /dev/null +++ b/ext/dom/tests/clone_nodes.phpt @@ -0,0 +1,72 @@ +--TEST-- +Clone nodes +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); + +$attr = $doc->documentElement->getAttributeNode('xmlns'); +var_dump($attr); + +$attrClone = clone $attr; +var_dump($attrClone->nodeValue); +var_dump($attrClone->parentNode->nodeName); + +unset($doc); +unset($attr); + +var_dump($attrClone->nodeValue); +var_dump($attrClone->parentNode->nodeName); + +echo "-- Clone DOMNode --\n"; + +$doc = new DOMDocument; +$doc->loadXML(''); + +$bar = $doc->documentElement->firstChild; +$barClone = clone $bar; +$bar->remove(); +unset($bar); + +var_dump($barClone->nodeName); + +$doc->firstElementChild->remove(); +unset($doc); + +var_dump($barClone->nodeName); +var_dump($barClone->parentNode); + +?> +--EXPECT-- +-- Clone DOMNameSpaceNode -- +object(DOMNameSpaceNode)#3 (8) { + ["nodeName"]=> + string(5) "xmlns" + ["nodeValue"]=> + string(19) "http://php.net/test" + ["nodeType"]=> + int(18) + ["prefix"]=> + string(0) "" + ["localName"]=> + string(5) "xmlns" + ["namespaceURI"]=> + string(19) "http://php.net/test" + ["ownerDocument"]=> + string(22) "(object value omitted)" + ["parentNode"]=> + string(22) "(object value omitted)" +} +string(19) "http://php.net/test" +string(3) "foo" +string(19) "http://php.net/test" +string(3) "foo" +-- Clone DOMNode -- +string(3) "bar" +string(3) "bar" +NULL diff --git a/ext/dom/tests/gh10234.phpt b/ext/dom/tests/gh10234.phpt new file mode 100644 index 0000000000000..5edc8fc6c1ff1 --- /dev/null +++ b/ext/dom/tests/gh10234.phpt @@ -0,0 +1,93 @@ +--TEST-- +GH-10234 (Setting DOMAttr::textContent results in an empty attribute value.) +--EXTENSIONS-- +dom +--FILE-- +loadXML(''); +$attribute = $document->documentElement->getAttributeNode('attribute'); + +echo "-- Attribute tests --\n"; + +var_dump($document->saveHTML()); +var_dump($attribute->textContent); + +$attribute->textContent = 'new value'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'hello & world'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'hi'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = 'quote "test"'; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = "quote 'test'"; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +$attribute->textContent = "quote '\"test\"'"; +var_dump($attribute->textContent); +var_dump($document->saveHTML()); + +echo "-- Document element tests --\n"; + +$document->documentElement->textContent = 'hello & world'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = 'hi'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = 'quote "test"'; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); + +$document->documentElement->textContent = "quote 'test'"; +var_dump($document->documentElement->textContent); +var_dump($document->saveHTML()); +?> +--EXPECT-- +-- Attribute tests -- +string(38) " +" +string(5) "value" +string(9) "new value" +string(42) " +" +string(13) "hello & world" +string(50) " +" +string(9) "hi" +string(54) " +" +string(12) "quote "test"" +string(45) " +" +string(12) "quote 'test'" +string(45) " +" +string(14) "quote '"test"'" +string(57) " +" +-- Document element tests -- +string(13) "hello & world" +string(74) "hello & world +" +string(9) "hi" +string(78) "<b>hi</b> +" +string(12) "quote "test"" +string(69) "quote "test" +" +string(12) "quote 'test'" +string(69) "quote 'test' +" diff --git a/ext/dom/tests/gh11288.phpt b/ext/dom/tests/gh11288.phpt new file mode 100644 index 0000000000000..f70bea80d9085 --- /dev/null +++ b/ext/dom/tests/gh11288.phpt @@ -0,0 +1,67 @@ +--TEST-- +GH-11288 (Error: Couldn't fetch DOMElement introduced in 8.2.6, 8.1.19) +--FILE-- + + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $fragment = $dom->createDocumentFragment(); + $fragment->append(...$span->childNodes); + $span->parentNode?->replaceChild($fragment, $span); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); + +$html = << + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith(...$span->childNodes); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); + +$html = << + +Loremipsum + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith('abc'); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(108) "Loremipsum" +string(108) "Loremipsum" +string(44) "abc" diff --git a/ext/dom/tests/gh11289.phpt b/ext/dom/tests/gh11289.phpt new file mode 100644 index 0000000000000..7771a486bd66b --- /dev/null +++ b/ext/dom/tests/gh11289.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-11289 (DOMException: Not Found Error introduced in 8.2.6, 8.1.19) +--FILE-- + + + +
+ + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$divs = iterator_to_array($dom->getElementsByTagName('div')->getIterator()); +foreach ($divs as $div) { + $fragment = $dom->createDocumentFragment(); + $fragment->appendXML('

Hi!

'); + $div->replaceWith(...$fragment->childNodes); +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(55) "

Hi!

" diff --git a/ext/dom/tests/gh11290.phpt b/ext/dom/tests/gh11290.phpt new file mode 100644 index 0000000000000..2900720301041 --- /dev/null +++ b/ext/dom/tests/gh11290.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-11290 (DOMElement::replaceWith causes crash) +--FILE-- + + + +

Loremipsumdolor

+ + +HTML; + +$dom = new DOMDocument(); +$dom->loadHTML($html); + +$spans = iterator_to_array($dom->getElementsByTagName('span')->getIterator()); +foreach ($spans as $span) { + if ('unwrap_me' === $span->getAttribute('class')) { + $span->replaceWith(...$span->childNodes); + } +} + +var_dump(str_replace("\n", "", $dom->saveHTML())); +?> +--EXPECT-- +string(67) "

Loremipsumdolor

" diff --git a/ext/dom/tests/gh11347.phpt b/ext/dom/tests/gh11347.phpt new file mode 100644 index 0000000000000..189231f925081 --- /dev/null +++ b/ext/dom/tests/gh11347.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-11347 (Memory leak when calling a static method inside an xpath query) +--EXTENSIONS-- +dom +--FILE-- +loadHTML('hello'); +$xpath = new DOMXpath($doc); +$xpath->registerNamespace("php", "http://php.net/xpath"); +$xpath->registerPHPFunctions(); +$xpath->query("//a[php:function('MyClass::dump', string(@href))]"); + +?> +Done +--EXPECT-- +string(15) "https://php.net/" +Done diff --git a/ext/dom/tests/gh9142.phpt b/ext/dom/tests/gh9142.phpt new file mode 100644 index 0000000000000..f72dfa823f38c --- /dev/null +++ b/ext/dom/tests/gh9142.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-9142 (DOMChildNode replaceWith() double-free error when replacing elements not separated by any whitespace) +--FILE-- +OneTwo'; + +($dom = new DOMDocument('1.0', 'UTF-8'))->loadHTML($document); + +foreach ((new DOMXPath($dom))->query('//var') as $var) { + $var->replaceWith($dom->createElement('p', $var->nodeValue)); +} + +var_dump($dom->saveHTML()); + +?> +--EXPECT-- +string(154) " +

One

Two

+" diff --git a/ext/dom/tests/xpath_domnamespacenode.phpt b/ext/dom/tests/xpath_domnamespacenode.phpt index f0bfbed10dda6..97059c18e54da 100644 --- a/ext/dom/tests/xpath_domnamespacenode.phpt +++ b/ext/dom/tests/xpath_domnamespacenode.phpt @@ -17,7 +17,7 @@ var_dump($nodes->item(0)); ?> --EXPECT-- -object(DOMNameSpaceNode)#3 (8) { +object(DOMNameSpaceNode)#4 (8) { ["nodeName"]=> string(9) "xmlns:xml" ["nodeValue"]=> diff --git a/ext/dom/tests/xpath_domnamespacenode_advanced.phpt b/ext/dom/tests/xpath_domnamespacenode_advanced.phpt new file mode 100644 index 0000000000000..bbc49dc54652d --- /dev/null +++ b/ext/dom/tests/xpath_domnamespacenode_advanced.phpt @@ -0,0 +1,75 @@ +--TEST-- +DOMXPath::query() can return DOMNodeList with DOMNameSpaceNode items - advanced variation +--EXTENSIONS-- +dom +--FILE-- +loadXML(<<<'XML' + + Hello PHP! + +XML); + +$xpath = new DOMXPath($dom); +$query = '//namespace::*'; + +echo "-- All namespace attributes --\n"; + +foreach ($xpath->query($query) as $attribute) { + echo $attribute->nodeName . ' = ' . $attribute->nodeValue . PHP_EOL; + var_dump($attribute->parentNode->tagName); +} + +echo "-- All namespace attributes with removal attempt --\n"; + +foreach ($xpath->query($query) as $attribute) { + echo "Before: ", $attribute->parentNode->tagName, "\n"; + // Second & third attempt should fail because it's no longer in the document + try { + $attribute->parentNode->remove(); + } catch (\DOMException $e) { + echo $e->getMessage(), "\n"; + } + // However, it should not cause a use-after-free + echo "After: ", $attribute->parentNode->tagName, "\n"; +} + +?> +--EXPECT-- +-- All namespace attributes -- +xmlns:xml = http://www.w3.org/XML/1998/namespace +string(4) "root" +xmlns:bar = http://example.com/bar +string(4) "root" +xmlns:foo = http://example.com/foo +string(4) "root" +xmlns:xml = http://www.w3.org/XML/1998/namespace +string(5) "child" +xmlns:bar = http://example.com/bar +string(5) "child" +xmlns:foo = http://example.com/foo +string(5) "child" +xmlns:baz = http://example.com/baz +string(5) "child" +-- All namespace attributes with removal attempt -- +Before: root +After: root +Before: root +Not Found Error +After: root +Before: root +Not Found Error +After: root +Before: child +After: child +Before: child +Not Found Error +After: child +Before: child +Not Found Error +After: child +Before: child +Not Found Error +After: child diff --git a/ext/dom/xpath.c b/ext/dom/xpath.c index 876d8b00dae0e..62e11f6b99bfb 100644 --- a/ext/dom/xpath.c +++ b/ext/dom/xpath.c @@ -101,24 +101,18 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, zval child; /* not sure, if we need this... it's copied from xpath.c */ if (node->type == XML_NAMESPACE_DECL) { - xmlNsPtr curns; - xmlNodePtr nsparent; - - nsparent = node->_private; - curns = xmlNewNs(NULL, node->name, NULL); - if (node->children) { - curns->prefix = xmlStrdup((xmlChar *) node->children); - } - if (node->children) { - node = xmlNewDocNode(node->doc, NULL, (xmlChar *) node->children, node->name); - } else { - node = xmlNewDocNode(node->doc, NULL, (xmlChar *) "xmlns", node->name); - } - node->type = XML_NAMESPACE_DECL; - node->parent = nsparent; - node->ns = curns; + xmlNodePtr nsparent = node->_private; + xmlNsPtr original = (xmlNsPtr) node; + + /* Make sure parent dom object exists, so we can take an extra reference. */ + zval parent_zval; /* don't destroy me, my lifetime is transfered to the fake namespace decl */ + php_dom_create_object(nsparent, &parent_zval, &intern->dom); + dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval); + + node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern); + } else { + php_dom_create_object(node, &child, &intern->dom); } - php_dom_create_object(node, &child, &intern->dom); add_next_index_zval(&fci.params[i], &child); } } else { @@ -182,7 +176,7 @@ static void dom_xpath_ext_function_php(xmlXPathParserContextPtr ctxt, int nargs, } cleanup: zend_string_release_ex(callable, 0); - zval_ptr_dtor_str(&fci.function_name); + zval_ptr_dtor_nogc(&fci.function_name); if (fci.param_count > 0) { for (i = 0; i < nargs - 1; i++) { zval_ptr_dtor(&fci.params[i]); @@ -421,24 +415,18 @@ static void php_xpath_eval(INTERNAL_FUNCTION_PARAMETERS, int type) /* {{{ */ zval child; if (node->type == XML_NAMESPACE_DECL) { - xmlNsPtr curns; - xmlNodePtr nsparent; + xmlNodePtr nsparent = node->_private; + xmlNsPtr original = (xmlNsPtr) node; - nsparent = node->_private; - curns = xmlNewNs(NULL, node->name, NULL); - if (node->children) { - curns->prefix = xmlStrdup((xmlChar *) node->children); - } - if (node->children) { - node = xmlNewDocNode(docp, NULL, (xmlChar *) node->children, node->name); - } else { - node = xmlNewDocNode(docp, NULL, (xmlChar *) "xmlns", node->name); - } - node->type = XML_NAMESPACE_DECL; - node->parent = nsparent; - node->ns = curns; + /* Make sure parent dom object exists, so we can take an extra reference. */ + zval parent_zval; /* don't destroy me, my lifetime is transfered to the fake namespace decl */ + php_dom_create_object(nsparent, &parent_zval, &intern->dom); + dom_object *parent_intern = Z_DOMOBJ_P(&parent_zval); + + node = php_dom_create_fake_namespace_decl(nsparent, original, &child, parent_intern); + } else { + php_dom_create_object(node, &child, &intern->dom); } - php_dom_create_object(node, &child, &intern->dom); add_next_index_zval(&retval, &child); } } else { diff --git a/ext/mbstring/libmbfl/filters/mbfilter_cp932.c b/ext/mbstring/libmbfl/filters/mbfilter_cp932.c index 506c24393906d..28e209fc90a54 100644 --- a/ext/mbstring/libmbfl/filters/mbfilter_cp932.c +++ b/ext/mbstring/libmbfl/filters/mbfilter_cp932.c @@ -68,7 +68,7 @@ static size_t mb_cp932_to_wchar(unsigned char **in, size_t *in_len, uint32_t *bu static void mb_wchar_to_cp932(uint32_t *in, size_t len, mb_convert_buf *buf, bool end); static void mb_wchar_to_sjiswin(uint32_t *in, size_t len, mb_convert_buf *buf, bool end); -static const unsigned char mblen_table_sjis[] = { /* 0x80-0x9f,0xE0-0xFF */ +static const unsigned char mblen_table_sjis[] = { /* 0x81-0x9f,0xE0-0xFF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, @@ -77,7 +77,7 @@ static const unsigned char mblen_table_sjis[] = { /* 0x80-0x9f,0xE0-0xFF */ 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, + 1, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, diff --git a/ext/mbstring/tests/mb_strlen.phpt b/ext/mbstring/tests/mb_strlen.phpt index 5ebfcd1aec065..81cacaf197763 100644 --- a/ext/mbstring/tests/mb_strlen.phpt +++ b/ext/mbstring/tests/mb_strlen.phpt @@ -35,6 +35,11 @@ print "-- Testing illegal bytes 0x80,0xFD-FF --\n"; print mb_strlen("\x80\xA1", 'SJIS') . "\n"; print mb_strlen("abc\xFD\xFE\xFF", 'SJIS') . "\n"; +echo "== CP932 ==\n"; +print mb_strlen("\x80\xA1", "CP932") . "\n"; +// 0xFD, 0xFE, 0xFF is reserved. +print mb_strlen("abc\xFD\xFE\xFF", 'CP932') . "\n"; + echo "== MacJapanese ==\n"; print mb_strlen("\x80\xA1", 'MacJapanese') . "\n"; print mb_strlen("abc\xFD\xFE\xFF", 'MacJapanese') . "\n"; @@ -91,6 +96,9 @@ try { -- Testing illegal bytes 0x80,0xFD-FF -- 2 6 +== CP932 == +2 +5 == MacJapanese == 2 6 diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index a0928799a79aa..29af15674ab90 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -390,6 +390,10 @@ static inline void accel_unlock_all(void) #ifdef ZEND_WIN32 accel_deactivate_sub(); #else + if (lock_file == -1) { + return; + } + struct flock mem_usage_unlock_all; mem_usage_unlock_all.l_type = F_UNLCK; diff --git a/ext/opcache/jit/zend_jit_internal.h b/ext/opcache/jit/zend_jit_internal.h index fb640ff9b97ca..544bcdd1f245b 100644 --- a/ext/opcache/jit/zend_jit_internal.h +++ b/ext/opcache/jit/zend_jit_internal.h @@ -602,6 +602,8 @@ struct _zend_jit_trace_stack_frame { uint32_t call_level; uint32_t _info; int used_stack; + int old_checked_stack; + int old_peek_checked_stack; zend_jit_trace_stack stack[1]; }; diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index b0a86318a4ec8..a4d7864332723 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -6578,7 +6578,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par op_array_ssa = &jit_extension->func_info.ssa; top = frame; if (frame->prev) { - checked_stack -= frame->used_stack; + checked_stack = frame->old_checked_stack; + peek_checked_stack = frame->old_peek_checked_stack; frame = frame->prev; stack = frame->stack; ZEND_ASSERT(&frame->func->op_array == op_array); @@ -6751,24 +6752,40 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par } } } + call->old_checked_stack = checked_stack; + call->old_peek_checked_stack = peek_checked_stack; if (p->info & ZEND_JIT_TRACE_FAKE_INIT_CALL) { frame->call_level++; - call->used_stack = 0; + call->used_stack = checked_stack = peek_checked_stack = 0; } else { if (p->func) { call->used_stack = zend_vm_calc_used_stack(init_opline->extended_value, (zend_function*)p->func); } else { call->used_stack = (ZEND_CALL_FRAME_SLOT + init_opline->extended_value) * sizeof(zval); } - checked_stack += call->used_stack; - if (checked_stack > peek_checked_stack) { - peek_checked_stack = checked_stack; + switch (init_opline->opcode) { + case ZEND_INIT_FCALL: + case ZEND_INIT_FCALL_BY_NAME: + case ZEND_INIT_NS_FCALL_BY_NAME: + case ZEND_INIT_METHOD_CALL: + case ZEND_INIT_DYNAMIC_CALL: + //case ZEND_INIT_STATIC_METHOD_CALL: + //case ZEND_INIT_USER_CALL: + //case ZEND_NEW: + checked_stack += call->used_stack; + if (checked_stack > peek_checked_stack) { + peek_checked_stack = checked_stack; + } + break; + default: + checked_stack = peek_checked_stack = 0; } } } else if (p->op == ZEND_JIT_TRACE_DO_ICALL) { call = frame->call; if (call) { - checked_stack -= call->used_stack; + checked_stack = call->old_checked_stack; + peek_checked_stack = call->old_peek_checked_stack; top = call; frame->call = call->prev; } @@ -6823,7 +6840,8 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par if (!(t->flags & ZEND_JIT_TRACE_USES_INITIAL_IP) || (ra && zend_jit_trace_stack_needs_deoptimization(stack, op_array->last_var + op_array->T))) { - uint32_t exit_point = zend_jit_trace_get_exit_point(opline, ZEND_JIT_EXIT_TO_VM); + /* Deoptimize to the first instruction of the loop */ + uint32_t exit_point = zend_jit_trace_get_exit_point(trace_buffer[1].opline, ZEND_JIT_EXIT_TO_VM); timeout_exit_addr = zend_jit_trace_get_exit_addr(exit_point); if (!timeout_exit_addr) { @@ -7175,8 +7193,6 @@ static void zend_jit_stop_hot_trace_counters(zend_op_array *op_array) uint32_t i; jit_extension = (zend_jit_op_array_trace_extension*)ZEND_FUNC_INFO(op_array); - zend_shared_alloc_lock(); - SHM_UNPROTECT(); for (i = 0; i < op_array->last; i++) { /* Opline with Jit-ed code handler is skipped. */ if (jit_extension->trace_info[i].trace_flags & @@ -7188,8 +7204,6 @@ static void zend_jit_stop_hot_trace_counters(zend_op_array *op_array) op_array->opcodes[i].handler = jit_extension->trace_info[i].orig_handler; } } - SHM_PROTECT(); - zend_shared_alloc_unlock(); } /* Get the tracing op_array. */ @@ -7228,6 +7242,9 @@ static void zend_jit_stop_persistent_script(zend_persistent_script *script) /* Get all scripts which are accelerated by JIT */ static void zend_jit_stop_counter_handlers(void) { + zend_shared_alloc_lock(); + /* mprotect has an extreme overhead, avoid calls to it for every function. */ + SHM_UNPROTECT(); for (uint32_t i = 0; i < ZCSG(hash).max_num_entries; i++) { zend_accel_hash_entry *cache_entry; for (cache_entry = ZCSG(hash).hash_table[i]; cache_entry; cache_entry = cache_entry->next) { @@ -7237,6 +7254,8 @@ static void zend_jit_stop_counter_handlers(void) zend_jit_stop_persistent_script(script); } } + SHM_PROTECT(); + zend_shared_alloc_unlock(); } static void zend_jit_blacklist_root_trace(const zend_op *opline, size_t offset) diff --git a/ext/opcache/tests/jit/init_fcall_003.phpt b/ext/opcache/tests/jit/init_fcall_003.phpt index f37344cbce4a9..180f0745c16c6 100644 --- a/ext/opcache/tests/jit/init_fcall_003.phpt +++ b/ext/opcache/tests/jit/init_fcall_003.phpt @@ -11,6 +11,8 @@ opcache.jit_hot_loop=64 opcache.jit_hot_func=127 opcache.jit_hot_return=8 opcache.jit_hot_side_exit=8 +--EXTENSIONS-- +opcache --FILE-- +--FILE-- + [ + 'local_cert' => '%s', + ]]); + + $server = stream_socket_server($serverUri, $errno, $errstr, $serverFlags, $serverCtx); + phpt_notify(); + + @stream_socket_accept($server, 1); + @stream_socket_accept($server, 1); +CODE; +$serverCode = sprintf($serverCode, $certFile); + +$clientCode = <<<'CODE' + $serverUri = "ssl://[::1]:64324"; + $clientFlags = STREAM_CLIENT_CONNECT; + $clientCtx = stream_context_create(['ssl' => [ + 'verify_peer' => false, + ]]); + + phpt_wait(); + + stream_context_set_option($clientCtx, 'ssl', 'peer_name', '2001:db8:85a3:8d3:1319:8a2e:370:7348'); + var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx)); + + stream_context_set_option($clientCtx, 'ssl', 'peer_name', '2001:db8:85a3:8d3:1319:8a2e:370:7349'); + var_dump(stream_socket_client($serverUri, $errno, $errstr, 1, $clientFlags, $clientCtx)); +CODE; + +include 'CertificateGenerator.inc'; +$certificateGenerator = new CertificateGenerator(); +$certificateGenerator->saveNewCertAsFileWithKey(null, $certFile, null, $san); + +include 'ServerClientTestCase.inc'; +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--CLEAN-- + +--EXPECTF-- +resource(%d) of type (stream) + +Warning: stream_socket_client(): Unable to locate peer certificate CN in %s on line %d + +Warning: stream_socket_client(): Failed to enable crypto in %s on line %d + +Warning: stream_socket_client(): Unable to connect to ssl://[::1]:64324 (Unknown error) in %s on line %d +bool(false) diff --git a/ext/openssl/xp_ssl.c b/ext/openssl/xp_ssl.c index 79dd0b4f9c78d..2e21c138d6b50 100644 --- a/ext/openssl/xp_ssl.c +++ b/ext/openssl/xp_ssl.c @@ -39,6 +39,7 @@ #ifdef PHP_WIN32 #include "win32/winutil.h" #include "win32/time.h" +#include #include /* These are from Wincrypt.h, they conflict with OpenSSL */ #undef X509_NAME @@ -50,6 +51,10 @@ # define MSG_DONTWAIT 0 #endif +#ifdef HAVE_ARPA_INET_H +#include +#endif + /* Flags for determining allowed stream crypto methods */ #define STREAM_CRYPTO_IS_CLIENT (1<<0) #define STREAM_CRYPTO_METHOD_SSLv2 (1<<1) @@ -125,6 +130,24 @@ #define PHP_X509_NAME_ENTRY_TO_UTF8(ne, i, out) \ ASN1_STRING_to_UTF8(&out, X509_NAME_ENTRY_get_data(X509_NAME_get_entry(ne, i))) +#if defined(HAVE_IPV6) && defined(HAVE_INET_PTON) +/* Used for IPv6 Address peer verification */ +#define EXPAND_IPV6_ADDRESS(_str, _bytes) \ + do { \ + snprintf(_str, 40, "%X:%X:%X:%X:%X:%X:%X:%X", \ + _bytes[0] << 8 | _bytes[1], \ + _bytes[2] << 8 | _bytes[3], \ + _bytes[4] << 8 | _bytes[5], \ + _bytes[6] << 8 | _bytes[7], \ + _bytes[8] << 8 | _bytes[9], \ + _bytes[10] << 8 | _bytes[11], \ + _bytes[12] << 8 | _bytes[13], \ + _bytes[14] << 8 | _bytes[15] \ + ); \ + } while(0) +#define HAVE_IPV6_SAN 1 +#endif + #if PHP_OPENSSL_API_VERSION < 0x10100 static RSA *php_openssl_tmp_rsa_cb(SSL *s, int is_export, int keylength); #endif @@ -436,6 +459,19 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) / GENERAL_NAMES *alt_names = X509_get_ext_d2i(peer, NID_subject_alt_name, 0, 0); int alt_name_count = sk_GENERAL_NAME_num(alt_names); +#ifdef HAVE_IPV6_SAN + /* detect if subject name is an IPv6 address and expand once if required */ + char subject_name_ipv6_expanded[40]; + unsigned char ipv6[16]; + bool subject_name_is_ipv6 = false; + subject_name_ipv6_expanded[0] = 0; + + if (inet_pton(AF_INET6, subject_name, &ipv6)) { + EXPAND_IPV6_ADDRESS(subject_name_ipv6_expanded, ipv6); + subject_name_is_ipv6 = true; + } +#endif + for (i = 0; i < alt_name_count; i++) { GENERAL_NAME *san = sk_GENERAL_NAME_value(alt_names, i); @@ -474,10 +510,17 @@ static bool php_openssl_matches_san_list(X509 *peer, const char *subject_name) / return 1; } } - /* No, we aren't bothering to check IPv6 addresses. Why? - * Because IP SAN names are officially deprecated and are - * not allowed by CAs starting in 2015. Deal with it. - */ +#ifdef HAVE_IPV6_SAN + else if (san->d.ip->length == 16 && subject_name_is_ipv6) { + ipbuffer[0] = 0; + EXPAND_IPV6_ADDRESS(ipbuffer, san->d.iPAddress->data); + if (strcasecmp((const char*)subject_name_ipv6_expanded, (const char*)ipbuffer) == 0) { + sk_GENERAL_NAME_pop_free(alt_names, GENERAL_NAME_free); + + return 1; + } + } +#endif } } diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 82560ddd83a61..ea5e6a01ff065 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -2425,6 +2425,10 @@ PHP_FUNCTION(preg_replace_callback_array) zend_argument_type_error(1, "must contain only valid callbacks"); goto error; } + if (!str_idx_regex) { + zend_argument_type_error(1, "must contain only string patterns as keys"); + goto error; + } ZVAL_COPY_VALUE(&fci.function_name, replace); diff --git a/ext/pcre/tests/preg_replace_callback_array_numeric_index_error.phpt b/ext/pcre/tests/preg_replace_callback_array_numeric_index_error.phpt new file mode 100644 index 0000000000000..55dfabce8649c --- /dev/null +++ b/ext/pcre/tests/preg_replace_callback_array_numeric_index_error.phpt @@ -0,0 +1,15 @@ +--TEST-- +preg_replace_callback_array() invalid pattern +--FILE-- + function () {}], + 'a', +); +?> +--EXPECTF-- +Fatal error: Uncaught TypeError: preg_replace_callback_array(): Argument #1 ($pattern) must contain only string patterns as keys in %s:%d +Stack trace: +#0 %s(%d): preg_replace_callback_array(Array, 'a') +#1 {main} + thrown in %s on line %d diff --git a/ext/pgsql/pgsql.c b/ext/pgsql/pgsql.c index 188570f7cead3..a1dc8fc63bd09 100644 --- a/ext/pgsql/pgsql.c +++ b/ext/pgsql/pgsql.c @@ -175,6 +175,7 @@ static void pgsql_link_free(pgsql_link_handle *link) PQclear(res); } if (!link->persistent) { + PQuntrace(link->conn); PQfinish(link->conn); } PGG(num_links)--; diff --git a/ext/pgsql/tests/pg_trace.phpt b/ext/pgsql/tests/pg_trace.phpt new file mode 100644 index 0000000000000..89b6027f854ea --- /dev/null +++ b/ext/pgsql/tests/pg_trace.phpt @@ -0,0 +1,20 @@ +--TEST-- +pg_trace +--EXTENSIONS-- +pgsql +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +bool(true) diff --git a/ext/phar/Makefile.frag b/ext/phar/Makefile.frag index e5646b2029261..7a867dd7df28f 100644 --- a/ext/phar/Makefile.frag +++ b/ext/phar/Makefile.frag @@ -35,7 +35,7 @@ TEST_PHP_EXECUTABLE_RES = $(shell echo "$(TEST_PHP_EXECUTABLE)" | grep -c 'Exec $(builddir)/phar.php: $(srcdir)/build_precommand.php $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) -@(echo "Generating phar.php"; \ - if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + if [ "$(TEST_PHP_EXECUTABLE_RES)" != 1 ]; then \ $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(srcdir)/build_precommand.php > $(builddir)/phar.php; \ else \ echo "Skipping phar.php generating during cross compilation"; \ @@ -43,7 +43,7 @@ $(builddir)/phar.php: $(srcdir)/build_precommand.php $(srcdir)/phar/*.inc $(srcd $(builddir)/phar.phar: $(builddir)/phar.php $(builddir)/phar/phar.inc $(srcdir)/phar/*.inc $(srcdir)/phar/*.php $(SAPI_CLI_PATH) -@(echo "Generating phar.phar"; \ - if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + if [ "$(TEST_PHP_EXECUTABLE_RES)" != 1 ]; then \ rm -f $(builddir)/phar.phar; \ rm -f $(srcdir)/phar.phar; \ $(PHP_PHARCMD_EXECUTABLE) $(PHP_PHARCMD_SETTINGS) $(builddir)/phar.php pack -f $(builddir)/phar.phar -a pharcommand -c auto -x \\.svn -p 0 -s $(srcdir)/phar/phar.php -h sha1 -b "$(PHP_PHARCMD_BANG)" $(srcdir)/phar/; \ @@ -53,7 +53,7 @@ $(builddir)/phar.phar: $(builddir)/phar.php $(builddir)/phar/phar.inc $(srcdir)/ fi) install-pharcmd: pharcmd - @(if [ $(TEST_PHP_EXECUTABLE_RES) -ne 1 ]; then \ + @(if [ "$(TEST_PHP_EXECUTABLE_RES)" != 1 ]; then \ $(mkinstalldirs) $(INSTALL_ROOT)$(bindir); \ $(INSTALL) $(builddir)/phar.phar $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix).phar; \ rm -f $(INSTALL_ROOT)$(bindir)/$(program_prefix)phar$(program_suffix); \ diff --git a/ext/readline/tests/bug77812-readline.phpt b/ext/readline/tests/bug77812-readline.phpt index a18686781718b..a2d6c212c536a 100644 --- a/ext/readline/tests/bug77812-readline.phpt +++ b/ext/readline/tests/bug77812-readline.phpt @@ -13,7 +13,6 @@ $php = getenv('TEST_PHP_EXECUTABLE'); $ini = getenv('TEST_PHP_EXTRA_ARGS'); $descriptorspec = [['pipe', 'r'], STDOUT, STDERR]; $proc = proc_open("$php $ini -a", $descriptorspec, $pipes); -var_dump($proc); fwrite($pipes[0], "echo << --EXPECTF-- -resource(%d) of type (process) Interactive shell php > echo << 1 && IS_SLASH_AT(ZSTR_VAL(path), path_len-1)) { - path_len--; + do { + path_len--; + } while (path_len > 1 && IS_SLASH_AT(ZSTR_VAL(path), path_len - 1)); intern->file_name = zend_string_init(ZSTR_VAL(path), path_len, 0); } else { intern->file_name = zend_string_copy(path); diff --git a/ext/spl/tests/gh11338.phpt b/ext/spl/tests/gh11338.phpt new file mode 100644 index 0000000000000..0a59cea9e7468 --- /dev/null +++ b/ext/spl/tests/gh11338.phpt @@ -0,0 +1,47 @@ +--TEST-- +GH-11338 (SplFileInfo empty getBasename with more than on slash) +--FILE-- +getBasename()); + var_dump($file->getFilename()); +} + +test('/dir/anotherdir/basedir//'); +test('/dir/anotherdir/basedir/'); +test('/dir/anotherdir/basedir'); +test('/dir/anotherdir//basedir'); +test('///'); +test('//'); +test('/'); +test(''); + +?> +--EXPECT-- +Testing: '/dir/anotherdir/basedir//' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir/basedir/' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir/basedir' +string(7) "basedir" +string(7) "basedir" +Testing: '/dir/anotherdir//basedir' +string(7) "basedir" +string(7) "basedir" +Testing: '///' +string(0) "" +string(1) "/" +Testing: '//' +string(0) "" +string(1) "/" +Testing: '/' +string(0) "" +string(1) "/" +Testing: '' +string(0) "" +string(0) "" diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index d97f8b5fa53e3..2b9e18d12f1f0 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -1982,7 +1982,9 @@ PHP_METHOD(SQLite3Result, fetchArray) Z_ADDREF(data); } } - zend_symtable_add_new(Z_ARR_P(return_value), result_obj->column_names[i], &data); + /* Note: we can't use the "add_new" variant here instead of "update" because + * when the same column name is encountered, the last result should be taken. */ + zend_symtable_update(Z_ARR_P(return_value), result_obj->column_names[i], &data); } } break; diff --git a/ext/sqlite3/tests/gh11451.phpt b/ext/sqlite3/tests/gh11451.phpt new file mode 100644 index 0000000000000..e518c39b18b6d --- /dev/null +++ b/ext/sqlite3/tests/gh11451.phpt @@ -0,0 +1,25 @@ +--TEST-- +GH-11451 (Invalid associative array containing duplicate keys) +--EXTENSIONS-- +sqlite3 +--FILE-- +query('SELECT 1 AS key, 2 AS key') + ->fetchArray(SQLITE3_ASSOC)); + +var_dump((new SQLite3(':memory:')) + ->query('SELECT 0 AS dummy, 1 AS key, 2 AS key') + ->fetchArray(SQLITE3_ASSOC)); +?> +--EXPECT-- +array(1) { + ["key"]=> + int(2) +} +array(2) { + ["dummy"]=> + int(0) + ["key"]=> + int(2) +} diff --git a/ext/standard/array.c b/ext/standard/array.c index 8a74704967fb6..19b7dc1f8e8ab 100644 --- a/ext/standard/array.c +++ b/ext/standard/array.c @@ -3728,7 +3728,12 @@ PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */ } } else { Z_TRY_ADDREF_P(src_zval); - zend_hash_next_index_insert(Z_ARRVAL_P(dest_zval), src_zval); + zval *zv = zend_hash_next_index_insert(Z_ARRVAL_P(dest_zval), src_zval); + if (EXPECTED(!zv)) { + Z_TRY_DELREF_P(src_zval); + zend_cannot_add_element(); + return 0; + } } zval_ptr_dtor(&tmp); } else { @@ -3737,6 +3742,10 @@ PHPAPI int php_array_merge_recursive(HashTable *dest, HashTable *src) /* {{{ */ } } else { zval *zv = zend_hash_next_index_insert(dest, src_entry); + if (UNEXPECTED(!zv)) { + zend_cannot_add_element(); + return 0; + } zval_add_ref(zv); } } ZEND_HASH_FOREACH_END(); @@ -5752,6 +5761,9 @@ PHP_FUNCTION(array_multisort) /* Do the actual sort magic - bada-bim, bada-boom. */ zend_sort(indirect, array_size, sizeof(Bucket *), php_multisort_compare, (swap_func_t)array_bucket_p_sawp); + if (EG(exception)) { + goto clean_up; + } /* Restructure the arrays based on sorted indirect - this is mostly taken from zend_hash_sort() function. */ for (i = 0; i < num_arrays; i++) { @@ -5781,15 +5793,15 @@ PHP_FUNCTION(array_multisort) } } } + RETVAL_TRUE; - /* Clean up. */ +clean_up: for (i = 0; i < array_size; i++) { efree(indirect[i]); } efree(indirect); efree(func); efree(arrays); - RETURN_TRUE; } /* }}} */ diff --git a/main/php_version.h b/main/php_version.h index f24a3aa56bcde..d79d13b232257 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 2 -#define PHP_RELEASE_VERSION 7 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.2.7-dev" -#define PHP_VERSION_ID 80207 +#define PHP_RELEASE_VERSION 8 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.2.8" +#define PHP_VERSION_ID 80208 diff --git a/run-tests.php b/run-tests.php index 0ecd414375ce6..cd1465e767209 100755 --- a/run-tests.php +++ b/run-tests.php @@ -2808,7 +2808,7 @@ function run_test(string $php, $file, array $env): string function error_may_be_retried(string $output): bool { - return preg_match('((timed out)|(connection refused))i', $output) === 1; + return preg_match('((timed out)|(connection refused)|(404: page not found)|(address already in use)|(mailbox already exists))i', $output) === 1; } /** diff --git a/sapi/cli/ps_title.c b/sapi/cli/ps_title.c index 8ff7ef719e17f..8eb14963c682a 100644 --- a/sapi/cli/ps_title.c +++ b/sapi/cli/ps_title.c @@ -169,19 +169,18 @@ char** save_ps_args(int argc, char** argv) end_of_area = argv[i] + strlen(argv[i]); } + if (!is_contiguous_area) { + goto clobber_error; + } + /* * check for contiguous environ strings following argv */ - for (i = 0; is_contiguous_area && (environ[i] != NULL); i++) + for (i = 0; environ[i] != NULL; i++) { - if (end_of_area + 1 != environ[i]) { - is_contiguous_area = false; + if (end_of_area + 1 == environ[i]) { + end_of_area = environ[i] + strlen(environ[i]); } - end_of_area = environ[i] + strlen(environ[i]); - } - - if (!is_contiguous_area) { - goto clobber_error; } ps_buffer = argv[0]; diff --git a/sapi/fpm/tests/bug77023-pm-dynamic-blocking-sigquit.phpt b/sapi/fpm/tests/bug77023-pm-dynamic-blocking-sigquit.phpt index cb33a2f6635a7..458948f676590 100644 --- a/sapi/fpm/tests/bug77023-pm-dynamic-blocking-sigquit.phpt +++ b/sapi/fpm/tests/bug77023-pm-dynamic-blocking-sigquit.phpt @@ -36,7 +36,7 @@ EOT; $tester = new FPM\Tester($cfg, $code); -$tester->start(); +$tester->start(extensions: ['pcntl']); $tester->expectLogStartNotices(); $tester->multiRequest(2); $tester->status([ diff --git a/sapi/fpm/tests/reload-uses-sigkill-as-last-measure.phpt b/sapi/fpm/tests/reload-uses-sigkill-as-last-measure.phpt index 3e5d19c429b8e..64dd8e736b603 100644 --- a/sapi/fpm/tests/reload-uses-sigkill-as-last-measure.phpt +++ b/sapi/fpm/tests/reload-uses-sigkill-as-last-measure.phpt @@ -1,5 +1,5 @@ --TEST-- -If SIGQUIT and SIGTERM during reloading fail, SIGKILL should be sent +FPM: If SIGQUIT and SIGTERM during reloading fail, SIGKILL should be sent --EXTENSIONS-- pcntl --SKIPIF-- @@ -35,7 +35,7 @@ pcntl_sigprocmask(SIG_BLOCK, [SIGQUIT, SIGTERM]); EOT; $tester = new FPM\Tester($cfg, $code); -$tester->start(); +$tester->start(extensions: ['pcntl']); $tester->expectLogStartNotices(); $tester->request()->expectEmptyBody(); $tester->signal('USR2');