PHP正则表达式崩溃apache

Posted

技术标签:

【中文标题】PHP正则表达式崩溃apache【英文标题】:PHP regex crashing apache 【发布时间】:2012-08-04 17:54:15 【问题描述】:

我有一个匹配模板系统的正则表达式,不幸的是,它似乎在一些适度琐碎的查找中使 apache(它在 Windows 上运行)崩溃。我已经研究了这个问题,并且有一些关于增加堆栈大小等的建议,但这些建议似乎都不起作用,而且我真的不喜欢通过增加限制来处理这些问题,因为它通常只是将 bug 推到了未来。

关于如何更改正则表达式以使其不太可能出错的任何想法?

我们的想法是捕捉最里面的块(在本例中为 block:testThis should be caught first!/block:test),然后我将 str_replace 出开始/结束标签并通过正则表达式重新运行整个事情,直到没有剩下的块为止。

正则表达式:

~(?P<opening>(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+))(?P<contents>(?:(?!/?block:[0-9a-z-_]+).)*)(?P<closing>/block:\3)~ism

示例模板:

<div class="f_sponsors s_banners">
    <div class="s_previous">&laquo;</div>
    <div class="s_sponsors">
        <ul>
            block:sponsors
            <li>
                <a href="var:url" target="_blank">
                    <img src="image/160x126/var:image"  title="var:name" />
                </a>
            block:testThis should be caught first!/block:test
            </li>
            /block:sponsors
        </ul>
    </div>
    <div class="s_next">&raquo;</div>
</div>

我想这是一个长镜头。 :(

【问题讨论】:

听起来很奇怪,我曾经有一个正则表达式不断吃掉一个特定的 Apache 实例,而 S大写!)标志修复了它。我猜这是一个未报告的内存泄漏或其他东西,研究过程导致它被避免。远射,但值得一试,我会说... @DaveRandom,我曾经遇到过同样的问题,但修复方法相同!让我们看看这是否适用于 OP 另一个想法是转义正则表达式中的文字 字符可能会有所帮助。它们在技术上是元字符,虽然 PCRE 似乎对未转义的花括号相当宽容,但如果您正确地转义它们,它可能会减少工作量。另外为什么要使用命名的捕获组而不是在反向引用中使用名称? /block:\3 => /block:(?P=name)。这对于您的正则表达式尤其如此,因为&lt;inverse&gt; 是可选的,在这种情况下&lt;name&gt; 将是\2,而不是\3 你的意思是在 ~ism 部分的 s 标志?因为如果结束标签与没有 s 的开始标签在不同的行上,它就不起作用?不过后面的参考名称很好。 感谢大家迄今为止在这方面的帮助! 【参考方案1】:

您可以使用atomic group: (?&gt;...)possessive quantifiers: ?+ *+ ++.. 来抑制/限制回溯并通过unrolling loop 技术加速匹配。我的解决方案:

\block:(\w++)\([^&lt;]++(?:(?!\\/?block:\1\b)[&lt;][^&lt;]*+)*+)\/block:\1\

我已经从 http://regexr.com?31p03 测试过。

匹配block:sponsors.../block:sponsors:\block:(sponsors)\([^&lt;]++(?:(?!\\/?block:\1\b)[&lt;][^&lt;]*+)*+)\/block:\1\http://regexr.com?31rb3

匹配block:test.../block:test:\block:(test)\([^&lt;]++(?:(?!\\/?block:\1\b)[&lt;][^&lt;]*+)*+)\/block:\1\http://regexr.com?31rb6

另一种解决方案: 在 PCRE 源代码中,您可以删除 config.h 的注释:/* #undef NO_RECURSE */

config.h复制以下文本: PCRE 使用递归函数调用来处理匹配时的回溯。这有时可能是堆栈大小有限的系统的问题。 定义 NO_RECURSE 以获取在 match() 函数中不使用递归的版本;相反,它通过使用 pcre_recurse_malloc() 从堆中获取内存来创建自己的堆栈。

或者您可以将 pcre.backtrack_limitpcre.recursion_limit 更改为 php.ini (http://www.php.net/manual/en/pcre.configuration.php)

【讨论】:

但是,当您将块放入块中时,它只会捕获最外面的块,而不是最里面的块。我已经更新了一些问题!【参考方案2】:

试试这个:

'~(?P<opening>\(?P<inverse>[!])?block:(?P<name>[a-z0-9\s_-]+)\)(?P<contents>[^]*(?:\(?!/block:(?P=name)\)[^]*)*)(?P<closing>\/block:(?P=name)\)~i'

或者,以可读的形式:

'~(?P<opening>
  \
  (?P<inverse>[!])?
  block:
  (?P<name>[a-z0-9\s_-]+)
  \
)
(?P<contents>
  [^]*(?:\(?!/block:(?P=name)\)[^]*)*
)
(?P<closing>
  \
  /block:(?P=name)
  \
)~ix'

最重要的部分在(?P&lt;contents&gt;..)组中:

[^]*(?:\(?!/block:(?P=name)\)[^]*)*

一开始,我们唯一感兴趣的字符是左大括号,所以我们可以用[^]* 吞下任何其他字符。只有在我们看到 之后,我们才会检查它是否是/block 标记的开头。如果不是,我们继续使用它并开始扫描下一个,并根据需要重复。

使用 RegexBuddy,我通过将光标放在 block:sponsors 标记的开头并进行调试来测试每个正则表达式。然后我从结束 /block:sponsors 标记中删除了结束括号以强制匹配失败并再次调试它。你的正则表达式成功了 940 步,失败了 2265 步。我的成功了 57 步,失败了 83 步。

在旁注中,我删除了 s 修饰符,因为我没有使用点 (.) 和 m 修饰符,因为它从来不需要。根据@DaveRandom 的优秀建议,我还使用了命名的反向引用(?P=name) 而不是\3。我避开了所有大括号(),因为我发现这样更容易阅读。


编辑:如果你想匹配 innermost 命名块,改变正则表达式的中间部分:

(?P<contents>
  [^]*(?:\(?!/block:(?P=name)\)[^]*)*
)

...对此(正如@Kobi 在他的评论中所建议的那样):

(?P<contents>
  [^]*(?:\(?!/?block:[a-z0-9\s_-]+\)[^]*)*
)

最初,(?P&lt;opening&gt;...) 组会抓取它看到的第一个开始标签,然后(?P&lt;contents&gt;..) 组会消耗任何东西——包括其他标签——只要它们不是匹配找到的结束标签由(?P&lt;opening&gt;...) 组。 (然后(?P&lt;closing&gt;...) 组将继续使用它。)

现在,(?P&lt;contents&gt;...) 组拒绝匹配 any 标记,打开或关闭(注意开头的 /?),无论名称是什么。因此,正则表达式最初开始匹配 block:sponsors 标记,但当它遇到 block:test 标记时,它会放弃匹配并返回搜索开始标记。它再次从block:test 标记处开始,这一次在找到/block:test 结束标记时成功完成匹配。

这样描述它听起来效率低下,但事实并非如此。我之前描述的技巧,啜饮非牙套,淹没了这些错误开始的影响。在几乎每个位置都进行负前瞻的地方,现在只有在遇到 时才会这样做。正如@godspeedlee 建议的那样,您甚至可以使用所有格量词:

(?P<contents>
  [^]*+(?:\(?!/?block:[a-z0-9\s_-]+\)[^]*+)*+
)

...因为你知道它永远不会消耗它以后必须回馈的任何东西。这会加快一些速度,但实际上并不是必需的。

【讨论】:

我已经稍微更新了这个问题,因为它只捕获了最外层的块。但是你的仍然不需要 ~sm 部分,所以我认为它在正确的轨道上! 非常好!我认为我理解OP想要什么......我认为“嵌套块”是指任何名称的嵌套块,而不仅仅是同名,它们被迭代替换。所以1 2 /2 /1 应该在1 之前捕获2。如果是这种情况,您可以轻松地将中间部分从[^]*(?:\(?!/block:(?P=name)\)[^]*)* 更改为[^]*(?:\(?!/?block:[a-z0-9\s_-]+\)[^]*)* - regexr.com?31qsf html 【参考方案3】:

解决方案必须是单个正则表达式吗?一种更有效的方法可能是简单地查找/block: 的第一次出现(可能是简单的字符串搜索或正则表达式),然后从该点向后搜索以找到其匹配的开始标签,适当地替换跨度并重复直到没有更多的块。如果每次都从模板顶部开始查找 first 结束标记,那么这将为您提供嵌套最深的块。

镜像算法也可以工作 - 查找最后一个开始标签,然后从那里向前搜索相应的结束标签:

<?php

$template = //...

while(true) 
  $last_open_tag = strrpos($template, 'block:');
  $last_inverted_tag = strrpos($template, '!block:');
  // $block_start is the index of the '' of the last opening block tag in the
  // template, or false if there are no more block tags left
  $block_start = max($last_open_tag, $last_inverted_tag);
  if($block_start === false) 
    // all done
    break;
   else 
    // extract the block name (the foo in block:foo) - from the character
    // after the next : to the character before the next , inclusive
    $block_name_start = strpos($template, ':', $block_start) + 1;
    $block_name = substr($template, $block_name_start,
        strcspn($template, '', $block_name_start));

    // we now have the start tag and the block name, next find the end tag.
    // $block_end is the index of the '' of the next closing block tag after
    // $block_start.  If this doesn't match the opening tag something is wrong.
    $block_end = strpos($template, '/block:', $block_start);
    if(strpos($template, $block_name.'', $block_end + 8) !== $block_end + 8) 
      // non-matching tag
      print("Non-matching tag found\n");
      break;
     else 
      // now we have found the innermost block
      // - its start tag begins at $block_start
      // - its content begins at
      //   (strpos($template, '', $block_start) + 1)
      // - its content ends at $block_end
      // - its end tag ends at ($block_end + strlen($block_name) + 9)
      //   [9 being the length of '/block:' plus '']
      // - the start tag was inverted iff $block_start === $last_inverted_tag
      $template = // do whatever you need to do to replace the template
    
  


echo $template;

【讨论】:

以上是关于PHP正则表达式崩溃apache的主要内容,如果未能解决你的问题,请参考以下文章

7.正则RE

忽略正则表达式搜索中的模式错误,不要使搜索崩溃

用于正则表达式模式匹配崩溃的 NSPredicate

Regular Expression(正则表达式)之邮箱验证

PHP 正则表达式总结

php 正则表达式