preg_match函数中的RegExp返回浏览器错误

时间:2011-10-01 14:42:37

标签: php regex apache wamp connection-reset

以下函数打破了我在$ pattern变量中提供的正则表达式。如果我改变正则表达式我很好,所以我认为这是问题所在。我没有看到这个问题,即使它们已经打开,我也没有收到标准的PHP错误。

function parseAPIResults($results){
//Takes results from getAPIResults, returns array.

    $pattern = '/\[(.|\n)+\]/';
    $resultsArray = preg_match($pattern, $results, $matches);

}
  

Firefox 6:连接已重置

     

Chrome 14:错误101(net :: ERR_CONNECTION_RESET):连接是   复位。

     

IE 8:Internet Explorer无法显示网页

更新:
Apache / PHP可能会崩溃。这是我运行脚本时的Apache错误日志:

  

[2011年10月1日星期六11:41:40] [通知]家长:孩子进程退出   状态255 - 重新启动。
  [2011年10月1日星期六11:41:40] [通知]   配置Apache / 2.2.11(Win32)PHP / 5.3.0 - 恢复正常   操作   

在Windows 7上运行WAMP 2.0。

4 个答案:

答案 0 :(得分:53)

简单的问题。复杂的答案!

是的,由于堆栈溢出,这类正则表达式会重复(并且无声地)使Apache / PHP崩溃并导致未处理的分段错误!

背景:

PHP preg_*系列正则表达式函数使用Philip Hazel强大的PCRE library。使用这个库,有一个特殊的正则表达式需要对其内部match()函数进行大量的递归调用,这会占用大量的堆栈空间(并且使用的堆栈空间与其大小成正比)主题字符串匹配)。因此,如果主题字符串太长,则将发生堆栈溢出和相应的分段错误。最后PCRE documentation部分标题为pcrestack的部分中描述了此行为。

PHP错误1:PHP设置:pcre.recursion_limit太大了。

PCRE文档描述了如何通过将递归深度限制为大致等于链接应用程序的堆栈大小除以500的安全值来避免堆栈溢出分段错误。当递归深度按建议正确限制时,库不会生成堆栈溢出,而是优雅地退出并显示错误代码。在PHP下,使用pcre.recursion_limit配置变量指定此最大递归深度,并且(不幸的是)默认值设置为100,000。 此值太大了!以下是各种可执行堆栈大小的pcre.recursion_limit安全值表:

Stacksize   pcre.recursion_limit
 64 MB      134217
 32 MB      67108
 16 MB      33554
  8 MB      16777
  4 MB      8388
  2 MB      4194
  1 MB      2097
512 KB      1048
256 KB      524

因此,对于Apache webserver(httpd.exe)的Win32版本,其堆栈大小(相对较小)为256KB,pcre.recursion_limit的正确值应设置为524.这可以使用以下PHP代码行完成:

ini_set("pcre.recursion_limit", "524"); // PHP default is 100,000.

将此代码添加到PHP脚本时,不会发生堆栈溢出,而是生成有意义的错误代码。也就是说,应该生成错误代码! (但不幸的是,由于另一个PHP错误,preg_match()没有。)

PHP Bug 2:preg_match()出错时不返回FALSE。

preg_match()的PHP文档说它在出错时返回FALSE。遗憾的是,PHP 5.3.3及更低版本存在一个错误(#52732),其中preg_match()在错误时不返回FALSE(而是返回int(0),这是相同的值在不匹配的情况下返回)。此错误已在PHP 5.3.4版中修复。

解决方案:

假设您将继续使用WAMP 2.0(使用PHP 5.3.0),解决方案需要考虑上述两个错误。以下是我的建议:

  • 需要将pcre.recursion_limit降低到安全值:524。
  • preg_match()返回int(1)以外的任何内容时,需要明确检查PCRE错误。
  • 如果preg_match()返回int(1),则匹配成功。
  • 如果preg_match()返回int(0),则匹配结果不成功,或者出现错误。

以下是脚本的修改版本(旨在从命令行运行),它确定导致递归限制错误的主题字符串长度:

<?php
// This test script is designed to be run from the command line.
// It measures the subject string length that results in a
// PREG_RECURSION_LIMIT_ERROR error in the preg_match() function.

echo("Entering TEST.PHP...\n");

// Set and display pcre.recursion_limit. (set to stacksize / 500).
// Under Win32 httpd.exe has a stack = 256KB and 8MB for php.exe.
//ini_set("pcre.recursion_limit", "524");       // Stacksize = 256KB.
ini_set("pcre.recursion_limit", "16777");   // Stacksize = 8MB.
echo(sprintf("PCRE pcre.recursion_limit is set to %s\n",
    ini_get("pcre.recursion_limit")));

function parseAPIResults($results){
    $pattern = "/\[(.|\n)+\]/";
    $resultsArray = preg_match($pattern, $results, $matches);
    if ($resultsArray === 1) {
        $msg = 'Successful match.';
    } else {
        // Either an unsuccessful match, or a PCRE error occurred.
        $pcre_err = preg_last_error();  // PHP 5.2 and above.
        if ($pcre_err === PREG_NO_ERROR) {
            $msg = 'Successful non-match.';
        } else {
            // preg_match error!
            switch ($pcre_err) {
                case PREG_INTERNAL_ERROR:
                    $msg = 'PREG_INTERNAL_ERROR';
                    break;
                case PREG_BACKTRACK_LIMIT_ERROR:
                    $msg = 'PREG_BACKTRACK_LIMIT_ERROR';
                    break;
                case PREG_RECURSION_LIMIT_ERROR:
                    $msg = 'PREG_RECURSION_LIMIT_ERROR';
                    break;
                case PREG_BAD_UTF8_ERROR:
                    $msg = 'PREG_BAD_UTF8_ERROR';
                    break;
                case PREG_BAD_UTF8_OFFSET_ERROR:
                    $msg = 'PREG_BAD_UTF8_OFFSET_ERROR';
                    break;
                default:
                    $msg = 'Unrecognized PREG error';
                    break;
            }
        }
    }
    return($msg);
}

// Build a matching test string of increasing size.
function buildTestString() {
    static $content = "";
    $content .= "A";
    return '['. $content .']';
}

// Find subject string length that results in error.
for (;;) { // Infinite loop. Break out.
    $str = buildTestString();
    $msg = parseAPIResults($str);
    printf("Length =%10d\r", strlen($str));
    if ($msg !== 'Successful match.') break;
}

echo(sprintf("\nPCRE_ERROR = \"%s\" at subject string length = %d\n",
    $msg, strlen($str)));

echo("Exiting TEST.PHP...");

?>

运行此脚本时,它会提供主题字符串当前长度的连续读数。如果pcre.recursion_limit的默认值太高,则允许您测量导致可执行文件崩溃的字符串长度。

评论:

  • 在调查这个问题的答案之前,我不知道在PCRE库中发生错误时preg_match()无法返回FALSE的PHP错误。这个bug肯定会引起很多使用preg_match的代码的质疑! (我当然会对我自己的PHP代码进行清点。)
  • 在Windows下,Apache webserver可执行文件(httpd.exe)使用256KB的堆栈大小构建。 PHP命令行可执行文件(php.exe)使用8MB的堆栈大小构建。 pcre.recursion_limit的安全值应根据脚本运行的可执行文件(分别为524和16777)进行设置。
  • 在* nix系统下,Apache Web服务器和命令行可执行文件通常都使用8MB的堆栈大小构建,因此不会经常遇到此问题。
  • PHP开发人员应将pcre.recursion_limit的默认值设置为安全值。
  • PHP开发人员应将preg_match()错误修复应用于PHP 5.2版。
  • 可以使用CFF Explorer免费软件程序手动修改Windows可执行文件的stacksize。您可以使用此程序来增加Apache httpd.exe可执行文件的堆栈大小。 (这可以在XP下运行,但Vista和Win7可能会抱怨。)

答案 1 :(得分:2)

我遇到了同样的问题。非常感谢ridgerunner发布的答案。

虽然知道为什么php崩溃是有帮助的,但对我来说这并没有真正解决问题。为了解决这个问题,我需要调整我的正则表达式以节省内存,这样php就不会崩溃了。

所以问题是如何改变正则表达式。上面发布的The link to the PCRE manual已经描述了一个与你的非常相似的示例正则表达式的解决方案。

那么如何修复你的正则表达式? 首先,你说你想匹配“a。或换行符”。 注意 ”。”正则表达式中的特殊字符不仅匹配点而且匹配任何字符,因此您需要将其转义。 (我希望我在这里没有弄错你的意思。)

$pattern = '/\[(\.|\n)+\]/';

接下来,我们可以复制括号内的量词:

$pattern = '/\[(\.+|\n+)+\]/';

这不会改变表达式的含义。现在我们使用所有格量词而不是正常量词:

$pattern = '/\[(\.++|\n++)++\]/';

所以这应该与你的原始正则表达式具有相同的含义,但是在php中工作而不会崩溃它。 为什么?占有量词“吃掉”角色,不允许回溯。因此,PCRE不必使用递归,堆栈也不会溢出。在括号内使用它们似乎是一个好主意,因为我们通常不需要对替代方法进行量化。

总而言之,最佳做法似乎是:

  • 尽可能使用占有量词。这意味着:++,* +,?+ {} +而不是+,*,?,{}。
  • 在可能的情况下移动替代括号内的量词

遵循这些规则,我能够解决自己的问题,我希望这会有助于其他人。

答案 2 :(得分:1)

我遇到了同样的问题,您需要将模式更改为

$pattern = '|/your pattern/|s';

&#39;&#39;最后基本上意味着将字符串视为一行。

答案 3 :(得分:0)

preg_match返回为模式找到的匹配数。当你有匹配时,它会导致php中的致命错误(例如print_r(1)导致错误)。 print_r(0)(当你改变模式并且没有匹配时)没有,只打印出0。

您想要print_r($matches)

顺便说一句,您的模式未正确转义。使用双引号意味着您需要转义括号前面的反斜杠。