Проверка UTF-8 в PHP без использования preg match()



Мне нужно проверить некоторые входные данные пользователя, закодированные в UTF-8. Многие рекомендовали использовать следующий код:



preg_match('/A(
[x09x0Ax0Dx20-x7E]
| [xC2-xDF][x80-xBF]
| xE0[xA0-xBF][x80-xBF]
| [xE1-xECxEExEF][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 года. 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;
}


Да, это очень долго. Надеюсь, я правильно понял, как работает это регулярное выражение. Также надеюсь, что это поможет другим.



Заранее спасибо!

734   5  

5 ответов:

Вы всегда можете использовать многобайтовые строковые функции:

Если вы хотите использовать его много и, возможно, изменить его когда-нибудь:

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 (), вот как UTF-8 может быть точно проверена в PHP в зависимости от вашей версии PHP.

Самый простой и наиболее обратно совместимый способ правильной проверки 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 из стека (и вызвать segfault). Если вы не проверяете строки с большим количеством многобайтовых символов (в диапазоне тысяч), это регулярное выражение должно хорошо служить вам.

Другой хорошей альтернативой является использование mb_check_encoding(), Если у вас есть mbstring расширение доступно. Проверка UTF-8 может быть выполнена следующим образом:

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

Обратите внимание, однако, что если вы используете версию PHP до 5.4.0, эта функция имеет некоторые недостатки в ее проверке:

  • до 5.4.0 функция принимает кодовую точку за пределами допустимого диапазона Юникода. Это означает, что он также позволяет использовать 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 - это использование флага " u " в PCRE. Хотя и плохо документированный, он также подтверждает предмет строка. Примером может быть:

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

Каждая строка должна соответствовать пустому шаблону, но использование флага 'u' будет соответствовать только допустимым строкам UTF-8. Однако, если вы не используете по крайней мере 5.5.10. Проверка имеет следующие недостатки:

  • до 5.5.10, он не распознает 3 и 4 байтовые последовательности как допустимые UTF-8. Поскольку он исключает большинство кодовых точек Юникода, это довольно серьезный недостаток.
  • до 5.2.5 он также допускает суррогаты и кодовые точки за пределами допустимого пространства Юникода (например, 5 и 6 байтовых символов)

Использование поведения флага ' u ' имеет одно преимущество: это самый быстрый из обсуждаемых методов. Если вам нужна скорость и вы используете самую последнюю и лучшую версию PHP, этот метод проверки может быть для вас.

Один из дополнительных способов проверки для UTF-8-через json_encode(), который ожидает, что входные строки будут в UTF-8. Это не работает до 5.5.0, но после этого он стал инвалидом. последовательности возвращают false вместо строки. Например:
function isValid($string)
{
    return json_encode($string) !== false;
}
Однако я бы не рекомендовал полагаться на это поведение в течение длительного времени. Предыдущие версии PHP просто выдавали ошибку на недопустимых последовательностях, поэтому нет никакой гарантии, что текущее поведение является окончательным.

Вы должны иметь возможность использовать iconv для проверки валидности. Просто попробуйте преобразовать его в UTF-16 и посмотрите, не появится ли ошибка.

Вы пробовали ereg() вместо preg_match? Возможно, у этого нет этой ошибки, и вам не нужен потенциально глючный обходной путь.

Вот решение, основанное на строковых функциях:

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

Comments

    Ничего не найдено.