Работаем с кириллическими доменами

Собственно речь пойдет о том, как работать с кирилическими (и не только) доменами в PHP, Node.js, Bash/Zsh и Python. Почему такой выбор языков? Это стек проекта GeekJob.ru, над которым я работаю.

IDN (Internationalized Domain Names — интернационализованные доменные имена) — это доменные имена, которые содержат символы национальных алфавитов.

К IDN относятся как адреса с нелатинскими буквами на традиционных доменах верхнего уровня, так и нелатинские домены — домены верхнего уровня, составленные из букв нелатинских алфавитов планеты: кириллица, арабский алфавит и др. IDN домены также существуют в крупных международных доменных зонах.

Punycode — стандартизированный метод преобразования последовательностей Unicode-символов в так называемые ACE-последовательности (ASCII Compatible Encoding — кодировка, совместимая с ASCII), которые состоят только из алфавитно-цифровых символов, как это разрешено в доменных именах. Punycode был разработан для однозначного преобразования доменных имен в последовательность ASCII-символов.

Что из себя представляет такой домен в виде пуникода:

https://александр-майоров.рф
В виде punycode последовательности будет выглядеть так:
https://xn----7sbabkjf0beiqjsayfh.xn--p1ai/

В такой вид, например, приводит JS класс URL:

new URL('https://александр-майоров.рф')
href: "https://xn----7sbabkjf0beiqjsayfh.xn--p1ai/"
origin: "https://xn----7sbabkjf0beiqjsayfh.xn--p1ai"
protocol: "https:"
host: "xn----7sbabkjf0beiqjsayfh.xn--p1ai"
hostname: "xn----7sbabkjf0beiqjsayfh.xn--p1ai"

А каким образом получить назад читаемый вид? Я выбрал стратегию, в которой в базе хранится оригинальное название домена, без конвертации. Поэтому на бэкенде у меня такие ссылки преобразуются. Пойдем по порядку, как сделать это в Node.js ?

Node.js

В Ноде до недавнего времени модуль punycode был встроен, но затем был вынесен (пруф https://nodejs.org/api/punycode.html)

Теперь модуль существует независимо от ноды в виде Punycode.js

Устанавливаем:

npm install punycode --save

Ну и далее работаем так:

punycode.toUnicode('xn----7sbabkjf0beiqjsayfh.xn--p1ai');

Важно! Конвертер должен принимать домен, без префикса протокола. Иначе на выходе будет неожиданный результат.

PHP

В PHP работа с IDN встроена и достаточно просто вызвать встроенную функцию:

<?php
echo idn_to_utf8('xn----7sbabkjf0beiqjsayfh.xn--p1ai');

Пруф: https://www.php.net/manual/ru/function.idn-to-utf8.php

Python

В питоне уже встроена возможность работать с IDN доменами и есть кодек idna, и все это доступно в строках. Но есть нюансы.

Если мы хотим получить punycode, то мы можем вызвать метод encode у обычной строки:

uri = "александр-майоров.рф"
print(uri.encode("idna"))

И на выходе мы получаем бинарную строку. А вот если хотим сделать наоборот, то мы должны так же работать с бинарными строками. Поэтому если у вас на входе обычная строка, то чтобы ее преобразовать пишем такой код:

uri = bytearray('xn----7sbabkjf0beiqjsayfh.xn--p1ai', 'utf8')
print(uri.decode("idna"))

Bash/Zsh

Если же вам нужно работать с доменами в шеле, то тут тоже есть нужные утилиты, одна из которых idn

Она есть почти под все платформы. Умеет как кодировать, так и декодировать:

idn "александр-майоров.рф"
и наоборот:
idn -u "xn----7sbabkjf0beiqjsayfh.xn--p1ai"