rowbot/idna

UTS#46 Unicode IDNA 兼容处理实现。

0.1.5 2022-01-10 19:51 UTC

This package is auto-updated.

Last update: 2024-09-19 00:53:20 UTC


README

License GitHub Workflow Status Code Coverage Version Downloads

这是一个完全符合 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版本(包括这个库)。
  • 这个库

    能够使用mbstringintl扩展将是有帮助的,但我们不能依赖于它们被安装,或者它们在安装时具有一致的Unicode版本。此外,像PCRE这样的扩展可能会在不支持Unicode的情况下编译,尽管我们确实依赖于PCREu修改符。因此,我们必须包含自己的Unicode数据。

常见问题解答

  • 我感到困惑!这是IDNA2003还是IDNA2008?

    答案相当复杂。长话短说;它既不是。

    以下是规范的内容

    为了满足用户对映射的期望,并提供与IDNA2003的最大兼容性,本规范指定了一个用于IDNA2008的映射。此外,为了更顺利地过渡到IDNA2008,本规范提供了一种Unicode算法,用于标准化处理,允许符合规范的实现最大限度地减少由IDNA2003和IDNA2008之间的差异引起的安全性和互操作性问题。这种Unicode IDNA兼容处理结构按照IDNA2003原则构建,但扩展了这些原则到Unicode 5.2及以后版本。它还纳入了IDNA2008提供的语料库扩展。

    更多信息可以在第2节《Unicode IDNA兼容性处理》和第7节《IDNA比较》中找到。(链接)(链接)

  • 推荐选项有哪些?

    默认选项是推荐选项,也是最为严格的。

    // 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

    第4节《处理》

    实现可以对显示给用户的最终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