一个多部分电子邮件中的“parts”是指什么?

10

一些背景信息...

不久前,我编写了一个处理电子邮件的Python程序,其中一个经常遇到的问题是要知道电子邮件是否为“多部分”的。

经过一番调查,我知道这与电子邮件中包含HTML或附件等有关...但我并没有真正理解它。

我的使用仅限于2个实例:

1. 当我需要保存原始电子邮件中的附件时

我在互联网上找到了这个代码(可能在这里 - 对不起,我无法再找到他了,未能给出作者的名字 :/),并将其粘贴到我的代码中。

def downloadAttachments(emailMsg, pathToSaveFile):
    """
    Save Attachments to pathToSaveFile (Example: pathToSaveFile = "C:\\Program Files\\")
    """
    att_path_list = []
    for part in emailMsg.walk():
        # multipart are just containers, so we skip them
        if part.get_content_maintype() == 'multipart':
            continue

        # is this part an attachment ?
        if part.get('Content-Disposition') is None:
            continue

        filename = part.get_filename()

        att_path = os.path.join(pathToSaveFile, filename)

        #Check if its already there
        if not os.path.isfile(att_path) :
            # finally write the stuff
            fp = open(att_path, 'wb')
            fp.write(part.get_payload(decode=True))
            fp.close()
        att_path_list.append(att_path)
    return att_path_list

2. 当我需要从原始电子邮件中获取文本时

也曾经从互联网上复制黏贴过来,但并不真正了解其工作原理。

def get_text(emailMsg):
    """
    Output: body of the email (text content)
    """
    if emailMsg.is_multipart():
        return get_text(emailMsg.get_payload(0))
    else:
        return emailMsg.get_payload(None, True)

我所了解的是...

如果电子邮件消息是多部分的,那么这些部分可以被遍历。

我的问题是

究竟是什么构成了这些部分?如何知道哪个是html?或者哪个是附件?或者只是正文?


1
Multipart 是一种在单个主体内编码(可能)多个数据元素的方式。这可能意味着除文本主体外还有附件或其他项目,但这并非必需。您也可以在多部分消息中仅编码单个消息正文。 - poke
如果 part.get('Content-Disposition')None,那么这是不正确的。这只是告诉你这个部分没有显式的处理方式;因此你必须推断出一个隐含的处理方式,这取决于该部分的类型。text/* 通常是隐含内联的,而大多数其他类型则是隐含附件的。 - tripleee
get_text() 同样很简单。如果您想决定要显示的“消息”,则应避免明确标记为 Content-Disposition: attachment 或嵌入在例如退回消息中的部分。如果存在 multipart/alternative(实际上可能同样标记为 multipart/mixedmultipart/related),则可能会有多个消息正文的呈现方式,您可以选择适合您用例的那一个。 - tripleee
问题基于不准确的心理模型。此设计的设计者有一个不同的模型:一条消息(或部分)可能有多个部分,这些部分可能是备选项(“multipart/alternative”)、串行等。0-n个部分中的每一个都必须有一个类型,例如HTML、JPEG、文本、Wordstar文档、音频/MP3或其他许多类型。每个部分可以被指定为内联或附件,如果没有指定,则接收者需要猜测。这个模型给发送者很大的灵活性...在我看来这是好的,因为现实世界的发送者并不是非常有纪律性的,他们会打破任何规则。 - arnt
@arnt "Gets to guess" 是不正确的;每种类型都定义了默认的处理方式。 - tripleee
2个回答

14
一封邮件消息由单个MIME部分或具有多个MIME部分的multipart结构组成。 如果没有multipart结构,则该消息与pre-MIME RFC822消息兼容,并且Content-type:等标头是可选的(如果您没有明确指定内容类型和编码,则Content-type:text / plain; charset =“us-ascii”和Content-transfer-encoding:7bit被隐含,但仍然很好为人类读者指定;在MIME之前,推断内容的类型和编码更像是一个猜测的情况)。 没有关于如何使用多部分消息的严格层次结构或指导方针。 MIME仅定义了一种将多个有效载荷收集到单个电子邮件消息中的方法。我认为最初的动机之一是能够在文本中嵌入图片;但是能够将二进制文件附加到文本消息中,并且更普遍地能够创建具有任意相关有效载荷的结构化消息是应用程序随意使用的东西。 一个常见的误解是假设一个层次结构,并将其分为“主要部分”和“从属”部分。当然可以创建这个结构,但这绝不是普遍做法。实际上,大多数多部分消息只有一系列部分而没有任何层次结构。用户的电子邮件客户端通常会选择其中一个“内联”部分作为首选的“主要”部分,在消息窗格中显示,但这绝不是标准或发送方可以强制执行的。 每个MIME部分都有一组标头,告诉您类型,编码和处理方式;对于text / *类型的部分,默认处理方式为“inline”(因此通常不会明确指定),而大多数其他部分的默认处理方式为“attachment”。您需要参考相关标准以进行严格定义,但可能需要谨慎对待,因为许多现实世界的应用程序并不特别符合RFC。

针对您的具体问题,找到最顶层的叶子部分(隐式或显式地)内联,并显示支持您用例的一个作为“主要”部分。如果您想强制使用HTML作为首选格式,可以这样做;但许多电子邮件应用程序将此推迟给用户决定,有些用户绝对会因技术必要性、身体残疾或个人喜好而更喜欢纯文本。

不幸的是,近来消息生产者的常见做法是创建一个包含text/plaintext/html成员的multipart/alternative容器,然后提供一个完全无用的text/plain部分,并在text/html部分中放置所有实际内容。在这种情况下,正确的安排应该是如果您无法放置任何有用内容,则不提供text/plain部分(但我猜他们只关心通过某些错误的垃圾邮件过滤器,而不是真正迎合收件人的偏好)。


Python 3.6+有一个经过重新设计的email库,其中包含一个方法get_body,它尝试为您猜测“主体部分”。 - tripleee
也许现在可以参考 https://dev59.com/DPTtpIgB1922wOYJfOJH#75905751,了解有关电子邮件中特定图像如何链接的类似阐述。 - tripleee

2
你要找的答案都在MIME标准中,特别是: 这些标准一起将电子邮件从纯文本、仅限英语的状态转变为现在我们发送Unicode poo、带有可爱小猫的专有位图以及数十种非一致性软件和中间框在路径中损坏信息的有趣方式。这些功能的更多详情请参见:

关于您问题中与 IMAP 相关的部分,即如何通过 IMAP 最佳地访问这些部分的 MIME 树,请参见 RFC3501,特别是讲解 BODYBODYSTRUCTURE 结构的章节。

如果您想惊叹于MIME的美妙表现,请看一下"MIME torture test"。有点棘手的是要找到它,因为github上的这个随机项目肯定不是我指的。这是来自IMAP创建者Mark Crispin的原始链接: 是的,这是很多阅读内容。不幸的是,除非您想创建像随机批量邮件发送程序那样的可怕物品,否则您真的需要理解上述所有内容以正确且安全地处理MIME。请不要跳过这些资源和标准。谢谢。

1
链接已经失效,但是搜索 torture-test.mbox 发现 https://www.mirrorservice.org/sites/ftp.cac.washington.edu/imap/mime-examples/ 似乎是一个可替代的位置。 - tripleee

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接