Ну, я использую PHP-класс idna_convert (http://idnaconv.net/index.html) для кодирования / декодирования доменных имен.
К сожалению, он не предоставляет интерфейс для проверки того, является ли доменное имя уже punycode или нет.
Какой лучший способ достичь этого? Было бы хорошо, если бы кто-то мог опубликовать исходный код, как проверить, является ли домен маленьким или нет (с объяснением, потому что код idna_convert мне не совсем понятен). Я уже знаю, как отловить исключение из idna_convert. 🙂
Кстати: idna_convert генерирует исключение при попытке преобразовать доменное имя в punycode, который уже является punycode (см. https://github.com/phlylabs/idna-convert/blob/master/src/Punycode.php; строка 157). Более того, я не очень понимаю, как работает их проверка.
Самый простой способ — просто все равно конвертировать и проверить, равен ли результат вводу.
РЕДАКТИРОВАТЬ: Вы можете расширить класс 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;
}
}
Единственное исключение, что encode()
Метод throws — это когда домен уже содержит punycode. Таким образом, вы можете сделать следующее:
try {
$punycode->encode($decoded);
} catch (\InvalidArgumentException $e) {
//do whatever is needed when already punycode
//or do nothing
}
Однако это обходное решение.
Это зависит от того, что именно вы хотите.
В качестве первой базовой проверки посмотрите, содержит ли доменное имя только символы ASCII. Если да, то домен уже «punycode», в том смысле, что он не может быть преобразован в дальнейшем. Чтобы проверить, содержит ли строка только символы ASCII, см. Определить, является ли текст UTF-8 все ASCII?.
Если вы хотите проверить, находится ли домен в форме IDN, разделите домен по точкам. .
и проверьте, начинается ли какая-либо из подстрок с xn--
,
Если в дополнение к этому вы хотите проверить, является ли домен IDN и является ли он действительным, просто попытайтесь декодировать его с помощью функции декодирования библиотеки.
Не очень легко проверить, находится ли домен в Punycode или нет. Необходимо выполнить несколько проверок по правилам, которые уже были сказаны @Wladston.
Это адаптированные примеры кода, которые я взял из ValidateHelper
класс из состава моей библиотеки: Вспомогательные классы для PrestaShop CMS. Я также добавил тест и результат его выполнения.
/**
* Validate helper.
*
* @author Maksim T. <[email protected]>
*/
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. <[email protected]>
*/
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. <[email protected]>
*/
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. <[email protected]>
*/
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. <[email protected]>
*/
class Test
{
/**
* Run the test.
*
* @author Maksim T. <[email protected]>
*/
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]