正则表达式中的最大十六进制值

时间:2014-01-06 16:31:55

标签: php regex preg-match

不使用u标记,可以使用的十六进制范围是[\x{00}-\x{ff}],但是u标记它会上升到4字节值\x{7fffffff}({{ 1}})。

所以,如果我执行以下代码:

[\x{00000000}-\x{7fffffff}]

会出现此错误

preg_match("/[\x{00000000}-\x{80000000}]+/u", $str, $match);

所以我无法将Warning: preg_match(): Compilation failed: character value in \x{...} sequence is too large 之类的字母与等效的十六进制值匹配。问题不在于如何匹配这些字母,而是如何匹配这些字母&此边界来自f0 a1 83 81修饰符应将字符串视为u

PCRE supports UTF-16 since v8.30

UTF-16

PCRE版本,PHP 5.3.24 - 5.3.28,5.4.14 - 5.5.7:

echo PCRE_VERSION;

PCRE版本,PHP 5.3.19 - 5.3.23,5.4.9 - 5.4.13:

8.32 2012-11-30

http://3v4l.org/CrPZ8

5 个答案:

答案 0 :(得分:8)

Unicode和UTF-8,UTF-16,UTF-32编码

Unicode是一个字符集,它指定从字符到代码点的映射,字符编码(UTF-8,UTF-16,UTF-32)指定如何存储Unicode代码点。

在Unicode中,字符映射到单个代码点,但根据编码方式的不同,它可以有不同的表示形式。

我不想重新讨论这个讨论,所以如果您仍然不清楚这一点,请阅读The Absolute Minimum Every Software Developer Absolutely, Positively Must Know About Unicode and Character Sets (No Excuses!)

使用问题中的示例,会映射到代码点U+210C1,但可以在UTF-8中编码为F0 A1 83 81,在UTF中编码为D844 DCC1 16和UTF-32中的000210C1

确切地说,上面的示例显示了如何将代码点映射到代码单元(字符编码形式)。代码单元如何映射到八位位组序列是另一回事。见Unicode encoding model

PCRE 8位,16位和32位库

由于PHP还没有采用PCRE2(版本10.10),所引用的文本来自原始PCRE的文档。

支持16位和32位库

PCRE包括对版本8.30中的16位字符串和版本8.32中的32位字符串的支持,以及默认的8位库。

  

除了支持8位字符串外,PCRE还支持16位字符串(来自版本8.30)和32位字符串(来自版本8.32),通过两个额外的库。它们可以和8位库一样构建,也可以代替8位库。 [...]

8位,16位,32位

的含义

这里的8位,16位和32位是指数据单元(代码单元)。

  

本文档中对字节和UTF-8的引用应在使用16位库时读取为16位数据单元和UTF-16的引用,或在使用32时读取为32位数据单元和UTF-32比特库,除非另有说明。有关16位和32位库的具体差异的更多详细信息,请参见pcre16和pcre32页面。

这意味着8位/ 16位/ 32位库需要模式和输入字符串为8位/ 16位/ 32位数据单元或有效UTF-8 / UTF的序列-16 / UTF-32字符串。

用于不同宽度的数据单元的不同API

PCRE为8位,16位和32位库提供了3组相同的API,分别由前缀(pcre_pcre16_pcre_32区分。)< / p>

  

16位和32位函数的运行方式与它们的8位函数相同;他们只是为他们的参数和结果使用不同的数据类型,他们的名字以pcre16_pcre32_而不是pcre_开头。对于名称中包含UTF8的每个选项(例如,PCRE_UTF8),都有相应的16位和32位名称,UTF8分别由UTF16或UTF32替换。这个设施实际上只是化妆品; 16位和32位选项名称定义相同的位值。

在PCRE2中,a similar function naming convention is used,其中8位/ 16位/ 32位函数分别具有_8_16_32后缀。只使用一个代码单元宽度的应用程序可以定义PCRE2_CODE_UNIT_WIDTH以使用没有后缀的函数的通用名称。

UTF模式与非UTF模式

设置UTF模式时(通过模式内选项(*UTF)(*UTF8)(*UTF16)(*UTF32) 1 或编译选项{ {1}},PCRE_UTF8PCRE_UTF16),所有数据单元序列都被解释为Unicode字符序列,其中包含从U + 0000到U + 10FFFF的所有代码点,代理项和BOM。

1 内嵌式选项PCRE_UTF32(*UTF8)(*UTF16)仅在相应的库中可用。您不能在8位库中使用(*UTF32),也不能在任何不匹配的组合中使用(*UTF16),因为它根本没有意义。 (*UTF)在所有库中都可用,并提供了一种在模式中指定UTF模式的可移植方式。

在UTF模式下,模式(数据单元序列)通过将序列解码为UTF-,将解释验证作为Unicode代码点序列8 / UTF-16 / UTF-32数据(取决于所使用的API),在编译之前。在匹配过程中,输入字符串也被解释并可选地验证为Unicode代码点序列。在此模式下,字符类匹配一个有效的Unicode代码点。

另一方面,当未设置UTF模式(非UTF模式)时,所有操作都直接处理数据单元序列。在此模式下,字符类与一个数据单元匹配,除了可以存储在单个数据单元中的最大值之外,对数据单元的值没有限制。此模式可用于匹配二进制数据中的结构。但是,在处理Unicode字符时不要使用此模式,除非您使用ASCII并且忽略其他语言。

  

对字符值的限制

     

使用八进制或十六进制数字指定的字符仅限于某些值,如下所示:

8-bit non-UTF mode    less than 0x100
8-bit UTF-8 mode      less than 0x10ffff and a valid codepoint
16-bit non-UTF mode   less than 0x10000
16-bit UTF-16 mode    less than 0x10ffff and a valid codepoint
32-bit non-UTF mode   less than 0x100000000
32-bit UTF-32 mode    less than 0x10ffff and a valid codepoint
     

无效的Unicode代码点是范围0xd800到0xdfff(所谓的&#34;代理&#34;代码点)和0xffef。

PHP和PCRE

PHP中的PCRE functionsa wrapper which translates PHP-specific flags and calls into PCRE API实现(如PHP 5.6.10分支中所示)。

源代码调用PCRE 8位库API(pcre_),因此传递到preg_函数的任何字符串都被解释为8位数据单元(字节)的序列。因此,即使构建了PCRE 16位和32位库,也无法通过PHP端的API访问它们。

因此,PHP中的PCRE函数需要:

  • ...非UTF模式下的字节数组(默认),库读取8位&#34;字符&#34;并编译以匹配8位&#34;字符&#34;。
  • 的字符串
  • ...包含UTF-8编码的Unicode字符串的字节数组,该库以Unicode字符读取并编译以匹配UTF-8 Unicode字符串。

这解释了问题中的行为:

  • 在非UTF模式下(没有u标志),十六进制正则表达式转义序列中的最大值为FF(如[\x{00}-\x{ff}]所示)
  • 在UTF模式下,十六进制正则表达式转义序列中超出0x10ffff(如\x{7fffffff})的任何值都是无意义的。

示例代码

此示例代码演示:

  • PHP字符串只是字节数组,并且不了解编码。
  • PCRE功能中UTF模式与非UTF模式之间的差异。
  • PCRE函数调用8位库
// NOTE: Save this file as UTF-8

// Take note of double-quoted string literal, which supports escape sequence and variable expansion
// The code won't work correctly with single-quoted string literal, which has restrictive escape syntax
// Read more at: https://php.net/language.types.string
$str_1 = "\xf0\xa1\x83\x81\xf0\xa1\x83\x81";
$str_2 = "";
$str_3 = "\xf0\xa1\x83\x81\x81\x81\x81\x81\x81";

echo ($str_1 === $str_2)."\n";

var_dump($str_3);

// Test 1a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_1, $match);
print_r($match); // Only match 

// Test 1b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_2, $match);
print_r($match); // Only match  (same as 1a)

// Test 1c
$match = null;
preg_match("/\xf0\xa1\x83\x81+/", $str_3, $match);
print_r($match); // Match  and the five bytes of 0x81

// Test 2a
$match = null;
preg_match("/+/", $str_1, $match);
print_r($match); // Only match  (same as 1a)

// Test 2b
$match = null;
preg_match("/+/", $str_2, $match);
print_r($match); // Only match  (same as 1b and 2a)

// Test 2c
$match = null;
preg_match("/+/", $str_3, $match);
print_r($match); // Match  and the five bytes of 0x81 (same as 1c)

// Test 3a
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_1, $match);
print_r($match); // Match two 

// Test 3b
$match = null;
preg_match("/\xf0\xa1\x83\x81+/u", $str_2, $match);
print_r($match); // Match two  (same as 3a)

// Test 4a
$match = null;
preg_match("/+/u", $str_1, $match);
print_r($match); // Match two  (same as 3a)

// Test 4b
$match = null;
preg_match("/+/u", $str_2, $match);
print_r($match); // Match two  (same as 3b and 4a)

由于PHP字符串只是一个字节数组,只要文件在某些​​与ASCII兼容的编码中正确保存,PHP就会很乐意读取这些字节,而无需关心它最初的编码。程序员是完全负责的用于正确编码和解码字符串。

由于上述原因,如果您以UTF-8编码保存上面的文件,您会看到$str_1$str_2是相同的字符串。 $str_1从转义序列解码,而$str_2从源代码逐字读取。因此,"/\xf0\xa1\x83\x81+/u""/+/u"下面是相同的字符串(同样适用于"/\xf0\xa1\x83\x81+/""/+/")。

上面的示例清楚地显示了UTF模式和非UTF模式之间的区别:

  • "/+/"被视为一系列字符F0 A1 83 81 2B,其中&#34;字符&#34;是一个字节。因此,生成的正则表达式匹配序列F0 A1 83,后跟字节81重复一次或多次。
  • 验证
  • "/+/u"并将其解释为UTF-8字符序列U+210C1 U+002B。因此,生成的正则表达式匹配UTF-8字符串中重复一次或多次的代码点U+210C1

匹配Unicode字符

除非输入包含其他二进制数据,否则强烈建议始终打开u模式。该模式可以访问所有工具以正确匹配Unicode字符,并且输入和模式都被验证为有效的UTF字符串。

再次,以为例,上面的示例显示了两种指定正则表达式的方法:

"/\xf0\xa1\x83\x81+/u"
"/+/u"

第一种方法不适用于单引号字符串 - 由于\x转义序列在单引号中无法识别,因此库将收到字符串\xf0\xa1\x83\x81+,它与UTF模式将匹配U+00F0 U+00A1 U+0083,然后重复U+0081一次或多次。除此之外,它也让下一个阅读代码的人感到困惑:他们怎么知道它是一个或多个重复的单个Unicode字符?

第二种方法运行良好,它甚至可以与单引号字符串一起使用,但您需要以UTF-8编码保存文件,尤其是像ÿ这样的字符的情况,因为该字符也是在单字节编码中有效。如果要匹配单个字符或一系列字符,此方法是一个选项。但是,作为角色范围的终点,可能不清楚您要匹配的是什么。比较a-zA-Z0-9א-ת,而不是一-龥(与大多数CJK Unified Ideographs block (4E00–9FFF)相匹配,但未分配的代码点除外结束)或一-十(这是一个不正确的尝试匹配中文字符的数字从1到10)。

第三种方法是直接在十六进制转义中指定代码点:

"/\x{210C1}/u"
'/\x{210C1}/u'

当文件以任何与ASCII兼容的编码保存时,可以使用单引号和双引号字符串,并且在字符范围内提供清晰的代码点。此方法的缺点是不知道字符的外观,并且在指定Unicode字符序列时也很难读取。

答案 1 :(得分:3)

  

所以我无法匹配像f0 a1的等效十六进制值这样的字母   83 81.问题不在于如何匹配这些字母,而是如何匹配这些字母   范围&amp;这个边界来自于u修饰符应该将字符串视为   UTF-16

你混合了两个导致这种混乱的概念。

F0 A1 83 81不是角色的十六进制值。这就是方法 UTF-8对字节流中该字符的代码点进行编码。

PHP支持\x{}模式的UTF-16代码点是正确的,但{}中的值表示UTF-16代码点,而不是实际使用的字节数编码字节流中的给定字符。

因此,\x{}可以使用的最大可能值实际为10FFFF

要与PHP匹配,您需要使用它的代码点,@ minitech在其评论中建议\x{0210c1}

"Validity of strings"PCRE documentation部分引用的进一步说明。

  

在进行任何其他处理之前,将检查整个字符串。   除了检查字符串的格式外,还有一个检查   确保所有代码点都位于U + 0到U + 10FFFF的范围内,   不包括代理区域。所谓的“非字符”代码   不排除积分,因为Unicode更正#9表明了这一点   他们不应该这样。

     

Unicode的“代理区域”中的字符保留供使用   UTF-16,它们成对使用,用值编码代码点   大于0xFFFF。由UTF-16对编码的代码点   可单独使用UTF-8和UTF-32编码。 (在   换句话说,整个替代品是UTF-16的软糖   不幸的是混淆了UTF-8和UTF-32。)

答案 2 :(得分:1)

我不确定php,但代码点上确实没有调控器 所以只有大约110万个有效的东西并不重要 这在任何时候都可能发生变化,但它并非真正取决于发动机 强制执行。保留的cp是有效范围内的孔,
在有效范围内有代理人,原因是无穷无尽的 除了字号以外没有其他限制。

对于UTF-32,你不能超过31位,因为32是符号位 0x00000000 - 0x7FFFFFFF

有意义,因为unsigned int作为数据类型是32位硬件寄存器的自然大小。

对于UTF-16,甚至更真实的是你可以看到屏蔽到16位的相同限制。 位32仍然是将0x0000 - 0xFFFF作为有效范围的符号位。

通常,如果您使用支持ICU的引擎,您应该可以使用它,
它将源和正则表达式转换为UTF-32。 Boost Regex就是这样一个引擎。

编辑:

关于UTF-16

我猜当Unicode超过16位时,他们在16位范围内为代理对打了一个洞。但它在这对之间只留下了20个总位数。

每个代理中的10位,其他6位用于确定hi或lo 看起来这样会使Unicode人员的限制为20位+额外的0xFFFF四舍五入到总共0x10FFFF的代码点,并且有无法使用的漏洞。

能够将所有代码点转换为不同的编码(8/16/32) 实际上必须是可兑换的。因此永远向后兼容的20位是
他们陷入了陷阱,但现在必须忍受。

无论如何,正则表达式引擎不会很快实施此限制,可能永远不会。 就代理而言,它们是 hole ,并且不能在模式之间转换格式错误的文字代理。这仅适用于转换期间的文字编码字符,而不是一个十六进制表示。例如,它很容易以UTF-16(仅)模式搜索未配对的代理人的文本,甚至是配对的文本。

但我认为正则表达式引擎并不真正关心漏洞或限制,他们只关心主题字符串的模式。不,引擎不会说:
&#39;嘿等等,模式为UTF-16我最好将\x{210C1}转换为\x{D844}\x{DCC1}。等等,如果我这样做了,如果量化\x{210C1}+,我该怎么办,开始在它周围注入正则表达式构造?更糟糕的是,如果它在课堂上[\x{210C1}]怎么办? Nah ..更好地将其限制为\x{FFFF}

我使用的一些方便的花花公子,伪代码代理转换:

 Definitions:
 ====================
 10-bits
  3FF = 000000  1111111111

 Hi Surrogate
 D800 = 110110  0000000000
 DBFF = 110110  1111111111 

 Lo Surrogate
 DC00 = 110111  0000000000
 DFFF = 110111  1111111111


 Conversions:
 ====================
 UTF-16 Surrogates to UTF-32
 if ( TESTFOR_SURROGATE_PAIR(hi,lo) )
 {
    u32Out = 0x10000 + (  ((hi & 0x3FF) << 10) | (lo & 0x3FF)  );
 }

 UTF-32 to UTF-16 Surrogates
 if ( u32In >= 0x10000)
 {
    u32In -= 0x10000;
    hi = (0xD800 + ((u32In & 0xFFC00) >> 10));
    lo = (0xDC00 + (u32In & 0x3FF));
 }

 Macro's:
 ====================
 #define TESTFOR_SURROGATE_HI(hs) (((hs & 0xFC00)) == 0xD800 )
 #define TESTFOR_SURROGATE_LO(ls) (((ls & 0xFC00)) == 0xDC00 )
 #define TESTFOR_SURROGATE_PAIR(hs,ls) ( (((hs & 0xFC00)) == 0xD800) && (((ls & 0xFC00)) == 0xDC00) )
 //
 #define PTR_TESTFOR_SURROGATE_HI(ptr) (((*ptr & 0xFC00)) == 0xD800 )
 #define PTR_TESTFOR_SURROGATE_LO(ptr) (((*ptr & 0xFC00)) == 0xDC00 )
 #define PTR_TESTFOR_SURROGATE_PAIR(ptr) ( (((*ptr & 0xFC00)) == 0xD800) && (((*(ptr+1) & 0xFC00)) == 0xDC00) )

答案 3 :(得分:1)

正如minitech在第一条评论中建议的那样,你必须使用代码点 - 对于这个角色,它是\x{210C1}。这也是UTF-32中的编码形式。 F0 AF AB BF是UTF-8编码序列(请参阅http://www.unicode.org/cgi-bin/GetUnihanData.pl?codepoint=210C1)。

有一些版本的PCRE,您可以使用最多\x{7FFFFFFF}的值。但我真的不知道可以与之相匹配的东西。

引用http://www.pcre.org/pcre.txt

  

在UTF-16模式下,字符代码为Unicode,范围 0到   0x10ffff ,但0xd800到0xdfff范围内的值除外   因为那些是&#34;代理&#34;成对使用的值进行编码   值大于0xffff。   

[...]

  在UTF-32模式下,字符代码为Unicode,范围 0到   0x10ffff ,但0xd800到0xdfff范围内的值除外   因为那些是&#34;代理&#34;在UTF-32中形成错误的值。

0x10ffff是您可以用来匹配角色的最大值(这是我从中提取的内容)。 0x10ffff目前也是unicode标准中定义的最大代码点(请参阅What are some of the differences between the UTFs?) - 因此上面的每个值都没有任何意义(或者我只是没有得到它)​​......

答案 4 :(得分:-1)

“但想知道正则表达式中的最大十六进制边界”: *在所有utf模式下:0x10ffff *原生8-bt模式:0xff *本机16位模式:0xffff *本机32位模式:0x1fffffff