From 3f171dc0a5f4d41b6cef7e66b0c4603219542f7d Mon Sep 17 00:00:00 2001 From: Daimona Eaytoy Date: Mon, 12 Aug 2019 14:23:46 +0200 Subject: [PATCH] Move keywords handlers to the Parser Just like we do for functions, it doesn't really make sense to have keywords separately, in AFPData. Change-Id: I208a9b1ce2bd12038e9fbcc515c48d604ec80eb8 --- includes/parser/AFPData.php | 106 ++----------------- includes/parser/AbuseFilterCachingParser.php | 2 +- includes/parser/AbuseFilterParser.php | 99 ++++++++++++++++- tests/phpunit/AFPDataTest.php | 25 ----- tests/phpunit/AbuseFilterParserTest.php | 1 + 5 files changed, 106 insertions(+), 127 deletions(-) diff --git a/includes/parser/AFPData.php b/includes/parser/AFPData.php index 6e99cda85..9dd38501a 100644 --- a/includes/parser/AFPData.php +++ b/includes/parser/AFPData.php @@ -13,9 +13,12 @@ class AFPData { // Special purpose for creating instances that will be populated later const DEMPTY = 'empty'; - // Translation table mapping shell-style wildcards to PCRE equivalents. - // Derived from - private static $wildcardMap = [ + /** + * Translation table mapping shell-style wildcards to PCRE equivalents. + * Derived from + * @internal + */ + public static $wildcardMap = [ '\*' => '.*', '\+' => '\+', '\-' => '\-', @@ -177,42 +180,6 @@ class AFPData { return new AFPData( $type, $res ); } - /** - * Checks if $a contains $b - * - * @param AFPData $a - * @param AFPData $b - * @return AFPData - */ - private static function containmentKeyword( AFPData $a, AFPData $b ) { - $a = $a->toString(); - $b = $b->toString(); - - if ( $a === '' || $b === '' ) { - return new AFPData( self::DBOOL, false ); - } - - return new AFPData( self::DBOOL, strpos( $a, $b ) !== false ); - } - - /** - * @param AFPData $a - * @param AFPData $b - * @return AFPData - */ - public static function keywordIn( AFPData $a, AFPData $b ) { - return self::containmentKeyword( $b, $a ); - } - - /** - * @param AFPData $a - * @param AFPData $b - * @return AFPData - */ - public static function keywordContains( AFPData $a, AFPData $b ) { - return self::containmentKeyword( $a, $b ); - } - /** * @param AFPData $d2 * @param bool $strict whether to also check types @@ -260,67 +227,6 @@ class AFPData { } } - /** - * @param AFPData $str - * @param AFPData $pattern - * @return AFPData - */ - public static function keywordLike( AFPData $str, AFPData $pattern ) { - $str = $str->toString(); - $pattern = '#^' . strtr( preg_quote( $pattern->toString(), '#' ), self::$wildcardMap ) . '$#u'; - Wikimedia\suppressWarnings(); - $result = preg_match( $pattern, $str ); - Wikimedia\restoreWarnings(); - - return new AFPData( self::DBOOL, (bool)$result ); - } - - /** - * @param AFPData $str - * @param AFPData $regex - * @param int $pos - * @param bool $insensitive - * @return AFPData - * @throws Exception - */ - public static function keywordRegex( AFPData $str, AFPData $regex, $pos, $insensitive = false ) { - $str = $str->toString(); - $pattern = $regex->toString(); - - $pattern = preg_replace( '!(\\\\\\\\)*(\\\\)?/!', '$1\/', $pattern ); - $pattern = "/$pattern/u"; - - if ( $insensitive ) { - $pattern .= 'i'; - } - - Wikimedia\suppressWarnings(); - $result = preg_match( $pattern, $str ); - Wikimedia\restoreWarnings(); - if ( $result === false ) { - throw new AFPUserVisibleException( - 'regexfailure', - // Coverage bug - // @codeCoverageIgnoreStart - $pos, - // @codeCoverageIgnoreEnd - [ $pattern ] - ); - } - - return new AFPData( self::DBOOL, (bool)$result ); - } - - /** - * @param AFPData $str - * @param AFPData $regex - * @param int $pos - * @return AFPData - */ - public static function keywordRegexInsensitive( AFPData $str, AFPData $regex, $pos ) { - return self::keywordRegex( $str, $regex, $pos, true ); - } - /** * @return AFPData */ diff --git a/includes/parser/AbuseFilterCachingParser.php b/includes/parser/AbuseFilterCachingParser.php index e06116fd3..5009decc1 100644 --- a/includes/parser/AbuseFilterCachingParser.php +++ b/includes/parser/AbuseFilterCachingParser.php @@ -203,7 +203,7 @@ class AbuseFilterCachingParser extends AbuseFilterParser { $this->raiseCondCount(); // @phan-suppress-next-line PhanParamTooMany Not every function needs the position - $result = AFPData::$func( $leftOperand, $rightOperand, $node->position ); + $result = $this->$func( $leftOperand, $rightOperand, $node->position ); } return $result; diff --git a/includes/parser/AbuseFilterParser.php b/includes/parser/AbuseFilterParser.php index 1e7de9071..fa8ad7461 100644 --- a/includes/parser/AbuseFilterParser.php +++ b/includes/parser/AbuseFilterParser.php @@ -744,7 +744,7 @@ class AbuseFilterParser { $this->raiseCondCount(); // @phan-suppress-next-line PhanParamTooMany Not every function needs the position - $result = AFPData::$func( $result, $r2, $this->mCur->pos ); + $result = $this->$func( $result, $r2, $this->mCur->pos ); } } } @@ -1649,6 +1649,103 @@ class AbuseFilterParser { return $value; } + /** + * Checks if $a contains $b + * + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + protected function containmentKeyword( AFPData $a, AFPData $b ) { + $a = $a->toString(); + $b = $b->toString(); + + if ( $a === '' || $b === '' ) { + return new AFPData( AFPData::DBOOL, false ); + } + + return new AFPData( AFPData::DBOOL, strpos( $a, $b ) !== false ); + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + protected function keywordIn( AFPData $a, AFPData $b ) { + return $this->containmentKeyword( $b, $a ); + } + + /** + * @param AFPData $a + * @param AFPData $b + * @return AFPData + */ + protected function keywordContains( AFPData $a, AFPData $b ) { + return $this->containmentKeyword( $a, $b ); + } + + /** + * @param AFPData $str + * @param AFPData $pattern + * @return AFPData + */ + protected function keywordLike( AFPData $str, AFPData $pattern ) { + $str = $str->toString(); + $pattern = '#^' . strtr( preg_quote( $pattern->toString(), '#' ), AFPData::$wildcardMap ) . '$#u'; + Wikimedia\suppressWarnings(); + $result = preg_match( $pattern, $str ); + Wikimedia\restoreWarnings(); + + return new AFPData( AFPData::DBOOL, (bool)$result ); + } + + /** + * @param AFPData $str + * @param AFPData $regex + * @param int $pos + * @param bool $insensitive + * @return AFPData + * @throws Exception + */ + protected function keywordRegex( AFPData $str, AFPData $regex, $pos, $insensitive = false ) { + $str = $str->toString(); + $pattern = $regex->toString(); + + $pattern = preg_replace( '!(\\\\\\\\)*(\\\\)?/!', '$1\/', $pattern ); + $pattern = "/$pattern/u"; + + if ( $insensitive ) { + $pattern .= 'i'; + } + + Wikimedia\suppressWarnings(); + $result = preg_match( $pattern, $str ); + Wikimedia\restoreWarnings(); + if ( $result === false ) { + throw new AFPUserVisibleException( + 'regexfailure', + // Coverage bug + // @codeCoverageIgnoreStart + $pos, + // @codeCoverageIgnoreEnd + [ $pattern ] + ); + } + + return new AFPData( AFPData::DBOOL, (bool)$result ); + } + + /** + * @param AFPData $str + * @param AFPData $regex + * @param int $pos + * @return AFPData + */ + protected function keywordRegexInsensitive( AFPData $str, AFPData $regex, $pos ) { + return $this->keywordRegex( $str, $regex, $pos, true ); + } + /** * @param array $args * @return AFPData diff --git a/tests/phpunit/AFPDataTest.php b/tests/phpunit/AFPDataTest.php index 6a5662e82..a64425c7f 100644 --- a/tests/phpunit/AFPDataTest.php +++ b/tests/phpunit/AFPDataTest.php @@ -36,31 +36,6 @@ * @covers AFPTreeNode */ class AFPDataTest extends AbuseFilterParserTestCase { - /** - * Test the 'regexfailure' exception - * - * @param string $expr The expression to test - * @param string $caller The function where the exception is thrown - * @covers AFPData::keywordRegex - * @dataProvider regexFailure - */ - public function testRegexFailureException( $expr, $caller ) { - $this->exceptionTest( 'regexfailure', $expr, $caller ); - } - - /** - * Data provider for testRegexFailureException - * The second parameter is the function where the exception is raised. - * One expression for each throw. - * - * @return array - */ - public function regexFailure() { - return [ - [ "'a' rlike '('", 'keywordRegex' ], - ]; - } - /** * Test the 'dividebyzero' exception * diff --git a/tests/phpunit/AbuseFilterParserTest.php b/tests/phpunit/AbuseFilterParserTest.php index a4416ecba..daedb0fdd 100644 --- a/tests/phpunit/AbuseFilterParserTest.php +++ b/tests/phpunit/AbuseFilterParserTest.php @@ -484,6 +484,7 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase { return [ [ "rcount('(','a')", 'funcRCount' ], [ "get_matches('this (should fail', 'any haystack')", 'funcGetMatches' ], + [ "'a' rlike '('", 'keywordRegex' ], ]; }