' UTF-8'编解码器无法编码字符' \ udcc2':不允许使用代理

时间:2017-04-11 02:06:59

标签: python email unicode utf-8 mime

我正在使用Python 3.6.0b2。

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

UnicodeEncodeError: 'utf-8' codec can't encode character '\udcc2' in position 30: surrogates not allowed

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

(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)

以下是由上述代码生成的错误:

(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

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

enter image description here

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

有人可以建议我在显示电子邮件地址显示名称时能避免出现Unicode错误吗?

1 个答案:

答案 0 :(得分:3)

这里有一个棘手的问题。你的直接例子并不强硬:根据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'

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

这是一个Python字节字符串,包含From:标题为 encoded-words 。此规范是 RFC 2047, MIME Part Three: Message Header Extensions for Non-ASCII Text

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

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

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

这违反了RFC 2047, Section 5, "Use of encoded-words in message headers" *的规则。它说:

  

每个'编码字'必须代表整数个字符   多个八位字节字符可能不会在相邻的“编码字”之间拆分。

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

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'

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

现在,您提出两个问题:

  

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

我的建议是“不”。如果您要处理任何电子邮件,则需要准备好处理错误形成的电子邮件。您将需要编写自定义错误处理代码 - 不仅仅是针对Unicode问题,还要处理所有问题 - 以应对毫无疑问需要清洗的奇怪内容。

  

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

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

  1. 看看class email.policy.EmailPolicy(**kw),看看你是否可以弄清楚如何扩展它来处理这种错误编码的内容。您正在policy中将此类的亲戚传递为BytesHeaderParser(policy=policy.default).parsebytes(email_bytes)

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

  3. 在异常处理程序中将您的调用传递给BytesHeaderParser(),该异常处理程序将仅针对失败的行尝试#2中的修复。修好了这一行后,您可以再次尝试BytesHeaderParser()

  4. 还会有其他问题。考虑构建您的代码,以便能够为无效内容提供越来越多的修复,因为您发现需要它们。