From f09da16f31f8bc75b80e04c6dae8e695c8e1e3c8 Mon Sep 17 00:00:00 2001 From: Fabien Potencier Date: Mon, 28 Mar 2022 22:04:49 +0200 Subject: [PATCH] [ExpressionLanguage] Fix matches when the regexp is not valid --- .../ExpressionLanguage/Node/BinaryNode.php | 21 +++++++++- .../Tests/Node/BinaryNodeTest.php | 42 ++++++++++++++++++- 2 files changed, 59 insertions(+), 4 deletions(-) diff --git a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php index 3820f880e7f31..2f49bd90c6cae 100644 --- a/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php +++ b/src/Symfony/Component/ExpressionLanguage/Node/BinaryNode.php @@ -12,6 +12,7 @@ namespace Symfony\Component\ExpressionLanguage\Node; use Symfony\Component\ExpressionLanguage\Compiler; +use Symfony\Component\ExpressionLanguage\SyntaxError; /** * @author Fabien Potencier @@ -46,8 +47,12 @@ public function compile(Compiler $compiler) $operator = $this->attributes['operator']; if ('matches' == $operator) { + if ($this->nodes['right'] instanceof ConstantNode) { + $this->evaluateMatches($this->nodes['right']->evaluate([], []), ''); + } + $compiler - ->raw('preg_match(') + ->raw('(static function ($regexp, $str) { set_error_handler(function ($t, $m) use ($regexp, $str) { throw new \Symfony\Component\ExpressionLanguage\SyntaxError(sprintf(\'Regexp "%s" passed to "matches" is not valid\', $regexp).substr($m, 12)); }); try { return preg_match($regexp, $str); } finally { restore_error_handler(); } })(') ->compile($this->nodes['right']) ->raw(', ') ->compile($this->nodes['left']) @@ -159,7 +164,7 @@ public function evaluate($functions, $values) return $left % $right; case 'matches': - return preg_match($right, $left); + return $this->evaluateMatches($right, $left); } } @@ -167,4 +172,16 @@ public function toArray() { return ['(', $this->nodes['left'], ' '.$this->attributes['operator'].' ', $this->nodes['right'], ')']; } + + private function evaluateMatches(string $regexp, string $str): int + { + set_error_handler(function ($t, $m) use ($regexp) { + throw new SyntaxError(sprintf('Regexp "%s" passed to "matches" is not valid', $regexp).substr($m, 12)); + }); + try { + return preg_match($regexp, $str); + } finally { + restore_error_handler(); + } + } } diff --git a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php index b45a1e57b9b17..fccc04abce4b8 100644 --- a/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php +++ b/src/Symfony/Component/ExpressionLanguage/Tests/Node/BinaryNodeTest.php @@ -11,9 +11,12 @@ namespace Symfony\Component\ExpressionLanguage\Tests\Node; +use Symfony\Component\ExpressionLanguage\Compiler; use Symfony\Component\ExpressionLanguage\Node\ArrayNode; use Symfony\Component\ExpressionLanguage\Node\BinaryNode; use Symfony\Component\ExpressionLanguage\Node\ConstantNode; +use Symfony\Component\ExpressionLanguage\Node\NameNode; +use Symfony\Component\ExpressionLanguage\SyntaxError; class BinaryNodeTest extends AbstractNodeTest { @@ -111,7 +114,7 @@ public function getCompileData() ['range(1, 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], - ['preg_match("/^[a-z]+/i\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ['(static function ($regexp, $str) { set_error_handler(function ($t, $m) use ($regexp, $str) { throw new \Symfony\Component\ExpressionLanguage\SyntaxError(sprintf(\'Regexp "%s" passed to "matches" is not valid\', $regexp).substr($m, 12)); }); try { return preg_match($regexp, $str); } finally { restore_error_handler(); } })("/^[a-z]+\$/", "abc")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))], ]; } @@ -160,7 +163,42 @@ public function getDumpData() ['(1 .. 3)', new BinaryNode('..', new ConstantNode(1), new ConstantNode(3))], - ['("abc" matches "/^[a-z]+/i$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+/i$/'))], + ['("abc" matches "/^[a-z]+$/")', new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('/^[a-z]+$/'))], ]; } + + public function testEvaluateMatchesWithInvalidRegexp() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('this is not a regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $node->evaluate([], []); + } + + public function testEvaluateMatchesWithInvalidRegexpAsExpression() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new NameNode('regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $node->evaluate([], ['regexp' => 'this is not a regexp']); + } + + public function testCompileMatchesWithInvalidRegexp() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new ConstantNode('this is not a regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $compiler = new Compiler([]); + $node->compile($compiler); + } + + public function testCompileMatchesWithInvalidRegexpAsExpression() + { + $node = new BinaryNode('matches', new ConstantNode('abc'), new NameNode('regexp')); + + $this->expectExceptionObject(new SyntaxError('Regexp "this is not a regexp" passed to "matches" is not valid: Delimiter must not be alphanumeric or backslash')); + $compiler = new Compiler([]); + $node->compile($compiler); + eval('$regexp = "this is not a regexp"; '.$compiler->getSource().';'); + } }