Skip to content
Closed
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
26 changes: 13 additions & 13 deletions src/wp-includes/interactivity-api/class-wp-interactivity-api.php
Original file line number Diff line number Diff line change
Expand Up @@ -183,8 +183,9 @@ final class WP_Interactivity_API {

/**
* Marker that makes the placeholders of the current `process_directives()`
* call recognizable. It occurs nowhere in the HTML that call received, and
* it differs from the marker of any call that the current one runs inside.
* call recognizable, or an empty string until the first `data-wp-html` host
* of the call renders and draws it. It holds a random UUID, so no text
* written before the call can realistically hold it.
*
* @since 7.2.0
* @var string
Expand Down Expand Up @@ -536,17 +537,11 @@ public function process_directives( string $html ): string {
/*
* The trusted HTML of `data-wp-html` hosts never takes part in the
* processing: each host receives a placeholder comment, and the comments
* are replaced by their HTML once the result is known. The marker that
* identifies them is chosen so that it occurs nowhere in the input, which
* makes it impossible for the input to contain a placeholder. It is also
* longer than the marker of any call this one runs inside, so their
* placeholders never coincide.
* are replaced by their HTML once the result is known. The call starts
* without the marker that identifies them: the first host that renders
* draws it, so a call without such a host does no marker work.
*/
$marker = '' === $this->html_placeholder_marker ? 'wp-interactivity-dangerous-html' : $this->html_placeholder_marker . '_';
while ( str_contains( $html, $marker ) ) {
$marker .= '_';
}
$this->html_placeholder_marker = $marker;
$this->html_placeholder_marker = '';
$this->html_placeholders = array();

try {
Expand Down Expand Up @@ -1698,8 +1693,13 @@ private function data_wp_html_processor( WP_Interactivity_API_Directives_Process
/*
* The HTML is held back and a placeholder comment takes its place, so no
* later step of the call scans it. A comment is a single token for the
* processors, and the marker in it occurs nowhere in the input.
* processors. Its marker holds a random UUID, drawn once per call by the
* first host that renders, so no text written before the call can
* realistically contain a placeholder.
*/
if ( '' === $this->html_placeholder_marker ) {
$this->html_placeholder_marker = 'wp-interactivity-dangerous-html-' . wp_generate_uuid4();
}
$placeholder = '<!--' . $this->html_placeholder_marker . ':' . count( $this->html_placeholders ) . '-->';
if ( $p->skip_to_balanced_tag_closer( $placeholder ) ) {
$this->html_placeholders[ $placeholder ] = $html;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1093,15 +1093,17 @@ public function test_host_inside_a_rendering_host_is_replaced_with_it() {
}

/**
* Tests that markup in the input that looks like a placeholder is not
* mistaken for one, however many marker variants it holds.
* Tests that markup in the input that looks like a placeholder, but holds a
* marker other than the call's own, is not mistaken for one. That includes
* the form of the call's own placeholders with a UUID other than the call's.
*
* @ticket 66246
*
* @covers ::process_directives
*/
public function test_substitution_cannot_be_forged() {
$lookalikes = '<!--wp-interactivity-dangerous-html:0--><!--wp-interactivity-dangerous-html_:0-->';
$lookalikes = '<!--wp-interactivity-dangerous-html:0--><!--wp-interactivity-dangerous-html_:0-->' .
'<!--wp-interactivity-dangerous-html-3f2b8c1e-7d4a-4e9b-a5c6-0d1e2f3a4b5c:0-->';
$this->set_state( array( 'html' => $this->token() ) );

$output = wp_interactivity_process_directives(
Expand All @@ -1114,33 +1116,6 @@ public function test_substitution_cannot_be_forged() {
);
}

/**
* Tests that HTML that looks like the placeholder of another host is
* inserted as given, not replaced by that host's HTML.
*
* @ticket 66246
*
* @covers ::process_directives
*/
public function test_inserted_html_is_not_searched_for_placeholders() {
$lookalike = '<!--wp-interactivity-dangerous-html:1-->';
$this->set_state(
array(
'first' => wp_interactivity_as_dangerous_html( $lookalike ),
'second' => wp_interactivity_as_dangerous_html( '<u>second</u>' ),
)
);

$output = wp_interactivity_process_directives(
'<div data-wp-interactive="test"><p data-wp-html="state.first">One</p><p data-wp-html="state.second">Two</p></div>'
);

$this->assertSame(
'<div data-wp-interactive="test"><p data-wp-html="state.first">' . $lookalike . '</p><p data-wp-html="state.second"><u>second</u></p></div>',
$output
);
}

/**
* Tests that unbalanced markup returns the input unchanged, with no HTML
* inserted.
Expand Down
Loading