Регулярное выражение для определения недопустимой строки UTF-8

В PHP мы можем использовать mb_check_encoding() чтобы определить, действительно ли строка UTF-8. Но это не переносное решение, так как требуется, чтобы расширение mbstring было скомпилировано и включено. Кроме того, он не укажет нам, какой символ недействителен.

Есть ли регулярное выражение (или другой другой 100% переносной метод), который может соответствовать недопустимым байтам UTF-8 в данной строке. Таким образом, эти байты могут быть заменены при необходимости (сохранение двоичной информации, например, при создании тестового выходного XML-файла, который включает двоичные данные). Поэтому преобразование символов в UTF-8 потеряло бы информацию. Итак, мы можем конвертировать:

 "foo" . chr(128) . chr(255) 

В

 "foo<128><255>" 

Поэтому просто «обнаруживая», что строка недостаточно хороша, нам нужно было бы определить, какие символы недействительны.

Вы можете использовать это регулярное выражение PCRE для проверки правильности UTF8 в строке. Если регулярное выражение совпадает, строка содержит недопустимые последовательности байтов. Он переносится на 100%, поскольку он не зависит от компиляции PCRE_UTF8.

 $regex = '/( [\xC0-\xC1] # Invalid UTF-8 Bytes | [\xF5-\xFF] # Invalid UTF-8 Bytes | \xE0[\x80-\x9F] # Overlong encoding of prior code point | \xF0[\x80-\x8F] # Overlong encoding of prior code point | [\xC2-\xDF](?![\x80-\xBF]) # Invalid UTF-8 Sequence Start | [\xE0-\xEF](?![\x80-\xBF]{2}) # Invalid UTF-8 Sequence Start | [\xF0-\xF4](?![\x80-\xBF]{3}) # Invalid UTF-8 Sequence Start | (?<=[\x0-\x7F\xF5-\xFF])[\x80-\xBF] # Invalid UTF-8 Sequence Middle | (?<![\xC2-\xDF]|[\xE0-\xEF]|[\xE0-\xEF][\x80-\xBF]|[\xF0-\xF4]|[\xF0-\xF4][\x80-\xBF]|[\xF0-\xF4][\x80-\xBF]{2})[\x80-\xBF] # Overlong Sequence | (?<=[\xE0-\xEF])[\x80-\xBF](?![\x80-\xBF]) # Short 3 byte sequence | (?<=[\xF0-\xF4])[\x80-\xBF](?![\x80-\xBF]{2}) # Short 4 byte sequence | (?<=[\xF0-\xF4][\x80-\xBF])[\x80-\xBF](?![\x80-\xBF]) # Short 4 byte sequence (2) )/x'; 

Мы можем протестировать его, создав несколько вариантов текста.

 // Overlong encoding of code point 0 $text = chr(0xC0) . chr(0x80); var_dump(preg_match($regex, $text)); // int(1) // Overlong encoding of 5 byte encoding $text = chr(0xF8) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80); var_dump(preg_match($regex, $text)); // int(1) // Overlong encoding of 6 byte encoding $text = chr(0xFC) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80) . chr(0x80); var_dump(preg_match($regex, $text)); // int(1) // High code-point without trailing characters $text = chr(0xD0) . chr(0x01); var_dump(preg_match($regex, $text)); // int(1) 

и т.д…

Фактически, поскольку это соответствует недопустимым байтам, вы можете использовать его в preg_replace для их замены:

 preg_replace($regex, '', $text); // Remove all invalid UTF-8 code-points 

Я поставил это здесь для полноты:

Предполагая, что PHP скомпилирован с помощью PCRE, он чаще всего также включен с UTF-8. Так, как явно задано в вопросе, это очень простое регулярное выражение может обнаруживать недопустимые строки UTF-8, потому что они не будут совпадать:

 preg_match('//u', $string); 

Затем вы можете утверждать, что модификатор u (PCRE_UTF8) не всегда доступен, и true, это может произойти, как показывает этот вопрос:

  • Каким должен быть флаг preg_match_all u ?

Однако в моем практическом разработчике это никогда не было проблемой. Более того, расширение PCRE вообще недоступно, что сделает любой ответ, содержащий pcre, бесполезным (даже моим здесь). Но чаще всего этот вопрос был скорее вопросом прошлого на сегодняшний день минус несколько лет.

Более длинный ответ, подобный этому, был дан в некотором дублирующем вопросе:

  • Как обнаружить неверную строку utf-8 в PHP?

Поэтому я думаю, что этот вопрос должен подчеркнуть больше преимуществ, предлагаемых предлагаемым кораблем.

W3C имеет страницу (называемую многоязычной кодировкой ), в которой указано следующее регулярное выражение Perl, которое соответствует допустимой строке UTF-8 .

(Обратите внимание, что это противоположно регулярному выражению, указанному в другом ответе на этот вопрос SO, который соответствует недопустимой строке UTF-8.)

 # Returns true if $field is UTF-8, and false otherwise. $field =~ m/\A( [\x09\x0A\x0D\x20-\x7E] # 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;