25 private const BANNED_AT_RULE = [
34 private const BANNED_FUNCS = [
47 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
48 return [
'invalid-control-character', 0, 0 ];
50 $cssParser = CSSParser::newFromString( $value );
51 $decList = $cssParser->parseDeclarationList();
52 $errors = $cssParser->getParseErrors();
60 $alteredStyle = Sanitizer::checkCss( $value );
61 if ( $alteredStyle === $value ) {
65 return [ $errors[0][0], $errors[0][1], $errors[0][2] ];
68 $res = $this->validateTokens( $decList->toTokenArray() );
69 if ( $res !==
true ) {
84 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
85 return [
'invalid-control-character', 0, 0 ];
87 $cssParser = CSSParser::newFromString( $value );
88 $cvList = $cssParser->parseComponentValueList();
89 $errors = $cssParser->getParseErrors();
91 return [ $errors[0][0], $errors[0][1], $errors[0][2] ];
94 $res = $this->validateTokens( $cvList->toTokenArray() );
95 if ( $res !==
true ) {
110 if ( preg_match(
'/[\000-\010\013\016-\037\177]/', $value ) ) {
111 return [
'invalid-control-character', 0, 0 ];
113 $cssParser = CSSParser::newFromString( $value );
114 $stylesheet = $cssParser->parseStylesheet();
116 $errors = $cssParser->getParseErrors();
118 return [ $errors[0][0], $errors[0][1], $errors[0][2] ];
121 $topLevelRules = $stylesheet->getRuleList();
122 foreach ( $topLevelRules as $rule ) {
123 if ( $rule instanceof AtRule ) {
124 $res = $this->validateAtRule( $rule );
125 if ( $res !==
true ) {
128 if ( $rule->getName() ===
'font-face' ) {
130 $res = $this->validateTokens( $rule->toTokenArray(),
true );
131 if ( $res !==
true ) {
139 $res = $this->validateTokens( $rule->toTokenArray() );
140 if ( $res !==
true ) {
153 private function validateAtRule( AtRule $rule ) {
154 $name = strtolower( $rule->getName() );
156 in_array( $name, self::BANNED_AT_RULE ) ||
157 preg_match(
'/[^-a-z]/', $name )
159 return [
"banned-at-rule-$name", $rule->getPosition()[0], $rule->getPosition()[1] ];
171 private function validateTokens( array $tokens, $allowDataFonts =
false ) {
180 for ( $i = 0; $i < count( $tokens ); $i++ ) {
181 $token = $tokens[$i];
183 if ( $token->type() === Token::T_URL ) {
185 !str_starts_with( $token->value(),
'#' ) &&
186 !( $allowDataFonts &&
187 ( str_starts_with( $token->value(),
'data:font/' )
188 || str_starts_with( $token->value(),
'data:;base64,' ) )
191 return [
'banned-url', $token->getPosition()[0], $token->getPosition()[1] ];
193 } elseif ( $token->type() === Token::T_BAD_URL ) {
197 return [
'banned-url', $token->getPosition()[0], $token->getPosition()[1] ];
198 } elseif ( $token->type() === Token::T_FUNCTION && strtolower( $token->value() ) ===
'url' ) {
199 for ( $j = $i + 1; $j < count( $tokens ) && $tokens[$j]->type() === Token::T_WHITESPACE; $j++ );
200 if ( $j < count( $tokens ) && $tokens[$j]->type() === Token::T_STRING ) {
202 str_starts_with( $tokens[$j]->value(),
'#' ) ||
204 ( str_starts_with( $tokens[$j]->value(),
'data:font/' )
205 || str_starts_with( $tokens[$j]->value(),
'data:;base64,' ) )
211 return [
'banned-url', $token->getPosition()[0], $token->getPosition()[1] ];
213 $token->type() === Token::T_FUNCTION &&
214 in_array( strtolower( $token->value() ), self::BANNED_FUNCS )
216 return [
'banned-function-' . $token->value(), $token->getPosition()[0], $token->getPosition()[1] ];