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

Posted

技术标签:

【中文标题】正则表达式检测无效的 UTF-8 字符串【英文标题】:Regex to detect invalid UTF-8 string 【发布时间】:2012-07-27 09:46:39 【问题描述】:

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

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

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

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

进入

"foo<128><255>"

所以只是“检测”字符串不够好,我们需要能够检测哪些字符是无效的。

【问题讨论】:

【参考方案1】:

这适用于检测 Unicode 字符、链接表情符号、俄语或中文:

private function has_unicode($string)

    $pattern = '/^.*[^\x00-\x00FF]+.*$/u';
    return preg_match($pattern, $string) ? true : false;

【讨论】:

【参考方案2】:

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

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

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

What is the preg_match_all u flag dependent on?

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

在不知何故重复的问题中给出了与此类似的更冗长的答案:

How can I detect a malformed UTF-8 string in PHP?

所以我认为这个问题应该突出建议的答案所带来的更多好处。

【讨论】:

可能是 PHP apache 模块和 apache 没有编译 PCRE UTF-8 支持?【参考方案3】:

您可以使用此 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

【讨论】:

@hakre:除了它取决于编译时选项(PCRE_UTF8)。所以它不是便携式的...... PCRE根本没有编译进去怎么办? @hakre 我认为在配置时不能禁用 pcre? @Jack:这是一个扩展,你可以在没有 PCRE 扩展的情况下编译 PHP。 github.com/php/php-src/tree/PHP-5.4/ext/pcre 和 --without-pcre-regex 切换 也许值得更改最后的建议以删除无效序列并用 U+FFFD "\xEF\xBF\xBD" 替换它们,请参阅 unicode.org/reports/tr36/#Ill-Formed_Subsequences【参考方案4】:

The W3C has a page (titled Multilingual form encoding) 列出以下 Perl 正则表达式,该表达式匹配有效的 UTF-8 字符串

(请注意,这与此 SO 问题的另一个答案中列出的正则表达式相反,它匹配 invalid 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;

【讨论】:

这个正则表达式不匹配有效的 ASCII(控制字符)[\x09\x0A\x0D\x20-\x7E] 应该是 [\x00-\x7F]

以上是关于正则表达式检测无效的 UTF-8 字符串的主要内容,如果未能解决你的问题,请参考以下文章

正则表达式查找带空格的整数中的无效字符

PCI 合规性正则表达式检测带空格的模式

在有效字符后输入无效字符时,正则表达式无法工作?

Unicode 正则表达式;无效的 XML 字符

用java正则表达式检测字符串中是不是含有某字符

C++ 正则检测字串,提取数字以及字符