用于检测无效 UTF-8 字符串的正则表达式

2022-08-30 19:36:40

在 PHP 中,我们可以使用 mb_check_encoding() 来确定字符串是否有效 UTF-8。但这不是一个可移植的解决方案,因为它需要编译和启用mbstring扩展。此外,它不会告诉我们哪个字符无效。

是否有正则表达式(或其他 100% 可移植方法)可以匹配给定字符串中无效的 UTF-8 字节?

这样,可以根据需要替换这些字节(保留二进制信息,例如在生成包含二进制数据的测试输出 XML 文件时)。因此,将字符转换为 UTF-8 会丢失信息。因此,我们可能想要转换:

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

"foo<128><255>"

因此,仅仅“检测”字符串不够好,我们就需要能够检测到哪些字符无效。


答案 1

您可以使用此 PCRE 正则表达式检查字符串中是否存在有效的 UTF-8。如果正则表达式匹配,则该字符串包含无效的字节序列。它是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
    | (?<=[\x00-\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

答案 2

假设PHP是用PCRE编译的,那么它通常也用UTF-8启用。因此,正如问题中明确要求的那样,这个非常简单的正则表达式可以检测无效的 UTF-8 字符串,因为这些字符串不匹配:

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

然后,您可以争辩说修饰符(PCRE_UTF8)并不总是可用的,并且确实如此,如以下问题所示,可能会发生这种情况:u

但是,在我实际的开发人员生活中,这从来都不是问题。PCRE扩展根本不可用,这更像是一个问题,这将使任何包含PCRE的答案变得无用(甚至是我在这里的答案)。但大多数情况下,这个问题更像是过去的问题,截至今天减去几年。

在以某种方式重复的问题中给出了与此类似的更冗长的答案:

因此,我认为这个问题应该突出建议答案附带的更多好处。


推荐