模拟php数组语言结构或使用正则表达式解析?

4

我从外部源获取的字符串如下:

array(1,2,3)

但也包括更大的数组,例如

array("a", "b", "c", array("1", "2", array("A", "B")), array("3", "4"), "d")

我希望在php中它们能够成为一个真正的数组。我知道我可以使用eval,但由于它们来自不受信任的来源,我宁愿不这样做。我也无法控制外部来源。

我应该使用一些正则表达式吗(如果是,是什么),还是有其他方法?


你的外部来源提供了一个类似于 "array(1,2,3)" 的字符串,你想将该文本转换为 PHP 数组? - Jonathan Mayhak
这将会很困难... 这不是 PHP 可以识别的序列化格式。 - Artefacto
你能控制外部来源吗?是否可以要求他们生成JSON或XML格式的数据? - kennytm
@Jonathan:是的,我希望将其放入PHP数组中(就像您使用eval()时获取的那样,但出于安全原因,不想使用eval。 @KennyTM:我无法控制外部来源,所以我必须处理这个。 - Nin
3个回答

11

在使用 Tokenizer 编写解析器时,我发现这并不像我预想的那么容易,于是我又想到了另一个方法:为什么不使用 eval 解析数组,但首先验证其是否包含有害内容?

所以,代码的作用是:检查数组的标记,确保其只包含一些允许的标记和字符,然后执行 eval。我希望已经包含了所有可能无害的标记,如果没有,请添加它们。(我故意没有包括 HEREDOC 和 NOWDOC,因为我认为它们不太可能被使用。)

function parseArray($code) {
    $allowedTokens = array(
        T_ARRAY                    => true,
        T_CONSTANT_ENCAPSED_STRING => true,
        T_LNUMBER                  => true,
        T_DNUMBER                  => true,
        T_DOUBLE_ARROW             => true,
        T_WHITESPACE               => true,
    );
    $allowedChars = array(
        '('                        => true,
        ')'                        => true,
        ','                        => true,
    );

    $tokens = token_get_all('<?php '.$code);
    array_shift($tokens); // remove opening php tag

    foreach ($tokens as $token) {
        // char token
        if (is_string($token)) {
            if (!isset($allowedChars[$token])) {
                throw new Exception('Disallowed token \''.$token.'\' encountered.');
            }
            continue;
        }

        // array token

        // true, false and null are okay, too
        if ($token[0] == T_STRING && ($token[1] == 'true' || $token[1] == 'false' || $token[1] == 'null')) {
            continue;
        }

        if (!isset($allowedTokens[$token[0]])) {
            throw new Exception('Disallowed token \''.token_name($token[0]).'\' encountered.');
        }
    }

    // fetch error messages
    ob_start();
    if (false === eval('$returnArray = '.$code.';')) {
        throw new Exception('Array couldn\'t be eval()\'d: '.ob_get_clean());
    }
    else {
        ob_end_clean();
        return $returnArray;
    }
}

var_dump(parseArray('array("a", "b", "c", array("1", "2", array("A", "B")), array("3", "4"), "d")'));

我认为这是安全与便利之间的一个很好的折衷方案-无需自己解析。

例如:

parseArray('exec("haha -i -thought -i -was -smart")');

将会抛出异常:

Disallowed token 'T_STRING' encountered.

我也有同样的想法 :) 我还没有放弃完全使用标记器来实现它的想法,但我会先探索你的脚本,谢谢。 - Nin

6
你可以做以下几点:

json_decode(str_replace(array('array(', ')'), array('[', ']'), $string)));

用方括号替换数组,然后使用json_decode。如果字符串只是一个具有标量值的多维数组,则进行str_replace不会破坏任何内容,并且您可以将其json_decode。如果它包含任何代码,它也将替换函数括号,然后Json将无效并返回NULL
当然,这是一种相当创造性的方法,但可能适合您。 编辑:另外,请参见其他用户指出的一些进一步限制的评论。

我现在无法测试,但如果它返回正确,那将是一个优雅的解决方案。 +1 - TCCV
@KennyTM 嗯,那不会起作用。尽管如此,我还是会将其保留在那里,以便原帖作者可以决定它是否有用。 - Gordon
这仅适用于数组。无法在关联数组上运行。 - NikiC
1
很有创意,但我喜欢它,这样做或许能行。反正它将会是相当简单的数组。 - Nin

2
我认为你应该使用分词器Tokenizer。也许我稍后会编写一个脚本来实现它。

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