@@ -831,14 +831,14 @@ public function set_token_value( string $new_value ): bool {
831831 $ this ->lexical_updates [] = array (
832832 'start ' => $ this ->token_value_starts_at ,
833833 'length ' => $ this ->token_value_length ,
834- 'text ' => $ this ->escape_url_value ( $ new_value ),
834+ 'text ' => $ this ->create_css_string ( $ new_value ),
835835 );
836836 return true ;
837837 case self ::TOKEN_STRING :
838838 $ this ->lexical_updates [] = array (
839839 'start ' => $ this ->token_starts_at ,
840840 'length ' => $ this ->token_length ,
841- 'text ' => $ this ->escape_url_value ( $ new_value ),
841+ 'text ' => $ this ->create_css_string ( $ new_value ),
842842 );
843843 return true ;
844844 default :
@@ -848,66 +848,42 @@ public function set_token_value( string $new_value ): bool {
848848 }
849849
850850 /**
851- * Escapes a URL value for use in quoted url() syntax .
851+ * Create a quoted CSS string from a plain PHP string value .
852852 *
853- * Always returns a quoted URL string since they're easier
854- * to escape. Quoted URLs are consumed using the string token
855- * rules, and the only values we need to escape in strings, are:
856- *
857- * * Trailing quote.
858- * * Newlines. That amounts to \n, \r, \f, \r\n when preprocessing is considered.
859- * * U+005C REVERSE SOLIDUS (\)
860- *
861- * @see https://www.w3.org/TR/css-syntax-3/#consume-url-token
853+ * @see https://www.w3.org/TR/css-syntax-3/#escaping
862854 */
863- private function escape_url_value ( string $ unescaped ): string {
864- $ escaped = '' ;
865- $ at = 0 ;
866- while ( $ at < strlen ( $ unescaped ) ) {
867- $ safe_len = strcspn ( $ unescaped , "\n\r\f\\\"" , $ at );
868- if ( $ safe_len > 0 ) {
869- $ escaped .= substr ( $ unescaped , $ at , $ safe_len );
870- $ at += $ safe_len ;
871- continue ;
872- }
873-
874- $ unsafe_char = $ unescaped [ $ at ];
875- switch ( $ unsafe_char ) {
876- case "\r" :
877- ++$ at ;
878- /**
879- * Add a trailing space to prevent accidentally creating a
880- * wrong escape sequence. This is a valid CSS syntax and
881- * CSS parsers will ignore that whitespace.
882- *
883- * Without the space, "carriage\return" would be encoded as "carriage\aeturn",
884- * making `e` a part of the escape sequence `\ae` which is not
885- * what the caller intended.
886- */
887- $ escaped .= '\\a ' ;
888- if ( strlen ( $ unescaped ) > $ at + 1 && "\n" === $ unescaped [ $ at + 1 ] ) {
889- ++$ at ;
890- }
891- break ;
892- case "\f" :
893- case "\n" :
894- ++$ at ;
895- $ escaped .= '\\a ' ;
896- break ;
897- case '\\' :
898- ++$ at ;
899- $ escaped .= '\\5C ' ;
900- break ;
901- case '" ' :
902- ++$ at ;
903- $ escaped .= '\\22 ' ;
904- break ;
905- default :
906- _doing_it_wrong ( __METHOD__ , 'Unexpected character in URL value: ' . $ unsafe_char , '1.0.0 ' );
907- break ;
908- }
909- }
910- return '" ' . $ escaped . '" ' ;
855+ private function create_css_string ( string $ value ): string {
856+ $ escaped = strtr (
857+ $ value ,
858+ array (
859+ '\\' => '\\5C ' ,
860+
861+ // Pre-processing replaces NULLs and some newlines. Replace and escape as necessary.
862+ "\0" => "\u{FFFD}" ,
863+
864+ // Normalize and replace newlines. https://www.w3.org/TR/css-syntax-3/#input-preprocessing
865+ "\r\n" => '\\A ' ,
866+ "\r" => '\\A ' ,
867+ "\f" => '\\A ' ,
868+
869+ // Newlines must be escaped in CSS strings.
870+ "\n" => '\\A ' ,
871+
872+ // HTML syntax may be problematic.
873+ '< ' => '\\3C ' ,
874+ '> ' => '\\3E ' ,
875+ '& ' => '\\26 ' ,
876+
877+ // CSS syntax may be problematic.
878+ ', ' => '\\2C ' ,
879+ '; ' => '\\3B ' ,
880+ '{ ' => '\\7B ' ,
881+ '} ' => '\\7D ' ,
882+ '" ' => '\\22 ' ,
883+ "' " => '\\27 ' ,
884+ )
885+ );
886+ return "\"{$ escaped }\"" ;
911887 }
912888
913889 /**
0 commit comments