2

I have a custom validation rule module that essentially allows users to set up CSV validation. My problem is I get it to this array:

Array(
    [field_name] => 'is_int(324230435)',
    [some_other_field] => 'strlen("some str") > 25'
)

I did some research and came across the eval() function.

refs: How to use string in IF condition in PHP

However, I really don't want to use eval() due to the security issues (ref: When is eval evil in php?)

Although it doesn't strictly say eval is evil, I still would prefer if there was an alternative method.

Am I being over-cautious about the usage of eval() - perhaps I should escape and use eval() or is there a better way?

treyBake
  • 6,440
  • 6
  • 26
  • 57
  • Well, you want to execute a string as code. `eval` does that. This operation has the fundamental caveat that you want to *execute arbitrary strings as code*, which means you need to trust those strings. The alternative here would be to create your own parser and evaluator for your own mini-DSL, in which you can only form certain valid expression and don't open yourself up to *any arbitrary PHP code*. – deceze Sep 13 '18 at 14:02
  • Example: https://symfony.com/doc/current/components/expression_language.html – deceze Sep 13 '18 at 14:03
  • What doesnt mix with eval is *user*, which you are going to do,. so its best not to use it. For 2 reasons, entering malicious code, and breaking the script by entering wrong syntax. You would be better off having allowed php functions in a drop down and then them selecting them adding the value and server-side executing them safely. So you would simply have a different array structure Array( [field_name] => [['func' => 'is_int', 'value' => 324230435]] ) – Lawrence Cherone Sep 13 '18 at 14:04
  • @deceze ah I see ... well I'm never a fan of trusting the user haha but I like the looks of that Symfony component - will give it a go ^.^ ty – treyBake Sep 13 '18 at 14:04
  • You could deconstruct the string and then use [call_user_func](http://php.net/manual/en/function.call-user-func.php) – Michel Sep 13 '18 at 14:11

2 Answers2

3

Well, executing arbitrary strings as code has the caveat that you're executing arbitrary code whichever way you do it. There's no better alternative to eval that would let you execute PHP code without… executing PHP code.

The sane way to go here is to define a DSL which gives your users a way to write certain limited expressions which are not PHP code, which you will parse and evaluate with specific limited capabilities.

A good library which does that is Symfony's ExpressionLanguage component. Beyond that you'd go into the domain of language parsers.

deceze
  • 510,633
  • 85
  • 743
  • 889
0

Just going off of @deceze answer and suggestion to use Symfony's ExpressionLanguage Component.

I installed it to my project via Composer and thought for anyone stumbling across the post it might be helpful to see it working (and in relation to my question):

# build array for testing rows against rules
$test = [];

# foreach csv row
foreach ($csv as $keey => $row)
{
    # 10000s of rows, just for simplicity - break after 3
    if ($keey == 0) {continue;}
    if ($keey >= 3) {continue;}

    # get array keys for 
    $keys = array_keys($row);

    foreach ($keys as $key)
    {
        # if row key is in the $conditions array, add to $test array for testing
        if (in_array($key, array_map('strtolower', array_keys($conditions)))) {
            $conditionType = array_keys($conditions[$key]);
            $conditionType = $conditionType[0];

            if ($conditionType === 'condition_suffix') {
                $brokenCondition = explode(' ', $conditions[$key][$conditionType]);

                # build array to pass into ->evaluate()
                $test[$key]['evaluate'] = 'field '. $brokenCondition[0] .' required'; # expression to actually test
                $test[$key]['pass'] = [ # works like pdo, pass in the names and give them a value
                    'field' => strlen($row[$key]),
                    'required' => $brokenCondition[1]
                ];
            } else {
                $test[$key]['evaluate'] = 'field == required';
                $test[$key]['pass'] = [
                    'field' => is_numeric($row[$key]),
                    'required' => true
                ];
            }
        }
    }
}

echo '#----------------------------------------------------------------------------#';

# show test arr for reference
echo '<pre>';
print_r($test);
echo '</pre>';

# foreach test row, check against the condition
foreach ($test as $key => $item)
{
    echo '<pre>';
    var_dump($key. ': ' .$expressionLanguage->evaluate(
        $item['evaluate'],
        $item['pass']
    ));
    echo '</pre>';

    echo '+----------------------------------------------------------------------------+';
}

This now evaluates my custom created php query strings via the ExpressionLanguage Symfony component. Thanks @deceze

refs:

https://symfony.com/doc/current/components/expression_language/syntax.html

https://symfony.com/doc/current/components/expression_language.html

treyBake
  • 6,440
  • 6
  • 26
  • 57