Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
CRAP
100.00% covered (success)
100.00%
1 / 1
ImportAtRuleSanitizer
100.00% covered (success)
100.00%
28 / 28
100.00% covered (success)
100.00%
4 / 4
10
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
1
 getIndex
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 handlesRule
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
2
 doSanitize
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
6
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\Alternative;
10use Wikimedia\CSS\Grammar\FunctionMatcher;
11use Wikimedia\CSS\Grammar\Juxtaposition;
12use Wikimedia\CSS\Grammar\Matcher;
13use Wikimedia\CSS\Grammar\MatcherFactory;
14use Wikimedia\CSS\Grammar\Quantifier;
15use Wikimedia\CSS\Objects\AtRule;
16use Wikimedia\CSS\Objects\CSSObject;
17use Wikimedia\CSS\Objects\Rule;
18use Wikimedia\CSS\Util;
19
20/**
21 * Sanitizes a CSS \@import rule
22 * @see https://www.w3.org/TR/2018/CR-css-cascade-4-20180828/#at-import
23 */
24class ImportAtRuleSanitizer extends RuleSanitizer {
25
26    /** @var Matcher */
27    protected $matcher;
28
29    /**
30     * @param MatcherFactory $matcherFactory
31     * @param array $options Additional options:
32     *  - strict: (bool) Only accept defined syntax in supports(). Default true.
33     *  - declarationSanitizer: (PropertySanitizer) Check supports() declarations against this
34     *    Sanitizer.
35     */
36    public function __construct( MatcherFactory $matcherFactory, array $options = [] ) {
37        $declarationSanitizer = $options['declarationSanitizer'] ?? null;
38        $strict = $options['strict'] ?? true;
39
40        $this->matcher = new Juxtaposition( [
41            new Alternative( [
42                $matcherFactory->url( 'css' ),
43                $matcherFactory->urlstring( 'css' ),
44            ] ),
45            Quantifier::optional( new FunctionMatcher( 'supports', new Alternative( [
46                $matcherFactory->cssSupportsCondition( $declarationSanitizer, $strict ),
47                $matcherFactory->cssDeclaration( $declarationSanitizer ),
48            ] ) ) ),
49            $matcherFactory->cssMediaQueryList(),
50        ] );
51    }
52
53    /** @inheritDoc */
54    public function getIndex() {
55        return -1000;
56    }
57
58    /** @inheritDoc */
59    public function handlesRule( Rule $rule ) {
60        return $rule instanceof AtRule && !strcasecmp( $rule->getName(), 'import' );
61    }
62
63    /** @inheritDoc */
64    protected function doSanitize( CSSObject $object ) {
65        if ( !$object instanceof AtRule || !$this->handlesRule( $object ) ) {
66            $this->sanitizationError( 'expected-at-rule', $object, [ 'import' ] );
67            return null;
68        }
69
70        if ( $object->getBlock() !== null ) {
71            $this->sanitizationError( 'at-rule-block-not-allowed', $object->getBlock(), [ 'import' ] );
72            return null;
73        }
74        if ( !$this->matcher->matchAgainst( $object->getPrelude(), [ 'mark-significance' => true ] ) ) {
75            $cv = Util::findFirstNonWhitespace( $object->getPrelude() );
76            if ( $cv ) {
77                $this->sanitizationError( 'invalid-import-value', $cv );
78            } else {
79                $this->sanitizationError( 'missing-import-source', $object );
80            }
81            return null;
82        }
83        return $this->fixPreludeWhitespace( $object, true );
84    }
85}