diff --git a/src/wp-includes/class-wp-styles.php b/src/wp-includes/class-wp-styles.php index 9b210b2df9d30..6d47bc15d56fd 100644 --- a/src/wp-includes/class-wp-styles.php +++ b/src/wp-includes/class-wp-styles.php @@ -183,12 +183,11 @@ public function do_item( $handle, $group = false ) { $inline_style = $this->print_inline_style( $handle, false ); if ( $inline_style ) { - $inline_style_tag = sprintf( - "\n", - esc_attr( $handle ), - $this->type_attr, - $inline_style - ); + $processor = new WP_HTML_Tag_Processor( "type_attr}>\n" ); + $processor->next_tag(); + $processor->set_attribute( 'id', "{$handle}-inline-css" ); + $processor->set_modifiable_text( "\n{$inline_style}\n" ); + $inline_style_tag = $processor->get_updated_html(); } else { $inline_style_tag = ''; } @@ -364,12 +363,11 @@ public function print_inline_style( $handle, $display = true ) { return $output; } - printf( - "\n", - esc_attr( $handle ), - $this->type_attr, - $output - ); + $processor = new WP_HTML_Tag_Processor( "type_attr}>\n" ); + $processor->next_tag(); + $processor->set_attribute( 'id', "{$handle}-inline-css" ); + $processor->set_modifiable_text( "\n{$output}\n" ); + echo $processor->get_updated_html(); return true; } 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..ebe61fbf270f6 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 @@ -145,14 +145,14 @@ public function value() { } /** - * Validate a received value for being valid CSS. + * Validate a received value for being safe HTML STYLE tag contents. * - * 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. + * @since 7.0.0 Relaxed to only check for safe HTML STYLE tag contents. * * @param string $value CSS to validate. * @return true|WP_Error True if the input was validated, otherwise WP_Error. @@ -163,8 +163,16 @@ public function validate( $value ) { $validity = new WP_Error(); - if ( preg_match( '#add( 'illegal_markup', __( 'Markup is not allowed in CSS.' ) ); + /** + * Check for a closing STYLE tag inside the CSS. + * + * STYLE tags are processed using the "generic raw text parsing algorithm." They contain + * raw text up until a matching closing tag. + * + * @see https://html.spec.whatwg.org/multipage/parsing.html#generic-raw-text-element-parsing-algorithm + */ + if ( false !== stripos( $css, 'add( 'illegal_markup', __( 'CSS must not contain possible closing STYLE tag "has_errors() ) { diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php index 908ebe4bcc777..6a3405166a3d5 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-global-styles-controller.php @@ -659,16 +659,25 @@ public function get_theme_items( $request ) { /** * Validate style.css as valid CSS. * - * Currently just checks for invalid markup. + * Currently just checks that CSS will not break an HTML STYLE tag. * * @since 6.2.0 * @since 6.4.0 Changed method visibility to protected. + * @since 7.0.0 Relaxed to only check for safe HTML STYLE tag contents. * * @param string $css CSS to validate. * @return true|WP_Error True if the input was validated, otherwise WP_Error. */ protected function validate_custom_css( $css ) { - if ( preg_match( '#\n"; if ( ! empty( $wp_styles->print_code ) ) { - echo "\n"; - echo $wp_styles->print_code; - echo sprintf( "\n/*# sourceURL=%s */", rawurlencode( $concat_source_url ) ); - echo "\n\n"; + $processor = new WP_HTML_Tag_Processor( "" ); + $processor->next_tag(); + $style_tag_contents = "\n{$wp_styles->print_code}\n" + . sprintf( "/*# sourceURL=%s */\n", rawurlencode( $concat_source_url ) ); + $processor->set_modifiable_text( $style_tag_contents ); + echo $processor->get_updated_html(); } } @@ -3217,7 +3219,10 @@ function wp_enqueue_block_support_styles( $style, $priority = 10 ) { add_action( $action_hook_name, static function () use ( $style ) { - echo "\n"; + $processor = new WP_HTML_Tag_Processor( "\n" ); + $processor->next_tag(); + $processor->set_modifiable_text( $style ); + echo $processor->get_updated_html(); }, $priority ); diff --git a/src/wp-includes/theme.php b/src/wp-includes/theme.php index 558bea6ae9e02..b8d35a255d35c 100644 --- a/src/wp-includes/theme.php +++ b/src/wp-includes/theme.php @@ -1953,11 +1953,13 @@ function _custom_background_cb() { $style .= $image . $position . $size . $repeat . $attachment; } - ?> - id="custom-background-css"> -body.custom-background { } - - " ); + $processor->next_tag(); + + $style_tag_content = 'body.custom-background { ' . trim( $style ) . ' }'; + $processor->set_modifiable_text( "\n{$style_tag_content}\n" ); + echo $processor->get_updated_html(); } /** @@ -1967,17 +1969,18 @@ function _custom_background_cb() { */ function wp_custom_css_cb() { $styles = wp_get_custom_css(); - if ( $styles || is_customize_preview() ) : - $type_attr = current_theme_supports( 'html5', 'style' ) ? '' : ' type="text/css"'; - ?> - id="wp-custom-css"> - - - ' ); + $processor->next_tag(); + if ( ! current_theme_supports( 'html5', 'style' ) ) { + $processor->set_attribute( 'type', 'text/css' ); + } + $processor->set_attribute( 'id', 'wp-custom-css' ); + $processor->set_modifiable_text( "\n{$styles}\n" ); + echo $processor->get_updated_html(); } /** diff --git a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php index 59986e597c71b..ab4395db02c2f 100644 --- a/tests/phpunit/tests/rest-api/rest-global-styles-controller.php +++ b/tests/phpunit/tests/rest-api/rest-global-styles-controller.php @@ -650,6 +650,7 @@ public function test_update_item_valid_styles_css() { /** * @covers WP_REST_Global_Styles_Controller::update_item * @ticket 57536 + * @ticket 64418 */ public function test_update_item_invalid_styles_css() { wp_set_current_user( self::$admin_id ); @@ -659,7 +660,9 @@ public function test_update_item_invalid_styles_css() { $request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id ); $request->set_body_params( array( - 'styles' => array( 'css' => '

test

body { color: red; }' ), + 'styles' => array( + 'css' => 'dispatch( $request ); @@ -826,4 +829,32 @@ public function test_global_styles_route_args_schema() { $this->assertArrayHasKey( 'type', $route_data[0]['args']['id'] ); $this->assertSame( 'integer', $route_data[0]['args']['id']['type'] ); } + + /** + * @covers WP_REST_Global_Styles_Controller::update_item + * @ticket 64418 + */ + public function test_update_allows_valid_css_with_more_syntax() { + wp_set_current_user( self::$admin_id ); + if ( is_multisite() ) { + grant_super_admin( self::$admin_id ); + } + $request = new WP_REST_Request( 'PUT', '/wp/v2/global-styles/' . self::$global_styles_id ); + $css = <<<'CSS' +@property --animate { + syntax: ""; + inherits: true; + initial-value: false; +} +CSS; + $request->set_body_params( + array( + 'styles' => array( 'css' => $css ), + ) + ); + + $response = rest_get_server()->dispatch( $request ); + $data = $response->get_data(); + $this->assertSame( $css, $data['styles']['css'] ); + } }