ギリシャのドメイン名のように国際化されたドメイン名形式である可能性があるphpでドメインURLを検証したい= http://παράδειγμα.δοκιμή 正規表現を使用して検証する方法はありますか?
3 に答える
これはいわゆるIDNドメインです。IDNドメインをサポートするクライアントは、 RFC 5890で指定されているIDNA2008標準を使用して正規化し、DNS解決のために送信する前に、 RFC3492で定義されているPunycodeエンコーディングを使用して残りのUnicode文字を置き換えます。
仕様により、UTF-8文字セットのすべての文字はIDNドメインでの使用に有効ですが、すべてのトップレベルドメインオーソリティはUnicode文字セット内で有効な文字を定義できるため、実際の正規表現を作成および維持することは困難です。
アプリケーションでIDNドメインを受け入れる場合は、エンコードされたバージョンを内部で使用する必要があります。PHP拡張intlは、IDNドメイン名をen-およびdecodeするための2つの関数をもたらします
echo idn_to_ascii('täst.de');
xn--tst-qla.de
エンコード後、ドメインは従来の正規表現チェックに合格します
簡単な検証:
$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を使用できます。
例えば
$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
)
)
独自のライブラリを作成する場合は、許可されたコードポイントの表 ( IANA — IDN プラクティスのリポジトリ、IDN 文字検証ガイダンス、IDNA パラメータ) と Unicode スクリプト プロパティの表 ( UNIDATA/Scripts.txt ) を使用する必要があります。
Gmail は、Unicode コンソーシアムの「<a href="http://www.unicode.org/reports/tr39/#Restriction_Level_Detection" rel="nofollow noreferrer">Highly Restricted」仕様 (グローバルな世界で Gmail を保護する) を採用しています。次の Unicode スクリプトの組み合わせが許可されています。
- 単一のスクリプト
- ラテン語 + 漢字 + ひらがな + カタカナ
- ラテン語 + 漢語 + ボポモフォ
- ラテン語 + ハングル + ハングル
一部の文字には複数のプロパティまたは間違ったプロパティがあるため、特別なスクリプト プロパティ値 (Common、Inherited、Unknown) に注意する必要がある場合があります。
たとえば、U+3099 (COMBINING KATAKANA-HIRAGANA VOICED SOUND MARK) には、「カタカナ」と「ひらがな」の 2 つのプロパティがあり、PCRE 関数はそれを「継承」として分類します。別の例は U+x2A708 です。U+2A708 (U+30C8 KATAKANA LETTER TO と U+30E2 KATAKANA LETTER MO の組み合わせ) の正しいスクリプト プロパティは "Katakana" ですが、Unicode 仕様では "Han" と誤分類されています。
IDN ホモグラフ攻撃を考慮する必要があるかもしれません。Google Chrome のIDN ポリシーは、ブラックリスト charsを採用しています。
Zend\Validator\Hostname を使用することをお勧めします。このライブラリは、日本語と中国語の許可されたコード ポイントのテーブルを使用します。
Symfony を使用している場合は、アプリのバージョンを egulias/email-validatornd を採用する 2.5 にアップグレードすることを検討してください ( Manual )。文字列が整形式のバイト シーケンスであるかどうかをさらに検証する必要があります。詳細は私のレポートを参照してください。
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 スクリプト プロパティを使用する場合は、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 '';
}
これはidnドメインです。最初にそれをちっぽけなコードバージョンに変換し、次にドメインを検証します。
しかし、正規表現で検証したい場合は
<?php
$domain = 'παράδειγμα.gr';
$regex = '#^([\w-]+://?|www[\.])?([^\-\s\,\;\:\+\/\\\?\^\`\=\&\%\"\'\*\#\<\>]*)\.[a-z]{2,7}$#';
if (preg_match($regex, $domain)) {
echo "VALID";
}
しかし、idn ドメインを検証するのは非常に複雑であるため、無効な文字が含まれていないことを検証しようとしましたが、リストは完全ではありません。
bevoをパニーコードに変換する方が良い
$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";
}
}