Проверка 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 года. 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; } 

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

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

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

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

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 } 

Учитывая, что до сих пор не существует явной функции isUtf8 () в PHP, вот как 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 по-прежнему сохраняется, но большая ее часть вызвана использованием повторного захвата подшаблона. Путем превращения шаблона в шаблон только один раз и захват нескольких одиночных байтов в одном подшаблоне, это должно помешать 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 функция принимает код за пределами допустимого диапазона 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 – это использовать флаг «u» в PCRE. Хотя он плохо документирован, он также проверяет тему. Примером может быть:

 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 он также позволяет суррогатам и кодам за пределами допустимого пространства Юникода (например, 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; } ?>