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
This commit is contained in:
Daimona Eaytoy 2019-08-12 14:23:46 +02:00
parent 2fdf091eb9
commit 3f171dc0a5
5 changed files with 106 additions and 127 deletions

View file

@ -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 <http://www.php.net/manual/en/function.fnmatch.php#100207>
private static $wildcardMap = [
/**
* Translation table mapping shell-style wildcards to PCRE equivalents.
* Derived from <http://www.php.net/manual/en/function.fnmatch.php#100207>
* @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
*/

View file

@ -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;

View file

@ -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

View file

@ -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
*

View file

@ -484,6 +484,7 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
return [
[ "rcount('(','a')", 'funcRCount' ],
[ "get_matches('this (should fail', 'any haystack')", 'funcGetMatches' ],
[ "'a' rlike '('", 'keywordRegex' ],
];
}