2009-08-15 39 views
5

我需要验证以UTF-8编码的某些用户输入。许多人使用下面的代码推荐:PHP中未使用preg_match的UTF-8验证()

preg_match('/\A(
    [\x09\x0A\x0D\x20-\x7E] 
    | [\xC2-\xDF][\x80-\xBF] 
    | \xE0[\xA0-\xBF][\x80-\xBF] 
    | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} 
    | \xED[\x80-\x9F][\x80-\xBF] 
    | \xF0[\x90-\xBF][\x80-\xBF]{2} 
    | [\xF1-\xF3][\x80-\xBF]{3} 
    | \xF4[\x80-\x8F][\x80-\xBF]{2} 
)*\z/x', $string); 

这是一个从http://www.w3.org/International/questions/qa-forms-utf-8采取正则表达式。一切都很好,直到我发现PHP中的错误至少在2006年以来一直存在。如果$ string太长,Preg_match()会导致seg错误。似乎没有任何解决方法。您可以在这里查看错误提交:http://bugs.php.net/bug.php?id=36463

现在,为了避免使用preg_match,我创建了一个函数,其功能与上述正则表达式完全相同。我不知道这个问题在Stack Overflow中是否合适,但我想知道我所做的功能是否正确。那就是:

EDIT [13.01.2010]: 如果有人有兴趣,有在以前版本的一些错误我已经张贴。以下是我的功能的最终版本。

function check_UTF8_string(&$string) { 
    $len = mb_strlen($string, "ISO-8859-1"); 
    $ok = 1; 

    for ($i = 0; $i < $len; $i++) { 
     $o = ord(mb_substr($string, $i, 1, "ISO-8859-1")); 

     if ($o == 9 || $o == 10 || $o == 13 || ($o >= 32 && $o <= 126)) { 

     } 
     elseif ($o >= 194 && $o <= 223) { 
      $i++; 
      $o2 = ord(mb_substr($string, $i, 1, "ISO-8859-1")); 
      if (!($o2 >= 128 && $o2 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 224) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 160 && $o2 <= 191) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif (($o >= 225 && $o <= 236) || $o == 238 || $o == 239) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 128 && $o2 <= 191) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 237) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $i += 2; 
      if (!($o2 >= 128 && $o2 <= 159) || !($o3 >= 128 && $o3 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 240) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 3; 
      if (!($o2 >= 144 && $o2 <= 191) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o >= 241 && $o <= 243) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 3; 
      if (!($o2 >= 128 && $o2 <= 191) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     elseif ($o == 244) { 
      $o2 = ord(mb_substr($string, $i + 1, 1, "ISO-8859-1")); 
      $o3 = ord(mb_substr($string, $i + 2, 1, "ISO-8859-1")); 
      $o4 = ord(mb_substr($string, $i + 3, 1, "ISO-8859-1")); 
      $i += 5; 
      if (!($o2 >= 128 && $o2 <= 143) || 
       !($o3 >= 128 && $o3 <= 191) || 
       !($o4 >= 128 && $o4 <= 191)) { 
       $ok = 0; 
       break; 
      } 
     } 
     else { 
      $ok = 0; 
      break; 
     } 
    } 

    return $ok; 
} 

是的,它很长。我希望我能正确理解正则表达式的工作原理。也希望它能帮助别人。

在此先感谢!

+0

你为什么要检查这么多特殊值?它可以更简单。 – 2009-08-16 00:38:37

+0

我试着检查W3C正则表达式正在检查的内容。 – liviucmg 2009-08-16 11:16:21

+0

如果字符串不是有效的UTF-8,你打算做什么?乱码数据比没有数据更好? – 2010-01-13 01:44:47

回答

7

您可以随时使用Multibyte String Functions

如果你想使用它了很多,可能在某个时候改变它:

1)首先设置你想在你的配置文件中使用的编码

/* Set internal character encoding to UTF-8 */ 
mb_internal_encoding("UTF-8"); 

2)检查字符串

if(mb_check_encoding($string)) 
{ 
    // do something 
} 

或者,如果你不改变其计划,你总是可以只是把编码直入功能:

if(mb_check_encoding($string, 'UTF-8')) 
{ 
    // do something 
} 
+0

+1,MB字符串函数是为这样一个任务而制作的。 – Boldewyn 2009-08-17 07:12:07

1

您是否试过ereg()而不是preg_match?也许这个没有这个bug,并且你不需要一个潜在的bug解决方法。

+1

我没有尝试ereg,它可能有效,但我并不想使用它,因为:“此函数(ereg)从PHP 5.3.0开始已经被拒绝,并且从PHP 6.0.0开始移除。这个功能非常令人沮丧。“ – liviucmg 2009-08-15 22:22:47

+1

好的,但你有一个机会,preg_match错误在6.0中修复。做一个if(function_exists('ereg'))'并使用preg_match作为后备。 – Boldewyn 2009-08-17 07:10:59

+0

但是,使用其他建议之一。 Chacha102中的一个非常好,而且由于您在示例中使用了mb_substr,所以我猜想,您已经启用了MB字符串函数。不要忘记接受他(或任何其他人)的答案。 – Boldewyn 2009-08-17 12:39:30

1

您应该可以使用iconv来检查有效性。只需尝试将其转换为UTF-16并查看是否出现错误。

0

这里是一个字符串函数基础的解决方案:

http://www.php.net/manual/en/function.mb-detect-encoding.php#85294

<?php 
function is_utf8($str) { 
    $c=0; $b=0; 
    $bits=0; 
    $len=strlen($str); 
    for($i=0; $i<$len; $i++){ 
     $c=ord($str[$i]); 
     if($c > 128){ 
      if(($c >= 254)) return false; 
      elseif($c >= 252) $bits=6; 
      elseif($c >= 248) $bits=5; 
      elseif($c >= 240) $bits=4; 
      elseif($c >= 224) $bits=3; 
      elseif($c >= 192) $bits=2; 
      else return false; 
      if(($i+$bits) > $len) return false; 
      while($bits > 1){ 
       $i++; 
       $b=ord($str[$i]); 
       if($b < 128 || $b > 191) return false; 
       $bits--; 
      } 
     } 
    } 
    return true; 
} 
?> 
2

鉴于在PHP中仍然没有明确的isUtf8()函数,下面是如何根据您的PHP版本在PHP中精确验证UTF-8。

最简单,最向后兼容的方式来正确验证使用功能,如UTF-8还是通过正则表达式:

function isValid($string) 
{ 
    return preg_match(
     '/\A(?> 
      [\x00-\x7F]+      # ASCII 
      | [\xC2-\xDF][\x80-\xBF]    # non-overlong 2-byte 
      | \xE0[\xA0-\xBF][\x80-\xBF]  # excluding overlongs 
      | [\xE1-\xEC\xEE\xEF][\x80-\xBF]{2} # straight 3-byte 
      | \xED[\x80-\x9F][\x80-\xBF]  # excluding surrogates 
      | \xF0[\x90-\xBF][\x80-\xBF]{2}  # planes 1-3 
      | [\xF1-\xF3][\x80-\xBF]{3}   # planes 4-15 
      | \xF4[\x80-\x8F][\x80-\xBF]{2}  # plane 16 
     )*\z/x', 
     $string 
    ) === 1; 
} 

注由W3C提供的正则表达式的两个关键区别。它只使用一次子模式,在第一个字符类后面有一个“+”量词。 PCRE崩溃的问题仍然存在,但其中大部分是由于使用重复捕获子模式造成的。通过将模式转换为仅一次模式并在单个子模式下捕获多个单字节字符,它应该防止PCRE快速耗尽堆栈(并导致段错误)。除非您使用大量多字节字符(数千个范围)验证字符串,否则此正则表达式应该很好地为您服务。

如果您有可用的mbstring扩展名,另一个好的选择是使用mb_check_encoding()。验证UTF-8可以做到简单,如:

function isValid($string) 
{ 
    return mb_check_encoding($string, 'UTF-8') === true; 
} 

但是请注意,如果你使用的PHP版本之前5.4.0,这个功能有一些缺陷,在它的验证:

  • 之前5.4.0该函数接受超出允许的Unicode范围的代码点。这意味着它也允许5和6字节的UTF-8字符。
  • 之前5.3.0该函数接受替代码点作为有效的UTF-8字符。
  • 之前5.2.5由于不按预期工作,该功能完全无法使用。

随着互联网还列出了许多其他的方式来验证UTF-8,我将在这里讨论其中的一些。请注意,在大多数情况下,应避免使用。使用mb_detect_encoding()有时可以验证UTF-8。如果你有至少PHP版本5.4.0 ,但它实际上是与严格的参数,通过工作:

function isValid($string) 
{ 
    return mb_detect_encoding($string, 'UTF-8', true) === 'UTF-8'; 
} 

明白,这不工作之前5.4.0这是非常重要的。该版本之前的版本很有缺陷,因为它只检查无效序列,但允许过长的序列和无效的代码点。另外,如果没有将严格参数设置为true(不实际执行没有严格参数的验证),则不应将其用于此目的。

验证UTF-8的一个很好的方法是通过使用PCRE中的'u'标志。虽然记录不完整,但它也验证了主题字符串。一个例子可以是:

function isValid($string) 
{ 
    return preg_match('//u', $string) === 1; 
} 

每个字符串应该匹配空格局,但“u”标志的使用将只匹配有效UTF-8字符串。但是,除非您至少使用5.5.10。验证如下是有缺陷的:

  • 之前5.5.10,它不识别3个4字节序列作为有效UTF-8。由于它排除了大部分的unicode代码点,这是非常严重的缺陷。
  • 此前5.2.5这也让代理人和代码点超出允许的Unicode空间(例如5和6字节字符)

使用“U”标志的行为确实有一个虽然优势:它是最快速的讨论方法。如果你需要速度,而且你正在运行最新,最好的PHP版本,这种验证方法可能适合你。

验证UTF-8的另一种方法是通过json_encode(),该方法预计输入字符串为UTF-8。它在5.5.0之前不起作用,但在此之后,无效序列返回false而不是字符串。例如:

function isValid($string) 
{ 
    return json_encode($string) !== false; 
} 

然而,我不会推荐依靠这种行为来持续。以前的PHP版本只是在无效序列上产生错误,所以不能保证当前的行为是最终的。