Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
97 / 97
100.00% covered (success)
100.00%
6 / 6
CRAP
100.00% covered (success)
100.00%
1 / 1
AbuseFilterTokenizer
100.00% covered (success)
100.00%
97 / 97
100.00% covered (success)
100.00%
6 / 6
32
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getCacheKey
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getTokens
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 tokenize
100.00% covered (success)
100.00%
7 / 7
100.00% covered (success)
100.00%
1 / 1
1
 nextToken
100.00% covered (success)
100.00%
42 / 42
100.00% covered (success)
100.00%
1 / 1
15
 readStringLiteral
100.00% covered (success)
100.00%
39 / 39
100.00% covered (success)
100.00%
1 / 1
13
1<?php
2
3namespace MediaWiki\Extension\AbuseFilter\Parser;
4
5use BagOStuff;
6use MediaWiki\Extension\AbuseFilter\Parser\Exception\UserVisibleException;
7
8/**
9 * Tokenizer for AbuseFilter rules.
10 */
11class AbuseFilterTokenizer {
12    /** @var int Tokenizer cache version. Increment this when changing the syntax. */
13    public const CACHE_VERSION = 4;
14    private const COMMENT_START_RE = '/\s*\/\*/A';
15    private const ID_SYMBOL_RE = '/[0-9A-Za-z_]+/A';
16    public const OPERATOR_RE =
17        '/(\!\=\=|\!\=|\!|\*\*|\*|\/|\+|\-|%|&|\||\^|\:\=|\?|\:|\<\=|\<|\>\=|\>|\=\=\=|\=\=|\=)/A';
18    private const BASE = '0(?<base>[xbo])';
19    private const DIGIT = '[0-9A-Fa-f]';
20    private const DIGITS = self::DIGIT . '+' . '(?:\.\d*)?|\.\d+';
21    private const RADIX_RE = '/(?:' . self::BASE . ')?(?<input>' . self::DIGITS . ')(?!\w)/Au';
22    private const WHITESPACE = "\011\012\013\014\015\040";
23
24    // Order is important. The punctuation-matching regex requires that
25    // ** comes before *, etc. They are sorted to make it easy to spot
26    // such errors.
27    public const OPERATORS = [
28        // Inequality
29        '!==', '!=', '!',
30        // Multiplication/exponentiation
31        '**', '*',
32        // Other arithmetic
33        '/', '+', '-', '%',
34        // Logic
35        '&', '|', '^',
36        // Setting
37        ':=',
38        // Ternary
39        '?', ':',
40        // Less than
41        '<=', '<',
42        // Greater than
43        '>=', '>',
44        // Equality
45        '===', '==', '=',
46    ];
47
48    public const PUNCTUATION = [
49        ',' => AFPToken::TCOMMA,
50        '(' => AFPToken::TBRACE,
51        ')' => AFPToken::TBRACE,
52        '[' => AFPToken::TSQUAREBRACKET,
53        ']' => AFPToken::TSQUAREBRACKET,
54        ';' => AFPToken::TSTATEMENTSEPARATOR,
55    ];
56
57    public const BASES = [
58        'b' => 2,
59        'x' => 16,
60        'o' => 8
61    ];
62
63    public const BASE_CHARS_RES = [
64        2  => '/^[01]+$/',
65        8  => '/^[0-7]+$/',
66        16 => '/^[0-9A-Fa-f]+$/',
67        10 => '/^[0-9.]+$/',
68    ];
69
70    public const KEYWORDS = [
71        'in', 'like', 'true', 'false', 'null', 'contains', 'matches',
72        'rlike', 'irlike', 'regex', 'if', 'then', 'else', 'end',
73    ];
74
75    /**
76     * @var BagOStuff
77     */
78    private $cache;
79
80    /**
81     * @param BagOStuff $cache
82     */
83    public function __construct( BagOStuff $cache ) {
84        $this->cache = $cache;
85    }
86
87    /**
88     * Get a cache key used to store the tokenized code
89     *
90     * @param string $code Not yet tokenized
91     * @return string
92     * @internal
93     */
94    public function getCacheKey( $code ) {
95        return $this->cache->makeGlobalKey( __CLASS__, self::CACHE_VERSION, crc32( $code ) );
96    }
97
98    /**
99     * Get the tokens for the given code.
100     *
101     * @param string $code
102     * @return array[]
103     * @phan-return array<int,array{0:AFPToken,1:int}>
104     */
105    public function getTokens( string $code ): array {
106        return $this->cache->getWithSetCallback(
107            $this->getCacheKey( $code ),
108            BagOStuff::TTL_DAY,
109            function () use ( $code ) {
110                return $this->tokenize( $code );
111            }
112        );
113    }
114
115    /**
116     * @param string $code
117     * @return array[]
118     * @phan-return array<int,array{0:AFPToken,1:int}>
119     */
120    private function tokenize( string $code ): array {
121        $tokens = [];
122        $curPos = 0;
123
124        do {