Парсинг писем из предоставленных EML-файлов: заголовки и части MIME
Парсинг писем преобразует предоставленные байты EML в заголовки сообщения, части MIME и метаданные для проверки. Используйте парсер, учитывающий MIME, чтобы сохранять повторяющиеся заголовки и декодировать текст согласно объявленной кодировке; не разбивайте сообщение регулярными выражениями. Разбор поля From или Reply-To описывает предоставленное сообщение, но не подтверждает, кто его отправил.
Этот процесс начинается с разрешённого EML-файла, предоставленного для проверки. Подключение к почтовому ящику не требуется. Руководство по извлечению данных из документов рассматривает деловые поля читаемых документов; здесь же сначала нужно понять структуру заголовков и составных частей сообщения.
Пример конфигурации, а не отчёт о проведённом тестировании. Интерфейсы рабочего процесса описаны в каталоге; Ai Flowi не проверяла их в рабочей среде для этого процесса EML. Функция и примеры с искусственно созданными данными ниже определяют предлагаемый контракт приёмки предоставленных байтов сообщения.
1. Определите границы байтовых данных и результата
Вам нужны AI Flowi Workflow, предоставленный образец EML с искусственно созданными данными или иной разрешённый образец и проверяющий. Для детерминированного локального разбора не нужны учётная запись электронной почты или ключ API модели. Если позже вы добавите модель или внешний сервис, используйте собственную учётную запись или ключ API провайдера и оплачивайте использование отдельно.
Сохраните исходные байты. Перекодирование EML-файла произвольным средством чтения текста до разбора может изменить сведения о кодировке. Этот пример принимает только байты, ограничивает сообщение размером 1 МиБ, допускает посещение не более 64 узлов MIME и ограничивает каждую декодированную часть обычного текста размером 256 КиБ. Это выбранные ограничения демонстрации, а не ограничения продукта или заявление о всесторонней защите от вредоносных входных данных.
Предполагаемый результат содержит исходные повторяющиеся заголовки, декодированные значения заголовков, части обычного текста, метаданные других частей и предупреждения. Храните их раздельно. Предупреждение означает, что проверяющий должен изучить источник; успешно возвращённый объект JSON не является одобрением содержимого сообщения.
По теме: Транскрибация аудио в Google Docs.
2. Изучите искусственно созданное сообщение с повторяющимися заголовками
From: sender@example.invalid
Reply-To: replies@example.invalid
Subject: =?utf-8?b?Q2Fmw6k=?=
Message-ID: <synthetic@example.invalid>
X-Trace: first
X-Trace: second
Content-Type: text/plain; charset=utf-8
Content-Transfer-Encoding: quoted-printable
Caf=C3=A9
Тема в заголовке и тело кодируют «Café» разными способами. Сохраните оба вхождения X-Trace в исходном порядке. Адреса и идентификатор сообщения — специально заданные условные значения. Reply-To указывает адрес для ответа внутри сообщения, а не доказывает, что отправитель контролирует этот адрес.
Составное сообщение с альтернативными представлениями может содержать обычный текст и HTML-версию одного содержимого. Не объединяйте их как независимые факты. В этом примере HTML не отображается, а текстовые вложения не становятся основным текстовым телом. Вложенные сообщения остаются отдельными записями метаданных; их заголовки и тело не переносятся во внешнее сообщение.
3. Используйте стандартную библиотеку Python с явно заданной политикой разбора
Предлагаемая функция использует BytesParser(policy=policy.default), сохраняет raw_items() и записывает ошибки в сообщении, выявленные парсером. Сначала текст строго декодируется в соответствии с объявленной кодировкой; при неизвестной кодировке или недопустимой последовательности текста функция записывает предупреждение и создаёт текстовое представление в UTF-8 с заменами, явно помеченное как содержащее потери информации.
Функция проверки EML с заданными ограничениями
from email import policy
from email.parser import BytesParser
def parse_eml(raw):
if not isinstance(raw, bytes):
raise TypeError("Supply EML bytes")
if len(raw) > 1024 * 1024:
raise ValueError("EML exceeds example byte limit")
# ponytail: materializes the capped message before the part check; use a streaming parser for stricter hostile-input resource limits.
msg = BytesParser(policy=policy.default).parsebytes(raw)
out = {"headers": list(msg.raw_items()), "decoded_headers": [],
"plain_text_parts": [], "other_parts": [], "warnings": []}
out["decoded_headers"] = [(name, str(value)) for name, value in msg.items()]
for name in ("From", "Subject", "Message-ID"):
if not msg.get_all(name):
out["warnings"].append("missing " + name)
pending, seen = [msg], 0
while pending:
part = pending.pop()
seen += 1
if seen > 64:
raise ValueError("EML exceeds example part limit")
kind, filename = part.get_content_type(), part.get_filename()
for header in part.values():
out["warnings"].extend(type(d).__name__ for d in getattr(header, "defects", ()))
if part.get_content_disposition() == "attachment" or filename is not None or kind == "message/rfc822":
out["other_parts"].append({"type": kind, "filename": filename})
if filename and (filename in (".", "..") or any(c in filename for c in "/\\") or any(ord(c) < 32 or ord(c) == 127 for c in filename)):
out["warnings"].append("unsafe attachment name")
elif part.is_multipart():
pending.extend(reversed(list(part.iter_parts())))
elif kind == "text/plain":
payload = part.get_payload(decode=True) or b""
if len(payload) > 256 * 1024:
raise ValueError("Text part exceeds example byte limit")
charset = part.get_content_charset() or "ascii"
try:
text = payload.decode(charset, errors="strict")
except (LookupError, UnicodeError):
out["warnings"].append("charset failure: " + charset)
text = payload.decode("utf-8", errors="replace")
out["plain_text_parts"].append(text)
else:
out["other_parts"].append({"type": kind, "filename": filename})
out["warnings"].extend(type(d).__name__ for d in part.defects)
return outВызовите parse_eml(raw) с уже предоставленными байтами. Пример не открывает произвольный путь к файлу и не загружает содержимое из сети. Имена вложений остаются метаданными: даже имя, пытающееся выйти из папки повторениями двух точек и косой черты, никогда не используется как путь назначения. Функция сообщает о подозрительных разделителях и управляющих символах; эта проверка не очищает имена файлов и не обнаруживает вредоносное ПО.
4. Отдельно настройте передачу в рабочем процессе
В каталоге Python Interpreter описан так: Выполняйте код Python с необязательными импортами. Используйте print() для просмотра результата.
Его обязательные поля global_imports и python_code предоставляют интерфейс выполнения. Экспорт не определяет доступ к файлам, доступные импорты или способ оборачивания напечатанного JSON в results.
На мобильных устройствах таблицы примеров и рабочего процесса находятся в областях с прокруткой. Клавиатурный фокус позволяет изучать столбцы за пределами первоначально видимой области.
| Этап | Предлагаемые входные данные | Граница проверки |
|---|---|---|
| Python Interpreter | Задайте global_imports для модулей стандартной библиотеки, разрешённых средой выполнения; поместите проверенную функцию в python_code. | Подтвердите импорты и разрешённый механизм передачи байтов. После тестового вызова с искусственными данными изучите results с объявленным типом JSON. |
| JSON Operations | data: проверенный JSON парсера; выберите "Select Keys" или проверенное "JQ Expression" через operations. | Сохраняйте заголовки, части и предупреждения раздельно. Не предполагайте, что stdout становится разобранным объектом без обёртки. |
| JSON Object Builder, необязательно | field_names: один проверенный ключ записи, например parsed_message. | Изучите сгенерированный подключаемый вход и json_object. Синтаксис нескольких ключей и типы сгенерированных входов не экспортированы. |
Определите ожидаемый результат предлагаемыми проверками на искусственных данных, затем изучите импорты и результаты на безвредном примере в своей рабочей области. У Python Interpreter нет экспортированного входа загрузки EML. Не придумывайте соединение Read File с интерпретатором и не предполагайте наличие в нём файлового дескриптора. До передачи в рабочий процесс подтвердите доставку байтов и сопоставления напечатанного результата.
5. Сохраняйте небольшой набор проверок ошибок
| Тестовый пример | Ожидаемый результат проверки |
|---|---|
| Повторяющийся X-Trace и закодированные тема и тело | Оба исходных значения сохраняются; декодированные тема и тело сохраняют символ с диакритическим знаком. |
| Отсутствуют From, Subject или Message-ID | Выводите предупреждения об отсутствующих заголовках, не придумывая значения. |
| Составное альтернативное представление обычного текста и HTML | Сохраняйте обычный текст как текст, а HTML — как метаданные части без отображения. |
| Неизвестная кодировка, недопустимый UTF-8 или повреждённый base64 | Записывайте предупреждения об ошибках декодирования или об ошибках в сообщении, выявленных парсером; текст с заменами не является точным подтверждением исходного содержимого. |
| Имя вложения с попыткой выхода из папки и вложенное сообщение | Сохраняйте метаданные без записи файлов и переноса вложенного текста в основное тело. |
| Отсутствует граница составного сообщения | Покажите ошибку структуры сообщения, выявленную парсером, не считая повреждённую структуру обычным полным текстом. |
| Неверный тип входных данных или превышенный лимит сообщения, текста либо частей | Выдайте указанную ошибку до принятия записи проверки. |
До принятия контракта примените эти критерии к разрешённым примерам вашего формата сообщений. Храните ожидаемое предупреждение рядом с каждым примером, чтобы его отсутствие было видно при проверке. Парсер может принять повреждённые данные и показать ошибки структуры сообщения; отсутствие исключения не доказывает правильную структуру или подлинность письма.
Каковы пределы доверия к разбору писем и когда нужно остановиться?
From, Reply-To и Message-ID — предоставленные заголовки. Аутентификация при передаче требует подтверждающих данных от доверенной принимающей системы; вставленный заголовок об аутентификации всё равно остаётся предоставленным текстом. Эта страница не гарантирует обнаружение подмены или проверку почтового ящика и не содержит инструкций отправки или получения.
Используйте контролируемый локальный парсер, если сообщение содержит чувствительные данные и внешняя обработка не нужна. Не исполняйте вложения, не отображайте HTML и не загружайте изображения по ссылкам. Отклоняйте данные сверх лимитов до добавления последующих действий и требуйте проверки источника при любой потере информации во время декодирования.
Тарифы: Go от USD8 в месяц при годовой оплате (USD10 помесячно), Plus от USD16 (USD20 помесячно), Pro 100 от USD80 (USD100 помесячно), Pro 200 от USD160 (USD200 помесячно) и Pro 500 от USD400 (USD500 помесячно), от 5,000 до 250,000 Flowi Tokens в месяц с возможностью пополнения; 1 токен за каждый запуск узла; использование сторонних API и моделей не включено.
Подсчитывайте запуски интерпретатора и операций обработки лишь после настройки передач между этапами. Отклонённое сообщение должно сохранять причину и ссылку на источник, не становясь принятой записью дальнейшей обработки. Данные об измеренном расходе ресурсов рабочего процесса для этого предлагаемого варианта проверки EML не приведены.
Автор: Ai Flowi · Опубликовано .