diff --git a/projects/plugins/boost/app/lib/critical-css/class-display-critical-css.php b/projects/plugins/boost/app/lib/critical-css/class-display-critical-css.php
index 0c24b8647640..71e2785e97f9 100644
--- a/projects/plugins/boost/app/lib/critical-css/class-display-critical-css.php
+++ b/projects/plugins/boost/app/lib/critical-css/class-display-critical-css.php
@@ -106,13 +106,31 @@ public function display_critical_css() {
echo ' tag (or any HTML tags) in output.
+ // Ensure the CSS cannot terminate the style element early.
// phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
- echo wp_strip_all_tags( $critical_css );
+ echo self::sanitize_css( $critical_css );
echo '';
}
+ /**
+ * Sanitize CSS for output inside a
tests/php
tests/php/lib/critical-css/Display_Critical_CSS_Test.php
+ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
tests/php/Jetpack_Boost_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php
@@ -30,6 +31,7 @@
tests/php/Jetpack_Boost_Test.php
tests/php/lib/critical-css/Display_Critical_CSS_Test.php
+ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php
tests/php/abilities/Boost_Abilities_Test.php
diff --git a/projects/plugins/boost/phpunit.9.xml.dist b/projects/plugins/boost/phpunit.9.xml.dist
index 1dc02f563aad..89d2eacddc6d 100644
--- a/projects/plugins/boost/phpunit.9.xml.dist
+++ b/projects/plugins/boost/phpunit.9.xml.dist
@@ -13,6 +13,7 @@
tests/php
tests/php/lib/critical-css/Display_Critical_CSS_Test.php
+ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
tests/php/Jetpack_Boost_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php
@@ -21,6 +22,7 @@
tests/php/Jetpack_Boost_Test.php
tests/php/lib/critical-css/Display_Critical_CSS_Test.php
+ tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Bg_Image_Test.php
tests/php/modules/optimizations/lcp/LCP_Optimize_Img_Tag_Test.php
tests/php/abilities/Boost_Abilities_Test.php
diff --git a/projects/plugins/boost/tests/php/lib/critical-css/Critical_CSS_Storage_Test.php b/projects/plugins/boost/tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
new file mode 100644
index 000000000000..3833a42058d2
--- /dev/null
+++ b/projects/plugins/boost/tests/php/lib/critical-css/Critical_CSS_Storage_Test.php
@@ -0,0 +1,100 @@
+clear_all_posts();
+ parent::tear_down();
+ }
+
+ /**
+ * Emulate WP_Query post_name lookups against the WorDBless post store.
+ *
+ * @param \WP_Post[]|null $posts Posts with which to short-circuit the query, or null.
+ * @param \WP_Query $query The running query.
+ * @return \WP_Post[]|null
+ */
+ public function emulate_name_query( $posts, $query ) {
+ $name = $query->get( 'name' );
+ $post_type = $query->get( 'post_type' );
+
+ if ( ! $name || ! $post_type ) {
+ return $posts;
+ }
+
+ foreach ( \WorDBless\Posts::init()->posts as $id => $post ) {
+ if ( $post->post_name === $name && $post->post_type === $post_type ) {
+ return array( get_post( $id ) );
+ }
+ }
+
+ return array();
+ }
+
+ /**
+ * Test that double quotes and inline SVG markup in CSS values survive a
+ * save/load round-trip.
+ */
+ public function test_store_css_round_trip_preserves_double_quotes_and_svg() {
+ $css = '.test-svg-background { background-image: url("data:image/svg+xml,"); }';
+
+ $storage = new Critical_CSS_Storage();
+ $storage->store_css( 'core_front_page', $css );
+
+ $result = $storage->get_css( array( 'core_front_page' ) );
+
+ $this->assertIsArray( $result );
+ $this->assertSame( 'core_front_page', $result['key'] );
+ $this->assertSame( $css, $result['css'] );
+ }
+
+ /**
+ * Test that updating an existing entry does not corrupt double quotes.
+ */
+ public function test_store_css_update_preserves_double_quotes() {
+ $storage = new Critical_CSS_Storage();
+ $storage->store_css( 'core_posts_page', '.a { content: "first"; }' );
+
+ $css = '.quote::before { content: "\201C"; font-family: "Times New Roman", serif; }';
+ $storage->store_css( 'core_posts_page', $css );
+
+ $result = $storage->get_css( array( 'core_posts_page' ) );
+
+ $this->assertIsArray( $result );
+ $this->assertSame( $css, $result['css'] );
+ }
+}
diff --git a/projects/plugins/boost/tests/php/lib/critical-css/Display_Critical_CSS_Test.php b/projects/plugins/boost/tests/php/lib/critical-css/Display_Critical_CSS_Test.php
index 18b1ea5af73f..66295383b0c4 100644
--- a/projects/plugins/boost/tests/php/lib/critical-css/Display_Critical_CSS_Test.php
+++ b/projects/plugins/boost/tests/php/lib/critical-css/Display_Critical_CSS_Test.php
@@ -70,19 +70,66 @@ public function test_display_critical_css_with_empty_css() {
}
/**
- * Test display_critical_css() strips HTML tags.
+ * Test display_critical_css() neutralizes closing style tags, so the CSS
+ * cannot terminate the style element early and break out into HTML context.
*/
- public function test_display_critical_css_strips_html() {
- $css_with_html = 'body { color: red; }';
- $instance = new Display_Critical_CSS( $css_with_html );
+ public function test_display_critical_css_neutralizes_style_breakout() {
+ $css_with_breakout = 'body { color: red; }';
+ $instance = new Display_Critical_CSS( $css_with_breakout );
ob_start();
$instance->display_critical_css();
$output = ob_get_clean();
- $this->assertStringNotContainsString( '