4

我经常收到带有附件的电子邮件,我必须提取这些附件并保存到磁盘。我基本上做了以下事情(在 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 文件并且是“引用打印”。在这些情况下,被写入的 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 文件,并且可以很好地处理附加为“quoted-printable”的其他内容(Excel 文件、csv 文件)。我知道 ZIP 附件内容足够干净,以至于我的普通电子邮件阅读器可以很好地将其保存到磁盘并完美地提取原始内容。(在保存我的 Python 没有执行的附件时,真正的电子邮件阅读器是否可能会执行一些错误更正?)

Python 是否存在一个已知问题,无法读取作为引用打印发送的 ZIP 文件?Python 包中是否还有其他功能email可以尝试正确破译这些内容?

4

1 回答 1

5

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

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

由于文本以外的类型的规范表示通常不包括换行符的表示,因此在引用的可打印编码中不应出现硬换行符(即旨在有意义并显示给用户的换行符)这样的类型。

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

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

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

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

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)

通过将每个硬(意外)换行符转换为编码\r\n(后跟我自己的软换行符,这样我就不必担心创建任何过长的行),解码功能尽职尽责地将所说\r\n的放入 ZIP,然后正确提取。

于 2017-05-30T21:23:22.090 回答