压缩附件会破坏email.message.Message.get_payload()

时间:2017-05-25 19:44:36

标签: python email email-attachments mime quoted-printable

我经常收到附件的电子邮件,我必须提取并保存到磁盘。我基本上做了以下(在Python 2.7中):

message = email.message_from_file(sys.stdin)
for part in message.walk():
    path = email.header.decode_header(part.get_filename())[0][0]
    content = part.get_payload(decode=True)
    with open(path, 'w') as f:
        f.write(content)

此方法适用于我迄今为止收到的所有类型的附件和Content-Transfer-Encoding的所有风格,除非附件是ZIP文件且Content-Transfer-Encoding是'引用可打印&#39 ;.在这些情况下,写入的ZIP文件比原始文件少一个字节(大约60-80%通过文件),unzip报告错误,如:

% unzip -l foo.zip
Archive:  foo.zip
error [foo.zip]:  missing 1 bytes in zipfile
  (attempting to process anyway)
  Length      Date    Time    Name
---------  ---------- -----   ----
   440228  01-00-1980 00:00   foo - bar.csv
---------                     -------
   440228                     1 file

% unzip foo.zip 
Archive:  foo.zip
error [foo.zip]:  missing 1 bytes in zipfile
  (attempting to process anyway)
error [foo.zip]:  attempt to seek before beginning of zipfile
  (please check that you have transferred or created the zipfile in the
  appropriate BINARY mode and that you have compiled UnZip properly)
  (attempting to re-compensate)
  inflating: foo - bar.csv   bad CRC 4c86de66  (should be a53f73b1)

然后解压缩的结果与原始CSV的大小差异大约0.01%,文件的最终20-40%左右是乱码。

现在,代码处理附加为' base64'的ZIP文件。很好,它处理其他内容(Excel文件,csv文件)作为" quoted-printable'正好。我知道ZIP附件内容没有损坏足够,我的常规电子邮件阅读器可以将它保存到磁盘上并且完美地提取原始内容。 (当保存我的Python没有做的附件时,真正的电子邮件阅读器是否可能正在执行一些错误纠正?)

Python是否存在已知问题无法读取以quoted-printable格式发送的ZIP文件? Python的email包中是否有其他功能我可以尝试正确解读这些内容吗?

1 个答案:

答案 0 :(得分:4)

这种情况下的问题是发件人的二进制附件(ZIP文件)写得不好,因此它们包含\r\n个序列。也就是说,ZIP格式的文件本身(不是正在压缩的文件)包含偶尔的 CR LF 对。我无法推测这些是如何进入ZIP输出的;我认为任何商业或开源拉链都不会在其输出中包含 CR LF ......

根据quoted-printable encoding的规则#4,原始“文本”中的换行符(在本例中为ZIP附件)必须在编码中表示为裸\r\n(然后解释为解码器的语言环境决定)。显然,当断行的确切形式具有意义时(例如当它本身就是编码时),这是非常糟糕的。 RFC甚至评论了包含文字换行符的二进制数据的奇怪性:

  

由于除文本以外的其他类型的规范表示通常不包括换行符的表示,因此在引用中不应出现硬线断点(即意图有意义且要向用户显示的换行符) - 这种类型的可打印编码。

所以在RFC结尾处有一个巨大的警告:

  

警告执行者:如果二进制数据采用quoted-printable编码,则必须注意将CR和LF字符分别编码为“= 0D”和“= 0A”。特别是,二进制数据中的CRLF序列应编码为“= 0D = 0A”。否则,如果CRLF表示为硬换行符,则可能会在具有不同换行符约定的平台上对其进行错误解码。

发件人在编码时显然没有遵守此警告,因此发件人和我之间的某些邮件传输代理或网关决定我的语言环境的适当换行符只是\n(通常是这样)

无论如何,我发现这是通过将我的quopri - 解码的附件逐字节与附加的ZIP文件的原始副本进行比较而出现的问题。两者是相同的,只是原始中的每个 CR LF 在我的解码中只是一个 LF 。因为\r显然是有意义的,并且因为QP编码中的每个其他换行都是由换行=字符正确开头的,所以我只是为所有QP编码{编写了以下转换{ {1}}此发件人的MIME类型:

application

通过将每个硬(意外)换行符转换为编码if part['Content-Disposition'].startswith('attachment') and \ part['Content-Transfer-Encoding'] == 'quoted-printable': rawContent = part.get_payload(decode=False) fixedRawContent = re.sub(r'([^=])\n', r'\1=0D=0A=\n', rawContent) decodedContent = quopri.decodestring(fixedRawContent) (后面是我自己的软换行符,这样我就不必担心创建任何超长行),解码函数尽职尽责地放置{{1} 1}}进入ZIP,然后正确提取。