Кириллические URL, РФ-домены и конвертация punycode
Мне тут понадобилось прикрутить к Тандемократии поиск по сайту нашего пока-еще-президента. Сайт его всю жизнь назывался «kremlin.ru», однако Дмитрий Анатольевич, как мы помним, не остался равнодушным к появлению зоны .рф и засквотил себе зеркальце — «президент.рф». И теперь это главное зеркало в Яндексе. Ладно, искать по host:президент.рф Яндекс.XML вполне умеет, тут хитростей нет. Но вот в поле URL он честно отдает «xn--d1abbgf6aiiy.xn--p1ai».
Ну, можно им назло так и показать. Но юзеров жалко — они ж не поймут. Хочется добиться, чтоб URL выглядел человекопонятно. Что для этого делаем:
1. Конвертируем punycode в кириллицу
Погуглив яндексом punycode-конвертер на php, быстро находим класс idna_convert на phpclasses.org (там нужна регистрация, но для ленивых есть прямая ссылка). Вроде класс вполне рабочий.
2. Отображаем кириллические урлы
Тут много ума не надо: urldecode в помощь 🙂 В теории могут попадаться урлы, закодированные из windows-1251, а не из utf-8. Тогда сверху можно положить какой-нибудь to_utf() на основе iconv().
3. Подсвечиваем имя домена и отбрасываем схему (http://)
4. Собираем все вместе
На выходе у меня получилась нижеследующая функция, которая делает все вышеописанное 🙂 То есть, если например какая-нибудь выставка ретро автомобилей заимела зеркало «выставка-авто.рф», и сделала страничку «/новости/» — наша функция страшный урл типа:
покажет красиво-понято, как-то так:
выставка-авто.рф/новости/
— а мы только того и хотим. Встречайте =)
function showCyrUrl($url) $url = parse_url($url);
$host = $url['host'];
if (preg_match("~^xn\-\-~", $host)) require_once('idna_convert.class.php');
$convert = new idna_convert();
$host = $convert->decode($host);
>
$path = isset($url['path']) ? urldecode($url['path']) : '';
$query = isset($url['query']) ? '?'.urldecode($url['query']) : '';
return sprintf(
'%s%s%s',
$host,
$path,
$query
);
>
Не забываем положить idna_convert.class.php в нужное место 🙂
Декодировать кириллический домен — punycode в php
С появлением кириллических доменов на русском языке возникла проблема их «отображения» в директориях, url запросах и других областях, где требуется использовать адреса сайтов.
Например, домен яндекс.рф в punycode должен выглядеть как: xn--d1acpjx3f.xn--p1ai. Согласитесь, выглядит не очень красиво. Но здесь ничего не поделаешь, так как линукс хостинги должны записывать названия директорий-доменов в латинице. Именно поэтому, если мы открываем фтп к корню русскоязычного сайта, то его домен будет в непонятных символах: xn--d1acpjx3f.xn--p1ai.
Рассмотрим библиотеку, которая сможет автоматически перевести любой русскоязычный домен в punycode на php и обратно. Для этого идеально подойдет idna_convert.class.php. Скачать его вы можете ниже.
Приведу рабочий пример, как пользоваться этим классом, он не раз меня выручал при создании программ:
header(‘Content-Type: text/html; charset=utf-8’);
include(‘idna_convert.class.php’);
//кодер/декодер домена
function coderurl($url) $idn = new idna_convert(array(‘idn_version’=>2008));
$url=(stripos($url, ‘xn--‘)!==false) ? $idn->decode($url) : $idn->encode($url);
echo $url;
>
?>
Эта функция позволяет приводить домены в понятный вид. Она работает как в прямом, так и в обратном преобразовании. Это означает, что если вы пропустите через нее нормальный домен в латинских символах, то на выходе его и получите, а если кириллический, функция преобразует его в punycode. Примеры запросов с результами:
coderurl(‘ребусто.рф’); // ребусто.рф -> xn--90ah2afhgf.xn--p1ai
coderurl(‘xn--90ah2afhgf.xn--p1ai’); // xn--90ah2afhgf.xn--p1ai -> ребусто.рф
coderurl(‘ya.ru’); // ya.ru -> ya.ru
?>
Принцип работы функции основан на том, что все кириллические домены начинаются с xn-- — по этим символам мы и делаем проверка, что пришло на вход функции. За счет coderurl() я легко преобразую домены в punycode и храню их в таком виде в БД. А если их надо извлечь для чтения пользователям, то преобразую обратно за счет этой же функции.
Преобразование punycode в PHP
Punycode – это специальная кодировка, используется для преобразования символов Unicode в ASCII для кодирования интернационализированных доменных имен (IDN). В PHP есть функции для преобразования:
echo idn_to_ascii('домен.рф'); // xn--d1acufc.xn--p1ai echo idn_to_utf8('xn--d1acufc.xn--p1ai'); // домен.рф
echo idn_to_ascii('http://домен.рф/category'); // xn--http://-5ggj3emj.xn--/category-k3h8b
Чтобы перекодировать домен в ссылке, нужно разбирать URL с помощью parse_url, сделать преобразование и собрать обратно.
function punycode_encode($url) < $parts = parse_url($url); $out = ''; if (!empty($parts['scheme'])) $out .= $parts['scheme'] . ':'; if (!empty($parts['host'])) $out .= '//'; if (!empty($parts['user'])) $out .= $parts['user']; if (!empty($parts['pass'])) $out .= ':' . $parts['pass']; if (!empty($parts['user'])) $out .= '@'; if (!empty($parts['host'])) $out .= idn_to_ascii($parts['host']); if (!empty($parts['port'])) $out .= ':' . $parts['port']; if (!empty($parts['path'])) $out .= $parts['path']; if (!empty($parts['query'])) $out .= '?' . $parts['query']; if (!empty($parts['fragment'])) $out .= '#' . $parts['fragment']; return $out; >echo punycode_encode('http://домен.рф/category'); // http://xn--d1acufc.xn--p1ai/category
Обратный перевод
function punycode_decode($url) < $parts = parse_url($url); $out = ''; if (!empty($parts['scheme'])) $out .= $parts['scheme'] . ':'; if (!empty($parts['host'])) $out .= '//'; if (!empty($parts['user'])) $out .= $parts['user']; if (!empty($parts['pass'])) $out .= ':' . $parts['pass']; if (!empty($parts['user'])) $out .= '@'; if (!empty($parts['host'])) $out .= idn_to_utf8($parts['host']); if (!empty($parts['port'])) $out .= ':' . $parts['port']; if (!empty($parts['path'])) $out .= $parts['path']; if (!empty($parts['query'])) $out .= '?' . $parts['query']; if (!empty($parts['fragment'])) $out .= '#' . $parts['fragment']; return $out; >echo punycode_decode('http://xn--d1acufc.xn--p1ai/category'); // http://домен.рф/category
Проверка правильности ввода домена на PHP
В одной из задач мне требовалось сделать поле ввода домена для пользователя, а затем проверить насколько корректно он его ввел. Вроде задача простая, но как оказалось с ней пришлось повозиться полдня. Так как пользователь мог ввести домен совершенно разных форматах как с ошибкой, так и без нее. С каким сложностями может возникнуть проверка домена на корректность?
За истину я взял чистый домен формата: domen.ru
Пользователь может ввести домен так:
- с приставкой: http:// https:// //
- в нем может быть www, а может отсутствовать
- на конце домена может быть остаток пути /ssilka
Начнем с очевидного. Пусть домен передается в переменной $_POST[«domen»] с формы:
//Сначала убираем http https //, если пользователь его вводил
$domen = preg_replace(‘/(http\:\/\/|https\:\/\/|\/\/)/’, », trim($_POST[«domen»]));
//Теперь заменяем множество точек на одну sait..5. ru => sait.5.ru — это пригодится в дальнейшем
$domen = preg_replace(‘/\.+/’, ‘.’, $domen);
/*Принудительно добавляем к домену http, чтобы выделить из ссылки домен за счет функции parse_url().
http://sa.ru/page => sa.ru
еда.рф/index.php?template=access => еда.рф*/
if(parse_url(‘http://’.$domen))
//удаляем www, если оно есть в домене
$domen = str_replace(‘www.’, », $domen);
//После всех этих преобразований мы можем вывести наш очищенный домен
echo ‘Домен: ‘.$domen;
Теперь, рассмотрим как будет обрабатывать скрипт домен при вводе различных форматов:
http://ya.ru => ya.ru
https://ya.ru => ya.ru
//ya.ru => ya.ru
ya.ru => ya.ru
www.ya.ru => ya.ru
http://www.ya.ru => ya.ru
https://www.ya.ru => ya.ru
//www.ya.ru => ya.ru
http://ya.ru/page => ya.ru
http://ya.ru?page => ya.ru
yait. 5. ru => yait.5.ru
http://poddomen.ya.ru/page => poddomen.ya.ru
еда.рф => еда.рф
www.еда.рф#ok => еда.рф
http:/./ya.ru => http (намеренная ошибка)
www.ya.ru 23 => ya.ru 23 (намеренная ошибка)
В целом все хорошо, за исключением последних 2-х примеров, где есть намеренная ошибка. Давайте теперь проферим домен на корректность с помощью FILTER_VALIDATE_URL. И пару моментов для него:
- для FILTER_VALIDATE_URL обязателен http — поэтому подставляем его
- домены типа: еда.рф надо преобразовывать в xn--80ahc.xn--p1ai иначе проверку не пройдут
- FILTER_VALIDATE_URL считает любой URL верным, если в нем нет точки. http, sait — будет верным при проверке (а в любом домене должны быть 1 точка). Исправляем это так: считаем есть ли точка в домене, если нет, то добавляем в конец, за счет чего будут идти такие преобразования:
host => host.
bad => bad.
sa.ru => sa.ru
xn--80ahc.xn--p1ai => xn--80ahc.xn--p1ai
выражения типа host. и bad. уже не пройдут проверку
//если в домене нет ни 1 точки, то он уже не корректен — добавим точку в конец домена, чтобы FILTER_VALIDATE_URL пометил его как не верный
$domen_to = substr_count($domen,’.’);
$domen = ($domen_to == 0) ? $domen.’.’ : $domen;
if(filter_var(‘http://’.$domen, FILTER_VALIDATE_URL)) < echo ' корректен‘;
>
else echo ‘ введен не корректно!’;
Теперь осталось сделать последнюю проверку на корректность ввода доменного расширения, для этого вставьте внутрь условия, где написано, что домен введен корректно код:
/*доппроверка на разрешенные доменные расширения
если в домене на конце есть разрешенное расшерение, заменяем все выражение на yes — корректно*/
$domen_ra = preg_replace(‘/.*\.(ru|com|xn--p1ai)$/i’, ‘yes’, $domen);
if($domen_ra == ‘yes’) <
echo ‘ и имеет разрешенное расширение’; //Здесь уже делаем нужные действия, например, запись в БД
> else <
echo ‘ и имеет запрещенное расширение!’;
>
Вот такой получился у меня замысловатый код проверки домена на корректность. Лучшего решения в сети не видел, поэтому пришлось сделать самому.