rowbot / idna
UTS#46 Unicode IDNA 兼容处理实现。
Requires
- php: >=7.1
- rowbot/punycode: ^1.0
- symfony/polyfill-intl-normalizer: ^1.18
Requires (Dev)
- guzzlehttp/guzzle: ^6.5 || ^7.0
- phpstan/phpstan: ^1.2
- phpstan/phpstan-deprecation-rules: ^1.0
- phpstan/phpstan-strict-rules: ^1.0
- phpunit/phpunit: ^7.0 || ^8.0 || ^9.0
- squizlabs/php_codesniffer: ^3.5.1
- symfony/cache: ^4.3 || ^5.0
README
这是一个完全符合 UTS#46 的实现,通常称为 Unicode IDNA 兼容处理。您可以在规范中的 第7节 IDNA 对比 中了解更多关于 IDNA2003、IDNA2008 和 UTS#46 之间的区别。
此库当前支持 Unicode 15.0.0,并实现了 IDNA 兼容处理的 15.0.0 版本,修订版 29。它具有使用 Unicode 11.0.0 到 15.0.0 的能力。虽然此库可能支持低于 11.0.0 的 Unicode 版本,但由于从 11.0.0 开始更改了 Unicode 测试文件的格式,因此尚未对这些低于 11.0.0 的 Unicode 版本进行测试。
需求
- PHP 7.1+
rowbot/punycode
symfony/polyfill-intl-normalizer
安装
composer require rowbot/idna
API
Idna::UNICODE_VERSION
用作字符串的数据文件的 Unicode 版本。
Idna::toAscii(string $domain, array $options = []): IdnaResult
将域名转换为 ASCII 形式。在执行 ASCII 转换时记录任何错误时,转换被认为失败,返回的域名字符串被认为是“垃圾”。您如何处理该结果完全取决于您。
toAscii 参数
-
$domain - 要转换为 ASCII 的域名。
-
$options - 用于自定义转换行为的选项数组。可能的选项包括
"CheckBidi"
- 检查域名字符串中是否存在双向字符错误。默认为 true。"CheckHyphens"
- 检查域名字符串中连字符的位置。默认为 true。"CheckJoiners"
- 检查域名字符串中连接代码点的错误。默认为 true。"UseSTD3ASCIIRules"
- 禁用除[a-zA-Z0-9-]
之外的 ASCII 字符。默认为 true。"Transitional_Processing"
- 是否使用过渡或非过渡处理。启用时,处理方式更类似于 IDNA2003;禁用时,处理方式类似于 IDNA2008。默认为 false,这意味着默认使用非过渡处理。"VerifyDnsLength"
- 验证域名字符串及其各个标签的长度。默认为 true。
注意: 所有选项都是区分大小写的。
use Rowbot\Idna\Idna; $result = Idna::toAscii('x-.xn--nxa'); // You must not use an ASCII domain that has errors. if ($result->hasErrors()) { throw new \Exception(); } echo $result->getDomain(); // x-.xn--nxa
Idna::toUnicode(string $domain, array $options = []): IdnaResult
将域名转换为 Unicode 形式。与 toAscii 转换不同,toUnicode 没有失败的概念。这意味着您始终可以使用返回的字符串。但是,当记录错误时,决定如何处理返回的域名字符串完全取决于您。
-
$domain - 要转换为 UTF-8 的域名。
-
$options - 用于自定义转换行为的选项数组。可能的选项包括
"CheckBidi"
- 检查域名字符串中是否存在双向字符错误。默认为 true。"CheckHyphens"
- 检查域名字符串中连字符的位置。默认为 true。"CheckJoiners"
- 检查域名字符串中连接代码点的错误。默认为 true。"UseSTD3ASCIIRules"
- 禁用除[a-zA-Z0-9-]
之外的 ASCII 字符。默认为 true。"Transitional_Processing"
- 是否使用过渡或非过渡处理。启用时,处理方式更类似于 IDNA2003;禁用时,处理方式类似于 IDNA2008。默认为 false,这意味着默认使用非过渡处理。
注意: 所有选项都是区分大小写的。
注意:
"VerifyDnsLength"
不是一个有效的选项。use Rowbot\Idna\Idna; $result = Idna::toUnicode('xn---with-SUPER-MONKEYS-pc58ag80a8qai00g7n9n.com'); echo $result->getDomain(); // 安室奈美恵-with-super-monkeys.com
IdnaResult 对象
成员
IdnaResult::getDomain(): string
返回转换后的域名字符串。
IdnaResult::getErrors(): int
返回一个表示在处理输入域名字符串时记录的所有错误的位掩码。
IdnaResult::hasError(int $error): bool
返回是否记录了特定的错误。
IdnaResult::hasErrors(): bool
返回是否在处理输入域名字符串时记录了错误。
IdnaResult::isTransitionalDifferent(): bool
如果输入的域名包含一个状态为"deviation"
的码点,则返回true
。这个状态表示,与IDNA2008相比,IDNA2003中对这些码点的处理方式不同。截至编写时,只有4个码点具有此状态。它们是U+00DF,U+03C2,U+200C和U+200D。
错误代码
-
Idna::ERROR_EMPTY_LABEL
域名或其标签之一为空字符串。
-
Idna::ERROR_LABEL_TOO_LONG
域名的某个标签超过63字节。
-
Idna::ERROR_DOMAIN_NAME_TOO_LONG
域名的长度超过253字节。
-
Idna::ERROR_LEADING_HYPHEN
域名的某个标签以连字符(-)开头。
-
Idna::ERROR_TRAILING_HYPHEN
域名的某个标签以连字符(-)结尾。
-
Idna::ERROR_HYPHEN_3_4
域名的某个标签在第三和第四位包含连字符(-)。
-
Idna::ERROR_LEADING_COMBINING_MARK
域名的某个标签以结合符号开头。
-
Idna::ERROR_DISALLOWED
域名包含不允许的字符。
-
Idna::ERROR_PUNYCODE
域名的某个标签以"xn--"开头,但不是有效的punycode。
-
Idna::ERROR_LABEL_HAS_DOT
域名的某个标签包含句点(.)。
-
Idna::ERROR_INVALID_ACE_LABEL
域名的某个标签是无效的ACE标签。
-
Idna::ERROR_BIDI
域名不满足IDNA的BiDi要求。
-
Idna::ERROR_CONTEXTJ
域名的某个标签不满足IDNA的CONTEXTJ要求。
PHP 中 Unicode 支持的 WTF
在PHP的任何版本中,都可能使用多种不同的Unicode版本。所以...这是什么情况?
-
这是什么意思?
这意味着,如果我提出相同的问题,下面列出的每个扩展都可以给我不同的答案。此外,由于以下扩展使用的Unicode版本也可以根据相同的PHP版本而不同,这种情况变得更加复杂。例如,我的PHP 7.2安装中使用的
intl
扩展可能使用Unicode版本11,但你的网络主机安装的PHP 7.2中的intl
扩展可能使用Unicode版本6。 -
这是怎么回事?
mbstring
扩展使用自己的Unicode版本。- 作为
mbstring
正则表达式功能背后的库的Onigurama
,使用自己的Unicode版本。 - 作为PHP中处理正则表达式的首选扩展的
PCRE
,使用自己的Unicode版本。 intl
扩展使用自己的Unicode版本。- 其他添加了自己的Unicode版本的扩展。
- 用户库使用自己的Unicode版本(包括这个库)。
-
这个库
能够使用
mbstring
或intl
扩展将是有帮助的,但我们不能依赖于它们被安装,或者它们在安装时具有一致的Unicode版本。此外,像PCRE
这样的扩展可能会在不支持Unicode的情况下编译,尽管我们确实依赖于PCRE
的u
修改符。因此,我们必须包含自己的Unicode数据。
常见问题解答
-
我感到困惑!这是IDNA2003还是IDNA2008?
答案相当复杂。长话短说;它既不是。
以下是规范的内容
为了满足用户对映射的期望,并提供与IDNA2003的最大兼容性,本规范指定了一个用于IDNA2008的映射。此外,为了更顺利地过渡到IDNA2008,本规范提供了一种Unicode算法,用于标准化处理,允许符合规范的实现最大限度地减少由IDNA2003和IDNA2008之间的差异引起的安全性和互操作性问题。这种Unicode IDNA兼容处理结构按照IDNA2003原则构建,但扩展了这些原则到Unicode 5.2及以后版本。它还纳入了IDNA2008提供的语料库扩展。
-
推荐选项有哪些?
默认选项是推荐选项,也是最为严格的。
// Default options. [ 'CheckHyphens' => true, 'CheckBidi' => true, 'CheckJoiners' => true, 'UseSTD3ASCIIRules' => true, 'Transitional_Processing' => false, 'VerifyDnsLength' => true, // Only for Idna::toAscii() ];
-
我必须提供所有选项吗?
不是的。您只需要指定您想要更改的选项。您指定的任何选项都将覆盖默认选项。
use Rowbot\Idna\Idna; $result = Idna::toAscii('x-.xn--nxa', ['CheckHyphens' => true]); $result->hasErrors(); // true $result->hasError(Idna::ERROR_TRAILING_HYPHEN); // true $result = Idna::toAscii('x-.xn--nxa', ['CheckHyphens' => false]); $result->hasErrors(); // false $result->hasError(Idna::ERROR_TRAILING_HYPHEN); // false
-
Transitional
处理和Non-transitional
处理之间的区别是什么?Transitional
处理旨在模仿IDNA2003。强烈建议使用Non-transitional
处理,它试图模仿IDNA2008。您可以通过检查IdnaResult::isTransitionalDifferent()
来始终检查在两种处理模式下域名是否会有所不同。 -
如果也能够对
idn_to_ascii()
和idn_to_utf8()
函数进行测试,那岂不是更好?这些函数来自intl
扩展。是的。如果能对ICU实现进行额外的兼容性检查,那将非常棒。然而,正如在上文《PHP中Unicode支持的WTFs》中提到的理由,对这些函数进行测试在最佳情况下也是不可靠的。
-
为什么
intl
扩展在无效域名中显示的字符像带问号的钻石形状,而您的实现却没有?$input = '憡?Ⴔ.XN--1UG73GL146A'; idn_to_utf8($input, 0, IDNA_INTL_VARIANT_UTS46, $info); echo $info['result']; // 憡��.xn--1ug73gl146a� echo ($info['errors'] & IDNA_ERROR_DISALLOWED) !== 0; // true $result = \Rowbot\Idna\Idna::toUnicode($input); echo $result->getDomain(); // 憡?Ⴔ.xn--1ug73gl146a echo $result->hasError(\Rowbot\Idna\Idna::ERROR_DISALLOWED); // true
实现可以对显示给用户的最终Unicode字符串进行进一步修改。例如,建议用U+FFFD替换不允许的字符,以便用户可以看到。同样,在步骤4或5的处理过程中失败的标签可以通过插入U+FFFD或其他视觉设备进行标记。
当前实现不进行这些推荐修改。
内部
构建
Unicode数据文件从https://www.unicode.org/Public获取。目前支持Unicode版本11.0.0-15.0.0。要更改库构建所使用的Unicode版本,必须首先更改\Rowbot\Idna::UNICODE_VERSION
常量的值,如下所示
class Idna { - public const UNICODE_VERSION = '13.0.0'; + public const UNICODE_VERSION = '14.0.0';
然后,要生成必要的数据文件,请执行以下命令
php bin/generateDataFiles.php
如果没有断言或异常发生,那么您已成功更改Unicode版本。现在应该执行测试,以确保一切正常。测试将自动获取适当的测试版本,因为测试文件不是由上述命令生成的。
vendor/bin/phpunit