如何验证国际化域名

时间:2013-01-14 05:52:02

标签: php regex dns

我想验证php中的域url,它可能是国际化的域名格式,如希腊语 域名= http://παράδειγμα.δοκιμή 他们是否可以使用正则表达式验证它?

3 个答案:

答案 0 :(得分:3)

如果您想创建自己的图书馆,则需要使用允许的代码点表(IANA — Repository of IDN PracticesIDN Character Validation GuidanceIDNA Parameters)和表格 Unicode脚本属性(UNIDATA/Scripts.txt)。

Gmail采用Unicode Consortium的“H ighly Restricted”规范(Protecting Gmail in a global world)。 允许使用以下Unicode脚本的组合。

  • 单一脚本
  • 拉丁语+汉语+平假名+片假名
  • Latin + Han + Bopomofo
  • Latin + Han + Hangul

您可能需要支付特殊脚本属性值(Common,Inherited,Unknown)的注意权,因为某些字符具有多个属性或错误的属性。

例如,U + 3099(COMKINING KATAKANA-HIRAGANA VOICED SOUND MARK)有两个属性(“Katakana”和“Hiragana”),PCRE功能将其归类为“继承”。另一个例子是U + x2A708。 Al +#2A708(U + 30C8 KATAKANA LETTER TO和U + 30E2 KATAKANA LETTER MO的组合)的右脚本属性是“Katakana”,Unicode规范将其错误分类为“Han”。

您可能需要考虑IDN homograph attack。 Google Chrome浏览器IDN policy采用the blacklist chars

我的建议是使用Zend \ Validator \ Hostname。该库使用the table of permitted code points表示日语和中文。

如果您使用Symfony,请考虑将版本的应用升级到2.5,它采用egulias / email-validatornd(Manual)。 您需要额外验证字符串是否格式正确。查看我的report a>细节。

不要忘记XSS和SQL注入。以下地址是基于RFC5322的有效电子邮件地址。

// From Japanese tutorial
// http://blog.tokumaru.org/2013/11/xsssqlrfc5322.html
"><script>alert('or/**/1=1#')</script>"@example.jp

我认为使用idn_to_ascii进行验证是值得怀疑的,因为idn_to_ascii几乎传递了所有字符。

for ($i = 0; $i < 0x110000; ++$i) {
    $c = utf8_chr($i);

    if ($c !== '' && false !== idn_to_ascii($c)) {
        $number = strtoupper(dechex($i));
        $length = strlen($number);

        if ($i < 0x10000) {
            $number = str_repeat('0', 4 - $length).$number;
        }

        $idn = $c.'example.com';

        echo 'U+'.$number.' ';
        echo ' '.$idn.' '. idn_to_ascii($idn);
        echo PHP_EOL;
    }
}

function utf8_chr($code_point) {

    if ($code_point < 0 || 0x10FFFF < $code_point || (0xD800 <= $code_point && $code_point <= 0xDFFF)) {
        return '';
    }

    if ($code_point < 0x80) {
        $hex[0] = $code_point;
        $ret = chr($hex[0]);
    } else if ($code_point < 0x800) {
        $hex[0] = 0x1C0 | $code_point >> 6;
        $hex[1] = 0x80  | $code_point & 0x3F;
        $ret = chr($hex[0]).chr($hex[1]);
    } else if ($code_point < 0x10000) {
        $hex[0] = 0xE0 | $code_point >> 12;
        $hex[1] = 0x80 | $code_point >> 6 & 0x3F;
        $hex[2] = 0x80 | $code_point & 0x3F;
        $ret = chr($hex[0]).chr($hex[1]).chr($hex[2]);
    } else  {
        $hex[0] = 0xF0 | $code_point >> 18;
        $hex[1] = 0x80 | $code_point >> 12 & 0x3F;
        $hex[2] = 0x80 | $code_point >> 6 & 0x3F;
        $hex[3] = 0x80 | $code_point  & 0x3F;
        $ret = chr($hex[0]).chr($hex[1]).chr($hex[2]).chr($hex[3]);
    }

    return $ret;
}

如果要通过Unicode脚本属性验证域,请使用PCRE函数。

以下代码显示如何获取Unicode脚本属性的名称。如果您想在JavaScript中使用Unicode脚本peroperties,请使用mathiasbynens/unicode-data

function get_unicode_script_name($c) {

  // http://php.net/manual/regexp.reference.unicode.php
  $names = [
    'Arabic', 'Armenian', 'Avestan', 'Balinese', 'Bamum', 'Batak', 'Bengali', 
    'Bopomofo', 'Brahmi', 'Braille', 'Buginese', 'Buhid', 'Canadian_Aboriginal',
    'Carian', 'Chakma', 'Cham', 'Cherokee', 'Common', 'Coptic', 'Cuneiform',
    'Cypriot', 'Cyrillic', 'Deseret', 'Devanagari', 'Egyptian_Hieroglyphs',
    'Ethiopic', 'Georgian', 'Glagolitic', 'Gothic', 'Greek', 'Gujarati', 
    'Gurmukhi', 'Han', 'Hangul', 'Hanunoo', 'Hebrew', 'Hiragana', 'Imperial_Aramaic',
    'Inherited', 'Inscriptional_Pahlavi', 'Inscriptional_Parthian', 'Javanese',
    'Kaithi', 'Kannada', 'Katakana', 'Kayah_Li', 'Kharoshthi', 'Khmer', 'Lao', 'Latin',
    'Lepcha', 'Limbu', 'Linear_B', 'Lisu', 'Lycian', 'Lydian', 'Malayalam', 'Mandaic',
    'Meetei_Mayek', 'Meroitic_Cursive', 'Meroitic_Hieroglyphs', 'Miao', 'Mongolian',
    'Myanmar', 'New_Tai_Lue', 'Nko', 'Ogham', 'Old_Italic', 'Old_Persian',
    'Old_South_Arabian', 'Old_Turkic', 'Ol_Chiki', 'Oriya', 'Osmanya', 'Phags_Pa',
    'Phoenician', 'Rejang', 'Runic', 'Samaritan', 'Saurashtra', 'Sharada', 'Shavian',
    'Sinhala', 'Sora_Sompeng', 'Sundanese', 'Syloti_Nagri', 'Syriac', 'Tagalog',
    'Tagbanwa', 'Tai_Le', 'Tai_Tham', 'Tai_Viet', 'Takri', 'Tamil', 'Telugu', 'Thaana',
    'Thai', 'Tibetan', 'Tifinagh', 'Ugaritic', 'Vai', 'Yi'
  ];

  $ret = [];

  foreach ($names as $name) {

    $pattern = '/\p{'.$name.'}/u';

    if (preg_match($pattern, $c)) {
        return $name;
    }
  }

  return '';
}

答案 1 :(得分:2)

这是idn个域名,我首先将其转换为puny code版本,然后将validate域转换为。{/ p>

但是,如果您真的想要通过正则表达式验证

<?php

$domain = 'παράδειγμα.gr';
$regex = '#^([\w-]+://?|www[\.])?([^\-\s\,\;\:\+\/\\\?\^\`\=\&\%\"\'\*\#\<\>]*)\.[a-z]{2,7}$#';
if (preg_match($regex, $domain)) {
    echo "VALID";
}

但是你让你运行错误的假设,因为验证一个idn域非常复杂,我试图验证没有无效的字符,但列表不完整。

更好地将bevore转换为惩罚代码

$regex = '#^([\w-]+://?|www[\.])?[a-z0-9]+[a-z0-9\-\.]*[a-z0-9]+\.[a-z]{2,7}$#';
if (preg_match($regex, idn_to_ascii($domain))) {
    echo "VALID";
}

如果您想要测试域是否可以解析,请尝试:

$regex = '#^([\w-]+://?|www[\.])?[a-z0-9]+[a-z0-9\-\.]*[a-z0-9]+\.[a-z]{2,7}$#';
$punny_domain = idn_to_ascii($domain);
if (preg_match($regex, $punny_domain)) {
    if (gethostbyname($punny_domain) != $punny_domain) {
        echo "VALID";
    }
}

答案 2 :(得分:1)

这是一个所谓的IDN domain。 支持IDN域的客户端使用RFC 5890中指定的IDNA2008标准对其进行规范化,然后在提交DNS解析之前使用Punycode中定义的RFC 3492编码替换剩余的unicode字符。

根据规范,UTF-8字符集中的每个字符都可以在IDN域中使用,但每个顶级域权限都可以在Unicode字符集中定义有效字符,因此很难create and maintain a real regex

如果要在应用程序中接受IDN域,则应在内部使用编码版本。 PHP extension intl带来两个函数来解码和解码IDN域名

echo idn_to_ascii('täst.de'); 
  

xn--tst-qla.de

编码后,域名将通过任何traditional regex check

简单验证:

$url = "http://example.com/";
if (preg_match('/^(http|https|ftp):\/\/([A-Z0-9][A-Z0-9_-]*(?:\.[A-Z0-9][A-Z0-9_-]*)+):?(\d+)?\/?/i', $url)) {
    echo 'OK';
} else {
    echo 'Invalid URL.';
}

修改

如果您想要真正的DNS验证,可以使用dns_get_record(PHP 5)或gethostbyaddr

e.g。

$domain = 'ελληνικά.idn.icann.org';
$idnDomain = idn_to_ascii( $domain );

if ( $dnsResult = dns_get_record( $idnDomain, DNS_ANY ) )
{
    echo $idnDomain , "\n";
    print_r( $dnsResult );
}
else
{
    echo "failed to lookup domain\n";
}

结果:

xn--hxargifdar.idn.icann.org
Array 
(
    [0] => Array
    (
        [host] => xn--hxargifdar.idn.icann.org
        [class] => IN
        [ttl] => 21456
        [type] => A
        [ip] => 199.7.85.10
    )
    [1] => Array
    (
        [host] => xn--hxargifdar.idn.icann.org
        [class] => IN
        [ttl] => 21600
        [type] => AAAA
        [ipv6] => 2620::2830:230:0:0:0:10
    )
)