Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
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
4 changes: 2 additions & 2 deletions src/wp-includes/formatting.php
Original file line number Diff line number Diff line change
Expand Up @@ -666,7 +666,7 @@ function get_html_split_regex() {
. '(?' // Conditional expression follows.
. $escaped // Find end of escaped element.
. '|' // ...else...
. '[^>]*>?' // Find end of normal element.
. '[^>\t\n\f\r \/]*+(?:[^>=]++|=[ \t\f\r\n]*+(?:"[^"]*+"|\'[^\']*+\')?)*+>?' // Find end of normal element, skipping quoted attribute values after the tag name.
. ')'
. ')/';
// phpcs:enable
Expand Down Expand Up @@ -704,7 +704,7 @@ function _get_wptexturize_split_regex( $shortcode_regex = '' ) {
. '(?(?=!--)' // Is this a comment?
. $comment_regex // Find end of comment.
. '|'
. '[^>]*>?' // Find end of element. If not found, match all input.
. '[^>\t\n\f\r \/]*+(?:[^>=]++|=[ \t\f\r\n]*+(?:"[^"]*+"|\'[^\']*+\')?)*+>?' // Find end of element, skipping quoted attribute values after the tag name. If not found, match all input.
. ')';
// phpcs:enable
}
Expand Down
94 changes: 94 additions & 0 deletions tests/phpunit/tests/formatting/wpHtmlSplit.php
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,100 @@ public static function data_basic_features() {
);
}

/**
* Ensures that a ">" inside a quoted attribute value does not end the tag.
*
* @ticket 63997
* @ticket 57381
* @ticket 63426
*
* @dataProvider data_gt_in_quoted_attribute_value
*
* @param string $input HTML to split.
* @param string[] $output Expected split.
*/
public function test_gt_in_quoted_attribute_value( string $input, array $output ): void {
$this->assertSame( $output, wp_html_split( $input ) );
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_gt_in_quoted_attribute_value(): array {
return array(
'Double-quoted value' => array(
'<p data-rule="a > b">text</p>',
array( '', '<p data-rule="a > b">', 'text', '</p>', '' ),
),
'Single-quoted value' => array(
"<p data-rule='a > b'>text</p>",
array( '', "<p data-rule='a > b'>", 'text', '</p>', '' ),
),
'Several values, one containing >' => array(
'<p data-a="a > b" data-b="c &gt; d">text</p>',
array( '', '<p data-a="a > b" data-b="c &gt; d">', 'text', '</p>', '' ),
),
'Ticket 63997: template attribute' => array(
'<div data-template="Label <% value %>" data-label="{A \'message\' string}">Content</div>',
array( '', '<div data-template="Label <% value %>" data-label="{A \'message\' string}">', 'Content', '</div>', '' ),
),
'Ticket 63426: markup in srcdoc' => array(
'<iframe srcdoc="<body></body>"></iframe>',
array( '', '<iframe srcdoc="<body></body>">', '', '</iframe>', '' ),
),
'Ticket 57381: Tailwind variant' => array(
'<div class="swiper [&>.swiper-pagination]:static">content</div>',
array( '', '<div class="swiper [&>.swiper-pagination]:static">', 'content', '</div>', '' ),
),
);
}

/**
* Ensures that inputs the split already handled split the same way as before.
*
* @ticket 63997
*
* @dataProvider data_unchanged_splits
*
* @param string $input HTML to split.
* @param string[] $output Expected split.
*/
public function test_unchanged_splits( string $input, array $output ): void {
$this->assertSame( $output, wp_html_split( $input ) );
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_unchanged_splits(): array {
return array(
'Apostrophe in an unquoted value does not reach the next tag' => array(
"<p>Hi</p>\n<a href=\"/x\" title=it's>It's here</a>",
array( '', '<p>', 'Hi', '</p>', "\n", "<a href=\"/x\" title=it's>", "It's here", '</a>', '' ),
),
'Stray quote after an unquoted value' => array(
'<b class=x">hello</b>',
array( '', '<b class=x">', 'hello', '</b>', '' ),
),
'Unclosed tag matches the rest of the input' => array(
"<p>Truncated <a href=\"http://example.com/it's",
array( '', '<p>', 'Truncated ', "<a href=\"http://example.com/it's", '' ),
),
'Quote in an attribute name' => array(
'<div "=noquote id="quote">x',
array( '', '<div "=noquote id="quote">', 'x' ),
),
'Quote after a < that does not start a tag' => array(
"<p>x <=' y is not a tag</p><p>and this isn't an attribute</p>",
array( '', '<p>', 'x ', "<=' y is not a tag</p>", '', '<p>', "and this isn't an attribute", '</p>', '' ),
),
);
}

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I reviewed the wptexturize() tests first, but some of the same comments apply to this file.

/**
* Automated performance testing of the main regex.
*
Expand Down
95 changes: 95 additions & 0 deletions tests/phpunit/tests/formatting/wpTexturize.php
Original file line number Diff line number Diff line change
Expand Up @@ -2111,6 +2111,101 @@ public function test_trailing_less_than() {
$this->assertSame( 'F&#8211;oo<', wptexturize( 'F--oo<', true ) );
}

/**
* Ensures that a ">" inside a quoted attribute value does not expose the
* rest of the tag to texturizing.
*
* @ticket 63997
* @ticket 57381
* @ticket 63426
*
* @dataProvider data_gt_in_quoted_attribute_value
*
* @param string $input HTML to texturize.
* @param string $expected Expected HTML.
*/
public function test_gt_in_quoted_attribute_value( string $input, string $expected ): void {
$this->assertEqualHTML( $expected, wptexturize( $input ) );
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_gt_in_quoted_attribute_value(): array {
return array(
'Double quotes inside a single-quoted value' => array(
'<p data-rule=\'a > 0 ? "yes" : "no"\'>It\'s text</p>',
'<p data-rule=\'a > 0 ? "yes" : "no"\'>It&#8217;s text</p>',
),
'Single quotes inside a double-quoted value' => array(
"<p data-rule=\"a > 0 ? 'yes' : 'no'\">It's text</p>",
"<p data-rule=\"a > 0 ? 'yes' : 'no'\">It&#8217;s text</p>",
),
'Ticket 63997: template attribute' => array(
'<div data-template="Label <% value %>" data-label="{A \'message\' string}">Content</div>',
'<div data-template="Label <% value %>" data-label="{A \'message\' string}">Content</div>',
),
'Ticket 63426: markup in srcdoc' => array(
'<iframe srcdoc="<p>It\'s inner</p>"></iframe>',
'<iframe srcdoc="<p>It\'s inner</p>"></iframe>',
),
'Ticket 57381: Tailwind variant' => array(
'<div class="swiper [&>.swiper-pagination]:static">content</div>',
'<div class="swiper [&amp;>.swiper-pagination]:static">content</div>',
),
);
}

/**
* Ensures that inputs wptexturize() already handled are texturized the same
* way as before.
*
* @ticket 63997
*
* @dataProvider data_unchanged_texturizing
*
* @param string $input HTML to texturize.
* @param string $expected Expected HTML.
*/
public function test_unchanged_texturizing( string $input, string $expected ): void {
$this->assertEqualHTML( $expected, wptexturize( $input ) );
}

/**
* Data provider.
*
* @return array[]
*/
public static function data_unchanged_texturizing(): array {
return array(
'Apostrophe in an unquoted value does not reach the next tag' => array(
"<p>Hi</p>\n<a href=\"/x\" title=it's>It's here</a>",
"<p>Hi</p>\n<a href=\"/x\" title=it's>It&#8217;s here</a>",
),
'Stray quote after an unquoted value' => array(
'<b class=x">It\'s bold</b>',
'<b class=x">It&#8217;s bold</b>',
),
'Quote after a < that does not start a tag' => array(
"<p>x <=' y is not a tag</p><p>and this isn't an attribute</p>",
"<p>x <=' y is not a tag</p><p>and this isn&#8217;t an attribute</p>",
),
);
}

/**
* Ensures that an unclosed tag at the end of the input is not texturized.
*
* @ticket 63997
*/
public function test_unclosed_tag_is_not_texturized(): void {
$input = "<p>Truncated <a href=\"http://example.com/it's";

$this->assertSame( $input, wptexturize( $input ) );
}

public static function data_whole_posts() {
require_once DIR_TESTDATA . '/formatting/whole-posts.php';
return data_whole_posts();
Expand Down
Loading