在PHP中解决一个数学问题/表达式,该表达式是一个字符串

5
用户可以输入类似于5 + 6546 ^ 242!sqrt(543)log(54)sin 5sin(50)的数学问题(表达式)。在一些重新格式化后(例如,将sin 5改为sin(5)),并进行评估后,PHP会给出正确的结果。
$problem = "5 + 5324";
eval("$result = " . $problem);
echo $problem . " = " . $result;

然而,这样做相当不安全:
/* If you read this, please, plz don't be stupid and DO NOT EXECUTE this code!!!!! */
$problem = "shell_exec('rm -rf /')";
eval("$result = " . $problem); /* Nukes system */
echo $problem . " = " . $result;

有人可以指导我如何解析和解决类似上面例子的数学问题,而且要安全吗?谢谢。


顺便说一下,eval只是evil的一个常见拼写错误吗?


5会使程序停止执行吗? - Ben
是的,那是其中一个“不安全”的情况。 - user142019
3个回答

1
理想情况下,我认为您需要创建某种语法解析器/词法分析引擎,可以将公式解析成其组成部分,然后在该基础上运行方程式。
这样任何不受控制的函数都将被忽略,系统也可以返回一个错误。

1

来看一下PHPExcel中的计算引擎...它实现了一个安全的公式解析器,可以处理大多数公式表达式(包括LOG()等函数以及2^3作为幂而不是二进制运算符),这些都可以被Excel自己计算。


0

在这种情况下,你基本上需要自己实现计算器 - 我曾经在一次工作面试中遇到过这个问题,所以这是我的代码。记住,对我来说这真的是遗留的东西,但我想它可能会给你一些启示:

<?php
if(isset($_POST['inp'])) {
    $time_start = microtime(true);

    $inp = preg_replace(array('/\s+/', '/Pi/', '/e/', '/T/', '/G/', '/M/', '/k/', '/m/', '/u/', '/n/', '/p/', '/f/'), 
                        array('', M_PI, exp(1), '*'. 1e12, '*'. 1e9, '*'. 1e6, '*'. 1e3, '*'. 1e-3, '*'. 1e-6, '*'. 1e-9, '*'. 1e-12, '*'. 1e-15),
                         $_POST['inp']);


    function rectify($exp, $mod = "+") {

        $res = recCalc($exp);
        debug("Pre rectify", $res);
        if($mod == '-') {
            $res *= -1;
        }
        debug("Post rectify", $res);
        return $res;
    }


    function do_error($str) {
        die($str);
        return false;
    }


    function recCalc($inp) {
        debug("RecCalc input", $inp);   

        $p = str_split($inp);
        $level = 0;

        foreach($p as $num) {
            if($num == '(' && ++$level == 1) {
                $num = 'BABRAX';

            } elseif($num == ')' && --$level == 0) {
                $num = 'DEBRAX';
            }
            $res[] = $num;

        }

        if($level != 0) {
            return do_error( 'Chyba: špatný počet závorek');
        }

        $res = implode('', $res);

        $res = preg_replace('#([\+\-]?)BABRAX(.+?)DEBRAX#e', "rectify('\\2', '\\1')", $res);

        debug("After parenthesis proccessing", $res);
        preg_match_all('#[+-]?([^+-]+)#', $res, $ar, PREG_PATTERN_ORDER);

        for($i = 0; $i <count($ar[0]); $i++) {
              $last = substr($ar[0][$i], -1, 1); 
              if($last == '/' || $last == '*' || $last == '^' || $last == 'E') {
                    $ar[0][$i] = $ar[0][$i].$ar[0][$i+1];
                    unset($ar[0][$i+1]);
              }
        }

        $result = 0;
        foreach($ar[0] as $num) {
            $result += multi($num);
        }
        debug("RecCalc output", $result);
        return $result;
    }

            function multi($inp) {
        debug("Multi input", $inp);

        $inp = explode(' ', ereg_replace('([\*\/\^])', ' \\1 ', $inp));

        foreach($inp as $va) {
            if($va != '*' && $va != '/' && $va != '^') {
                $v[] = (float)$va;
            } else {
                $v[] = $va;
            }
        }
        $inp = $v;
        //predpokladame, ze prvni prvek je cislo, ktere budeme dale nasobit
        $res = $inp[0];
        for($i = 1; $i< count($inp); $i++) {

            if($inp[$i] == '*') {
                $res *= $inp[$i + 1];
            } elseif($inp[$i] == '/') {
                if($inp[$i + 1] == 0) do_error('Dělení nulou');

                $res /= $inp[$i + 1];
            } elseif($inp[$i] == '^') {
                $res = pow($res, $inp[$i + 1]);
            }
        }
        debug("Multi output", $res);
        return $res;
    }


    function debug($msg, $var) {
        if(isset($_POST['out']) && $_POST['out'] == '1') {
            echo "\n".$msg.": ".$var;
        }
    }
    echo '<pre>';


    if(eregi('(^[\*\/\+\^])|[a-dg-z \?<>;:"\'\\|\}\{_]|([\*\/\+\-\^]$)', $inp)) {
        do_error('Nalezen neplatný či nesmyslný znak. Překontorlujte si prosím syntax.');
    }

    $result = recCalc($inp);

    $time_end = microtime(true);
    $time = ($time_end - $time_start) *1000;
    $time .= 'ms';

    echo "\n<strong>".$result."</strong>";
    debug('Execution time', $time);
    echo '</pre>';

} else {


?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<title>Calculator</title>
<style type="text/css">
<!--
body {
    font: 100% Verdana, Arial, Helvetica, sans-serif;
    background: #666666;
    margin: 0; /* it's good practice to zero the margin and padding of the body element to account for differing browser defaults */
    padding: 0;
    text-align: center; /* this centers the container in IE 5* browsers. The text is then set to the left aligned default in the #container selector */
    color: #000000;
}
.oneColElsCtr #container {
    width: 46em;
    background: #FFFFFF;
    margin: 0 auto; /* the auto margins (in conjunction with a width) center the page */
    border: 1px solid #000000;
    text-align: left; /* this overrides the text-align: center on the body element. */
}
.oneColElsCtr #mainContent {
    padding: 0 20px; /* remember that padding is the space inside the div box and margin is the space outside the div box */
}
.noshow {
    display: none;
}
-->
</style>
<link rel="stylesheet" href="styles/COHEN_style.css"/>
<script src="scripts/spry/SpryData.js"></script>
<!--<script src="scripts/spry/xpath.js"></script>-->
<script src="scripts/spry/SpryUtils.js"></script>
<script src="scripts/spry/SpryDOMUtils.js" type="text/javascript"></script>
<script src="scripts/spry/SpryCollapsiblePanel.js" type="text/javascript"></script>

<script type="text/javascript">
<!--
function updateResultDiv(req) 
    {
        Spry.Utils.setInnerHTML('result', req.xhRequest.responseText);
    }
function submitit() {
    if(document.getElementById('auto').checked) {
        Spry.Utils.submitForm(document.getElementById('calc'), updateResultDiv);
    }
}
-->
</script>
<link href="scripts/spry/SpryCollapsiblePanel.css" rel="stylesheet" type="text/css" />
</head>

<body class="oneColElsCtr">

<div id="container">
  <div id="mainContent">
    <h1>Calculator</h1>
    <form method="post" id="calc" onsubmit="return Spry.Utils.submitForm(document.getElementById('calc'), updateResultDiv);">
    <input type="text" name="inp" size="80" value="" onkeyup="submitit()"/><br />
    <input type="checkbox" value="1" name="out" /><label>Debug</label>  <input onclick="Spry.$$('#submit').toggleClassName('noshow');" checked="checked" type="checkbox" value="1" id="auto" /><label for="auto">Count automatically</label><br />
    <input class="noshow" value="Počítej" type="submit" id="submit"  />
    </form>
    <div id="result">

    </div>
</div>
</div>
</body>
</html>

我写的时候进行了检查,所以抱歉如果有奇怪的注释。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接