Lar*_*rax 4 python email utf-8 python-3.x rfc5322
我最近遇到了一个我想用Python电子邮件模块解析的EML文件.在from标题中,有以下文字:
From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrp?=
=?utf-8?b?g6g=?=" <email@address.com>
Run Code Online (Sandbox Code Playgroud)
因此名称分为两部分.当我连接代码并手动解码为十六进制时,我得到以下结果,这是正确的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
Run Code Online (Sandbox Code Playgroud)
但是,当我调用Python电子邮件解析器时parse,最后3个字节未正确解码.相反,当我读到它的值时message['from'],有代理人:
dce9:20:dc83:dca8
Run Code Online (Sandbox Code Playgroud)
因此,当我,例如,想要打印字符串,它最终结束
UnicodeEncodeError('utf-8', '???/????????/????\udce9\udc83\udca8', 17, 18, 'surrogates not allowed')
Run Code Online (Sandbox Code Playgroud)
当我将From标题中的2个编码部分加入一个时,看起来像这样:
From: "=?utf-8?b?5b2t5Lul5Zu9L+esrOS6jOS6i+S4mumDqOmhueebrumDqC/nrKzkuozkuovkuJrpg6g=?=" <email@address.com>
Run Code Online (Sandbox Code Playgroud)
该字符串由库正确解码,可以打印得很好.
这是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==--
Run Code Online (Sandbox Code Playgroud)
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'])
Run Code Online (Sandbox Code Playgroud)
结果:
彭以国/第二事业部项目部/第二事业
这似乎是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)
Run Code Online (Sandbox Code Playgroud)
并在加载时将其用作您的策略:
custom_policy = UnfoldingEncodedStringHeaderPolicy()
with open(argv[1], 'r', encoding='utf-8') as eml_file:
msg = Parser(policy=custom_policy).parse(eml_file)
Run Code Online (Sandbox Code Playgroud)
演示:
>>> 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>'
Run Code Online (Sandbox Code Playgroud)
我提交了Python问题#35547来跟踪这个问题.
| 归档时间: |
|
| 查看次数: |
169 次 |
| 最近记录: |