Проверка 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;
}
Да, это очень долго. Надеюсь, я правильно понял, как работает это регулярное выражение. Также надеюсь, что это поможет другим.
Заранее спасибо!
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 по-прежнему заключается в регулярном выражении с использованием таких функций, как:
Обратите внимание на два ключевых отличия регулярного выражения, предлагаемого W3C. он использует только один раз подшаблон и имеет Квантор ' + ' после первого класса символов. Проблема в нестандартном сбой по-прежнему сохраняется, но большая его часть вызвана использованием повторяющегося захвата подшаблона. Превращая шаблон в шаблон только один раз и захватывая несколько однобайтовых символов в одном подшаблоне, он должен предотвратить быстрый выход PCRE из стека (и вызвать segfault). Если вы не проверяете строки с большим количеством многобайтовых символов (в диапазоне тысяч), это регулярное выражение должно хорошо служить вам.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; }Другой хорошей альтернативой является использование
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 вместо строки. Например:Однако я бы не рекомендовал полагаться на это поведение в течение длительного времени. Предыдущие версии PHP просто выдавали ошибку на недопустимых последовательностях, поэтому нет никакой гарантии, что текущее поведение является окончательным.function isValid($string) { return json_encode($string) !== false; }
Вы должны иметь возможность использовать 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