Fix 3 loi nghiem trong: eval() -> safe parser, Contract::saved() infinite loop, DB Transaction for schedule generation

This commit is contained in:
2026-04-28 08:04:30 +00:00
parent 49aa20a634
commit e229da5e8c
7 changed files with 361 additions and 78 deletions

View File

@@ -72,35 +72,116 @@ class MailMergeService
$evalExpression = $expression;
foreach ($values as $key => $value) {
if (is_numeric($value)) {
$evalExpression = str_replace($key, (float) $value, $evalExpression);
$evalExpression = str_replace($key, $value, $evalExpression);
}
}
// Chỉ cho phép số và các phép toán cơ bản
// Chỉ cho phép số, dấu chấm, dấu phẩy và các phép toán cơ bản
$evalExpression = str_replace(',', '.', $evalExpression);
$evalExpression = preg_replace('/[^0-9.\+\-\*\/\(\)\s]/', '', $evalExpression);
$evalExpression = str_replace(' ', '', $evalExpression);
if (empty($evalExpression)) return 0;
try {
// Eval an toàn với chỉ phép toán
$result = self::safeEval($evalExpression);
$result = self::safeCalculate($evalExpression);
return (float) $result;
} catch (\Throwable $e) {
return 0;
}
}
protected static function safeEval(string $expression): float
protected static function safeCalculate(string $expression): float
{
// Loại bỏ các hàm nguy hiểm, chỉ giữ phép toán
$expression = preg_replace('/[^0-9.\+\-\*\/\(\)\s]/', '', $expression);
// Tokenize: tách số và operators
$tokens = [];
$number = '';
for ($i = 0; $i < strlen($expression); $i++) {
$char = $expression[$i];
if (ctype_digit($char) || $char === '.') {
$number .= $char;
} else {
if ($number !== '') {
$tokens[] = (float) $number;
$number = '';
}
$tokens[] = $char;
}
}
if ($number !== '') {
$tokens[] = (float) $number;
}
if (empty($expression) || preg_match('/[a-zA-Z]/', $expression)) {
// Shunting yard algorithm: infix → postfix
$output = [];
$stack = [];
$precedence = ['+' => 1, '-' => 1, '*' => 2, '/' => 2];
foreach ($tokens as $token) {
if (is_numeric($token)) {
$output[] = $token;
} elseif ($token === '(') {
$stack[] = $token;
} elseif ($token === ')') {
while (!empty($stack) && end($stack) !== '(') {
$output[] = array_pop($stack);
}
array_pop($stack); // pop '('
} else {
// Operator
while (!empty($stack) && end($stack) !== '(' &&
isset($precedence[end($stack)]) &&
$precedence[end($stack)] >= $precedence[$token]) {
$output[] = array_pop($stack);
}
$stack[] = $token;
}
}
while (!empty($stack)) {
$output[] = array_pop($stack);
}
// Evaluate postfix
$evalStack = [];
foreach ($output as $token) {
if (is_numeric($token)) {
$evalStack[] = $token;
} else {
$b = array_pop($evalStack);
$a = array_pop($evalStack);
if ($a === null || $b === null) {
throw new \InvalidArgumentException('Invalid expression');
}
switch ($token) {
case '+':
$evalStack[] = bcadd((string) $a, (string) $b, 10);
break;
case '-':
$evalStack[] = bcsub((string) $a, (string) $b, 10);
break;
case '*':
$evalStack[] = bcmul((string) $a, (string) $b, 10);
break;
case '/':
if ((float) $b == 0) throw new \InvalidArgumentException('Division by zero');
$evalStack[] = bcdiv((string) $a, (string) $b, 10);
break;
}
}
}
if (count($evalStack) !== 1) {
throw new \InvalidArgumentException('Invalid expression');
}
// Dùng bc math nếu có, hoặc eval đơn giản
return (float) eval('return ' . $expression . ';');
return (float) $evalStack[0];
}
/**