Введение
Кодирование доменных имен 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.
Punycode is a representation of Unicode with the limited ASCII character subset used for Internet hostnames. Using Punycode, host names containing Unicode characters are transcoded to a subset of ASCII consisting of letters, digits, and hyphens, which is called the letter–digit–hyphen (LDH) subset. For example, München (German name for Munich) is encoded as Mnchen 3ya. While the Domain Name System (DNS) technically supports arbitrary sequences of octets in domain name labels, the DNS standards recommend the use of the LDH subset of ASCII conventionally used for host names, and require that string comparisons between DNS domain names should be case insensitive. The Punycode syntax is a method of encoding strings containing Unicode characters, such as internationalized domain names (IDNA), into the LDH subset of ASCII favored by DNS. It is specified in IETF Request for Comments 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, а более сложная схема, описанная ниже, которая позволяет объединять числа без каких-либо разделителей.
These numbers are strictly increasing. For the second and subsequent inserted characters, the difference between the number and the previous one is written. The number is encoded using the letters "a" through "z" and the digits "0" through "9". It is not base 36 but a more complex scheme described below, which allows the numbers to be concatenated, with nothing separating them.
Кодирование числа переменной длины
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"), и т. д.
A number system with little endian ordering is used which allows variable length codes without separate delimiters: a digit lower than a threshold value marks that it is the most significant digit, hence the end of the number. The threshold value depends on the position in the number and also on previous insertions, to increase efficiency. Correspondingly the weights of the digits vary. In this case a number system with 36 symbols is used, with the case insensitive 'a' through 'z' equal to the decimal numbers 0 through 25, and '0' through '9' equal to the decimal numbers 26 through 35. Thus "kva", corresponds to the decimal number string "10 21 0". To decode this string of symbols, a sequence of thresholds will be needed, in this case it's (1, 1, 26, 26, ). The weight (or place value) of the least significant digit is always 1: 'k' (=10) with a weight of 1 equals 10. After this, the weight of the next digit depends on the first threshold: generally, for any n, the weight of the (n+1) th digit is the weight of the previous one times (36 − threshold of the n th digit). So the second symbol has a place value of 36 minus the previous threshold value, in this case, 35. Therefore, the sum of the first two symbols 'k' (=10) and 'v' (=21) is 10 × 1 + 21 × 35. Since the second symbol is not less than its threshold value of 1, there is more to come. However, since the third symbol in this example is 'a' (=0), we may ignore calculating its weight. Therefore, "kva" represents the decimal number (10 × 1) + (21 × 35) = 745. Number 745 will be encoded as 10 + 21 × 35 + 0 (base 35 used for second digit, the most significant digit 0 needed as terminator), 10 → 'k', 21 → 'v', 0 → 'a', so "bücher" → "bcher kva". The thresholds themselves are determined for each successive encoded character by an algorithm keeping them between 1 and 26 inclusive. The case can then be used to provide information about the original case of the string. Because special characters are sorted by their code points by encoding algorithm, for the insertion of a second special character in "bücher", the first possibility is "büücher" with code "bcher kvaa", the second "bücüher" with code "bcher kvab", etc. After "bücherü" with code "bcher kvae" comes codes representing insertion of ý, the Unicode character following ü, starting with "ýbücher" with code "bcher kvaf" (different from "übücher" coded "bcher jvab"), etc.
Префикс 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).