Введение

Кодирование доменных имен Unicode
Punycode — это представление Unicode с использованием ограниченного подмножества символов ASCII, применяемого для имен хостов в Интернете. С помощью Punycode имена хостов, содержащие символы Unicode, транскодируются в подмножество ASCII, состоящее из букв, цифр и дефисов, которое называется подмножеством "буквы-цифры-дефис" (LDH). Например, München (немецкое название Мюнхена) кодируется как Mnchen 3ya. Хотя система доменных имен (DNS) технически поддерживает произвольные последовательности октетов в метках доменных имен, стандарты DNS рекомендуют использовать подмножество LDH ASCII, традиционно применяемое для имен хостов, и требуют, чтобы сравнение строк между DNS-доменными именами было нечувствительно к регистру. Синтаксис Punycode — это метод кодирования строк, содержащих символы Unicode, таких как интернационализированные доменные имена (IDNA), в подмножество LDH ASCII, предпочтительное для DNS. Он описан в IETF RFC 3492.

Описание

Как указано в RFC 3492, "Punycode является реализацией более общего алгоритма, называемого Bootstring, который позволяет строкам, составленным из небольшого набора "базовых" кодовых точек, однозначно представлять любую строку кодовых точек, взятую из большего набора". Punycode определяет параметры для общего алгоритма Bootstring, чтобы соответствовать характеристикам текста Unicode. В этом разделе демонстрируется процедура кодирования Punycode на примере строки "bücher" (Bücher – немецкое слово, означающее "книги"), которая преобразуется в метку "bcher kva". Для упрощения алгоритмов кодирования и декодирования не предпринималось попыток предотвратить кодирование некоторых значений в недопустимые значения Unicode; однако их следует проверять и обнаруживать в процессе декодирования. Punycode разработан для работы со всеми системами письменности и для самооптимизации, стремясь адаптироваться к диапазонам наборов символов внутри строки по мере работы. Он оптимизирован для случаев, когда строка состоит из нуля или более символов ASCII и, дополнительно, символов только из одной другой системы письменности, но способен обрабатывать любую произвольную строку Unicode. Следует отметить, что для использования в DNS строка доменного имени предполагается нормализованной с помощью nameprep и (для доменов верхнего уровня) отфильтрованной в соответствии с официально зарегистрированной таблицей языков перед кодированием Punycode, а протокол DNS устанавливает ограничения на допустимую длину результирующей строки Punycode.

Разделение символов ASCII

Во-первых, все символы ASCII в строке копируются с ввода на выход, игнорируя любые другие символы. Например, "bücher" копируется в "bcher". Если какие-либо символы были скопированы, то есть если во входной строке был хотя бы один символ ASCII, к выходной строке добавляется символ ASCII-дефиса (например, "bücher" → "bcher-", но "ü" → ""). Следует отметить, что дефис сам по себе является символом ASCII. Таким образом, он может присутствовать во входной строке и, если это так, будет скопирован в выходную строку. Это не создает неоднозначности: если в выходной строке есть дефис, то добавленный всегда будет последним. Он отмечает конец последовательности символов ASCII.

Кодирование не-ASCII символов

Не-ASCII символы сортируются по значению Unicode, от меньшего к большему (если символ встречается несколько раз, они сортируются по позиции). Каждый из них затем кодируется в виде единого числа. Это число определяет как место для вставки символа, так и сам символ для вставки. *i* – это индекс в результирующей строке, куда вставляется код, начиная с 0 (для вставки в начало). *n* – количество возможных точек вставки (текущая длина результирующей строки плюс один). *j* – это кодовая точка Unicode вставляемого символа минус 127. Закодированное число равно *n* × *j* + *i*. Деля на *n* и находя остаток, декодер может определить *j* и *i*. Существует шесть возможных мест для вставки символа в строку "bcher" (включая перед первым символом и после последнего). Между последней кодовой точкой ASCII (127 = 0x7F, конец ASCII) и "ü" (кодовая точка 252 = 0xFC, см. Дополнение Unicode Latin-1) находится 124 кодовых точки. "ü" вставляется в позицию 1, после "b". Таким образом, кодировщик добавит число, а декодер сможет восстановить его, используя и . Эти числа строго возрастают. Для второго и последующих вставленных символов записывается разница между текущим числом и предыдущим. Число кодируется с использованием букв "a"–"z" и цифр "0"–"9". Это не система счисления с основанием 36, а более сложная схема, описанная ниже, которая позволяет объединять числа без каких-либо разделителей.

Кодирование числа переменной длины

Punycode использует обобщенные целые числа переменной длины для представления этих значений. Например, вот как используется "kva" для представления кодового номера 745: используется система счисления с порядком младшего значащего байта (little endian), которая позволяет использовать коды переменной длины без отдельных разделителей: цифра, меньшая порогового значения, указывает, что это самая старшая цифра, а значит, конец числа. Пороговое значение зависит от позиции в числе, а также от предыдущих вставок, чтобы повысить эффективность. Соответственно, веса цифр различаются. В этом случае используется система с 36 символами, где буквы 'a'–'z' (без учета регистра) соответствуют десятичным числам от 0 до 25, а цифры '0'–'9' – десятичным числам от 26 до 35. Таким образом, "kva" соответствует десятичной строке "10 21 0". Для декодирования этой строки символов потребуется последовательность пороговых значений, в данном случае (1, 1, 26, 26, ). Вес (или разряд) наименее значащей цифры всегда равен 1: 'k' (=10) с весом 1 равен 10. После этого вес следующей цифры зависит от первого порогового значения: в общем случае, для любого n, вес (n+1)-й цифры равен весу предыдущей, умноженному на (36 − пороговое значение n-й цифры). Таким образом, второй символ имеет разряд, равный 36 минус предыдущее пороговое значение, то есть 35. Следовательно, сумма первых двух символов 'k' (=10) и 'v' (=21) равна 10 × 1 + 21 × 35. Поскольку второй символ не меньше своего порогового значения 1, будет продолжение. Однако, поскольку третий символ в этом примере – 'a' (=0), мы можем не вычислять его вес. Следовательно, "kva" представляет собой десятичное число (10 × 1) + (21 × 35) = 745. Число 745 будет закодировано как 10 + 21 × 35 + 0 (основание 35 используется для второй цифры, наиболее значащая цифра 0 нужна в качестве терминатора), 10 → 'k', 21 → 'v', 0 → 'a', поэтому "bücher" → "bcher kva". Сами пороговые значения определяются для каждого последовательно кодируемого символа алгоритмом, который поддерживает их в диапазоне от 1 до 26 включительно. Регистр символов затем можно использовать для передачи информации об исходном регистре строки. Поскольку специальные символы сортируются по своим кодовым точкам алгоритмом кодирования, для вставки второго специального символа в "bücher" первой возможностью будет "büücher" с кодом "bcher kvaa", второй – "bücüher" с кодом "bcher kvab" и т. д. После "bücherü" с кодом "bcher kvae" следуют коды, представляющие вставку ý, символа Unicode, следующего за ü, начиная с "ýbücher" с кодом "bcher kvaf" (отличается от "übücher", закодированного как "bcher jvab"), и т. д.

Префикс ACE для интернационализированных доменных имен

Чтобы предотвратить ошибочное декодирование Punycode из-за дефисов в неинтернационализированных доменных именах, к последовательностям Punycode в интернационализированных доменных именах добавляется префикс "xn". Это называется ACE (ASCII Compatible Encoding). Таким образом, доменное имя "bücher.tld" в URL будет представлено как "xn--bcher-kva.tld".

Примеры

В следующей таблице приведены примеры кодировок Punycode для различных типов ввода. Ввод Punycode Описание Пустая строка. Только символы ASCII, один, строчная. Только символы ASCII, один, прописная. Только символы ASCII, один, цифра. Только символы ASCII, один, дефис. Только символы ASCII, два дефиса. Только символы ASCII, более одного, без дефисов. Только символы ASCII, один дефис. Только символы ASCII с пробелами. Только символы ASCII, смешанные символы. Без символов ASCII, один кириллический символ. Без символов ASCII, один символ Latin 1 Supplement. Без символов ASCII, один греческий символ. Без символов ASCII, один символ CJK. Без символов ASCII, один эмодзи. Без символов ASCII, более одного символа. Смешанная строка, с одним символом, не являющимся ASCII. Двойная кодировка Punycode для "München". Смешанная строка, с одним символом, не являющимся ASCII, и дефисом. Смешанная строка, с одним пробелом, одним дефисом и одним символом, не являющимся ASCII. Смешанная строка, два не-ASCII символа. Русский, без ASCII. Тайский, без ASCII. Корейский, без ASCII. Японский, без ASCII. Японский с ASCII. Смешанные не-ASCII сценарии (Latin 1 Supplement и CJK).