Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.46% covered (success)
94.46%
494 / 523
89.06% covered (warning)
89.06%
57 / 64
CRAP
0.00% covered (danger)
0.00%
0 / 1
FilterEvaluator
94.46% covered (success)
94.46%
494 / 523
89.06% covered (warning)
89.06%
57 / 64
186.59
0.00% covered (danger)
0.00%
0 / 1
 __construct
90.91% covered (success)
90.91%
10 / 11
0.00% covered (danger)
0.00%
0 / 1
2.00
 toggleConditionLimit
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 raiseCondCount
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
3.33
 setVariables
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheVersion
23.08% covered (danger)
23.08%
3 / 13
0.00% covered (danger)
0.00%
0 / 1
3.82
 resetState
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
1
 checkSyntaxThrow
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 checkSyntax
87.50% covered (warning)
87.50%
7 / 8
0.00% covered (danger)
0.00%
0 / 1
2.01
 checkConditions
62.50% covered (warning)
62.50%
15 / 24
0.00% covered (danger)
0.00%
0 / 1
6.32
 parse
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 evaluateExpression
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTree
100.00% covered (success)
100.00%
25 / 25
100.00% covered (success)
100.00%
1 / 1
1
 evalTree
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
2
 evalNode
99.36% covered (success)
99.36%
155 / 156
0.00% covered (danger)
0.00%
0 / 1
58
 callFunc
100.00% covered (success)
100.00%
19 / 19
100.00% covered (success)
100.00%
1 / 1
8
 callKeyword
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
4
 varExists
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 getVarValue
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
4
 setUserVariable
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 funcLc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcUc
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcLen
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
2
 funcSpecialRatio
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
2
 funcCount
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
5
 funcRCount
100.00% covered (success)
100.00%
14 / 14
100.00% covered (success)
100.00%
1 / 1
3
 funcGetMatches
100.00% covered (success)
100.00%
17 / 17
100.00% covered (success)
100.00%
1 / 1
2
 funcIPInRange
100.00% covered (success)
100.00%
10 / 10
100.00% covered (success)
100.00%
1 / 1
3
 funcIPInRanges
100.00% covered (success)
100.00%
12 / 12
100.00% covered (success)
100.00%
1 / 1
4
 funcCCNorm
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 funcSanitize
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 funcContainsAny
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcContainsAll
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcCCNormContainsAny
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcCCNormContainsAll
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 contains
100.00% covered (success)
100.00%
15 / 15
100.00% covered (success)
100.00%
1 / 1
7
 funcEqualsToAny
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 equalsToAny
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
3
 ccnorm
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rmspecials
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rmdoubles
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 rmwhitespace
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 funcRMSpecials
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcRMWhitespace
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcRMDoubles
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcNorm
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
1
 funcSubstr
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
2
 funcStrPos
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 funcStrReplace
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 funcStrReplaceRegexp
100.00% covered (success)
100.00%
16 / 16
100.00% covered (success)
100.00%
1 / 1
2
 funcStrRegexEscape
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 funcSetVar
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 containmentKeyword
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
3
 keywordIn
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 keywordContains
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 keywordLike
100.00% covered (success)
100.00%
4 / 4
100.00% covered (success)
100.00%
1 / 1
1
 keywordRegex
100.00% covered (success)
100.00%
13 / 13
100.00% covered (success)
100.00%
1 / 1
3
 keywordRegexInsensitive
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 castString
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 castInt
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 castFloat
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 castBool
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 maybeDiscardNode
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
2
 mungeRegexp
100.00% covered (success)
100.00%
2 / 2
100.00% covered (success)
100.00%
1 / 1
1
 checkRegexMatchesEmpty
25.00% covered (danger)
25.00%
2 / 8
0.00% covered (danger)
0.00%
0 / 1
6.80
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Parser;
4
5use BagOStuff;
6use Exception;
7use IBufferingStatsdDataFactory;
8use InvalidArgumentException;
9use Language;
10use MediaWiki\Extension\AbuseFilter\KeywordsManager;
11use MediaWiki\Extension\AbuseFilter\Parser\Exception\ConditionLimitException;
12use MediaWiki\Extension\AbuseFilter\Parser\Exception\ExceptionBase;
13use MediaWiki\Extension\AbuseFilter\Parser\Exception\InternalException;
14use MediaWiki\Extension\AbuseFilter\Parser\Exception\UserVisibleException;
15use MediaWiki\Extension\AbuseFilter\Parser\Exception\UserVisibleWarning;
16use MediaWiki\Extension\AbuseFilter\Variables\VariableHolder;
17use MediaWiki\Extension\AbuseFilter\Variables\VariablesManager;
18use MediaWiki\Parser\Sanitizer;
19use Psr\Log\LoggerInterface;
20use Wikimedia\Equivset\Equivset;
21use 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 */
31class 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++;