2017-04-11 137 views
1

我正在使用Python 3.6.0b2。'utf-8'编解码器无法编码字符' udcc2':代理不允许

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

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

下面是测试情况下一段代码,显示了如何重现该问题:

(venv3.6) [email protected]:/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?= <[email protected]>\r\n' 
msg = BytesHeaderParser(policy=policy.default).parsebytes(email_bytes) 
print(msg['from']) 
print(msg['from'].addresses[0].display_name) 

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

(venv3.6) [email protected]:/opt/mailripper$ python test.py 
"John Smith, Prince2®,PMP®, CSM� �, ITIL®, ISTQB®" <[email protected]> 
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电子邮件客户端,这似乎能够解析就OK了(这是截图,剪裁要小)显示的显示名称:

enter image description here

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

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

回答

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?= <[email protected]>\r\n' 

这可能是最初的字符串:From: John, PMP®, CSM®, <[email protected]>

这是一个Python字节字符串,包含From:标头,作为编码字。规格为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?开始第二个编码字之后,有一个序列=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?= <[email protected]>\r\n' 
b'From: =?utf-8?Q?John=2C_PMP=C2=AE=2C_CSM?=\r\n =?utf-8?Q?=C2=AE=2C?= <[email protected]>\r\n' 

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

现在,你问两个问题:

我的目标是能够处理任何电子邮件没有统一代码错误,并没有 编写自定义的Unicode错误处理的C颂歌 - 这可能吗?

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

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

在这个例子中,我可以看到三种方法:

  1. 看看的classemail.policy.EmailPolicy(**kw),看看你能弄清楚如何扩展它来处理这类的编码不正确的内容。您正在通过policyBytesHeaderParser(policy=policy.default).parsebytes(email_bytes)这个班级的亲属。

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

  3. 将你的电话打包到BytesHeaderParser()的异常处理程序中,该程序将仅在失败的行中尝试修复#2。固定线路后,您可以再次尝试BytesHeaderParser()

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

相关问题