Add support for regex string replacements.

Bug: T285468
Change-Id: I25f8ad1b58cc10f4c6f6ef5ebab99fe58ec71b1e
This commit is contained in:
proc 2022-04-17 16:05:10 +01:00
parent 81b99aad53
commit 1d1215bafb
No known key found for this signature in database
GPG key ID: EC6A757378AD117F
6 changed files with 46 additions and 2 deletions

View file

@ -343,6 +343,7 @@
"abusefilter-edit-builder-funcs-substr": "Substring (substr)",
"abusefilter-edit-builder-funcs-strpos": "Position of substring in string (strpos)",
"abusefilter-edit-builder-funcs-str_replace": "Replace substring with string (str_replace)",
"abusefilter-edit-builder-funcs-str_replace_regexp": "Regular expression search and replace (str_replace_regexp)",
"abusefilter-edit-builder-funcs-rescape": "Escape string as literal in regex (rescape)",
"abusefilter-edit-builder-funcs-set_var": "Set variable (set_var)",
"abusefilter-edit-builder-funcs-sanitize": "Normalize HTML entities into unicode characters (sanitize)",

View file

@ -385,6 +385,7 @@
"abusefilter-edit-builder-funcs-substr": "{{doc-important|Do not translate \"'''substr'''\".}} Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.",
"abusefilter-edit-builder-funcs-strpos": "{{doc-important|Do not translate \"'''strpos'''\".}} Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.",
"abusefilter-edit-builder-funcs-str_replace": "{{doc-important|Do not translate \"'''str_replace'''\".}} Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.",
"abusefilter-edit-builder-funcs-str_replace_regexp": "{{doc-important|Do not translate \"'''str_replace_regexp'''\".}} Abuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.\n\n\"regexp\" stands for \"regular expression\".",
"abusefilter-edit-builder-funcs-rescape": "{{doc-important|Do not translate \"'''rescape'''\".}}\nAbuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.\n\n\"regex\" stands for \"regular expression\".",
"abusefilter-edit-builder-funcs-set_var": "{{doc-important|Do not translate \"'''set_var'''\".}}\nAbuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.",
"abusefilter-edit-builder-funcs-sanitize": "{{doc-important|Do not translate \"'''sanitize'''\".}}\nAbuse filter syntax option in a dropdown from the group {{msg-mw|abusefilter-edit-builder-group-funcs}}.",

View file

@ -68,6 +68,7 @@ class KeywordsManager {
'substr(subject, offset, length)' => 'substr',
'strpos(haystack, needle)' => 'strpos',
'str_replace(subject, search, replace)' => 'str_replace',
'str_replace_regexp(subject, search, replace)' => 'str_replace_regexp',
'rescape(string)' => 'rescape',
'set_var(var,value)' => 'set_var',
'sanitize(string)' => 'sanitize',

View file

@ -59,6 +59,7 @@ class FilterEvaluator {
'strlen' => 'funcLen',
'strpos' => 'funcStrPos',
'str_replace' => 'funcStrReplace',
'str_replace_regexp' => 'funcStrReplaceRegexp',
'rescape' => 'funcStrRegexEscape',
'set' => 'funcSetVar',
'set_var' => 'funcSetVar',
@ -96,6 +97,7 @@ class FilterEvaluator {
'strlen' => [ 1, 1 ],
'strpos' => [ 2, 3 ],
'str_replace' => [ 3, 3 ],
'str_replace_regexp' => [ 3, 3 ],
'rescape' => [ 1, 1 ],
'set' => [ 2, 2 ],
'set_var' => [ 2, 2 ],
@ -1289,6 +1291,35 @@ class FilterEvaluator {
return new AFPData( AFPData::DSTRING, str_replace( $search, $replace, $subject ) );
}
/**
* @param array $args
* @param int $position
* @return AFPData
*/
protected function funcStrReplaceRegexp( $args, int $position ) {
$subject = $args[0]->toString();
$search = $args[1]->toString();
$replace = $args[2]->toString();
$this->checkRegexMatchesEmpty( $args[1], $search, $position );
// phpcs:ignore Generic.PHP.NoSilencedErrors.Discouraged
$result = @preg_replace(
$this->mungeRegexp( $search ),
$replace,
$subject
);
if ( $result === null ) {
throw new UserVisibleException(
'regexfailure',
$position,
[ $search ]
);
}
return new AFPData( AFPData::DSTRING, $result );
}
/**
* @param array $args
* @return AFPData

View file

@ -0,0 +1,3 @@
str_replace_regexp( "foobarbaz", "bar", "" ) === 'foobaz' &
str_replace_regexp( "foo1bar1baz", "\d", "" ) === 'foobarbaz' &
str_replace_regexp( "foobarbaz", "(bar)", "$1baz" ) === 'foobarbazbaz'

View file

@ -96,9 +96,11 @@ class ParserTest extends ParserTestCase {
[ '1 === 1', true ],
[ 'rescape( "abc* (def)" )', 'abc\* \(def\)' ],
[ 'str_replace( "foobarbaz", "bar", "-" )', 'foo-baz' ],
[ 'str_replace_regexp( "foo1bar1baz", "\d", "" )', 'foobarbaz' ],
[ 'str_replace_regexp( "foobarbaz", "(bar)", "$1baz" )', 'foobarbazbaz' ],
[ 'rmdoubles( "foobybboo" )', 'fobybo' ],
[ 'lcase("FÁmí")', 'fámí' ],
[ 'substr( "foobar", 0, 3 )', 'foo' ]
[ 'substr( "foobar", 0, 3 )', 'foo' ],
];
}
@ -552,6 +554,7 @@ class ParserTest extends ParserTestCase {
[ "rcount('(','a')", 'funcRCount' ],
[ "get_matches('this (should fail', 'any haystack')", 'funcGetMatches' ],
[ "'a' rlike '('", 'keywordRegex' ],
[ "str_replace_regexp('any string', 'a bad (regex', 'any replacement')", 'funcStrReplaceRegexp' ],
];
}
@ -679,6 +682,7 @@ class ParserTest extends ParserTestCase {
public function threeParamsFuncs() {
return [
[ 'str_replace' ],
[ 'str_replace_regexp' ],
];
}
@ -702,6 +706,7 @@ class ParserTest extends ParserTestCase {
[ "ip_in_range( 'a', 'b', 'c' )" ],
[ "substr( 'a', 'b', 'c', 'd' )" ],
[ "str_replace( 'a', 'b', 'c', 'd', 'e' )" ],
[ "str_replace_regexp( 'a', 'b', 'c', 'd', 'e' )" ],
];
}
@ -761,7 +766,8 @@ class ParserTest extends ParserTestCase {
[ 'bool()', 'noparams' ],
[ 'ip_in_range(1)', 'notenoughargs' ],
[ 'set_var("x")', 'notenoughargs' ],
[ 'str_replace("x","y")', 'notenoughargs' ]
[ 'str_replace("x","y")', 'notenoughargs' ],
[ 'str_replace_regexp("x","y")', 'notenoughargs' ],
];
}
@ -1099,6 +1105,7 @@ class ParserTest extends ParserTestCase {
return [
[ "norm(,,,)" ],
[ "str_replace(,'x','y')" ],
[ "str_replace_regexp(,'x','y')" ],
[ "contains_any(,)" ],
[ "contains_any(,,)" ],
[ "contains_any(1,2,,)" ],