From ddf904fa0f8efac38f822bc1e063a6790c1a5ee1 Mon Sep 17 00:00:00 2001 From: Yuri Kuznetsov Date: Wed, 24 Apr 2024 13:22:22 +0300 Subject: [PATCH] formula backslash escaping --- .../Espo/Core/Formula/Functions/ValueType.php | 9 +-- application/Espo/Core/Formula/Parser.php | 59 +++++++++++++++++-- .../unit/Espo/Core/Formula/EvaluatorTest.php | 50 ++++++++++++++++ 3 files changed, 106 insertions(+), 12 deletions(-) diff --git a/application/Espo/Core/Formula/Functions/ValueType.php b/application/Espo/Core/Formula/Functions/ValueType.php index 5ec2274723..4a3080abe3 100644 --- a/application/Espo/Core/Formula/Functions/ValueType.php +++ b/application/Espo/Core/Formula/Functions/ValueType.php @@ -30,7 +30,6 @@ namespace Espo\Core\Formula\Functions; use Espo\Core\Formula\Exceptions\Error; - use Espo\Core\Formula\ArgumentList; class ValueType extends BaseFunction @@ -41,12 +40,6 @@ class ValueType extends BaseFunction throw new Error("Bad value."); } - $value = $args[0]->getData(); - - if (is_string($value)) { - $value = str_replace("\\n", "\n", $value); - } - - return $value; + return $args[0]->getData(); } } diff --git a/application/Espo/Core/Formula/Parser.php b/application/Espo/Core/Formula/Parser.php index bb43e3e493..a7fd43139a 100644 --- a/application/Espo/Core/Formula/Parser.php +++ b/application/Espo/Core/Formula/Parser.php @@ -1002,10 +1002,7 @@ class Parser $expression[0] === "'" && $expression[strlen($expression) - 1] === "'" || $expression[0] === "\"" && $expression[strlen($expression) - 1] === "\"" ) { - $subExpression = substr($expression, 1, strlen($expression) - 2); - $subExpression = str_replace(["\\\\", "\\\""], ["\\", "\""], $subExpression); - - return new Value($subExpression); + return new Value(self::stripStringSlashes($expression)); } if ($expression[0] === "$") { @@ -1290,4 +1287,58 @@ class Parser return $argumentList; } + + static private function stripStringSlashes(string $expression): string + { + $string = substr($expression, 1, strlen($expression) - 2); + + /** @var array{bool, string}[] $tokens */ + $tokens = []; + + $stripList = ["\\\\", "\\\"", "\\n", "\\t", "\\r"]; + $replaceList = ["\\", "\"", "\n", "\t", "\r"]; + + $k = 0; + + for ($i = 0; $i < strlen($string); $i++) { + $part = substr($string, $i, 2); + + if (in_array($part, $stripList)) { + $len = strlen($part); + + $before = substr($string, $k, $i - $k); + + if (strlen($before)) { + $tokens[] = [false, $before]; + } + + $tokens[] = [true, $part]; + + $i += $len - 1; + $k = $i + 1; + } + + if ($i >= strlen($string) - 1) { + $after = substr($string, $k); + + if (strlen($after)) { + $tokens[] = [false, $after]; + } + } + } + + $result = ''; + + foreach ($tokens as $token) { + if (!$token[0]) { + $result .= $token[1]; + + continue; + } + + $result .= str_replace($stripList, $replaceList, $token[1]); + } + + return $result; + } } diff --git a/tests/unit/Espo/Core/Formula/EvaluatorTest.php b/tests/unit/Espo/Core/Formula/EvaluatorTest.php index 08ce19ae47..499eaaa68c 100644 --- a/tests/unit/Espo/Core/Formula/EvaluatorTest.php +++ b/tests/unit/Espo/Core/Formula/EvaluatorTest.php @@ -1449,4 +1449,54 @@ class EvaluatorTest extends \PHPUnit\Framework\TestCase $this->assertEquals("\"", $result); } + + public function testStrings3(): void + { + $expression = '"\\n"'; + + /** @noinspection PhpUnhandledExceptionInspection */ + $result = $this->evaluator->process($expression); + + $this->assertEquals("\n", $result); + } + + public function testStrings4(): void + { + $expression = '"\\\\n"'; + + /** @noinspection PhpUnhandledExceptionInspection */ + $result = $this->evaluator->process($expression); + + $this->assertEquals("\\n", $result); + } + + public function testStrings5(): void + { + $expression = '"test\\\\nest\\\\nest"'; + + /** @noinspection PhpUnhandledExceptionInspection */ + $result = $this->evaluator->process($expression); + + $this->assertEquals("test\\nest\\nest", $result); + } + + public function testStrings6(): void + { + $expression = '"\\n\\\\test"'; + + /** @noinspection PhpUnhandledExceptionInspection */ + $result = $this->evaluator->process($expression); + + $this->assertEquals("\n\\test", $result); + } + + public function testStrings7(): void + { + $expression = '"\\n\\\\test\\n"'; + + /** @noinspection PhpUnhandledExceptionInspection */ + $result = $this->evaluator->process($expression); + + $this->assertEquals("\n\\test\n", $result); + } }