diff --git a/src/wp-admin/includes/class-pclzip.php b/src/wp-admin/includes/class-pclzip.php index 1fdc8b9f41296..e8eb37ddd892a 100644 --- a/src/wp-admin/includes/class-pclzip.php +++ b/src/wp-admin/includes/class-pclzip.php @@ -3988,7 +3988,7 @@ function privExtractFileUsingTempFile(&$p_entry, &$p_options) // ----- Write gz file format header - $v_binary_data = pack('va1a1Va1a1', 0x8b1f, Chr($p_entry['compression']), Chr(0x00), time(), Chr(0x00), Chr(3)); + $v_binary_data = pack('va1a1Va1a1', 0x8b1f, chr($p_entry['compression']), chr(0x00), time(), chr(0x00), chr(3)); @fwrite($v_dest_file, $v_binary_data, 10); // ----- Read the file by PCLZIP_READ_BLOCK_SIZE octets blocks @@ -4616,10 +4616,10 @@ function privReadEndCentralDir(&$p_central_dir) $v_byte = @fread($this->zip_fd, 1); // ----- Add the byte - //$v_bytes = ($v_bytes << 8) | Ord($v_byte); + //$v_bytes = ($v_bytes << 8) | ord($v_byte); // Note we mask the old value down such that once shifted we can never end up with more than a 32bit number // Otherwise on systems where we have 64bit integers the check below for the magic number will fail. - $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | Ord($v_byte); + $v_bytes = ( ($v_bytes & 0xFFFFFF) << 8) | ord($v_byte); // ----- Compare the bytes if ($v_bytes == 0x504b0506) diff --git a/src/wp-includes/functions.php b/src/wp-includes/functions.php index 93b4df2df4505..c297864859aa4 100644 --- a/src/wp-includes/functions.php +++ b/src/wp-includes/functions.php @@ -1031,14 +1031,19 @@ function is_new_day() { * This is a convenient function for easily building URL queries. * It sets the separator to '&' and uses the _http_build_query() function. * + * Unlike PHP's native http_build_query(), this function does NOT URL-encode + * the keys or values. Callers are responsible for encoding values beforehand + * with urlencode() or rawurlencode(), or late-escaping the output with + * esc_url() before use. + * * @since 2.3.0 * - * @see _http_build_query() Used to build the query + * @see _http_build_query() Used to build the query. * @link https://www.php.net/manual/en/function.http-build-query.php for more on what * http_build_query() does. * - * @param array $data URL-encode key/value pairs. - * @return string URL-encoded string. + * @param array $data Array of key/value pairs to build the query from. + * @return string Query string, without URL encoding applied. */ function build_query( $data ) { return _http_build_query( $data, null, '&', '', false ); diff --git a/src/wp-includes/html-api/class-wp-html-processor.php b/src/wp-includes/html-api/class-wp-html-processor.php index 56ea0f705c2b8..c0bdbfe570021 100644 --- a/src/wp-includes/html-api/class-wp-html-processor.php +++ b/src/wp-includes/html-api/class-wp-html-processor.php @@ -2918,8 +2918,7 @@ private function step_in_body(): bool { case '-STRONG': case '-TT': case '-U': - $this->run_adoption_agency_algorithm(); - return true; + return $this->run_adoption_agency_algorithm(); /* * > A start tag whose tag name is one of: "applet", "marquee", "object" @@ -3251,41 +3250,57 @@ private function step_in_body(): bool { $this->insert_html_element( $this->state->current_token ); return true; } else { - /* - * > Any other end tag - */ + return $this->step_in_body_any_other_end_tag(); + } - /* - * Find the corresponding tag opener in the stack of open elements, if - * it exists before reaching a special element, which provides a kind - * of boundary in the stack. For example, a `` should not - * close anything beyond its containing `P` or `DIV` element. - */ - foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { - if ( 'html' === $node->namespace && $token_name === $node->node_name ) { - break; - } + $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); + // This unnecessary return prevents tools from inaccurately reporting type errors. + return false; + } - if ( self::is_special( $node ) ) { - // This is a parse error, ignore the token. - return $this->step(); - } + /** + * Parses an "any other end tag" token in the "in body" insertion mode. + * + * @since 7.1.0 + * @ignore + * + * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. + * + * @return bool Whether an element was found. + */ + private function step_in_body_any_other_end_tag(): bool { + $token_name = $this->get_token_name(); + + /* + * Find the corresponding tag opener in the stack of open elements, if + * it exists before reaching a special element, which provides a kind + * of boundary in the stack. For example, a `` should not + * close anything beyond its containing `P` or `DIV` element. + */ + foreach ( $this->state->stack_of_open_elements->walk_up() as $node ) { + if ( 'html' === $node->namespace && $token_name === $node->node_name ) { + break; } - $this->generate_implied_end_tags( $token_name ); - if ( $node !== $this->state->stack_of_open_elements->current_node() ) { - // @todo Record parse error: this error doesn't impact parsing. + if ( self::is_special( $node ) ) { + // This is a parse error, ignore the token. + return $this->step(); } + } - foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { - $this->state->stack_of_open_elements->pop(); - if ( $node === $item ) { - return true; - } + $this->generate_implied_end_tags( $token_name ); + if ( $node !== $this->state->stack_of_open_elements->current_node() ) { + // @todo Record parse error: this error doesn't impact parsing. + } + + foreach ( $this->state->stack_of_open_elements->walk_up() as $item ) { + $this->state->stack_of_open_elements->pop(); + if ( $node === $item ) { + return true; } } - $this->bail( 'Should not have been able to reach end of IN BODY processing. Check HTML API code.' ); + $this->bail( 'Should not have been able to reach end of IN BODY "any other end tag" processing. Check HTML API code.' ); // This unnecessary return prevents tools from inaccurately reporting type errors. return false; } @@ -6221,8 +6236,10 @@ private function reset_insertion_mode_appropriately(): void { * @throws WP_HTML_Unsupported_Exception When encountering unsupported HTML input. * * @see https://html.spec.whatwg.org/#adoption-agency-algorithm + * + * @return bool Whether an element was found. */ - private function run_adoption_agency_algorithm(): void { + private function run_adoption_agency_algorithm(): bool { $budget = 1000; $subject = $this->get_tag(); $current_node = $this->state->stack_of_open_elements->current_node(); @@ -6234,13 +6251,13 @@ private function run_adoption_agency_algorithm(): void { ! $this->state->active_formatting_elements->contains_node( $current_node ) ) { $this->state->stack_of_open_elements->pop(); - return; + return true; } $outer_loop_counter = 0; while ( $budget-- > 0 ) { if ( $outer_loop_counter++ >= 8 ) { - return; + return true; } /* @@ -6263,18 +6280,18 @@ private function run_adoption_agency_algorithm(): void { // > If there is no such element, then return and instead act as described in the "any other end tag" entry above. if ( null === $formatting_element ) { - $this->bail( 'Cannot run adoption agency when "any other end tag" is required.' ); + return $this->step_in_body_any_other_end_tag(); } // > If formatting element is not in the stack of open elements, then this is a parse error; remove the element from the list, and return. if ( ! $this->state->stack_of_open_elements->contains_node( $formatting_element ) ) { $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; + return true; } // > If formatting element is in the stack of open elements, but the element is not in scope, then this is a parse error; return. if ( ! $this->state->stack_of_open_elements->has_element_in_scope( $formatting_element->node_name ) ) { - return; + return true; } /* @@ -6310,7 +6327,7 @@ private function run_adoption_agency_algorithm(): void { if ( $formatting_element->bookmark_name === $item->bookmark_name ) { $this->state->active_formatting_elements->remove_node( $formatting_element ); - return; + return true; } } } @@ -6319,6 +6336,8 @@ private function run_adoption_agency_algorithm(): void { } $this->bail( 'Cannot run adoption agency when looping required.' ); + // This unnecessary return prevents tools from inaccurately reporting type errors. + return false; } /** diff --git a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php index 095f12380dfbe..bb87995153906 100644 --- a/src/wp-includes/interactivity-api/class-wp-interactivity-api.php +++ b/src/wp-includes/interactivity-api/class-wp-interactivity-api.php @@ -1086,8 +1086,7 @@ private function data_wp_bind_processor( WP_Interactivity_API_Directives_Process */ private function data_wp_class_processor( WP_Interactivity_API_Directives_Processor $p, string $mode ) { if ( 'enter' === $mode ) { - $all_class_directives = $p->get_attribute_names_with_prefix( 'data-wp-class--' ); - $entries = $this->get_directive_entries( $p, 'class' ); + $entries = $this->get_directive_entries( $p, 'class' ); foreach ( $entries as $entry ) { if ( empty( $entry['suffix'] ) ) { continue; diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php index a29e5ba863026..6f438562fd535 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessor-serialize.php @@ -265,6 +265,36 @@ public function test_unexpected_closing_tags_are_removed() { ); } + /** + * Ensures that unexpected closing formatting tags are ignored. + * + * @ticket 65372 + * + * @dataProvider data_unexpected_closing_formatting_tags + * + * @param string $html HTML containing an unexpected closing formatting tag. + * @param string $expected Expected normalized output. + */ + public function test_unexpected_closing_formatting_tags_are_ignored( string $html, string $expected ) { + $this->assertSame( + $expected, + WP_HTML_Processor::normalize( $html ), + 'Should have ignored unexpected closing formatting tags.' + ); + } + + /** + * Data provider. + * + * @return array[] + */ + public static function data_unexpected_closing_formatting_tags() { + return array( + 'Unexpected A end tag' => array( 'onetwo', 'onetwo' ), + 'Unexpected B end tag' => array( 'onetwo', 'onetwo' ), + ); + } + /** * Ensures that self-closing elements in foreign content retain their self-closing flag. * diff --git a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php index ffc99ad58fd8e..b8cfbe36d2208 100644 --- a/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php +++ b/tests/phpunit/tests/html-api/wpHtmlProcessorSemanticRules.php @@ -405,6 +405,66 @@ public function test_in_body_any_other_end_tag_with_unclosed_non_special_element $this->assertSame( array( 'HTML', 'BODY', 'DIV', 'DIV' ), $processor->get_breadcrumbs(), 'Failed to produce expected DOM nesting: SPAN should be closed and DIV should be its sibling.' ); } + /** + * Verifies that when the adoption agency algorithm finds no matching + * active formatting element, it acts like "any other end tag". + * + * @covers WP_HTML_Processor::step_in_body + * + * @ticket 65372 + * + * @dataProvider data_in_body_adoption_agency_falls_back_to_any_other_end_tag + * + * @param string $formatting_tag_name Formatting tag name with no active formatting element. + */ + public function test_in_body_adoption_agency_falls_back_to_any_other_end_tag( string $formatting_tag_name ) { + $processor = WP_HTML_Processor::create_fragment( "