diff --git a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php index aab0e475304ea..0d8324e367a3a 100644 --- a/src/wp-includes/customize/class-wp-customize-custom-css-setting.php +++ b/src/wp-includes/customize/class-wp-customize-custom-css-setting.php @@ -144,35 +144,6 @@ public function value() { return $value; } - /** - * Validate a received value for being valid CSS. - * - * Checks for imbalanced braces, brackets, and comments. - * Notifications are rendered when the customizer state is saved. - * - * @since 4.7.0 - * @since 4.9.0 Checking for balanced characters has been moved client-side via linting in code editor. - * @since 5.9.0 Renamed `$css` to `$value` for PHP 8 named parameter support. - * - * @param string $value CSS to validate. - * @return true|WP_Error True if the input was validated, otherwise WP_Error. - */ - public function validate( $value ) { - // Restores the more descriptive, specific name for use within this method. - $css = $value; - - $validity = new WP_Error(); - - if ( preg_match( '#add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); - } - - if ( ! $validity->has_errors() ) { - $validity = parent::validate( $css ); - } - return $validity; - } - /** * Store the CSS setting value in the custom_css custom post type for the stylesheet. * diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 44343569a61b1..32a5c13b67953 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -2134,6 +2134,16 @@ function wp_update_custom_css_post( $css, $args = array() ) { // Update post if it already exists, otherwise create a new one. $post = wp_get_custom_css_post( $args['stylesheet'] ); + + /** + * Temporarily remove the {@see wp_filter_post_kses()} `content_save_pre` filter. CSS text is + * stored in post_content, but the filter would process it as HTML and may mangle valid CSS. + */ + $kses_filter_priority = has_filter( 'content_save_pre', 'wp_filter_post_kses' ); + if ( false !== $kses_filter_priority ) { + remove_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority ); + } + if ( $post ) { $post_data['ID'] = $post->ID; $r = wp_update_post( wp_slash( $post_data ), true ); @@ -2153,6 +2163,10 @@ function wp_update_custom_css_post( $css, $args = array() ) { } } + if ( false !== $kses_filter_priority ) { + add_filter( 'content_save_pre', 'wp_filter_post_kses', $kses_filter_priority ); + } + if ( is_wp_error( $r ) ) { return $r; } diff --git a/tests/phpunit/tests/customize/custom-css-setting.php b/tests/phpunit/tests/customize/custom-css-setting.php index 65cc3f717fe59..0899e640af320 100644 --- a/tests/phpunit/tests/customize/custom-css-setting.php +++ b/tests/phpunit/tests/customize/custom-css-setting.php @@ -375,27 +375,59 @@ public function filter_update_custom_css_data( $data, $args ) { } /** - * Tests that validation errors are caught appropriately. + * Ensure that dangerous STYLE tag contents do not break HTML output. * - * Note that the $validity \WP_Error object must be reset each time - * as it picks up the Errors and passes them to the next assertion. - * - * @covers WP_Customize_Custom_CSS_Setting::validate + * @ticket 64418 + * @covers ::wp_update_custom_css_post + * @covers ::wp_custom_css_cb */ - public function test_validate() { - - // Empty CSS throws no errors. - $result = $this->setting->validate( '' ); - $this->assertTrue( $result ); + public function test_wp_custom_css_cb_escapes_dangerous_html() { + wp_update_custom_css_post( + '*::before { content: ""; }', + array( + 'stylesheet' => $this->setting->stylesheet, + ) + ); + $output = get_echo( 'wp_custom_css_cb' ); + $expected = <<<'HTML' + +HTML; + $this->assertEqualHTML( $expected, $output ); + } - // Basic, valid CSS throws no errors. - $basic_css = 'body { background: #f00; } h1.site-title { font-size: 36px; } a:hover { text-decoration: none; } input[type="text"] { padding: 1em; }'; - $result = $this->setting->validate( $basic_css ); - $this->assertTrue( $result ); + /** + * @ticket 64418 + * @covers WP_Customize_Custom_CSS_Setting::validate + */ + public function test_validate_accepts_css_property_at_rule() { + $css = <<<'CSS' +@property --animate { + syntax: ""; + inherits: true; + initial-value: false; +} +CSS; + $this->assertTrue( $this->setting->validate( $css ) ); + } - // Check for markup. - $unclosed_comment = $basic_css . ''; - $result = $this->setting->validate( $unclosed_comment ); - $this->assertArrayHasKey( 'illegal_markup', $result->errors ); + /** + * @ticket 64418 + * @covers ::wp_update_custom_css_post + * @covers ::wp_custom_css_cb + */ + public function test_save_and_print_property_at_rule() { + $css = <<<'CSS' +@property --animate { + syntax: ""; + inherits: true; + initial-value: false; +} +CSS; + wp_update_custom_css_post( $css, array( 'stylesheet' => $this->setting->stylesheet ) ); + $output = get_echo( 'wp_custom_css_cb' ); + $expected = "\n"; + $this->assertEqualHTML( $expected, $output ); } }