我最近遇到了要使用Python电子邮件模块解析的EML文件。
from
标头中包含以下文本:
From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrp?=
=?utf-8?b?g6g=?=" <email@address.com>
因此,名称由两部分编码。当我连接代码并将其手动解码为十六进制时,会得到以下结果,这是正确的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')
当我将From
标头中的2个编码部分合并为一个时,如下所示:
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'])
结果:
彭以国/第二事业部项目部/第二事业�
答案 0 :(得分:5)
email.parser
基础结构如何处理包含From头和其他结构化头的编码字标记的多行头的展开似乎是一个问题。对于非结构化标头(例如Subject
),它可以正确执行此操作。
您的标头在两行中有两个encoded word部分。这是完全正常的,一个编码字令牌具有有限的空间(有最大长度限制),因此您的UTF-8数据被分成两个这样的字,并且在它们之间有一个行分隔符和空格。一切都很好。无论生成什么电子邮件,都是错误的在UTF-8字符中间进行拆分(严格禁止RFC2047声明),此类数据的 decoder 不应在已解码字节之间插入空格。正是多余的空间阻止了email
标头处理加入代理和修复数据。
因此,在处理结构化标题时,这似乎是在解析标题时出现了一个错误;解析器无法正确处理编码字之间的空格,此处的空格是由折叠的标题行引入的。然后,这导致在两个编码字部分之间保留了空间,从而阻止了正确的解码。因此,尽管RFC2047确实声明了编码字段必须包含完整字符(不得拆分多字节编码),但它也声明可以使用CRLF SPACE分隔符和任何空格分隔编码字。编码字之间的距离将被忽略。
您可以通过提供自定义策略类来解决此问题,该类将在您自己实施Policy.header_fetch_parse()
method的行中删除开头的空白。
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 issue #35547进行跟踪。