Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
100.00% |
45 / 45 |
|
100.00% |
8 / 8 |
CRAP | |
100.00% |
1 / 1 |
Sanitizer | |
100.00% |
45 / 45 |
|
100.00% |
8 / 8 |
20 | |
100.00% |
1 / 1 |
getSanitizationErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
stashSanitizationErrors | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
clearSanitizationErrors | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
sanitizationError | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
sanitizeObj | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
3 | |||
sanitizeList | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
sanitizeRules | |
100.00% |
18 / 18 |
|
100.00% |
1 / 1 |
7 | |||
doSanitize | n/a |
0 / 0 |
n/a |
0 / 0 |
0 | |||||
sanitize | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 |
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\Objects\CSSObject; |
10 | use Wikimedia\CSS\Objects\CSSObjectList; |
11 | use Wikimedia\CSS\Objects\RuleList; |
12 | use Wikimedia\ScopedCallback; |
13 | |
14 | /** |
15 | * Base class for CSS sanitizers |
16 | */ |
17 | abstract 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 | } |