]> scripts.mit.edu Git - autoinstalls/mediawiki.git/blob - vendor/wikimedia/css-sanitizer/src/Sanitizer/FontFaceAtRuleSanitizer.php
MediaWiki 1.30.2
[autoinstalls/mediawiki.git] / vendor / wikimedia / css-sanitizer / src / Sanitizer / FontFaceAtRuleSanitizer.php
1 <?php
2 /**
3  * @file
4  * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5  */
6
7 namespace Wikimedia\CSS\Sanitizer;
8
9 use Wikimedia\CSS\Grammar\Alternative;
10 use Wikimedia\CSS\Grammar\FunctionMatcher;
11 use Wikimedia\CSS\Grammar\Juxtaposition;
12 use Wikimedia\CSS\Grammar\KeywordMatcher;
13 use Wikimedia\CSS\Grammar\MatcherFactory;
14 use Wikimedia\CSS\Grammar\Quantifier;
15 use Wikimedia\CSS\Grammar\TokenMatcher;
16 use Wikimedia\CSS\Grammar\UnorderedGroup;
17 use Wikimedia\CSS\Objects\AtRule;
18 use Wikimedia\CSS\Objects\CSSObject;
19 use Wikimedia\CSS\Objects\Rule;
20 use Wikimedia\CSS\Objects\Token;
21 use Wikimedia\CSS\Util;
22
23 /**
24  * Sanitizes a CSS \@font-face rule
25  * @see https://www.w3.org/TR/2013/CR-css-fonts-3-20131003/#font-resources
26  */
27 class FontFaceAtRuleSanitizer extends RuleSanitizer {
28
29         /** @var PropertySanitizer */
30         protected $propertySanitizer;
31
32         /**
33          * @param MatcherFactory $matcherFactory
34          */
35         public function __construct( MatcherFactory $matcherFactory ) {
36                 $matchData = self::fontMatchData( $matcherFactory );
37
38                 $this->propertySanitizer = new PropertySanitizer();
39                 $this->propertySanitizer->setKnownProperties( [
40                         'font-family' => $matchData['familyName'],
41                         'src' => Quantifier::hash( new Alternative( [
42                                 new Juxtaposition( [
43                                         $matcherFactory->url( 'font' ),
44                                         Quantifier::optional(
45                                                 new FunctionMatcher( 'format', Quantifier::hash( $matcherFactory->string() ) )
46                                         ),
47                                 ] ),
48                                 new FunctionMatcher( 'local', $matchData['familyName'] ),
49                         ] ) ),
50                         'font-style' => $matchData['font-style'],
51                         'font-weight' => new Alternative( [
52                                 new KeywordMatcher( [ 'normal', 'bold' ] ), $matchData['numWeight']
53                         ] ),
54                         'font-stretch' => $matchData['font-stretch'],
55                         'unicode-range' => Quantifier::hash(
56                                 new TokenMatcher( Token::T_UNICODE_RANGE, function ( Token $t ) {
57                                         list( $start, $end ) = $t->range();
58                                         return $start <= $end && $end <= 0x10ffff;
59                                 } )
60                         ),
61                         'font-variant' => $matchData['font-variant'],
62                         'font-feature-settings' => $matchData['font-feature-settings'],
63                 ] );
64         }
65
66         /**
67          * Get some shared data for font declaration matchers
68          * @param MatcherFactory $matcherFactory
69          * @return array
70          */
71         public static function fontMatchData( MatcherFactory $matcherFactory ) {
72                 $featureValueName = $matcherFactory->ident();
73                 $featureValueNameHash = Quantifier::hash( $featureValueName );
74                 $ret = [
75                         'familyName' => new Alternative( [
76                                 $matcherFactory->string(),
77                                 Quantifier::plus( $matcherFactory->ident() ),
78                         ] ),
79                         'numWeight' => new TokenMatcher( Token::T_NUMBER, function ( Token $t ) {
80                                 return $t->typeFlag() === 'integer' && preg_match( '/^[1-9]00$/', $t->representation() );
81                         } ),
82                         'font-style' => new KeywordMatcher( [ 'normal', 'italic', 'oblique' ] ),
83                         'font-stretch' => new KeywordMatcher( [
84                                 'normal', 'ultra-condensed', 'extra-condensed', 'condensed', 'semi-condensed', 'semi-expanded',
85                                 'expanded', 'extra-expanded', 'ultra-expanded'
86                         ] ),
87                         'font-feature-settings' => new Alternative( [
88                                 new KeywordMatcher( 'normal' ),
89                                 Quantifier::hash( new Juxtaposition( [
90                                         new TokenMatcher( Token::T_STRING, function ( Token $t ) {
91                                                 return preg_match( '/^[\x20-\x7e]{4}$/', $t->value() );
92                                         } ),
93                                         Quantifier::optional( new Alternative( [
94                                                 $matcherFactory->integer(),
95                                                 new KeywordMatcher( [ 'on', 'off' ] ),
96                                         ] ) )
97                                 ] ) )
98                         ] ),
99                         'ligatures' => [
100                                 new KeywordMatcher( [ 'common-ligatures', 'no-common-ligatures' ] ),
101                                 new KeywordMatcher( [ 'discretionary-ligatures', 'no-discretionary-ligatures' ] ),
102                                 new KeywordMatcher( [ 'historical-ligatures', 'no-historical-ligatures' ] ),
103                                 new KeywordMatcher( [ 'contextual', 'no-contextual' ] )
104                         ],
105                         'alt' => [
106                                 new FunctionMatcher( 'stylistic', $featureValueName ),
107                                 new KeywordMatcher( 'historical-forms' ),
108                                 new FunctionMatcher( 'styleset', $featureValueNameHash ),
109                                 new FunctionMatcher( 'character-variant', $featureValueNameHash ),
110                                 new FunctionMatcher( 'swash', $featureValueName ),
111                                 new FunctionMatcher( 'ornaments', $featureValueName ),
112                                 new FunctionMatcher( 'annotation', $featureValueName ),
113                         ],
114                         'capsKeywords' => [
115                                 'small-caps', 'all-small-caps', 'petite-caps', 'all-petite-caps', 'unicase', 'titling-caps'
116                         ],
117                         'numeric' => [
118                                 new KeywordMatcher( [ 'lining-nums', 'oldstyle-nums' ] ),
119                                 new KeywordMatcher( [ 'proportional-nums', 'tabular-nums' ] ),
120                                 new KeywordMatcher( [ 'diagonal-fractions', 'stacked-fractions' ] ),
121                                 new KeywordMatcher( 'ordinal' ),
122                                 new KeywordMatcher( 'slashed-zero' ),
123                         ],
124                         'eastAsian' => [
125                                 new KeywordMatcher( [ 'jis78', 'jis83', 'jis90', 'jis04', 'simplified', 'traditional' ] ),
126                                 new KeywordMatcher( [ 'full-width', 'proportional-width' ] ),
127                                 new KeywordMatcher( 'ruby' ),
128                         ]
129                 ];
130                 $ret['font-variant'] = new Alternative( [
131                         new KeywordMatcher( [ 'normal', 'none' ] ),
132                         UnorderedGroup::someOf( array_merge(
133                                 $ret['ligatures'],
134                                 $ret['alt'],
135                                 [ new KeywordMatcher( $ret['capsKeywords'] ) ],
136                                 $ret['numeric'],
137                                 $ret['eastAsian']
138                         ) )
139                 ] );
140                 return $ret;
141         }
142
143         public function handlesRule( Rule $rule ) {
144                 return $rule instanceof AtRule && !strcasecmp( $rule->getName(), 'font-face' );
145         }
146
147         protected function doSanitize( CSSObject $object ) {
148                 if ( !$object instanceof Rule || !$this->handlesRule( $object ) ) {
149                         $this->sanitizationError( 'expected-at-rule', $object, [ 'font-face' ] );
150                         return null;
151                 }
152
153                 if ( $object->getBlock() === null ) {
154                         $this->sanitizationError( 'at-rule-block-required', $object, [ 'font-face' ] );
155                         return null;
156                 }
157
158                 // No non-whitespace prelude allowed
159                 if ( Util::findFirstNonWhitespace( $object->getPrelude() ) ) {
160                         $this->sanitizationError( 'invalid-font-face-at-rule', $object );
161                         return null;
162                 }
163
164                 $ret = clone( $object );
165                 $this->fixPreludeWhitespace( $ret, false );
166                 $this->sanitizeDeclarationBlock( $ret->getBlock(), $this->propertySanitizer );
167
168                 return $ret;
169         }
170 }