При интеграции механизмов отправки электронной почты из платформы 1С разработчики часто сталкиваются с проблемой отправки писем на адреса с кириллическими доменами (например, в зоне .рф). Большинство почтовых серверов и ретрансляторов (relay) отклоняют отправку или возвращают ошибки, если адрес не преобразован в специальное представление Punycode (IDN / ACE) с префиксом xn--. Почтовые клиенты выполняют такую трансформацию автоматически, однако во встроенном языке 1С штатных глобальных процедур для кодирования Punycode не предусмотрено.
Разберем детально, как устроена обработка таких адресов, какие существуют технические ограничения и как реализовать надежный алгоритм кодирования средствами встроенного языка 1С.
Перед тем как перейти к программной реализации, проанализируем важные стандарты и ограничения:
@. Локальную часть (имя почтового ящика до знака @) кодировать в Punycode нельзя.пользователь@домен.рф), отправка возможна только при условии, что почтовый сервер отправителя и получателя полностью поддерживают протокол SMTPUTF8. Если такой поддержки нет, имя почтового ящика обязательно должно содержать только латинские символы.Рассмотрим по шагам готовый алгоритм преобразования строк в 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), можно рассмотреть следующие альтернативы:
libidn2 или ICU. Этот подход обеспечивает максимальное быстродействие и полную кроссплатформенность на серверах под управлением Windows и Linux.System.Globalization.IdnMapping, методы которого GetAscii и GetUnicode выполняют стандартизированное прямое и обратное преобразование.idn2 в среде Linux через метод ЗапуститьПриложение.В большинстве типовых сценариев отправки почты по доменам .рф встроенной функции на языке 1С оказывается полностью достаточно для решения проблемы с почтовым релеем.