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