“utf-8”编解码器无法编码字符“\udcc2”:不允许代理

Duk*_*gal 3 python email unicode mime utf-8

我正在使用 Python 3.6.0b2。

我正在解析很多电子邮件。这一个特定的电子邮件是一个问题,因为我无法打印电子邮件地址的显示名称。尝试打印电子邮件地址显示名称会给出:

UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed
Run Code Online (Sandbox Code Playgroud)

这是一段测试用例代码,显示了如何重现问题:

(venv3.6) mailripper@ip-10-0-0-112:/opt/mailripper$ cat test.py
from email import policy
from email.headerregistry import Address
from email.parser import BytesHeaderParser, BytesParser

email_bytes = b'From: =?utf-8?Q?John_Smith=2C_Prince2=C2=AE=2CPMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C_ITIL=C2=AE=2C_ISTQB=C2=AE?= <jon.smith@example.org>\r\n'
msg = BytesHeaderParser(policy=policy.default).parsebytes(email_bytes)
print(msg['from'])
print(msg['from'].addresses[0].display_name)
Run Code Online (Sandbox Code Playgroud)

这是上面代码生成的错误:

(venv3.6) mailripper@ip-10-0-0-112:/opt/mailripper$ python test.py
"John Smith, Prince2®,PMP®, CSM? ?, ITIL®, ISTQB®" <jon.smith@example.org>
Traceback (most recent call last):
  File "test.py", line 8, in <module>
    print(msg['from'].addresses[0].display_name)
UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed
Run Code Online (Sandbox Code Playgroud)

这是 OSX 电子邮件客户端中显示的显示名称,它似乎能够解析它(这是一个截图,裁剪得很小):

在此处输入图片说明

我的目标是能够在没有 unicode 错误的情况下处理任何电子邮件,并且无需编写自定义的 unicode 错误处理代码 - 这可能吗?

任何人都可以建议我可以做些什么来避免在显示电子邮件地址显示名称时出现 Unicode 错误?

Jim*_*unt 5

你在这里遇到了一个棘手的问题。您的直接示例并不困难:根据RFC 2047的规则,它是无效的。该email.parser模块有理由拒绝它。但是,电子邮件充满了根据规则无效的内容。电子邮件工具通常会努力从无效内容中挽救一些东西。您希望您的工具对无效内容做什么?

这是您的示例无效的内容。我把它缩短了一点。它的相关部分写道,

b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2?=\r\n =?utf-8?Q?=AE=2C?= <jon@eg.org>\r\n'
Run Code Online (Sandbox Code Playgroud)

这可能最初是字符串:From: John, PMP®, CSM®, <jon@eg.org>

这是一个 Python 字节字符串,包含一个From:作为编码字的标头。对此的规范是RFC 2047, MIME Part Three: Message Header Extensions for Non-ASCII Text

在示例中,您会看到两个序列,每个序列=?utf-8?Q??=RFC 2047, Section 2, “Syntax of encoding-words”告诉我们这些标记了两个编码词的开始和结束,并且它们使用 UTF-8 字符集和 Quoted-Printable 编码。在“PMP”之后,是序列=C2=AE。这对 2 个八位字节的 UTF-8 序列进行编码0xC2 0xAE,即字符“®”。该序列=2C对 1 个八位字节的 UTF-8(和 ASCII)序列 0x2C 进行编码,即字符 ','。

第一个?=和第二个之间的部分=?utf-8?Q?读取,\r\n。这是文字,不是根据 RFC 2047 编码的。它是通过插入行尾和前导空白来延续长标题行。这也是相当合法的。

现在照顾“CSM”。请注意,有一个序列=C2,然后是第?=一个结束第一个编码字的序列。在第二个=?utf-8?Q?开始第二个编码字之后,有一个序列=AE。这是相同的 2 个八位字节 UTF-8 序列0xC2 0xAE,再次表示字符“®”。但是,UTF-8 字符的两个八位字节被拆分为相邻的encoding-words

这违反了RFC 2047 第 5 节“在消息头中使用编码字” * 的规则。它在那里说:

每个“编​​码字”必须代表整数个字符。
一个多八位字节的字符不能被分割成相邻的“编码字”。

输入的这两个渲染中的任何一个都是有效的:

b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM=C2=AE?=\r\n =?utf-8?Q?=2C?= <jon@eg.org>\r\n'
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM?=\r\n =?utf-8?Q?=C2=AE=2C?= <jon@eg.org>\r\n'
Run Code Online (Sandbox Code Playgroud)

(这是我阅读规范时。我没有运行代码来检查。)

现在,你问两个问题:

我的目标是能够在没有 unicode 错误的情况下处理任何电子邮件,并且无需编写自定义的 unicode 错误处理代码 - 这可能吗?

我的建议是“不”。如果您想处理任何电子邮件,您需要准备好处理格式不正确的电子邮件。您将需要编写自定义的错误处理代码——不仅仅是针对 Unicode 问题,而是针对所有事情——来处理毫无疑问会被淘汰的奇怪的东西。

任何人都可以建议我可以做些什么来避免在显示电子邮件地址显示名称时出现 Unicode 错误?

对于这个例子,我可以看到三种方法:

  1. 看一看这个 email.policy.EmailPolicy(**kw),看看你是否能想出如何扩展它来处理这类错误编码的内容。您通过这个类的一个相对的policyBytesHeaderParser(policy=policy.default).parsebytes(email_bytes)

  2. 预处理所有标题行,查看此问题的连续编码字末尾和开头的字节。使用您自己的代码修复它,然后将更正后的标题提供给BytesHeaderParser(). 也许您可以编写一个可以检测问题的正则表达式

  3. 将您的调用包装BytesHeaderParser()在异常处理程序中,该处理程序将仅针对失败的行尝试 #2 中的修复。固定线路后,您可以再试BytesHeaderParser()一次。

也会有其他问题。当您发现需要时,考虑构建您的代码,以便能够容纳越来越多的无效内容修复程序。