mirror of
https://gerrit.wikimedia.org/r/mediawiki/extensions/AbuseFilter.git
synced 2024-11-24 06:03:49 +00:00
Add new methods for checking DUNDEFINED recursively, use them
The problem is explained at T250570#6068702; basically, the previous check didn't account for DUNDEFINED nested deep inside arrays. Bug: T250570 Change-Id: Iacee2db54ca00108de6339bb3dae70af7e2eeb56
This commit is contained in:
parent
35294711bd
commit
1d6b9f6617
|
@ -376,6 +376,42 @@ class AFPData {
|
|||
return new AFPData( $type, $res );
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether this instance contains the DUNDEFINED type, recursively
|
||||
* @return bool
|
||||
*/
|
||||
public function hasUndefined() : bool {
|
||||
if ( $this->type === self::DUNDEFINED ) {
|
||||
return true;
|
||||
}
|
||||
if ( $this->type === self::DARRAY ) {
|
||||
foreach ( $this->data as $el ) {
|
||||
if ( $el->hasUndefined() ) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a clone of this instance where DUNDEFINED is replaced with DNULL
|
||||
* @return $this
|
||||
*/
|
||||
public function cloneAsUndefinedReplacedWithNull() : self {
|
||||
if ( $this->type === self::DUNDEFINED ) {
|
||||
return new self( self::DNULL );
|
||||
}
|
||||
if ( $this->type === self::DARRAY ) {
|
||||
$data = [];
|
||||
foreach ( $this->data as $el ) {
|
||||
$data[] = $el->cloneAsUndefinedReplacedWithNull();
|
||||
}
|
||||
return new self( self::DARRAY, $data );
|
||||
}
|
||||
return clone $this;
|
||||
}
|
||||
|
||||
/** Convert shorteners */
|
||||
|
||||
/**
|
||||
|
|
|
@ -1148,8 +1148,8 @@ class AbuseFilterParser extends AFPTransitionBase {
|
|||
// @todo This is subpar.
|
||||
$hasUndefinedArg = false;
|
||||
foreach ( $args as $i => $arg ) {
|
||||
if ( $arg->type === AFPData::DUNDEFINED ) {
|
||||
$args[$i] = new AFPData( AFPData::DNULL );
|
||||
if ( $arg->hasUndefined() ) {
|
||||
$args[$i] = $arg->cloneAsUndefinedReplacedWithNull();
|
||||
$hasUndefinedArg = true;
|
||||
}
|
||||
}
|
||||
|
@ -1184,12 +1184,12 @@ class AbuseFilterParser extends AFPTransitionBase {
|
|||
$this->raiseCondCount();
|
||||
|
||||
$hasUndefinedOperand = false;
|
||||
if ( $lhs->getType() === AFPData::DUNDEFINED ) {
|
||||
$lhs = new AFPData( AFPData::DNULL );
|
||||
if ( $lhs->hasUndefined() ) {
|
||||
$lhs = $lhs->cloneAsUndefinedReplacedWithNull();
|
||||
$hasUndefinedOperand = true;
|
||||
}
|
||||
if ( $rhs->getType() === AFPData::DUNDEFINED ) {
|
||||
$rhs = new AFPData( AFPData::DNULL );
|
||||
if ( $rhs->hasUndefined() ) {
|
||||
$rhs = $rhs->cloneAsUndefinedReplacedWithNull();
|
||||
$hasUndefinedOperand = true;
|
||||
}
|
||||
if ( $hasUndefinedOperand ) {
|
||||
|
|
|
@ -256,4 +256,65 @@ class AFPDataTest extends AbuseFilterParserTestCase {
|
|||
$this->expectExceptionMessage( 'Refusing to cast' );
|
||||
AFPData::castTypes( $data, AFPData::DNULL );
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AFPData $obj
|
||||
* @param bool $expected
|
||||
* @covers AFPData::hasUndefined
|
||||
* @dataProvider provideHasUndefined
|
||||
*/
|
||||
public function testHasUndefined( AFPData $obj, bool $expected ) {
|
||||
$this->assertSame( $expected, $obj->hasUndefined() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for testHasUndefined
|
||||
*/
|
||||
public function provideHasUndefined() {
|
||||
return [
|
||||
[ new AFPData( AFPData::DUNDEFINED ), true ],
|
||||
[ new AFPData( AFPData::DNULL ), false ],
|
||||
[ new AFPData( AFPData::DSTRING, '' ), false ],
|
||||
[ new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DUNDEFINED ) ] ), true ],
|
||||
[ new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] ), false ],
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param AFPData $obj
|
||||
* @param AFPData $expected
|
||||
* @covers AFPData::cloneAsUndefinedReplacedWithNull
|
||||
* @dataProvider provideCloneAsUndefinedReplacedWithNull
|
||||
*/
|
||||
public function testCloneAsUndefinedReplacedWithNull( AFPData $obj, AFPData $expected ) {
|
||||
$this->assertEquals( $expected, $obj->cloneAsUndefinedReplacedWithNull() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Provider for testHasUndefined
|
||||
*/
|
||||
public function provideCloneAsUndefinedReplacedWithNull() {
|
||||
return [
|
||||
[
|
||||
new AFPData( AFPData::DUNDEFINED ),
|
||||
new AFPData( AFPData::DNULL )
|
||||
],
|
||||
[
|
||||
new AFPData( AFPData::DNULL ),
|
||||
new AFPData( AFPData::DNULL )
|
||||
],
|
||||
[
|
||||
new AFPData( AFPData::DSTRING, '' ),
|
||||
new AFPData( AFPData::DSTRING, '' )
|
||||
],
|
||||
[
|
||||
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DUNDEFINED ) ] ),
|
||||
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] )
|
||||
],
|
||||
[
|
||||
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] ),
|
||||
new AFPData( AFPData::DARRAY, [ new AFPData( AFPData::DNULL ) ] )
|
||||
],
|
||||
];
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1009,6 +1009,12 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
|
|||
[ "false & (var := [ 1,2,3 ]); var[] := 'baz'; 'baz' in var" ],
|
||||
// But allow overwriting the whole variable
|
||||
[ "false & (var := [ 1,2,3 ]); var := [4,5,6]; var !== [4,5,6]" ],
|
||||
// Recursive DUNDEFINED replacement, T250570
|
||||
[ '"x" in [ user_name ]' ],
|
||||
[ 'string(user_name) in [ user_name ]' ],
|
||||
[ 'string(user_name) in [ "x" ]' ],
|
||||
[ '[ [ user_name ] ] in [ [ user_name ] ]' ],
|
||||
[ 'equals_to_any( [ user_name ], [ user_name ] )' ],
|
||||
];
|
||||
}
|
||||
|
||||
|
@ -1197,6 +1203,7 @@ class AbuseFilterParserTest extends AbuseFilterParserTestCase {
|
|||
return [
|
||||
[ 'accountname rlike "("', 'regexfailure' ],
|
||||
[ 'contains_any( new_wikitext, "foo", 3/0 )', 'dividebyzero' ],
|
||||
[ 'contains_any( added_lines, [ user_name, [ 3/0 ] ] )', 'dividebyzero' ],
|
||||
[ 'rcount( "(", added_lines )', 'regexfailure' ],
|
||||
[ 'get_matches( "(", new_wikitext )', 'regexfailure' ],
|
||||
[ 'added_lines contains string(3/0)', 'dividebyzero' ],
|
||||
|
|
Loading…
Reference in a new issue