Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
5 / 5
CRAP
100.00% covered (success)
100.00%
1 / 1
StylesheetSanitizer
100.00% covered (success)
100.00%
34 / 34
100.00% covered (success)
100.00%
5 / 5
8
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 newDefault
100.00% covered (success)
100.00%
20 / 20
100.00% covered (success)
100.00%
1 / 1
1
 getRuleSanitizers
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 setRuleSanitizers
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 doSanitize
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
1<?php
2/**
3 * @file
4 * @license https://opensource.org/licenses/Apache-2.0 Apache-2.0
5 */
6
7namespace Wikimedia\CSS\Sanitizer;
8
9use Wikimedia\CSS\Grammar\MatcherFactory;
10use Wikimedia\CSS\Objects\CSSObject;
11use Wikimedia\CSS\Objects\RuleList;
12use Wikimedia\CSS\Objects\Stylesheet;
13use Wikimedia\CSS\Util;
14
15/**
16 * Sanitizes a CSS stylesheet or rule list
17 * @see https://www.w3.org/TR/2019/CR-css-syntax-3-20190716/#css-stylesheets
18 */
19class StylesheetSanitizer extends Sanitizer {
20
21    /** @var RuleSanitizer[] */
22    protected $ruleSanitizers;
23
24    /**
25     * @param RuleSanitizer[] $ruleSanitizers Sanitizers to test rules. For
26     *  each rule in the sheet, the first sanitizer that handles that rule gets
27     *  to sanitize it.
28     */
29    public function __construct( array $ruleSanitizers = [] ) {
30        $this->setRuleSanitizers( $ruleSanitizers );
31    }
32
33    /**
34     * Create and return a default StylesheetSanitizer.
35     * @note This method exists more to be an example of how to put everything
36     *  together than to be used directly.
37     * @return StylesheetSanitizer
38     */
39    public static function newDefault() {
40        // First, we need a matcher factory for the stuff all the sanitizers
41        // will need.
42        $matcherFactory = MatcherFactory::singleton();
43
44        // This is the sanitizer for a single "property: value", that gets used by
45        // StyleRuleSanitizer and various others.
46        $propertySanitizer = new StylePropertySanitizer( $matcherFactory );
47
48        // These are sanitizers for different types of rules that can appear in
49        // stylesheets and can be nested inside @media and @supports blocks.
50        // The keys in the array aren't used for anything by the library, but
51        // may help humans reading it.
52        $ruleSanitizers = [
53            'style' => new StyleRuleSanitizer( $matcherFactory->cssSelectorList(), $propertySanitizer ),
54            '@font-face' => new FontFaceAtRuleSanitizer( $matcherFactory ),
55            '@keyframes' => new KeyframesAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
56            '@page' => new PageAtRuleSanitizer( $matcherFactory, $propertySanitizer ),
57            '@media' => new MediaAtRuleSanitizer( $matcherFactory->cssMediaQueryList() ),
58            '@supports' => new SupportsAtRuleSanitizer( $matcherFactory, [
59                'declarationSanitizer' => $propertySanitizer,
60            ] ),
61        ];
62
63        // Inject the above list into the @media and @supports sanitizers.
64        $ruleSanitizers['@media']->setRuleSanitizers( $ruleSanitizers );
65        $ruleSanitizers['@supports']->setRuleSanitizers( $ruleSanitizers );
66
67        // Now we can put together the StylesheetSanitizer
68        return new StylesheetSanitizer( $ruleSanitizers + [
69            // Note there's intentionally no "@charset" sanitizer, as that at-rule
70            // was removed in the Editor's Draft in favor of special handling
71            // in the parser.
72            '@import' => new ImportAtRuleSanitizer( $matcherFactory, [
73                'declarationSanitizer' => $propertySanitizer,
74            ] ),
75            '@namespace' => new NamespaceAtRuleSanitizer( $matcherFactory ),
76        ] );
77    }
78
79    /**
80     * Access the list of rule sanitizers
81     * @return RuleSanitizer[]
82     */
83    public function getRuleSanitizers() {
84        return $this->ruleSanitizers;
85    }
86
87    /**
88     * Set the list of rule sanitizers
89     * @param RuleSanitizer[] $ruleSanitizers
90     */
91    public function setRuleSanitizers( array $ruleSanitizers ) {
92        Util::assertAllInstanceOf( $ruleSanitizers, RuleSanitizer::class, '$ruleSanitizers' );
93        $this->ruleSanitizers = $ruleSanitizers;
94    }
95
96    /** @inheritDoc */
97    protected function doSanitize( CSSObject $object ) {
98        $isSheet = $object instanceof Stylesheet;
99        if ( $isSheet ) {
100            '@phan-var Stylesheet $object';
101            $object = $object->getRuleList();
102        }
103        if ( !$object instanceof RuleList ) {
104            $this->sanitizationError( 'expected-stylesheet', $object );
105            return null;
106        }
107
108        $ret = $this->sanitizeRules( $this->ruleSanitizers, $object );
109        if ( $isSheet ) {
110            $ret = new Stylesheet( $ret );
111        }
112
113        return $ret;
114    }
115}