Как конвертировать кириллический домен в Punycode в 1С:Предприятие?

Программист 1С v8.3 (Обычные формы) IT и автоматизация бизнеса
← На главную

При интеграции механизмов отправки электронной почты из платформы 1С разработчики часто сталкиваются с проблемой отправки писем на адреса с кириллическими доменами (например, в зоне .рф). Большинство почтовых серверов и ретрансляторов (relay) отклоняют отправку или возвращают ошибки, если адрес не преобразован в специальное представление Punycode (IDN / ACE) с префиксом xn--. Почтовые клиенты выполняют такую трансформацию автоматически, однако во встроенном языке 1С штатных глобальных процедур для кодирования Punycode не предусмотрено.

Разберем детально, как устроена обработка таких адресов, какие существуют технические ограничения и как реализовать надежный алгоритм кодирования средствами встроенного языка 1С.

Особенности и правила обработки кириллических e-mail

Перед тем как перейти к программной реализации, проанализируем важные стандарты и ограничения:

  1. Кодируется только доменная часть. Алгоритм Punycode стандартизирован исключительно для доменных имен — той части адреса, которая следует после символа @. Локальную часть (имя почтового ящика до знака @) кодировать в Punycode нельзя.
  2. Ограничения на имя ящика. Если почтовый ящик также назван кириллицей (например, пользователь@домен.рф), отправка возможна только при условии, что почтовый сервер отправителя и получателя полностью поддерживают протокол SMTPUTF8. Если такой поддержки нет, имя почтового ящика обязательно должно содержать только латинские символы.
  3. Ограничение длины в DNS. Длина отдельного сегмента домена после конвертации не может превышать 63 символа, а общая длина доменного имени — 253 символа.

Реализация кодирования Punycode на встроенном языке 1С

Рассмотрим по шагам готовый алгоритм преобразования строк в Punycode. Он разбивает переданный домен на сегменты (по разделителю .), проверяет наличие не-ASCII символов и применяет алгоритм кодирования только к тем частям, которые содержат национальные символы.

Разберем код основной функции разбиения и маршрутизации сегментов домена:


Функция КодироватьДомен(Домен) Экспорт
    
    Результат = "";
    МассивПодстрок = СтроковыеФункцииКлиентСервер.РазложитьСтрокуВМассивПодстрок(Домен, ".");
    
    Для Каждого Элемент Из МассивПодстрок Цикл
        
        ЕстьНеАНСИ = Ложь;
        
        Для ц = 1 По СтрДлина(Элемент) Цикл
            Если КодСимвола(Элемент, ц) >= 128 Тогда
                ЕстьНеАНСИ = Истина;
                Прервать;
            КонецЕсли;
        КонецЦикла;
        
        Если ЕстьНеАНСИ Тогда
            Результат = Результат + ?(ПустаяСтрока(Результат), "", ".") + "xn--" + Кодировать(НРег(Элемент));
        Иначе
            Результат = Результат + ?(ПустаяСтрока(Результат), "", ".") + Элемент;
        КонецЕсли;
        
    КонецЦикла;
    
    Возврат Результат;
    
КонецФункции

Теперь посмотрим на реализацию базового алгоритма кодирования отдельного сегмента домена:


Функция Кодировать(Знач Кириллица)
    
    INITIAL_N = 128;
    INITIAL_BIAS = 72;
    DELIMITER = "-";
    Integer_MAX_VALUE = 2147483647;
    TMAX = 26;
    TMIN = 1;
    DAMP = 700;
    BASE = 36;
    
    n = INITIAL_N;
    delta = 0;
    bias = INITIAL_BIAS;
    output = "";
    
    // Переносим все базовые ASCII-символы в результат
    b = 0;
    Для i = 1 По СтрДлина(Кириллица) Цикл 
        Если КодСимвола(Кириллица, i) < 128 Тогда
            output = output + Сред(Кириллица, i, 1);
            b = b + 1;
        КонецЕсли;
    КонецЦикла;
    
    // Добавляем разделитель при наличии базовых символов
    Если (b > 0) Тогда
        output = output + DELIMITER;
    КонецЕсли;
    
    h = b;
    Пока (h < СтрДлина(Кириллица)) Цикл
        m = Integer_MAX_VALUE;
        
        // Поиск минимального кода символа >= n
        Для i = 1 По СтрДлина(Кириллица) Цикл 
            c = КодСимвола(Кириллица, i);
            Если (c >= n) И (c < m) Тогда
                m = c;
            КонецЕсли;
        КонецЦикла;
        
        delta = delta + (m - n) * (h + 1);
        n = m;
        
        Для j = 1 По СтрДлина(Кириллица) Цикл
            c = КодСимвола(Кириллица, j);
            Если (c < n) Или (c < INITIAL_N) Тогда
                delta = delta + 1;
            КонецЕсли;
            
            Если (c = n) Тогда
                q = delta;
                k = BASE;
                
                Пока Истина Цикл
                    Если (k <= bias) Тогда
                        t = TMIN;
                    ИначеЕсли (k >= bias + TMAX) Тогда
                        t = TMAX;
                    Иначе   
                        t = k - bias;
                    КонецЕсли;
                    
                    Если (q < t) Тогда
                        Прервать;
                    КонецЕсли;
                    
                    output = output + Символ(digit2codepoint(t + (q - t) % (BASE - t)));
                    q = Цел((q - t) / (BASE - t));
                    k = k + BASE;
                КонецЦикла;
                
                output = output + Символ(digit2codepoint(q));
                bias = adapt(delta, h + 1, ?(h = b, Истина, Ложь));
                delta = 0;
                h = h + 1;
            КонецЕсли;
        КонецЦикла;
        
        delta = delta + 1;
        n = n + 1;
    КонецЦикла;
    
    Возврат output;
    
КонецФункции

Для корректного вычисления смещений и преобразования чисел в допустимые символы используются вспомогательные функции:


Функция digit2codepoint(d)
    
    Если (d < 26) Тогда
        Возврат d + КодСимвола("a");
    ИначеЕсли (d < 36) Тогда
        Возврат d - 26 + КодСимвола("0");
    КонецЕсли;
    
КонецФункции

Функция adapt(Знач delta, Знач numpoints, first)
    
    DAMP = 700;
    BASE = 36;
    TMAX = 26;
    TMIN = 1;
    SKEW = 38;
    
    Если (first) Тогда
        delta = Цел(delta / DAMP);
    Иначе
        delta = Цел(delta / 2);
    КонецЕсли;
    
    delta = delta + Цел(delta / numpoints);
    k = 0;
    
    Пока (delta > Цел(((BASE - TMIN) * TMAX) / 2)) Цикл
        delta = Цел(delta / (BASE - TMIN));
        k = k + BASE;
    КонецЦикла;
    
    Возврат k + Цел(((BASE - TMIN + 1) * delta) / (delta + SKEW));
    
КонецФункции

Альтернативные способы реализации

Если требуется обрабатывать сложные интернационализированные строки с диакритическими знаками или нестандартными алфавитами (где критически важна нормализация по стандартам IDNA2008), можно рассмотреть следующие альтернативы:

  1. Использование внешних компонент (Native API): Подключение скомпилированной библиотеки на основе libidn2 или ICU. Этот подход обеспечивает максимальное быстродействие и полную кроссплатформенность на серверах под управлением Windows и Linux.
  2. Использование классов .NET (только под Windows): Обращение к системному классу System.Globalization.IdnMapping, методы которого GetAscii и GetUnicode выполняют стандартизированное прямое и обратное преобразование.
  3. Использование системных утилит: Вызов утилиты idn2 в среде Linux через метод ЗапуститьПриложение.

В большинстве типовых сценариев отправки почты по доменам .рф встроенной функции на языке 1С оказывается полностью достаточно для решения проблемы с почтовым релеем.

← На главную