Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
8 / 8
CRAP
100.00% covered (success)
100.00%
1 / 1
Sanitizer
100.00% covered (success)
100.00%
45 / 45
100.00% covered (success)
100.00%
8 / 8
20
100.00% covered (success)
100.00%
1 / 1
 getSanitizationErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 stashSanitizationErrors
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 clearSanitizationErrors
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 sanitizationError
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 sanitizeObj
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 sanitizeList
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 sanitizeRules
100.00% covered (success)
100.00%
18 / 18
100.00% covered (success)
100.00%
1 / 1
7
 doSanitize
n/a
0 / 0
n/a
0 / 0
0
 sanitize
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
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\Objects\CSSObject;
10use Wikimedia\CSS\Objects\CSSObjectList;
11use Wikimedia\CSS\Objects\RuleList;
12use Wikimedia\ScopedCallback;
13
14/**
15 * Base class for CSS sanitizers
16 */
17abstract class Sanitizer {
18
19    /** @var array Sanitization errors. Each error is [ string $tag, int $line, int $pos ] */
20    protected $sanitizationErrors = [];
21
22    /**
23     * Return all sanitization errors seen so far
24     * @return array Array of [ string $tag, int $line, int $pos, ... ]
25     */
26    public function getSanitizationErrors() {
27        return $this->sanitizationErrors;
28    }
29
30    /**
31     * Temporarily clear sanitization errors
32     *
33     * Errors will be cleared, then restored when the returned ScopedCallback
34     * goes out of scope or is consumed.
35     *
36     * @return ScopedCallback
37     */
38    public function stashSanitizationErrors() {
39        $reset = new ScopedCallback( function ( $e ) {
40            $this->sanitizationErrors = $e;
41        }, [ $this->sanitizationErrors ] );
42        $this->sanitizationErrors = [];
43        return $reset;
44    }
45
46    /**
47     * Clear sanitization errors
48     */
49    public function clearSanitizationErrors() {
50        $this->sanitizationErrors = [];
51    }
52
53    /**
54     * Record a sanitization error
55     * @param string $tag Error tag
56     * @param CSSObject $object Report the error starting at this object
57     * @param array $data Extra data about the error.
58     */
59    protected function sanitizationError( $tag, CSSObject $object, array $data = [] ) {
60        [ $line, $pos ] = $object->getPosition();
61        $this->sanitizationErrors[] = array_merge( [ $tag, $line, $pos ], $data );
62    }
63
64    /**
65     * Run another sanitizer over a CSSObject
66     * @param Sanitizer $sanitizer
67     * @param CSSObject $object
68     * @return CSSObject|null
69     */
70    protected function sanitizeObj( Sanitizer $sanitizer, CSSObject $object ) {
71        $newObj = $sanitizer->doSanitize( $object );
72        $errors = $sanitizer->getSanitizationErrors();
73        if ( $errors && $sanitizer !== $this ) {
74            $this->sanitizationErrors = array_merge( $this->sanitizationErrors, $errors );
75            $sanitizer->clearSanitizationErrors();
76        }
77        return $newObj;
78    }
79
80    /**
81     * Run a sanitizer over all CSSObjects in a CSSObjectList
82     * @param Sanitizer $sanitizer
83     * @param CSSObjectList $list
84     * @return CSSObjectList
85     */
86    protected function sanitizeList( Sanitizer $sanitizer, CSSObjectList $list ) {
87        $class = get_class( $list );
88        $ret = new $class;
89        foreach ( $list as $obj ) {
90            $newObj = $sanitizer->doSanitize( $obj );
91            if ( $newObj ) {
92                $ret->add( $newObj );
93            }
94        }
95
96        $errors = $sanitizer->getSanitizationErrors();
97        if ( $errors && $sanitizer !== $this ) {
98            $this->sanitizationErrors = array_merge( $this->sanitizationErrors, $errors );
99            $sanitizer->clearSanitizationErrors();
100        }
101
102        return $ret;
103    }
104
105    /**
106     * Run a set of RuleSanitizers over all rules in a RuleList
107     * @param RuleSanitizer[] $ruleSanitizers
108     * @param RuleList $list
109     * @return RuleList
110     */
111    protected function sanitizeRules( array $ruleSanitizers, RuleList $list ) {
112        $ret = new RuleList();
113        $curIndex = -INF;
114        foreach ( $list as $rule ) {
115            foreach ( $ruleSanitizers as $sanitizer ) {
116                if ( $sanitizer->handlesRule( $rule ) ) {
117                    $indexes = $sanitizer->getIndex();
118                    if ( is_array( $indexes ) ) {
119                        [ $testIndex, $setIndex ] = $indexes;
120                    } else {
121                        $testIndex = $setIndex = $indexes;
122                    }
123                    if ( $testIndex < $curIndex ) {
124                        $this->sanitizationError( 'misordered-rule', $rule );
125                    } else {
126                        $curIndex = $setIndex;
127                        $rule = $this->sanitizeObj( $sanitizer, $rule );
128                        if ( $rule ) {
129                            $ret->add( $rule );
130                        }
131                    }
132                    continue 2;
133                }
134            }
135            $this->sanitizationError( 'unrecognized-rule', $rule );
136        }
137        return $ret;
138    }
139
140    /**
141     * Sanitize a CSS object
142     * @param CSSObject $object
143     * @return CSSObject|null Sanitized version of the object, or null if
144     *  sanitization failed
145     */
146    abstract protected function doSanitize( CSSObject $object );
147
148    /**
149     * Sanitize a CSS object
150     * @param CSSObject $object
151     * @return CSSObject|null Sanitized version of the object, or null if
152     *  sanitization failed
153     */
154    public function sanitize( CSSObject $object ) {
155        return $this->doSanitize( $object );
156    }
157}