views:

253

answers:

3

The user can enter a math problem (expression) like 5 + 654, 6 ^ 24, 2!, sqrt(543), log(54), sin 5, sin(50). After some reformatting (e.g. change sin 5 into sin(5)), and doing an eval, PHP gives me the right result:

$problem = "5 + 5324";
eval("$result = " . $problem);
echo $problem . " = " . $result;

However, this is quite unsafe:

/* 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;

Can anyone point me in the right direction parsing and solving a math question like the examples above, which is safe? Thanks.


Btw, isn't eval just a common misspelling of evil?

+1  A: 

Ideally, I think you would have to create some sort of grammar parser/lexer engine that could parse out the formula into its parts and then run the equation on that.

That way any rogue functions would just be ignored, and the system could return an error.

webdestroya
A: 

Well you pretty much need to implement your own calculator in that case - I got it at a job interview once so here is my code. Remember it's really legacy stuff for me but I figured it might give you some ideas:

<?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"&gt;
<html xmlns="http://www.w3.org/1999/xhtml"&gt;
<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>

Also I wrote it in check so sorry for weird comments.

Jakub Hampl
+1  A: 

Take a look at the calculation engine in PHPExcel... it implements a safe formula parser that can handle most formulaic expressions (including functions such as LOG(), and 2^3 as a power rather than a binary operator) that can be calculated by Excel itself.

Mark Baker