mirror of
https://github.com/gosticks/wordpress-develop.git
synced 2026-05-19 02:34:40 +00:00
Script Loader: Harden removal of script tag wrappers.
* Add `wp_remove_surrounding_empty_script_tags()` to more precisely remove script tag wrappers and warn when doing it wrong. * Add clarifying comments for XML escaping logic in `wp_get_inline_script_tag()`. * Leverage `WP_HTML_Tag_Processor` in `test_remove_frameless_preview_messenger_channel`. * Reuse `assertEqualMarkup` in `test_blocking_dependent_with_delayed_dependency`. * Normalize whitespace in `parse_markup_fragment` for `assertEqualMarkup`. Follow-up to [56687]. Props dmsnell, westonruter, flixos90. See #58664. git-svn-id: https://develop.svn.wordpress.org/trunk@56748 602fd350-edb4-49c9-b593-d223f7449a82
This commit is contained in:
@@ -171,6 +171,7 @@
|
||||
<element value="lastChild"/>
|
||||
<element value="nodeName"/>
|
||||
<element value="nodeType"/>
|
||||
<element value="nodeValue"/>
|
||||
<element value="parentNode"/>
|
||||
<element value="preserveWhiteSpace"/>
|
||||
<element value="textContent"/>
|
||||
|
||||
@@ -474,7 +474,7 @@ final class WP_Customize_Manager {
|
||||
} )( wp.customize, <?php echo wp_json_encode( $settings ); ?> );
|
||||
</script>
|
||||
<?php
|
||||
$message .= wp_get_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
$message .= wp_get_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
wp_die( $message );
|
||||
@@ -2109,7 +2109,7 @@ final class WP_Customize_Manager {
|
||||
} )();
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2230,7 +2230,7 @@ final class WP_Customize_Manager {
|
||||
})( _wpCustomizeSettings.values );
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -5019,7 +5019,7 @@ final class WP_Customize_Manager {
|
||||
?>
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -7619,7 +7619,7 @@ function wp_post_preview_js() {
|
||||
}());
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -2853,9 +2853,43 @@ function wp_get_inline_script_tag( $javascript, $attributes = array() ) {
|
||||
);
|
||||
}
|
||||
|
||||
// Ensure markup is XHTML compatible if not HTML5.
|
||||
/*
|
||||
* XHTML extracts the contents of the SCRIPT element and then the XML parser
|
||||
* decodes character references and other syntax elements. This can lead to
|
||||
* misinterpretation of the script contents or invalid XHTML documents.
|
||||
*
|
||||
* Wrapping the contents in a CDATA section instructs the XML parser not to
|
||||
* transform the contents of the SCRIPT element before passing them to the
|
||||
* JavaScript engine.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* <script>console.log('…');</script>
|
||||
*
|
||||
* In an HTML document this would print "…" to the console,
|
||||
* but in an XHTML document it would print "…" to the console.
|
||||
*
|
||||
* <script>console.log('An image is <img> in HTML');</script>
|
||||
*
|
||||
* In an HTML document this would print "An image is <img> in HTML",
|
||||
* but it's an invalid XHTML document because it interprets the `<img>`
|
||||
* as an empty tag missing its closing `/`.
|
||||
*
|
||||
* @see https://www.w3.org/TR/xhtml1/#h-4.8
|
||||
*/
|
||||
if ( ! $is_html5 ) {
|
||||
$javascript = str_replace( ']]>', ']]]]><![CDATA[>', $javascript ); // Escape any existing CDATA section.
|
||||
/*
|
||||
* If the string `]]>` exists within the JavaScript it would break
|
||||
* out of any wrapping CDATA section added here, so to start, it's
|
||||
* necessary to escape that sequence which requires splitting the
|
||||
* content into two CDATA sections wherever it's found.
|
||||
*
|
||||
* Note: it's only necessary to escape the closing `]]>` because
|
||||
* an additional `<![CDATA[` leaves the contents unchanged.
|
||||
*/
|
||||
$javascript = str_replace( ']]>', ']]]]><![CDATA[>', $javascript );
|
||||
|
||||
// Wrap the entire escaped script inside a CDATA section.
|
||||
$javascript = sprintf( "/* <![CDATA[ */\n%s\n/* ]]> */", $javascript );
|
||||
}
|
||||
|
||||
@@ -3299,3 +3333,51 @@ function wp_add_editor_classic_theme_styles( $editor_settings ) {
|
||||
|
||||
return $editor_settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes leading and trailing _empty_ script tags.
|
||||
*
|
||||
* This is a helper meant to be used for literal script tag construction
|
||||
* within `wp_get_inline_script_tag()` or `wp_print_inline_script_tag()`.
|
||||
* It removes the literal values of "<script>" and "</script>" from
|
||||
* around an inline script after trimming whitespace. Typlically this
|
||||
* is used in conjunction with output buffering, where `ob_get_clean()`
|
||||
* is passed as the `$contents` argument.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* // Strips exact literal empty SCRIPT tags.
|
||||
* $js = '<script>sayHello();</script>;
|
||||
* 'sayHello();' === wp_remove_surrounding_empty_script_tags( $js );
|
||||
*
|
||||
* // Otherwise if anything is different it warns in the JS console.
|
||||
* $js = '<script type="text/javascript">console.log( "hi" );</script>';
|
||||
* 'console.error( ... )' === wp_remove_surrounding_empty_script_tags( $js );
|
||||
*
|
||||
* @private
|
||||
* @since 6.4.0
|
||||
*
|
||||
* @see wp_print_inline_script_tag()
|
||||
* @see wp_get_inline_script_tag()
|
||||
*
|
||||
* @param string $contents Script body with manually created SCRIPT tag literals.
|
||||
* @return string Script body without surrounding script tag literals, or
|
||||
* original contents if both exact literals aren't present.
|
||||
*/
|
||||
function wp_remove_surrounding_empty_script_tags( $contents ) {
|
||||
$contents = trim( $contents );
|
||||
$opener = '<SCRIPT>';
|
||||
$closer = '</SCRIPT>';
|
||||
|
||||
if (
|
||||
strlen( $contents ) > strlen( $opener ) + strlen( $closer ) &&
|
||||
strtoupper( substr( $contents, 0, strlen( $opener ) ) ) === $opener &&
|
||||
strtoupper( substr( $contents, -strlen( $closer ) ) ) === $closer
|
||||
) {
|
||||
return substr( $contents, strlen( $opener ), -strlen( $closer ) );
|
||||
} else {
|
||||
$error_message = __( 'Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.' );
|
||||
_doing_it_wrong( __FUNCTION__, $error_message, '6.4' );
|
||||
return sprintf( 'console.error(%s)', wp_json_encode( __( 'Function wp_remove_surrounding_empty_script_tags() used incorrectly in PHP.' ) . ' ' . $error_message ) );
|
||||
}
|
||||
}
|
||||
|
||||
@@ -205,7 +205,7 @@ function the_block_template_skip_link() {
|
||||
}() );
|
||||
</script>
|
||||
<?php
|
||||
$skip_link_script = str_replace( array( '<script>', '</script>' ), '', ob_get_clean() );
|
||||
$skip_link_script = wp_remove_surrounding_empty_script_tags( ob_get_clean() );
|
||||
$script_handle = 'wp-block-template-skip-link';
|
||||
wp_register_script( $script_handle, false );
|
||||
wp_add_inline_script( $script_handle, $skip_link_script );
|
||||
|
||||
@@ -3800,7 +3800,7 @@ function wp_customize_support_script() {
|
||||
}());
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -120,7 +120,7 @@ class WP_Widget_Archives extends WP_Widget {
|
||||
})();
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
} else {
|
||||
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
|
||||
|
||||
|
||||
@@ -108,7 +108,7 @@ class WP_Widget_Categories extends WP_Widget {
|
||||
</script>
|
||||
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
} else {
|
||||
$format = current_theme_supports( 'html5', 'navigation-widgets' ) ? 'html5' : 'xhtml';
|
||||
|
||||
|
||||
@@ -105,7 +105,7 @@ function login_header( $title = 'Log In', $message = '', $wp_error = null ) {
|
||||
?>
|
||||
<script>if("sessionStorage" in window){try{for(var key in sessionStorage){if(key.indexOf("wp-autosave-")!=-1){sessionStorage.removeItem(key)}}}catch(e){}};</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -419,12 +419,12 @@ function login_footer( $input_id = '' ) {
|
||||
if ( ! empty( $input_id ) ) {
|
||||
ob_start();
|
||||
?>
|
||||
<script>
|
||||
<script type="text/javascript">
|
||||
try{document.getElementById('<?php echo $input_id; ?>').focus();}catch(e){}
|
||||
if(typeof wpOnload==='function')wpOnload();
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1362,7 +1362,7 @@ switch ( $action ) {
|
||||
?>
|
||||
<script>setTimeout( function(){ new wp.customize.Messenger({ url: '<?php echo wp_customize_url(); ?>', channel: 'login' }).send('login') }, 1000 );</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
?>
|
||||
@@ -1627,7 +1627,7 @@ switch ( $action ) {
|
||||
}());
|
||||
</script>
|
||||
<?php
|
||||
wp_print_inline_script_tag( str_replace( array( '<script>', '</script>' ), '', ob_get_clean() ) );
|
||||
wp_print_inline_script_tag( wp_remove_surrounding_empty_script_tags( ob_get_clean() ) );
|
||||
}
|
||||
|
||||
login_footer();
|
||||
|
||||
@@ -3135,8 +3135,8 @@ class Tests_WP_Customize_Manager extends WP_UnitTestCase {
|
||||
$manager = new WP_Customize_Manager( array( 'messenger_channel' => 'preview-0' ) );
|
||||
ob_start();
|
||||
$manager->remove_frameless_preview_messenger_channel();
|
||||
$output = ob_get_clean();
|
||||
$this->assertStringContainsString( '<script', $output );
|
||||
$processor = new WP_HTML_Tag_Processor( ob_get_clean() );
|
||||
$this->assertTrue( $processor->next_tag( 'script' ), 'Failed to find expected SCRIPT element in output.' );
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -260,8 +260,11 @@ JS;
|
||||
wp_enqueue_script( 'main-script-a3', '/main-script-a3.js', array(), null, compact( 'strategy' ) );
|
||||
wp_enqueue_script( 'dependent-script-a3', '/dependent-script-a3.js', array( 'main-script-a3' ), null );
|
||||
$output = get_echo( 'wp_print_scripts' );
|
||||
$expected = str_replace( "'", '"', "<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js' data-wp-strategy='{$strategy}'></script>" );
|
||||
$this->assertStringContainsString( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
|
||||
$expected = <<<JS
|
||||
<script type='text/javascript' src='/main-script-a3.js' id='main-script-a3-js' data-wp-strategy='{$strategy}'></script>
|
||||
<script id="dependent-script-a3-js" src="/dependent-script-a3.js" type="text/javascript"></script>
|
||||
JS;
|
||||
$this->assertEqualMarkup( $expected, $output, 'Blocking dependents must force delayed dependencies to become blocking.' );
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -2997,6 +3000,15 @@ HTML
|
||||
}
|
||||
}
|
||||
|
||||
// Normalize other whitespace nodes.
|
||||
$xpath = new DOMXPath( $dom );
|
||||
foreach ( $xpath->query( '//text()' ) as $node ) {
|
||||
/** @var DOMText $node */
|
||||
if ( preg_match( '/^\s+$/', $node->nodeValue ) ) {
|
||||
$node->nodeValue = ' ';
|
||||
}
|
||||
}
|
||||
|
||||
return $dom;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
/**
|
||||
* Test wp_remove_surrounding_empty_script_tags().
|
||||
*
|
||||
* @group dependencies
|
||||
* @group scripts
|
||||
* @ticket 58664
|
||||
* @covers ::wp_remove_surrounding_empty_script_tags
|
||||
*/
|
||||
class Tests_Functions_wpRemoveSurroundingEmptyScriptTags extends WP_UnitTestCase {
|
||||
|
||||
/**
|
||||
* Data provider for test.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public function get_data_to_test_wp_remove_surrounding_empty_script_tags() {
|
||||
$error_js = 'console.error("Function wp_remove_surrounding_empty_script_tags() used incorrectly in PHP. Expected string to start with script tag (without attributes) and end with script tag, with optional whitespace.")';
|
||||
return array(
|
||||
'basic_case' => array(
|
||||
'<script>alert("hello")</script>',
|
||||
'alert("hello")',
|
||||
false,
|
||||
),
|
||||
'BASIC_CASE' => array(
|
||||
'<SCRIPT>alert("hello")</SCRIPT>',
|
||||
'alert("hello")',
|
||||
false,
|
||||
),
|
||||
'whitespace_basic_case' => array(
|
||||
' <script>alert("hello")</script> ',
|
||||
'alert("hello")',
|
||||
false,
|
||||
),
|
||||
'missing_tags' => array(
|
||||
'alert("hello")',
|
||||
$error_js,
|
||||
true,
|
||||
),
|
||||
'missing_start_tag' => array(
|
||||
'alert("hello")</script>',
|
||||
$error_js,
|
||||
true,
|
||||
),
|
||||
'missing_end_tag' => array(
|
||||
'<script>alert("hello")',
|
||||
$error_js,
|
||||
true,
|
||||
),
|
||||
'erroneous attributes' => array(
|
||||
'<script type="text/javascript">alert("hello")</script>',
|
||||
$error_js,
|
||||
true,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test scenarios for wp_remove_surrounding_empty_script_tags().
|
||||
*
|
||||
* @dataProvider get_data_to_test_wp_remove_surrounding_empty_script_tags
|
||||
*
|
||||
* @param string $input Input.
|
||||
* @param string $expected Expected.
|
||||
* @param bool $expect_doing_it_wrong Whether input is _doing_it_wrong().
|
||||
*/
|
||||
public function test_wp_remove_surrounding_empty_script_tags( $input, $expected, $expect_doing_it_wrong ) {
|
||||
if ( $expect_doing_it_wrong ) {
|
||||
$this->setExpectedIncorrectUsage( 'wp_remove_surrounding_empty_script_tags' );
|
||||
}
|
||||
|
||||
$this->assertSame(
|
||||
$expected,
|
||||
wp_remove_surrounding_empty_script_tags( $input )
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user