Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 15 additions & 4 deletions ext/dom/html5_parser.c
Original file line number Diff line number Diff line change
Expand Up @@ -39,21 +39,24 @@ 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(
lexbor_array_obj_t *array,
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);
item->node = node;
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)
Expand Down Expand Up @@ -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
)
{
Expand All @@ -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. */
Expand Down Expand Up @@ -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)) {
Expand All @@ -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
);
}

Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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) {
Expand All @@ -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
)
{
Expand All @@ -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) {
Expand Down
1 change: 1 addition & 0 deletions ext/dom/html5_parser.h
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
9 changes: 5 additions & 4 deletions ext/dom/inner_html_mixin.c
Original file line number Diff line number Diff line change
Expand Up @@ -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. */
Expand Down Expand Up @@ -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);
}
Expand Down Expand Up @@ -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);
Expand Down
26 changes: 26 additions & 0 deletions ext/dom/tests/modern/html/interactions/gh23334.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
GH-23334 (Template contents must not be reachable through the ID cache)
--EXTENSIONS--
dom
--FILE--
<?php
$html = '<!doctype html><body><div id="host"><template id="tpl"><div id="inner">HELLO</div></template></div>';

$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
var_dump($doc->getElementById('inner'));
var_dump((new Dom\XPath($doc))->query("id('inner')")->length);
var_dump($doc->getElementById('tpl')->innerHTML);

$doc = Dom\HTMLDocument::createFromString($html, LIBXML_NOERROR);
$tpl = $doc->getElementById('tpl');
$tpl->innerHTML = '<p id="set">WORLD</p>';
var_dump((new Dom\XPath($doc))->query("id('set')")->length);
var_dump($tpl->innerHTML);
$doc->getElementById('host')->innerHTML = '';
?>
--EXPECT--
NULL
int(0)
string(27) "<div id="inner">HELLO</div>"
int(0)
string(21) "<p id="set">WORLD</p>"
Loading