在处理已一个PHPwind对于敏感词识别错误的问题中,发现了phpWind对于多字节处理存在很大的问题,例如admin\cache.php中如下代码:
$query = $db->query("SELECT * FROM pw_wordfb WHERE classid NOT IN ($classid) ORDER BY id");
while (@extract($db->fetch_array($query))) {
if ($word) {
$word = trim(preg_quote($word,'/'));
//$word = preg_replace("/\\\{(\d+)\\\}/", ".{0,\\1}", $word);
switch ($type) {
case 1:
$wordsfb[$word] = $wordreplace;
break;
case 2:
$alarm[$word] = $wordreplace;
break;
case 3:
$replace[$word] = $wordreplace;
break;
default:
$replace[$word] = $wordreplace;
break;
}
}
}
这里对于关键词作了$word = trim(preg_quote($word,'/'));
处理,这条语句咋一眼看上去没有什么问题,但是别忘了,preg_quote是为单字节而设计的函数,因此当用他处理多字节串字符串的时候就有可能出错,为什么说有可能呢?那是因为这跟编码有关系,例如,在GBK编码下执行:
echo preg_quote('遊行');
他会莫名奇妙的输出運[行
,这其实一点也不奇怪,这是因为遊行
对应的字节码为DF 5B D0 D0
,这其中5B
对应相对于ASCII中的编码即为[
,也就是说 preg_quote函数把DF 5B
(遊)这个整体拆分开来处理了,把遊
的第二个编码当成了单个的ascii码[
处理,给他也加上了\
反斜杠进行转义,于是原先的编码就变成了DF 5C 5B D0 D0
,即運[行
。
其实仔细考虑下,多字节编码的问题其实是普遍存在的,例如我们常用的str_replace函数,还有spilt函数,这些常用函数其实都是存在多字节处理出错风险的。只是因为大部分情况下不大容易出错,因此我们就忽略这些风险。当然对于多字节的字符串的处理PHP官方库中还是有相对应的mb_api
进行处理的,PHP多字节处理函数,但是发现这其中居然没有mb_str_replace()
这个函数,这是我最急需的一个函数,在该页的comments中发现了一个宝贝。
function mb_str_replace($search, $replace, $subject) {
if(is_array($subject)) {
$ret = array();
foreach($subject as $key => $val) {
$ret[$key] = mb_str_replace($search, $replace, $val);
}
return $ret;
}
foreach((array) $search as $key => $s) {
if($s == '') {
continue;
}
$r = !is_array($replace) ? $replace : (array_key_exists($key, $replace) ? $replace[$key] : '');
$pos = mb_strpos($subject, $s);
while($pos !== false) {
$subject = mb_substr($subject, 0, $pos) . $r . mb_substr($subject, $pos + mb_strlen($s));
$pos = mb_strpos($subject, $s, $pos + mb_strlen($r));
}
}
return $subject;
}
该函数能比较完美的处理多字节的字符串替换需求。基于此也诞生了多字符串正则转义函数。
function mb_preg_quote($subject){
$search = array('.','\\','+','*','?','[','^',']','$','(',')','{','}','=','!','<','>','|',':','-');
$replace = array('\.','\\\\','\+','\*','\?','\[','\^','\]','\$','\(','\)','\{','\}','\=','\!','\<','\>','\|','\:','\-');
return mb_str_replace($search,$replace,$subject);
}
当然,要想多字节字符串处理函数有效,需要确保两点