Code Coverage |
||||||||||
Lines |
Functions and Methods |
Classes and Traits |
||||||||
Total | |
94.46% |
494 / 523 |
|
89.06% |
57 / 64 |
CRAP | |
0.00% |
0 / 1 |
FilterEvaluator | |
94.46% |
494 / 523 |
|
89.06% |
57 / 64 |
186.59 | |
0.00% |
0 / 1 |
__construct | |
90.91% |
10 / 11 |
|
0.00% |
0 / 1 |
2.00 | |||
toggleConditionLimit | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
raiseCondCount | |
66.67% |
2 / 3 |
|
0.00% |
0 / 1 |
3.33 | |||
setVariables | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getCacheVersion | |
23.08% |
3 / 13 |
|
0.00% |
0 / 1 |
3.82 | |||
resetState | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
1 | |||
checkSyntaxThrow | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
1 | |||
checkSyntax | |
87.50% |
7 / 8 |
|
0.00% |
0 / 1 |
2.01 | |||
checkConditions | |
62.50% |
15 / 24 |
|
0.00% |
0 / 1 |
6.32 | |||
parse | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
evaluateExpression | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
getTree | |
100.00% |
25 / 25 |
|
100.00% |
1 / 1 |
1 | |||
evalTree | |
100.00% |
7 / 7 |
|
100.00% |
1 / 1 |
2 | |||
evalNode | |
99.36% |
155 / 156 |
|
0.00% |
0 / 1 |
58 | |||
callFunc | |
100.00% |
19 / 19 |
|
100.00% |
1 / 1 |
8 | |||
callKeyword | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
4 | |||
varExists | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
getVarValue | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
4 | |||
setUserVariable | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
funcLc | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcUc | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcLen | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
2 | |||
funcSpecialRatio | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
2 | |||
funcCount | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
5 | |||
funcRCount | |
100.00% |
14 / 14 |
|
100.00% |
1 / 1 |
3 | |||
funcGetMatches | |
100.00% |
17 / 17 |
|
100.00% |
1 / 1 |
2 | |||
funcIPInRange | |
100.00% |
10 / 10 |
|
100.00% |
1 / 1 |
3 | |||
funcIPInRanges | |
100.00% |
12 / 12 |
|
100.00% |
1 / 1 |
4 | |||
funcCCNorm | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
funcSanitize | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
funcContainsAny | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcContainsAll | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcCCNormContainsAny | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcCCNormContainsAll | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
contains | |
100.00% |
15 / 15 |
|
100.00% |
1 / 1 |
7 | |||
funcEqualsToAny | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
equalsToAny | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
3 | |||
ccnorm | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
rmspecials | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
rmdoubles | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
rmwhitespace | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
funcRMSpecials | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcRMWhitespace | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcRMDoubles | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcNorm | |
100.00% |
6 / 6 |
|
100.00% |
1 / 1 |
1 | |||
funcSubstr | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
2 | |||
funcStrPos | |
100.00% |
11 / 11 |
|
100.00% |
1 / 1 |
5 | |||
funcStrReplace | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
funcStrReplaceRegexp | |
100.00% |
16 / 16 |
|
100.00% |
1 / 1 |
2 | |||
funcStrRegexEscape | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
funcSetVar | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
containmentKeyword | |
100.00% |
5 / 5 |
|
100.00% |
1 / 1 |
3 | |||
keywordIn | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
keywordContains | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
keywordLike | |
100.00% |
4 / 4 |
|
100.00% |
1 / 1 |
1 | |||
keywordRegex | |
100.00% |
13 / 13 |
|
100.00% |
1 / 1 |
3 | |||
keywordRegexInsensitive | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
castString | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
castInt | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
castFloat | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
castBool | |
100.00% |
1 / 1 |
|
100.00% |
1 / 1 |
1 | |||
maybeDiscardNode | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
2 | |||
mungeRegexp | |
100.00% |
2 / 2 |
|
100.00% |
1 / 1 |
1 | |||
checkRegexMatchesEmpty | |
25.00% |
2 / 8 |
|
0.00% |
0 / 1 |
6.80 |
1 | <?php |
2 | |
3 | namespace MediaWiki\Extension\AbuseFilter\Parser; |
4 | |
5 | use BagOStuff; |
6 | use Exception; |
7 | use IBufferingStatsdDataFactory; |
8 | use InvalidArgumentException; |
9 | use Language; |
10 | use MediaWiki\Extension\AbuseFilter\KeywordsManager; |
11 | use MediaWiki\Extension\AbuseFilter\Parser\Exception\ConditionLimitException; |
12 | use MediaWiki\Extension\AbuseFilter\Parser\Exception\ExceptionBase; |
13 | use MediaWiki\Extension\AbuseFilter\Parser\Exception\InternalException; |
14 | use MediaWiki\Extension\AbuseFilter\Parser\Exception\UserVisibleException; |
15 | use MediaWiki\Extension\AbuseFilter\Parser\Exception\UserVisibleWarning; |
16 | use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder; |
17 | use MediaWiki\Extension\AbuseFilter\Variables\VariablesManager; |
18 | use MediaWiki\Parser\Sanitizer; |
19 | use Psr\Log\LoggerInterface; |
20 | use Wikimedia\Equivset\Equivset; |
21 | use Wikimedia\IPUtils; |
22 | |
23 | /** |
24 | * This class evaluates an AST generated by the filter parser. |
25 | * |
26 | * @todo Override checkSyntax and make it only try to build the AST. That would mean faster results, |
27 | * and no need to mess with DUNDEFINED and the like. However, we must first try to reduce the |
28 | * amount of runtime-only exceptions, and try to detect them in the AFPTreeParser instead. |
29 | * Otherwise, people may be able to save a broken filter without the syntax check reporting that. |
30 | */ |
31 | class FilterEvaluator { |
32 | private const CACHE_VERSION = 1; |
33 | |
34 | public const FUNCTIONS = [ |
35 | 'lcase' => 'funcLc', |
36 | 'ucase' => 'funcUc', |
37 | 'length' => 'funcLen', |
38 | 'string' => 'castString', |
39 | 'int' => 'castInt', |
40 | 'float' => 'castFloat', |
41 | 'bool' => 'castBool', |
42 | 'norm' => 'funcNorm', |
43 | 'ccnorm' => 'funcCCNorm', |
44 | 'ccnorm_contains_any' => 'funcCCNormContainsAny', |
45 | 'ccnorm_contains_all' => 'funcCCNormContainsAll', |
46 | 'specialratio' => 'funcSpecialRatio', |
47 | 'rmspecials' => 'funcRMSpecials', |
48 | 'rmdoubles' => 'funcRMDoubles', |
49 | 'rmwhitespace' => 'funcRMWhitespace', |
50 | 'count' => 'funcCount', |
51 | 'rcount' => 'funcRCount', |
52 | 'get_matches' => 'funcGetMatches', |
53 | 'ip_in_range' => 'funcIPInRange', |
54 | 'ip_in_ranges' => 'funcIPInRanges', |
55 | 'contains_any' => 'funcContainsAny', |
56 | 'contains_all' => 'funcContainsAll', |
57 | 'equals_to_any' => 'funcEqualsToAny', |
58 | 'substr' => 'funcSubstr', |
59 | 'strlen' => 'funcLen', |
60 | 'strpos' => 'funcStrPos', |
61 | 'str_replace' => 'funcStrReplace', |
62 | 'str_replace_regexp' => 'funcStrReplaceRegexp', |
63 | 'rescape' => 'funcStrRegexEscape', |
64 | 'set' => 'funcSetVar', |
65 | 'set_var' => 'funcSetVar', |
66 | 'sanitize' => 'funcSanitize', |
67 | ]; |
68 | |
69 | /** |
70 | * The minimum and maximum amount of arguments required by each function. |
71 | * @var int[][] |
72 | */ |
73 | public const FUNC_ARG_COUNT = [ |
74 | 'lcase' => [ 1, 1 ], |
75 | 'ucase' => [ 1, 1 ], |
76 | 'length' => [ 1, 1 ], |
77 | 'string' => [ 1, 1 ], |
78 | 'int' => [ 1, 1 ], |
79 | 'float' => [ 1, 1 ], |
80 | 'bool' => [ 1, 1 ], |
81 | 'norm' => [ 1, 1 ], |
82 | 'ccnorm' => [ 1, 1 ], |
83 | 'ccnorm_contains_any' => [ 2, INF ], |
84 | 'ccnorm_contains_all' => [ 2, INF ], |
85 | 'specialratio' => [ 1, 1 ], |
86 | 'rmspecials' => [ 1, 1 ], |
87 | 'rmdoubles' => [ 1, 1 ], |
88 | 'rmwhitespace' => [ 1, 1 ], |
89 | 'count' => [ 1, 2 ], |
90 | 'rcount' => [ 1, 2 ], |
91 | 'get_matches' => [ 2, 2 ], |
92 | 'ip_in_range' => [ 2, 2 ], |
93 | 'ip_in_ranges' => [ 2, INF ], |
94 | 'contains_any' => [ 2, INF ], |
95 | 'contains_all' => [ 2, INF ], |
96 | 'equals_to_any' => [ 2, INF ], |
97 | 'substr' => [ 2, 3 ], |
98 | 'strlen' => [ 1, 1 ], |
99 | 'strpos' => [ 2, 3 ], |
100 | 'str_replace' => [ 3, 3 ], |
101 | 'str_replace_regexp' => [ 3, 3 ], |
102 | 'rescape' => [ 1, 1 ], |
103 | 'set' => [ 2, 2 ], |
104 | 'set_var' => [ 2, 2 ], |
105 | 'sanitize' => [ 1, 1 ], |
106 | ]; |
107 | |
108 | // Functions that affect parser state, and shouldn't be cached. |
109 | private const ACTIVE_FUNCTIONS = [ |
110 | 'funcSetVar', |
111 | ]; |
112 | |
113 | public const KEYWORDS = [ |
114 | 'in' => 'keywordIn', |
115 | 'like' => 'keywordLike', |
116 | 'matches' => 'keywordLike', |
117 | 'contains' => 'keywordContains', |
118 | 'rlike' => 'keywordRegex', |
119 | 'irlike' => 'keywordRegexInsensitive', |
120 | 'regex' => 'keywordRegex', |
121 | ]; |
122 | |
123 | /** |
124 | * @var bool Are we allowed to use short-circuit evaluation? |
125 | */ |
126 | private $mAllowShort; |
127 | |
128 | /** |
129 | * @var VariableHolder |
130 | */ |
131 | private $mVariables; |
132 | /** |
133 | * @var int The current amount of conditions being consumed |
134 | */ |
135 | private $mCondCount; |
136 | /** |
137 | * @var bool Whether the condition limit is enabled. |
138 | */ |
139 | private $condLimitEnabled = true; |
140 | /** |
141 | * @var string|null The ID of the filter being parsed, if available. Can also be "global-$ID" |
142 | */ |
143 | private $mFilter; |
144 | /** |
145 | * @var bool Whether we can allow retrieving _builtin_ variables not included in $this->mVariables |
146 | */ |
147 | private $allowMissingVariables = false; |
148 | |
149 | /** |
150 | * @var BagOStuff Used to cache the AST and the tokens |
151 | */ |
152 | private $cache; |
153 | /** |
154 | * @var bool Whether the AST was retrieved from cache |
155 | */ |
156 | private $fromCache = false; |
157 | /** |
158 | * @var LoggerInterface Used for debugging |
159 | */ |
160 | private $logger; |
161 | /** |
162 | * @var Language Content language, used for language-dependent functions |
163 | */ |
164 | private $contLang; |
165 | /** |
166 | * @var IBufferingStatsdDataFactory |
167 | */ |
168 | private $statsd; |
169 | |
170 | /** @var KeywordsManager */ |
171 | private $keywordsManager; |
172 | |
173 | /** @var VariablesManager */ |
174 | private $varManager; |
175 | |
176 | /** @var int */ |
177 | private $conditionsLimit; |
178 | |
179 | /** @var UserVisibleWarning[] */ |
180 | private $warnings = []; |
181 | |
182 | /** |
183 | * @var array Cached results of functions |
184 | */ |
185 | private $funcCache = []; |
186 | |
187 | /** |
188 | * @var Equivset |
189 | */ |
190 | private $equivset; |
191 | |
192 | /** |
193 | * Create a new instance |
194 | * |
195 | * @param Language $contLang Content language, used for language-dependent function |
196 | * @param BagOStuff $cache Used to cache the AST and the tokens |
197 | * @param LoggerInterface $logger Used for debugging |
198 | * @param KeywordsManager $keywordsManager |
199 | * @param VariablesManager $varManager |
200 | * @param IBufferingStatsdDataFactory $statsdDataFactory |
201 | * @param Equivset $equivset |
202 | * @param int $conditionsLimit |
203 | * @param VariableHolder|null $vars |
204 | */ |
205 | public function __construct( |
206 | Language $contLang, |
207 | BagOStuff $cache, |
208 | LoggerInterface $logger, |
209 | KeywordsManager $keywordsManager, |
210 | VariablesManager $varManager, |
211 | IBufferingStatsdDataFactory $statsdDataFactory, |
212 | Equivset $equivset, |
213 | int $conditionsLimit, |
214 | VariableHolder $vars = null |
215 | ) { |
216 | $this->contLang = $contLang; |
217 | $this->cache = $cache; |
218 | $this->logger = $logger; |
219 | $this->statsd = $statsdDataFactory; |
220 | $this->keywordsManager = $keywordsManager; |
221 | $this->varManager = $varManager; |
222 | $this->equivset = $equivset; |
223 | $this->conditionsLimit = $conditionsLimit; |
224 | $this->resetState(); |
225 | if ( $vars ) { |
226 | $this->mVariables = $vars; |
227 | } |
228 | } |
229 | |
230 | /** |
231 | * For use in batch scripts and the like |
232 | * |
233 | * @param bool $enable True to enable the limit, false to disable it |
234 | */ |
235 | public function toggleConditionLimit( $enable ) { |
236 | $this->condLimitEnabled = $enable; |
237 | } |
238 | |
239 | /** |
240 | * @throws ConditionLimitException |
241 | */ |
242 | private function raiseCondCount() { |
243 | $this->mCondCount++; |