diff --git a/ext/dom/html5_parser.c b/ext/dom/html5_parser.c index 24f89cb0fafb..d946979b705d 100644 --- a/ext/dom/html5_parser.c +++ b/ext/dom/html5_parser.c @@ -39,6 +39,7 @@ typedef struct work_list_item { uintptr_t current_active_namespace; xmlNodePtr lxml_parent; xmlNsPtr lxml_ns; + bool in_template; } work_list_item; static void lexbor_libxml2_bridge_work_list_item_push( @@ -46,7 +47,8 @@ static void lexbor_libxml2_bridge_work_list_item_push( lxb_dom_node_t *node, uintptr_t current_active_namespace, xmlNodePtr lxml_parent, - xmlNsPtr lxml_ns + xmlNsPtr lxml_ns, + bool in_template ) { work_list_item *item = (work_list_item *) lexbor_array_obj_push_wo_cls(array); @@ -54,6 +56,7 @@ static void lexbor_libxml2_bridge_work_list_item_push( item->current_active_namespace = current_active_namespace; item->lxml_parent = lxml_parent; item->lxml_ns = lxml_ns; + item->in_template = in_template; } static unsigned short sanitize_line_nr(size_t line) @@ -109,6 +112,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( xmlNodePtr root, bool compact_text_nodes, bool create_default_ns, + bool in_template_contents, php_dom_private_data *private_data ) { @@ -124,13 +128,14 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( lexbor_array_obj_init(&work_list, WORK_LIST_INIT_SIZE, sizeof(work_list_item)); for (lxb_dom_node_t *node = start_node; node != NULL; node = node->prev) { - lexbor_libxml2_bridge_work_list_item_push(&work_list, node, LXB_NS__UNDEF, root, NULL); + lexbor_libxml2_bridge_work_list_item_push(&work_list, node, LXB_NS__UNDEF, root, NULL, in_template_contents); } work_list_item *current_stack_item; while ((current_stack_item = lexbor_array_obj_pop(&work_list)) != NULL) { lxb_dom_node_t *node = current_stack_item->node; xmlNodePtr lxml_parent = current_stack_item->lxml_parent; + bool in_template = current_stack_item->in_template; /* CDATA section and processing instructions don't occur in parsed HTML documents. * The historical types are not emitted by the parser either. */ @@ -173,7 +178,9 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( * Other types of nodes contain their children directly. */ xmlNodePtr lxml_child_parent = lxml_element; lxb_dom_node_t *child_node = element->node.last_child; + bool child_in_template = in_template; if (lxb_html_tree_node_is(&element->node, LXB_TAG_TEMPLATE)) { + child_in_template = true; if (create_default_ns) { lxml_child_parent = xmlNewDocFragment(lxml_doc); if (UNEXPECTED(lxml_child_parent == NULL)) { @@ -198,7 +205,8 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( child_node, entering_namespace, lxml_child_parent, - current_lxml_ns + current_lxml_ns, + child_in_template ); } @@ -275,7 +283,7 @@ static lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert( /* xmlIsID does some other stuff too that is irrelevant here. */ if (local_name_length == 2 && local_name[0] == 'i' && local_name[1] == 'd' && lxml_attr->ns == NULL) { - if (xmlAddID(NULL, lxml_doc, value, lxml_attr) == 0) { + if (in_template || xmlAddID(NULL, lxml_doc, value, lxml_attr) == 0) { /* If the ID already exists, the ID attribute still needs to be marked as an ID. */ lxml_attr->atype = XML_ATTRIBUTE_ID; } @@ -369,6 +377,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_document( (xmlNodePtr) lxml_doc, compact_text_nodes, create_default_ns, + false, private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { @@ -385,6 +394,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( xmlNodePtr *fragment_out, bool compact_text_nodes, bool create_default_ns, + bool in_template_contents, php_dom_private_data *private_data ) { @@ -398,6 +408,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( fragment, compact_text_nodes, create_default_ns, + in_template_contents, private_data ); if (status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK) { diff --git a/ext/dom/html5_parser.h b/ext/dom/html5_parser.h index 76e60ea8c676..98f47eedf744 100644 --- a/ext/dom/html5_parser.h +++ b/ext/dom/html5_parser.h @@ -79,6 +79,7 @@ lexbor_libxml2_bridge_status lexbor_libxml2_bridge_convert_fragment( xmlNodePtr *fragment_out, bool compact_text_nodes, bool create_default_ns, + bool in_template_contents, php_dom_private_data *private_data ); void lexbor_libxml2_bridge_report_errors( diff --git a/ext/dom/inner_html_mixin.c b/ext/dom/inner_html_mixin.c index e81ca5a7b238..ef7f3051dc4b 100644 --- a/ext/dom/inner_html_mixin.c +++ b/ext/dom/inner_html_mixin.c @@ -181,7 +181,7 @@ static lxb_dom_document_cmode_t dom_translate_quirks_mode(php_libxml_quirks_mode } /* https://html.spec.whatwg.org/#html-fragment-parsing-algorithm */ -static xmlNodePtr dom_html_fragment_parsing_algorithm(dom_object *obj, xmlNodePtr context_node, const zend_string *input, php_libxml_quirks_mode quirks_mode) +static xmlNodePtr dom_html_fragment_parsing_algorithm(dom_object *obj, xmlNodePtr context_node, const zend_string *input, php_libxml_quirks_mode quirks_mode, bool is_template) { /* The whole algorithm is implemented in Lexbor, we just have to be the adapter between the * data structures used in PHP and what Lexbor expects. */ @@ -209,7 +209,7 @@ static xmlNodePtr dom_html_fragment_parsing_algorithm(dom_object *obj, xmlNodePt xmlNodePtr fragment = NULL; if (node != NULL) { /* node->last_child could be NULL, but that is allowed. */ - lexbor_libxml2_bridge_status status = lexbor_libxml2_bridge_convert_fragment(node->last_child, context_node->doc, &fragment, true, true, php_dom_get_private_data(obj)); + lexbor_libxml2_bridge_status status = lexbor_libxml2_bridge_convert_fragment(node->last_child, context_node->doc, &fragment, true, true, is_template, php_dom_get_private_data(obj)); if (UNEXPECTED(status != LEXBOR_LIBXML2_BRIDGE_STATUS_OK)) { php_dom_throw_error(INVALID_STATE_ERR, true); } @@ -342,18 +342,19 @@ zend_result dom_element_inner_html_write(dom_object *obj, zval *newval) { DOM_PROP_NODE(xmlNodePtr, context_node, obj); + bool is_template = php_dom_ns_is_fast(context_node, php_dom_ns_is_html_magic_token) && xmlStrEqual(context_node->name, BAD_CAST "template"); xmlNodePtr fragment; if (context_node->doc->type == XML_DOCUMENT_NODE) { fragment = dom_xml_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval)); } else { - fragment = dom_html_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval), obj->document->quirks_mode); + fragment = dom_html_fragment_parsing_algorithm(obj, context_node, Z_STR_P(newval), obj->document->quirks_mode, is_template); } if (fragment == NULL) { return FAILURE; } - if (php_dom_ns_is_fast(context_node, php_dom_ns_is_html_magic_token) && xmlStrEqual(context_node->name, BAD_CAST "template")) { + if (is_template) { context_node = php_dom_ensure_templated_content(php_dom_get_private_data(obj), context_node); if (context_node == NULL) { xmlFreeNode(fragment); diff --git a/ext/dom/tests/modern/html/interactions/gh23334.phpt b/ext/dom/tests/modern/html/interactions/gh23334.phpt new file mode 100644 index 000000000000..e6425e640fb3 --- /dev/null +++ b/ext/dom/tests/modern/html/interactions/gh23334.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-23334 (Template contents must not be reachable through the ID cache) +--EXTENSIONS-- +dom +--FILE-- +
WORLD
'; +var_dump((new Dom\XPath($doc))->query("id('set')")->length); +var_dump($tpl->innerHTML); +$doc->getElementById('host')->innerHTML = ''; +?> +--EXPECT-- +NULL +int(0) +string(27) "WORLD
"