2010-09-24 129 views
4

在运行时使用在字符串中生成的字符串在PHP中使用preg_replace()时,可以使用preg_quote()来保护搜索字符串中的特殊正则表达式字符(例如'$'或'+' 。但是,在替换字符串中处理这个问题的正确方法是什么?拿这个代码,例如:避免处理替换字符串中的特殊preg字符

<?php 

$haystack = '...a bit of sample text...'; 
$replacement = '\\HELLO WORLD$1.+-'; 
$replacement_quoted = preg_quote($replacement); 

var_dump('--replacement', $replacement, '--replacement_quoted', 
    $replacement_quoted, '--haystack', $haystack); 

$result1 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement ."$3", $haystack); 
$result2 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_quoted ."$3", $haystack); 

$replacement_new1 = str_replace('$', '\$', $replacement); 
$replacement_new2 = str_replace('\\', '\\\\', $replacement_new1); 

$result3 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new1 ."$3", $haystack); 
$result4 = preg_replace("@(bit) (of) (sample)@is", "\${1}" . $replacement_new2 ."$3", $haystack); 

var_dump('--result1 (not quoted)', $result1, '--result2 (quoted)', $result2, 
    '--result3 ($ escaped)', $result3, '--result4 (\ and $ escaped)', $result3); 

?> 

下面是输出:

string(13) "--replacement" 
string(17) "\HELLO WORLD$1.+-" 
string(20) "--replacement_quoted" 
string(22) "\\HELLO WORLD\$1\.\+\-" 
string(10) "--haystack" 
string(26) "...a bit of sample text..." 
string(22) "--result1 (not quoted)" 
string(40) "...a bit\HELLO WORLDbit.+-sample text..." 
string(18) "--result2 (quoted)" 
string(42) "...a bit\HELLO WORLD$1\.\+\-sample text..." 
string(21) "--result3 ($ escaped)" 
string(39) "...a bit\HELLO WORLD$1.+-sample text..." 
string(27) "--result4 (\ and $ escaped)" 
string(39) "...a bit\HELLO WORLD$1.+-sample text..." 

正如你所看到的,你不能赢得preg_quote()。如果您不调用它,只是传递未修改的字符串(result1),则无论相应的捕获组包含什么,看起来像捕获令牌(上面的$ 1)的任何内容都将被替换为 。如果你确实调用了它(result2),那么你对捕获组没有任何问题,但是任何其他特殊的PCRE字符(例如*)也会被转义,并且转义字符会在输出中生存。我也很感兴趣的是,两个版本都在输出中生成一个\。

只有通过手动引用字符,特别是$,才能使它起作用。这可以在result3和result4中看到。但是,继续使用\的奇怪现象,result3和\ result4会在输出中再次产生一个\。在替换字符串的开始处添加六个\字符只会在result1,result3和result4的最终输出中产生两个\,并且result2中会有三个字符。

所以,似乎大多数问题都通过手动转义$字符来处理。看起来\角色也需要逃脱,但我需要更多地思考那个角色,以确定什么是攻击。在任何情况下,这都非常难看 - 在令人讨厌的\ $ {1}语法和不得不手动转义某些字符之间,代码只是闻起来很烂并且容易出错。有什么我失踪?有没有一种干净的方式来做到这一点?

回答

1

好吧,好吧,我不认为有任何真正令人满意的方式来处理这个问题。问题有两个:\字符和$字符。其他PCRE特殊字符在替换中似乎并不特殊。

\的情况下,事情实际上表现如您所期望的那样,您需要通过\将其转义,并通过PHP定义并将其传递到preg_replace()。在我的测试代码中,我只是将自己与两层转义混淆了。至于$,它应该放在PHP端,并且\转到preg_replace()。而已。

下面是一些代码来证明这一切:

<?php 

ini_set('display_errors', 1); 
ini_set('error_reporting', E_ALL | E_STRICT); 

//real string: "test1 $1 test2 \\1 test3 \${1}" 

//real string manually \-escaped once for representing as a PHP string 
$test = 'test1 $1 test2 \\\\1 test3 \\${1}'; 
var_dump('--test (starting PHP string - should match real string)', $test); 

$test = str_replace(array('\\', '$'), array('\\\\', '\\$'), $test); 
var_dump('--test (PHP string $-escaped and \-escaped again for preg_replace)', $test); 

$result = preg_replace("/bar/", $test, 'foo bar baz'); 

var_dump('--result - bar should be replaced with original real string', $result); 

?> 

输出:

string(55) "--test (starting PHP string - should match real string)" 
string(30) "test1 $1 test2 \\1 test3 \${1}" 
string(66) "--test (PHP string $-escaped and \-escaped again for preg_replace)" 
string(35) "test1 \$1 test2 \\\\1 test3 \\\${1}" 
string(59) "--result - bar should be replaced with original real string" 
string(38) "foo test1 $1 test2 \\1 test3 \${1} baz" 

我的感觉是preg_quote()应该是这里的解决方案,这将是,如果preg_replace()会忽略转义字符等比\本身和$(例如,+)。然而,它并没有强迫人们进行手动转义。事实上,我认为这是一个错误,并会在php.net上继续提交。

+0

我提交了一个错误 - [(#52962)](http://bugs.php.net/bug.php?id=52962)。 – 2010-10-01 01:01:53