2009-08-15 38 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版本只是在無效序列上產生錯誤,所以不能保證當前的行爲是最終的。