Неправильное декодирование в почтовом модуле Python3 - PullRequest
0 голосов
/ 20 декабря 2018

Я недавно столкнулся с файлом EML, который я хотел проанализировать с модулем электронной почты Python.В заголовке from был следующий текст:

From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrp?=
=?utf-8?b?g6g=?=" <email@address.com>

Таким образом, имя закодировано в 2 частях.Когда я объединяю код и декодирую его вручную в шестнадцатеричный формат, я получаю следующий результат, который является правильной строкой UTF-8:

e5 bd ad e4 bb a5 e5 9b bd 2f e7 ac ac e4 ba 8c e4 ba 8b e4 b8 9a e9 83 a8 e9 a1 b9 e7 9b ae e9 83 a8 2f e7 ac ac e4 ba 8c e4 ba 8b e4 b8 9a e9 83 a8

Однако, когда я вызываю анализатор электронной почты Python parse, последний3 байта не декодируются правильно.Вместо этого, когда я читаю значения message['from'], появляются суррогаты:

dce9:20:dc83:dca8

Поэтому, когда я, например, хочу напечатать строку, она заканчивается на

UnicodeEncodeError('utf-8', '彭以国/第二事业部项目部/第二事业\udce9\udc83\udca8', 17, 18, 'surrogates not allowed')

Когда я соединяю 2 закодированных части в заголовке From в одну, которая выглядит следующим образом:

From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrpg6g=?=" <email@address.com>

Строка правильно декодируется библиотекой и может быть напечатана очень хорошо.

Это ошибка внутри почтового модуля Python?Допустимо ли двойное кодированное значение даже в стандарте EML?

Вот пример файла EML + код Python для воспроизведения неправильного декодирования (на самом деле это не вызывает исключения, что происходит позже, т. Е. С невозможностью SQLAlchemyзакодировать строку обратно в UTF-8)

EML:

Content-Type: multipart/mixed; boundary="===============2193163039290138103=="
MIME-Version: 1.0
Date: Wed, 25 Aug 2018 19:21:23 +0100
From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrp?=
 =?utf-8?b?g6g=?=" <addr@addr.com>
Message-Id: <12312924463694945698.525C0AC435BA7D0E@xxxxx.com>
Subject: Sample subject
To: addr@addr.com

--===============2193163039290138103==
MIME-Version: 1.0
Content-Type: text/plain; charset="utf-8"
Content-Transfer-Encoding: base64

VGhpcyBpcyBhIHNhbXBsZSB0ZXh0

--===============2193163039290138103==--

Код Python:

from email.parser import Parser
from email import policy
from sys import argv


with open(argv[1], 'r', encoding='utf-8') as eml_file:
    msg = Parser(policy=policy.default).parse(eml_file)

print(msg['from'])

Результат:

彭 以 国 / 第10 事业 部 项目 部 / 第二 事业 � ��

1 Ответ

0 голосов
/ 20 декабря 2018

Похоже, это проблема с тем, как инфраструктура email.parser обрабатывает развертывание многострочных заголовков, содержащих токены кодированных слов для заголовка From и других структурированных заголовков.Он делает это правильно для неструктурированных заголовков, таких как Subject.

В вашем заголовке есть две кодированных слова части в двух отдельных строках.Это совершенно нормально, токен закодированного слова имеет ограниченное пространство (существует ограничение максимальной длины), и поэтому ваши данные UTF-8 были разделены на два таких слова, и между ними есть разделитель строк и пробел.Все отлично и хорошо.Что бы ни было сгенерировано, электронное письмо было неправильно разделено в середине символа UTF-8 (RFC2047 утверждает, что это строго запрещено), декодер таких данных не должен вставлять пробелы между декодированными байтами.Это дополнительное пространство, которое препятствует присоединению обработчика заголовка email к суррогатам и восстановлению данных.

Таким образом, это похоже на ошибку в способе анализа заголовков при обработке структурированных заголовков;синтаксический анализатор неправильно обрабатывает пробелы между закодированными словами, здесь пробел был введен свернутой строкой заголовка.Это приводит к тому, что пространство между двумя частями кодированного слова сохраняется, что препятствует правильному декодированию.Таким образом, в то время как RFC2047 заявляет, что секции кодированных слов ДОЛЖНЫ содержать целые символы (многобайтовые кодировки не должны разбиваться), он также утверждает, что закодированные слова могут быть разделены разделителями CRLF SPACE и любыми пробелами между ними.закодированные слова следует игнорировать.

Вы можете обойти это, предоставив собственный класс политики, который удаляет начальные пробелы из строк в вашей собственной реализации Policy.header_fetch_parse() метода .

import re
from email.policy import EmailPolicy

class UnfoldingEncodedStringHeaderPolicy(EmailPolicy):
    def header_fetch_parse(self, name, value):
        # remove any leading white space from header lines
        # that separates apparent encoded-word tokens before further processing 
        # using somewhat crude CRLF-FWS-between-encoded-word matching
        value = re.sub(r'(?<=\?=)((?:\r\n|[\r\n])[\t ]+)(?==\?)', '', value)
        return super().header_fetch_parse(name, value)

и используйте это в качестве политики при загрузке:

custom_policy = UnfoldingEncodedStringHeaderPolicy()

with open(argv[1], 'r', encoding='utf-8') as eml_file:
    msg = Parser(policy=custom_policy).parse(eml_file)

Демонстрация:

>>> from io import StringIO
>>> from email.parser import Parser
>>> from email.policy import default as default_policy
>>> custom_policy = UnfoldingEncodedStringHeaderPolicy()
>>> Parser(policy=default_policy).parse(StringIO(data))['from']
'彭以国/第二事业部项目部/第二事业� �� <addr@addr.com>'
>>> Parser(policy=custom_policy).parse(StringIO(data))['from']
'彭以国/第二事业部项目部/第二事业部 <addr@addr.com>'

Я подал Проблема с Python # 35547 чтобы отследить это.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...