【问题标题】:'utf-8' codec can't encode character '\udcc2': surrogates not allowed“utf-8”编解码器无法编码字符“\udcc2”:不允许代理
【发布时间】:2017-09-06 05:33:24
【问题描述】:

我正在使用 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 电子邮件客户端中显示的显示名称,它似乎可以解析它(这是一个屏幕截图,裁剪得很小):

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

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

【问题讨论】:

    标签: python email unicode utf-8 mime


    【解决方案1】:

    你有一个棘手的问题。您的直接示例并不难:根据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®, &lt;jon@eg.org&gt;

    这是一个 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 字节的 UTF-8(和 ASCII)序列 0x2C,即字符 ','。

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

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

    这违反了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),看看你是否能弄清楚如何扩展它来处理这种编码不正确的内容。您在BytesHeaderParser(policy=policy.default).parsebytes(email_bytes) 中将此类的亲戚作为policy 传递。

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

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

    还会有其他问题。当您发现自己需要它们时,考虑构建您的代码以适应越来越多的无效内容修复。

    【讨论】:

      猜你喜欢
      • 2019-06-29
      • 2020-11-06
      • 2020-07-17
      • 2019-11-10
      • 1970-01-01
      • 1970-01-01
      • 2015-08-24
      • 2017-09-27
      • 1970-01-01
      相关资源
      最近更新 更多