Объединить две реальные MIME-части и разобрать неполный набор

Учебные задачи

Полный учебный модуль для отдельного локального почтового стенда: реальные IMAP/SMTP-контракты, свои сообщения/UID/папки и проверяемые эффекты. В ходе подготовки справочника почта и сеть не выполняются.

Где и как запускать

Полный клиентский общий модуль УчебнаяПочта85, толстое управляемое Windows-приложение; собственный loopback-сервер и отдельный полный модуль формы вызова.

Подготовка

  1. Новая пустая конфигурация УчебнаяПочта85, платформа8.5.1.1343, управляемое приложение и именно толстый Windows-клиент. Учебный клиент и отдельный тестовый почтовый сервер на одной машине; рабочие почтовые профили не используются.

  2. Установленный ИнтернетПочта использует SMTP/POP3/IMAP, не MAPI. Нативная справка прямо говорит: SSL есть, проверки сертификата клиента/сервера нет. Код этого урока не выдаётся за защищённую production-интеграцию.

  3. Ваш собственный локальный сервер слушает только127.0.0.1:1143(IMAP) и127.0.0.1:1025(SMTP). Исходящая доставка/relay/DNS и внешние подключения запрещены настройкой сервера; SMTP только принимает в вашу локальную тестовую очередь.

  4. Создайте нового отдельного пользователя lab85 с собственным ASCII-паролем не более64 символов и отдельной почтовой областью. IMAP поддерживает hierarchy с выбираемыми родительскими папками, UIDPLUS, Flagged/Deleted/Seen и обычную тестовую аутентификацию; SMTP поддерживает AUTH LOGIN для lab85.

  5. Локальная модель намеренно допускает обычную аутентификацию; это не шифрование и не безопасный рецепт для LAN/интернета. Настройка SSLIMAP устанавливается до нестандартного порта1143, потому что смена SSL-флага меняет порт по умолчанию.

  6. Только один клиент/автор и никакие параллельные доставщики. Квота тестового пользователя не более1МиБ, каждое учебное письмо не более4КиБ. Нет чужих сообщений, ссылок на реальные вложения и рабочих папок.

  7. Для первого урока вручную подготовьте папку LAB85_Read с ровно одним письмом: тема MAIL-LAB85-READ, простой текст MAIL-LAB85, без вложений, нечастичное, без Deleted. Этот исходный образец не удаляется.

  8. Для второго и четвёртого урока новые пространства LAB85_UUID/LAB85_MERGE_UUID создаёт только сам учебный код. IMAP-перенос требует UIDPLUS. INBOX используется только как место выхода из своей папки: его письма не выбираются, не помечаются и не очищаются.

  9. Общие → Общие модули → УчебнаяПочта85: Клиент(управляемое приложение)=Истина, Сервер/ВызовСервера/Глобальный/Привилегированный/ВнешнееСоединение=Ложь, повторное использование выключено. Основной полный модуль одного урока вставляется целиком, одноимённые функции не склеиваются.

  10. Обработки → ЗапускПочты85 → управляемая форма Форма: СогласенНаЛокальнуюПочту85 и Занято85 Булево исходно Ложь; ПарольУчебнойПочты85 Строка, Итог85 Строка неограниченной длины только для просмотра. Пароль не сохраняйте в настройках/истории; включите скрытое отображение поля в редакторе формы.

  11. Отдельный полный модуль формы — дополнительный блок. Команда ВыполнитьПочту85 назначается кнопке, Действие=ВыполнитьПочту85, вызывает клиентский УчебнаяПочта85.ВыполнитьПример85(согласие,пароль); после успеха/ошибки реквизит пароля очищается.

  12. F7 и обновление только своего стенда. Сначала читающий урок, затем собственный цикл IMAP, затем SMTP и MIME. Разрешение на каждый будущий ручной сетевой опыт отдельно; здесь его выполнения нет.

  13. У MIME-урока две ASCII-части message/partial создаются из настоящих исходных MIME-байтов и разбираются УстановитьИсходныеДанные; в собственную папку добавляются по IMAP. Номера1/2, общий id, total2, 7bit и разрыв по CRLF соответствуют RFC2046 §5.2.2: https://www.rfc-editor.org/rfc/rfc2046.html#section-5.2.2 .

  14. runtimeVerifiedScenarios=0:1С/серверы/GUI не запускались, реальная почта и сеть не вызывались, сообщения не отправлялись/удалялись и новые резервные копии/архивы не создавались. Приведённые эффекты относятся только к будущему отдельно разрешённому ручному выполнению.

Функция ВыполнитьПример85(Согласие, Пароль) Экспорт
    Если Не Согласие Тогда
        ВызватьИсключение "Нужен свой локальный изолированный почтовый стенд УчебнаяПочта85 и согласие";
    КонецЕсли;
    Если ТипЗнч(Пароль) <> Тип("Строка") Или СтрДлина(Пароль) = 0 Или СтрДлина(Пароль) > 64 Тогда
        ВызватьИсключение "Нужен отдельный ASCII-пароль своего локального тестового пользователя";
    КонецЕсли;
    Почта = Неопределено;
    СвояПапка = "LAB85_MERGE_" + Строка(Новый УникальныйИдентификатор);
    Попытка
        Почта = Новый ИнтернетПочта;
        Почта.Подключиться(СвойПрофиль85(Пароль, Ложь), ПротоколИнтернетПочты.IMAP);
        Если Почта.ПолучитьПочтовыеЯщики().Найти(СвояПапка) <> Неопределено Тогда
            ВызватьИсключение "Своя случайная папка существует; чужое содержимое не используется";
        КонецЕсли;
        Почта.СоздатьПочтовыйЯщик(СвояПапка);
        Почта.ТекущийПочтовыйЯщик = СвояПапка;
        Если Почта.ПолучитьКоличествоСообщений() <> 0 Тогда
            ВызватьИсключение "Своя новая папка не пуста";
        КонецЕсли;
        Для Номер = 1 По 2 Цикл
            Часть = СоздатьСвоюMIMEЧасть85(СвояПапка, Номер);
            Отказы = Почта.Послать(Часть, ОбработкаТекстаИнтернетПочтовогоСообщения.НеОбрабатывать, ПротоколИнтернетПочты.IMAP);
            Если ТипЗнч(Отказы) <> Тип("Соответствие") Тогда
                ВызватьИсключение "Нужен настоящий ответ IMAP-добавления своей MIME-части";
            КонецЕсли;
            Если Отказы.Количество() <> 0 Или Почта.ПолучитьКоличествоСообщений() <> Номер Тогда
                ВызватьИсключение "Своё добавление MIME-части не подтверждено";
            КонецЕсли;
        КонецЦикла;
        Идентификаторы = Почта.ПолучитьИдентификаторы(Новый Массив);
        Если Идентификаторы.Количество() <> 2 Или Идентификаторы[0] = Идентификаторы[1] Тогда
            ВызватьИсключение "Нужны два разных настоящих UID своей папки, не придуманные значения";
        КонецЕсли;
        Части = Почта.Выбрать(Ложь, Идентификаторы, Ложь);
        Если Части.Количество() <> 2 Тогда
            ВызватьИсключение "Получены не обе реальные серверные части";
        КонецЕсли;
        Для Каждого Часть Из Части Цикл
            Если Не Часть.Частичное Или Часть.Идентификатор.Количество() <> 1 Тогда
                ВызватьИсключение "Сервер должен сохранить обе собственные MIME-части";
            КонецЕсли;
            Если Идентификаторы.Найти(Часть.Идентификатор[0]) = Неопределено
                Или СтрНайти(Часть.Тема, СвояПапка + " part ") <> 1 Тогда
                ВызватьИсключение "Реальный UID и тема своей части не подтверждены";
            КонецЕсли;
        КонецЦикла;
        НеполныйНабор = Новый Массив;
        НеполныйНабор.Добавить(Части[0]);
        НеСобрано = Почта.ОбъединитьЧастичные(НеполныйНабор);
        Если НеСобрано.Количество() <> 1 Или Не НеСобрано[0].Частичное Тогда
            ВызватьИсключение "Одной из двух частей недостаточно для полного письма";
        КонецЕсли;
        Результаты = Почта.ОбъединитьЧастичные(Части);
        Если Результаты.Количество() <> 1 Тогда
            ВызватьИсключение "Ожидается одно реально объединённое сообщение";
        КонецЕсли;
        Собрано = Результаты[0];
        Если Собрано.Частичное Или Собрано.Тема <> СвояПапка Или Собрано.Тексты.Количество() <> 1
            Или Собрано.Идентификатор.Количество() <> 2 Тогда
            ВызватьИсключение "Объединённый MIME-объект не соответствует своему полному письму";
        КонецЕсли;
        Для Каждого Идентификатор Из Идентификаторы Цикл
            Если Собрано.Идентификатор.Найти(Идентификатор) = Неопределено Тогда
                ВызватьИсключение "Объединённый результат потерял настоящий серверный UID части";
            КонецЕсли;
        КонецЦикла;
        Тело = СокрЛП(СтрЗаменить(Собрано.Тексты.Получить(0).Текст, Символы.ВК, ""));
        Ожидаемое = "First:" + СвояПапка + Символы.ПС + "Second:" + СвояПапка;
        Если Тело <> Ожидаемое Тогда
            ВызватьИсключение "Обе строки тела своего MIME-письма не объединены в правильном порядке";
        КонецЕсли;
        Остались = Почта.ПолучитьИдентификаторы(Новый Массив);
        Если Почта.ТекущийПочтовыйЯщик <> СвояПапка Или Почта.ПолучитьКоличествоСообщений() <> 2
            Или Остались.Количество() <> 2 Тогда
            ВызватьИсключение "Серверная папка изменилась; удаление запрещено";
        КонецЕсли;
        Для Каждого Идентификатор Из Идентификаторы Цикл
            Если Остались.Найти(Идентификатор) = Неопределено Тогда
                ВызватьИсключение "В серверной папке появились другие сообщения; удаление запрещено";
            КонецЕсли;
        КонецЦикла;
        Почта.ТекущийПочтовыйЯщик = "INBOX";
        Почта.УдалитьПочтовыйЯщик(СвояПапка);
        Если Почта.ПолучитьПочтовыеЯщики().Найти(СвояПапка) <> Неопределено Тогда
            ВызватьИсключение "Удаление только своей папки с двумя своими частями не подтверждено";
        КонецЕсли;
        Почта.Отключиться();
        Почта = Неопределено;
        Возврат "Неполный набор остался частичным; две реальные серверные части собраны в RAM, обе строки и два настоящих UID проверены. Удалена только собственная подтверждённая папка с двумя своими частями.";
    Исключение
        ЗакрытьПослеОшибки85(Почта);
        ВызватьИсключение "MIME-опыт не завершён. Свою папку " + СвояПапка + " проверьте вручную; автоматического удаления или повторного IMAP-добавления после ошибки нет.";
    КонецПопытки;
КонецФункции

Функция СвойПрофиль85(Пароль, ДляSMTP)
    Профиль = Новый ИнтернетПочтовыйПрофиль;
    Профиль.АдресСервераPOP3 = "";
    Профиль.АдресСервераIMAP = "";
    Профиль.АдресСервераSMTP = "";
    Профиль.Таймаут = 15;
    Если ДляSMTP Тогда
        Профиль.ИспользоватьSSLSMTP = Ложь;
        Профиль.АдресСервераSMTP = "127.0.0.1";
        Профиль.ПортSMTP = 1025;
        Профиль.ПользовательSMTP = "lab85";
        Профиль.ПарольSMTP = Пароль;
        Профиль.АутентификацияSMTP = СпособSMTPАутентификации.Login;
        Профиль.ТолькоЗащищеннаяАутентификацияSMTP = Ложь;
    Иначе
        Профиль.ИспользоватьSSLIMAP = Ложь;
        Профиль.АдресСервераIMAP = "127.0.0.1";
        Профиль.ПортIMAP = 1143;
        Профиль.ПользовательIMAP = "lab85";
        Профиль.ПарольIMAP = Пароль;
        Профиль.ТолькоЗащищеннаяАутентификацияIMAP = Ложь;
    КонецЕсли;
    Возврат Профиль;
КонецФункции

Процедура ЗакрытьПослеОшибки85(Почта)
    Если Почта <> Неопределено Тогда
        Попытка
            Почта.Отключиться();
        Исключение
            ВызватьИсключение "Опыт и отключение не подтверждены; проверьте только свой локальный сервер. Сырые ошибки и пароль не выводятся.";
        КонецПопытки;
    КонецЕсли;
КонецПроцедуры

Функция СоздатьСвоюMIMEЧасть85(Маркер, Номер)
    Перевод = Символы.ВК + Символы.ПС;
    Если Номер = 1 Тогда
        Тело = "Subject: " + Маркер + Перевод
            + "Message-ID: <" + Маркер + "@lab.invalid>" + Перевод
            + "MIME-Version: 1.0" + Перевод
            + "Content-Type: text/plain; charset=us-ascii" + Перевод
            + "Content-Transfer-Encoding: 7bit" + Перевод + Перевод
            + "First:" + Маркер + Перевод;
    ИначеЕсли Номер = 2 Тогда
        Тело = "Second:" + Маркер + Перевод;
    Иначе
        ВызватьИсключение "У своего MIME-письма ровно две части с номерами1 и2";
    КонецЕсли;
    Исходный = "From: lab85@lab.invalid" + Перевод
        + "To: sink85@lab.invalid" + Перевод
        + "Date: Fri, 09 Oct 2026 10:00:00 +0000" + Перевод
        + "Subject: " + Маркер + " part " + Строка(Номер) + Перевод
        + "Message-ID: <" + Маркер + "-part" + Строка(Номер) + "@lab.invalid>" + Перевод
        + "MIME-Version: 1.0" + Перевод
        + "Content-Type: message/partial; id=""" + Маркер + "@lab.invalid""; number=" + Строка(Номер) + "; total=2" + Перевод
        + "Content-Transfer-Encoding: 7bit" + Перевод + Перевод + Тело;
    Часть = Новый ИнтернетПочтовоеСообщение;
    Часть.УстановитьИсходныеДанные(ПолучитьДвоичныеДанныеИзСтроки(Исходный, КодировкаТекста.UTF8, Ложь));
    Если Не Часть.Частичное Тогда
        ВызватьИсключение "Настоящий MIME-разбор должен распознать message/partial";
    КонецЕсли;
    Возврат Часть;
КонецФункции
Управляемая команда локального почтового опыта

УчебнаяПочта85 → Обработки → ЗапускПочты85 → Формы → Форма → Модуль

Полный модуль своей формы: команда ВыполнитьПочту85, Действие=ВыполнитьПочту85; согласие и отдельный пароль передаются клиентскому общему модулю.

&НаКлиенте
Процедура ВыполнитьПочту85(Команда)
    Если Не СогласенНаЛокальнуюПочту85 Или Занято85 Тогда
        ВызватьИсключение "Нужно отдельное согласие на свой локальный почтовый опыт";
    КонецЕсли;
    Занято85 = Истина;
    Попытка
        Итог85 = УчебнаяПочта85.ВыполнитьПример85(СогласенНаЛокальнуюПочту85, ПарольУчебнойПочты85);
    Исключение
        ПарольУчебнойПочты85 = "";
        Занято85 = Ложь;
        Итог85 = "Опыт не подтверждён. Проверьте только свой локальный стенд; подробности с учётными данными не отображаются.";
        ВызватьИсключение "Локальный почтовый опыт не завершён";
    КонецПопытки;
    ПарольУчебнойПочты85 = "";
    Занято85 = Ложь;
КонецПроцедуры

Разбор по шагам

  1. Создаётся собственная пустая случайная папка, две MIME-части формируются по RFC2046 и реально разбираются1С.

  2. IMAP-добавления подтверждаются, затем два разных UID получаются с настоящего сервера, не задаются вручную.

  3. Обе реально полученные части проверяются по Частичное, собственной теме и серверным UID.

  4. Одна из двух частей передаётся ОбъединитьЧастичные: ожидается прежний частичный объект, не полный результат.

  5. Полный набор возвращает одно нечастичное письмо; тема, две строки в порядке1/2 и оба настоящих UID проверяются.

  6. Серверные два UID и количество снова подтверждаются: объединение было в RAM, не на сервере.

  7. Удаляется только своя подтверждённая папка с двумя своими частями после выхода в INBOX; при ошибке удаления нет.

Как проверить результат

  1. Реальный MIME-разбор даёт Частичное=Истина, readonly-флаг не присваивается.

  2. Два настоящих серверных UID различаются.

  3. Неполный набор остаётся частичным.

  4. Полный результат содержит тему-маркер, First/Second и оба серверных UID.

  5. Объединение не объявляется серверной операцией; удаление только собственного подтверждённого пространства.

Ожидаемый результат

Ожидаемые результаты относятся к будущему отдельно разрешённому ручному опыту. Реальная1С/почта/сеть не запускались, runtimeVerifiedScenarios=0.

На что обратить внимание

  1. Выбрать() по умолчанию удаляет сообщения. Для чтения указаны Ложь и Ложь: не удалять и не отмечать прочитанными. Последний флаг актуален для IMAP, для POP3 допустима только Истина.

  2. Пустой или неуказанный второй массив Выбрать означает получить все письма, не получить ничего. Во всех уроках перед выборкой проверяется строго положительное собственное число UID.

  3. Фильтр ПолучитьЗаголовки/ПолучитьИдентификаторы действует только IMAP; POP3 игнорирует его. Неизвестный ключ фильтра тоже может игнорироваться. Читающая папка содержит ровно один собственный проверяемый образец.

  4. ПолучитьЗаголовки возвращает ограниченный набор полей: тело/вложения ещё не получены. Полное письмо отдельно загружается по настоящему UID и проверяется по теме, составу и нормализованному простому тексту.

  5. UID уникален в пределах папки, не всей системы. После переноса или переименования код снова получает реальные идентификаторы, не использует порядковый номер строки или старый UID другого ящика.

  6. СимволРазделитель получен от своего сервера, не жёстко задан /. Иерархия создаётся только внутри своего случайного корня; сервер без подходящей иерархии не объявляется совместимым.

  7. Подписка и выбор текущей папки — разные действия. Список подписанных имён проверяется после подписки и отмены; изменение подписки не отправляет письмо.

  8. Послать(...,IMAP) добавляет сообщение в текущую папку IMAP, это не SMTP-доставка получателю. SMTP-урок отдельно использует SMTP и локальную тестовую очередь.

  9. Послать возвращает Соответствие отказов получателей. Пустое соответствие означает принятие на этом этапе, не подтверждение окончательной доставки или прочтения; исключение не разрешает слепой повтор отправки.

  10. SSL-флаг/ТолькоЗащищеннаяАутентификация не доказывает проверку сертификата. Нативный ИнтернетПочта этого не предоставляет; профиль данного урока допускается только на отдельном loopback-стенде без реальных секретов.

  11. АутентификацияSMTP/СпособSMTPАутентификации.Login устарели и не рекомендуются с8.3.1. Здесь они явно показывают контракт отдельного тестового AUTH LOGIN в толстом клиенте; это не рекомендация для новой production-интеграции.

  12. УстановитьФлагиСообщений принимает соответствие UID→ФлагиИнтернетПочтовогоСообщения, не массив UID. Истина ставит, Ложь снимает, Неопределено оставляет флаг; неподдерживаемый флаг сервер может проигнорировать.

  13. Урок меняет Flagged в реально полученном объекте флагов, перечитывает результат и требует поддержку Flagged/Deleted. Снимок других флагов не атомарен; поэтому нет сторонних клиентов и доставщиков.

  14. УдалитьСообщения при IMAP сначала ставит Deleted, ОтменитьУдалениеСообщений снимает пометку, ОчиститьУдаленныеСообщения удаляет ВСЕ Deleted текущей папки. Очистка разрешена только в своей папке с одним подтверждённым собственным письмом.

  15. УдалитьПочтовыйЯщик удаляет папку со всем содержимым. Второй урок удаляет лишь свои проверенные пустые папки; MIME-урок — только свою папку с двумя подтверждёнными UID. Восстановление/корзина не обещаются.

  16. ПереместитьВПочтовыйЯщик требует UIDPLUS и существующую собственную папку. При ошибке/частичном эффекте код ничего не удаляет и не повторяет автоматически; проверьте своё пространство вручную.

  17. message/partial — не multipart/mixed и не вручную установленный readonly-флаг Частичное. Две реально разобранные части имеют общий MIME-id, но разные Message-ID и настоящие UID своей папки.

  18. ОбъединитьЧастичные работает с реальными полученными объектами в RAM: неполный набор остаётся частичным, полный набор проверяется по двум строкам и двум настоящим UID. Это не серверное объединение писем.

  19. Нормализация текста допускает только обычные конечные пробелы/переводы строк данного маленького ASCII-образца. Это не проверка произвольного бинарного вложения и не лимит потока получения; квота и неизменяемый тестовый сервер обязательны.

  20. Пароль и сырые серверные исключения не выводятся. Очистка реквизита не гарантирует стирание всех копий в RAM. Ошибка отключения тоже сообщается явно, а не проглатывается.

  21. Отключиться закрывает сессию, но не является откатом отправки, перемещения, пометок или удаления. После сбоя фактическое состояние проверяется только на своём сервере.

  22. 1С/SMTP/IMAP не запускались: runtimeVerifiedScenarios=0. Статические тесты проверяют контракт и код, не фактическое поведение конкретного почтового сервера.

Пример не запускался в вашей базе. Перед применением проверьте его в указанном учебном контексте.

Не хотите разбираться сами?

Адаптируем это решение под вашу базу. Оценка бесплатно.

Оставить задачу

1С, 1С:Предприятие — товарные знаки ООО «1С». Сайт не является официальным представительством фирмы «1С» и не аффилирован с ней.