Skip to content

Commit 7a58dc0

Browse files
committed
HTML API: Prevent bookmark exhaustion from throwing.
When bookmark exhaustion occurs during processing, return `false` instead of throwing an `Exception`. Developed in #10616. Props jonsurrell, westonruter, dmsnell. Fixes #64394. git-svn-id: https://develop.svn.wordpress.org/trunk@61755 602fd350-edb4-49c9-b593-d223f7449a82
1 parent 89ea38c commit 7a58dc0

2 files changed

Lines changed: 121 additions & 2 deletions

File tree

src/wp-includes/html-api/class-wp-html-processor.php

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1042,8 +1042,17 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ): bool {
10421042
$token_name = $this->get_token_name();
10431043

10441044
if ( self::REPROCESS_CURRENT_NODE !== $node_to_process ) {
1045+
try {
1046+
$bookmark_name = $this->bookmark_token();
1047+
} catch ( Exception $e ) {
1048+
if ( self::ERROR_EXCEEDED_MAX_BOOKMARKS === $this->last_error ) {
1049+
return false;
1050+
}
1051+
throw $e;
1052+
}
1053+
10451054
$this->state->current_token = new WP_HTML_Token(
1046-
$this->bookmark_token(),
1055+
$bookmark_name,
10471056
$token_name,
10481057
$this->has_self_closing_flag(),
10491058
$this->release_internal_bookmark_on_destruct
@@ -1153,6 +1162,12 @@ public function step( $node_to_process = self::PROCESS_NEXT_NODE ): bool {
11531162
* otherwise might involve messier calling and return conventions.
11541163
*/
11551164
return false;
1165+
} catch ( Exception $e ) {
1166+
if ( self::ERROR_EXCEEDED_MAX_BOOKMARKS === $this->last_error ) {
1167+
return false;
1168+
}
1169+
// Rethrow any other exceptions for higher-level handling.
1170+
throw $e;
11561171
}
11571172
}
11581173

@@ -6315,6 +6330,8 @@ private function insert_foreign_element( WP_HTML_Token $token, bool $only_add_to
63156330
*
63166331
* @since 6.7.0
63176332
*
6333+
* @throws Exception When unable to allocate a bookmark for the next token in the input HTML document.
6334+
*
63186335
* @param string $token_name Name of token to create and insert into the stack of open elements.
63196336
* @param string|null $bookmark_name Optional. Name to give bookmark for created virtual node.
63206337
* Defaults to auto-creating a bookmark name.

tests/phpunit/tests/html-api/wpHtmlProcessor.php

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,7 +1068,7 @@ public function test_ensure_next_token_method_extensibility( $html, $expected_to
10681068
/**
10691069
* Ensure that lowercased tag_name query matches tags case-insensitively.
10701070
*
1071-
* @group 62427
1071+
* @ticket 62427
10721072
*/
10731073
public function test_next_tag_lowercase_tag_name() {
10741074
// The upper case <DIV> is irrelevant but illustrates the case-insentivity.
@@ -1079,4 +1079,106 @@ public function test_next_tag_lowercase_tag_name() {
10791079
$processor = WP_HTML_Processor::create_fragment( '<svg><RECT>' );
10801080
$this->assertTrue( $processor->next_tag( array( 'tag_name' => 'rect' ) ) );
10811081
}
1082+
1083+
/**
1084+
* Ensure that the processor does not throw errors in cases of extreme HTML nesting.
1085+
*
1086+
* @ticket 64394
1087+
*
1088+
* @expectedIncorrectUsage WP_HTML_Tag_Processor::set_bookmark
1089+
*/
1090+
public function test_deep_nesting_fails_process_without_error() {
1091+
$html = str_repeat( '<i>', WP_HTML_Processor::MAX_BOOKMARKS * 2 );
1092+
$processor = WP_HTML_Processor::create_fragment( $html );
1093+
1094+
while ( $processor->next_token() ) {
1095+
// Process tokens.
1096+
}
1097+
1098+
$this->assertSame(
1099+
WP_HTML_Processor::ERROR_EXCEEDED_MAX_BOOKMARKS,
1100+
$processor->get_last_error(),
1101+
'Failed to report exceeded-max-bookmarks error.'
1102+
);
1103+
}
1104+
1105+
/**
1106+
* @ticket 64394
1107+
*
1108+
* @expectedIncorrectUsage WP_HTML_Tag_Processor::set_bookmark
1109+
*/
1110+
public function test_deep_nesting_fails_processing_virtual_tokens_without_error() {
1111+
/*
1112+
* This test has some variability depending on how the virtual tokens align.
1113+
* In order to ensure that bookmarks are exhausted on a virtual token
1114+
* without throwing an error, 3 documents are parsed with different "offsets"
1115+
* to ensure that the bookmarks are exhaused on a virtual token in at least one of the runs.
1116+
*
1117+
* "<table><td><table><td>…" produces:
1118+
* └─TABLE (real)
1119+
* └─TBODY (virtual)
1120+
* └─TR (virtual)
1121+
* └─TD (real)
1122+
* └─TABLE (real)
1123+
* └─TBODY (virtual)
1124+
* └─TR (virtual)
1125+
* └─TD (real)
1126+
* └─…
1127+
*/
1128+
$html_table_td = str_repeat( '<table><td>', WP_HTML_Processor::MAX_BOOKMARKS * 2 );
1129+
1130+
// Offset 0
1131+
$processor = WP_HTML_Processor::create_fragment( $html_table_td );
1132+
while ( $processor->next_token() ) {
1133+
// Process tokens.
1134+
}
1135+
$this->assertSame(
1136+
WP_HTML_Processor::ERROR_EXCEEDED_MAX_BOOKMARKS,
1137+
$processor->get_last_error(),
1138+
'Failed to report exceeded-max-bookmarks error.'
1139+
);
1140+
1141+
// Offset 1
1142+
$processor = WP_HTML_Processor::create_fragment( "<div>{$html_table_td}" );
1143+
while ( $processor->next_token() ) {
1144+
// Process tokens.
1145+
}
1146+
$this->assertSame(
1147+
WP_HTML_Processor::ERROR_EXCEEDED_MAX_BOOKMARKS,
1148+
$processor->get_last_error(),
1149+
'Failed to report exceeded-max-bookmarks error.'
1150+
);
1151+
1152+
// Offset 2
1153+
$processor = WP_HTML_Processor::create_fragment( "<div><div>{$html_table_td}" );
1154+
while ( $processor->next_token() ) {
1155+
// Process tokens.
1156+
}
1157+
$this->assertSame(
1158+
WP_HTML_Processor::ERROR_EXCEEDED_MAX_BOOKMARKS,
1159+
$processor->get_last_error(),
1160+
'Failed to report exceeded-max-bookmarks error.'
1161+
);
1162+
}
1163+
1164+
/**
1165+
* @ticket 64394
1166+
*
1167+
* @expectedIncorrectUsage WP_HTML_Tag_Processor::set_bookmark
1168+
*/
1169+
public function test_prevents_unbounded_bookmarking() {
1170+
$processor = WP_HTML_Processor::create_full_parser( '<!DOCTYPE html><html>' );
1171+
$processor->next_tag();
1172+
1173+
// This might fail before the MAX_BOOKMARK limit, which is okay.
1174+
foreach ( range( 0, WP_HTML_Processor::MAX_BOOKMARKS ) as $n ) {
1175+
if ( ! $processor->set_bookmark( "{$n}" ) ) {
1176+
break;
1177+
}
1178+
}
1179+
1180+
$this->assertFalse(
1181+
$processor->set_bookmark( 'beyond the limit' )
1182+
);
1183+
}
10821184
}

0 commit comments

Comments
 (0)