2

好吧,我使用 idna_convert PHP 类 ( http://idnaconv.net/index.html ) 来编码/解码域名。

不幸的是,它似乎没有提供一个接口来检查一个域名是否已经是 punycode。

实现这一目标的最佳方法是什么?如果有人可以发布源代码如何验证域是否是 punycode,那就太好了(有解释,因为 idna_convert 代码对我来说不是很清楚)。我已经知道如何从 idna_convert 捕获异常。:-)

顺便说一句:当您尝试将域名转换为已经是 punycode 的 punycode 时,idna_convert 会引发异常(请参阅https://github.com/phlylabs/idna-convert/blob/master/src/Punycode.php;第 157 行) . 此外,我真的不明白他们的支票是如何运作的。

4

4 回答 4

1

这取决于你到底想要什么。

作为第一个基本检查,查看域名是否仅包含 ASCII 字符。如果是,那么该域是“已经是 punycode”,在某种意义上它不能被进一步转换。要检查字符串是否仅包含 ASCII 字符,请参阅确定 UTF-8 文本是否全部为 ASCII?.

如果最重要的是,您想检查域是否在 IDN 表单中,在点处拆分域. 并检查是否有任何子字符串以xn--.

如果除此之外,您想检查域是否为 IDN 并且有效,只需尝试使用库的 decode 函数对其进行解码。

于 2016-05-19T22:53:40.470 回答
1

最简单的方法 - 无论如何都转换它并检查结果是否等于输入。

编辑:您可以使用这样的检查扩展 Punycode 类:

class PunycodeCheck extends Punycode
{
  public function check_encoded($decoded)
  {
      $extract = self::byteLength(self::punycodePrefix);
      $check_pref = $this->UnicodeTranscoder->utf8_ucs4array(self::punycodePrefix);
      $check_deco = array_slice($decoded, 0, $extract);
      if ($check_pref == $check_deco) 
          return true;
      return false;
   }
}
于 2016-05-13T12:05:02.960 回答
1

检查一个域是否在 Punycode 中并不容易。需要通过@Wladston 已经说过的规则来实施几次检查。

这是我从ValidateHelper我的库组合中的类中获取的改编代码示例:PrestaShop CMS 的帮助类。我还添加了测试及其执行结果。

/**
 * Validate helper.
 *
 * @author Maksim T. <zapalm@yandex.com>
 */
class ValidateHelper
{
    /**
     * Checks if the given domain is in Punycode.
     *
     * @param string $domain The domain to check.
     *
     * @return bool Whether the domain is in Punycode.
     *
     * @see https://developer.mozilla.org/en-US/docs/Mozilla/Internationalized_domain_names_support_in_Mozilla#ASCII-compatible_encoding_.28ACE.29
     *
     * @author Maksim T. <zapalm@yandex.com>
     */
    public static function isPunycodeDomain($domain)
    {
        $hasPunycode = false;

        foreach (explode('.', $domain) as $part) {
            if (false === static::isAscii($part)) {
                return false;
            }

            if (static::isPunycode($part)) {
                $hasPunycode = true;
            }
        }

        return $hasPunycode;
    }

    /**
     * Checks if the given value is in ASCII character encoding.
     *
     * @param string $value The value to check.
     *
     * @return bool Whether the value is in ASCII character encoding.
     *
     * @see https://en.wikipedia.org/wiki/ASCII
     *
     * @author Maksim T. <zapalm@yandex.com>
     */
    public static function isAscii($value)
    {
        return ('ASCII' === mb_detect_encoding($value, 'ASCII', true));
    }

    /**
     * Checks if the given value is in Punycode.
     *
     * @param string $value The value to check.
     *
     * @return bool Whether the value is in Punycode.
     *
     * @throws \LogicException If the string is not encoded by UTF-8.
     *
     * @see https://en.wikipedia.org/wiki/Punycode
     *
     * @author Maksim T. <zapalm@yandex.com>
     */
    public static function isPunycode($value)
    {
        if (false === static::isAscii($value)) {
            return false;
        }

        if ('UTF-8' !== mb_detect_encoding($value, 'UTF-8', true)) {
            throw new \LogicException('The string should be encoded by UTF-8 to do the right check.');
        }

        return (0 === mb_stripos($value, 'xn--', 0, 'UTF-8'));
    }
}

/**
 * Test Punycode domain validator.
 *
 * @author Maksim T. <zapalm@yandex.com>
 */
class Test
{
    /**
     * Run the test.
     *
     * @author Maksim T. <zapalm@yandex.com>
     */
    public static function run()
    {
        $domains = [
            // White list
            'почта@престашоп.рф'          => false, // Russian, Unicode
            'modulez.ru'                  => false, // English, ASCII
            'xn--80aj2abdcii9c.xn--p1ai'  => true,  // Russian, ASCII
            'xn--80a1acn3a.xn--j1amh'     => true,  // Ukrainian, ASCII
            'xn--srensen-90a.example.com' => true,  // German, ASCII
            'xn--mxahbxey0c.xn--xxaf0a'   => true,  // Greek, ASCII
            'xn--fsqu00a.xn--4rr70v'      => true,  // Chinese, ASCII

            // Black List
            'xn--престашоп.xn--рф'        => false, // Russian, Unicode
            'xn--prestashop.рф'           => false, // Russian, Unicode
        ];

        foreach ($domains as $domain => $isPunycode) {
            echo 'TEST: ' . $domain . (ValidateHelper::isPunycodeDomain($domain)
                ? ' is in Punycode [' . ($isPunycode ? 'OK' : 'FAIL') . ']'
                : ' is NOT in Punycode [' . (false === $isPunycode ? 'OK' : 'FAIL') . ']'
            ) . PHP_EOL;
        }
    }
}

Test::run();

// The output result:
//
// TEST: почта@престашоп.рф is NOT in Punycode [OK]
// TEST: modulez.ru is NOT in Punycode [OK]
// TEST: xn--80aj2abdcii9c.xn--p1ai is in Punycode [OK]
// TEST: xn--80a1acn3a.xn--j1amh is in Punycode [OK]
// TEST: xn--srensen-90a.example.com is in Punycode [OK]
// TEST: xn--mxahbxey0c.xn--xxaf0a is in Punycode [OK]
// TEST: xn--fsqu00a.xn--4rr70v is in Punycode [OK]
// TEST: xn--престашоп.xn--рф is NOT in Punycode [OK]
// TEST: xn--prestashop.рф is NOT in Punycode [OK]
于 2018-12-27T03:07:41.857 回答
0

encode()方法抛出的唯一异常是当域已经是 punycode 时。因此,您可以执行以下操作:

try {
    $punycode->encode($decoded);
} catch (\InvalidArgumentException $e) {
    //do whatever is needed when already punycode
    //or do nothing
}

但是,这是一种解决方法。

于 2016-05-13T12:19:57.513 回答