python - Python3电子邮件模块中的解码不正确
问题描述
我最近遇到了一个我想用 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 电子邮件 Parserparse
时,最后 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'])
结果:
彭以国/第二事业部项目部/第二事业部 ��
解决方案
这似乎是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来跟踪它。
推荐阅读
- python - 将 wav 文件转换为图像并再次转换回来
- python - matplotlib中直方图绘制的基本问题
- javascript - 用 javascript 中的 post 方法在 location.replace 中发送数据
- tensorflow - 隐藏状态张量的顺序与返回的张量不同
- r - 在分组数据条件下为唯一 ID 创建变量
- python - 如何使用 Python 抓取嵌入在网站中的表格
- swift - 确定 NSCollectionViewCompositionalLayout 的部分标识符
- sdn - 如何在 cent os 7 中安装 opendaylight 钠
- c++ - 在比较中处理 int 和 std::vector::size_type
- php - koel - 执行 php artisan koel:init 命令时出错