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

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

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

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

В

"foo<128><255>"

Итак, просто "обнаруживая", что строка недостаточно хороша, нам нужно будет определить, какие символы недопустимы.

4b9b3361

Вы можете использовать это регулярное выражение 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
34
ответ дан 29 июля '12 в 15:53
источник

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

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

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

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

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

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

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

9
ответ дан 29 июля '12 в 17:02
источник

В 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;
3
ответ дан 29 дек. '13 в 20:09
источник