diff --git a/src/wp-includes/fonts/class-wp-font-utils.php b/src/wp-includes/fonts/class-wp-font-utils.php index 0ec36abc3f64b..b01a81a2ed80f 100644 --- a/src/wp-includes/fonts/class-wp-font-utils.php +++ b/src/wp-includes/fonts/class-wp-font-utils.php @@ -77,6 +77,49 @@ public static function sanitize_font_family( $font_family ) { return self::maybe_add_quotes( $output ); } + /** + * This transforms a font name string into a valid, quoted, CSS font-family value. + * + * This expects a single font family and produces a single CSS string. This is suitable for the + * `@font-face` at-rule `font-family` descriptor. It is not suitable for the `font-family` property of the same name. + */ + public static function font_name_to_css_font_face_font_family_value( string $font_family ): string { + // Escape existing backslashes before any other processing. + $result = strtr( $font_family, array( '\\' => '\\5C ' ) ); + + /* + * CSS Unicode escaping for problematic characters. + * https://www.w3.org/TR/css-syntax-3/#escaping + * + * These characters are not required by CSS but may be problematic in WordPress: + * + * - Normalize and replace newlines. https://www.w3.org/TR/css-syntax-3/#input-preprocessing + * - "<", ">", and "&" are replaced to prevent issues with KSES and other sanitization that + * is confused by HTML-like text. + * is confused by HTML-like text. + * - `,`, `"` and `'` are replaced to prevent issues where font families may be processed later. + * + * Note that the Unicode escape sequences are used rather than backslash-escaping so the + * problematic characters are removed completely. + */ + $result = strtr( + $result, + array( + "\r\n" => '\\A ', + "\r" => '\\A ', + "\f" => '\\A ', + "\n" => '\\A ', + ',' => '\\2C ', + '"' => '\\22 ', + "'" => '\\27 ', + '<' => '\\3C ', + '>' => '\\3E ', + '&' => '\\26 ', + ) + ); + return "\"{$result}\""; + } + /** * Generates a slug from font face properties, e.g. `open sans;normal;400;100%;U+0-10FFFF` * diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php index 6b1d44d440247..289cf2e8bd1a0 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-faces-controller.php @@ -540,7 +540,7 @@ public function get_item_schema() { 'type' => 'string', 'default' => '', 'arg_options' => array( - 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + 'sanitize_callback' => array( 'WP_Font_Utils', 'font_name_to_css_font_face_font_family_value' ), ), ), 'fontStyle' => array( diff --git a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php index a99a885d3945c..5e89d4e226e1e 100644 --- a/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php +++ b/src/wp-includes/rest-api/endpoints/class-wp-rest-font-families-controller.php @@ -344,7 +344,7 @@ public function get_item_schema() { 'description' => __( 'CSS font-family value.' ), 'type' => 'string', 'arg_options' => array( - 'sanitize_callback' => array( 'WP_Font_Utils', 'sanitize_font_family' ), + 'sanitize_callback' => array( 'WP_Font_Utils', 'font_name_to_css_font_face_font_family_value' ), ), ), 'preview' => array(