簡體   English   中英

PHP中的UTF-8驗證,不使用preg_match()

[英]UTF-8 validation in PHP without using 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中是否合適,但我想知道我所做的功能是否正確。 這里是:

編輯[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;
}

是的,這很長。 我希望我已經正確理解了正則表達式是如何工作的。 也希望它對別人有所幫助。

提前致謝!

您始終可以使用多字節字符串函數

如果你想要使用它很多,並可能在某個時候改變它:

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
}

鑒於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;
}

但請注意,如果您使用的是5.4.0之前的PHP版本,則此函數在其驗證中存在一些缺陷:

  • 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 ,它確實可以使用strict參數:

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

了解這在5.4.0之前不起作用是非常重要的。 它在該版本之前是非常有缺陷的,因為它只檢查無效序列但允許超長序列和無效代碼點。 此外,如果沒有將strict參數設置為true,則不應將其用於此目的(如果沒有strict參數,它實際上不會進行驗證)。

驗證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版本只會在無效序列上產生錯誤,因此無法保證當前行為是最終的。

你嘗試過ereg()而不是preg_match嗎? 也許這個沒有那個bug,你不需要一個潛在的錯誤解決方法。

您應該能夠使用iconv來檢查有效性。 只需嘗試將其轉換為UTF-16,看看是否收到錯誤。

這是一個基於字符串函數的解決方案:

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;
}
?>

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM