65.9K
CodeProject 正在变化。 阅读更多。
Home

MIME 消息组合器/分析器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.89/5 (76投票s)

2004年1月4日

4分钟阅读

viewsIcon

644085

downloadIcon

4628

MIME 的 C++ 实现。

引言

此实现旨在为开发符合 MIME 标准的应用程序提供一个易于使用、封装良好的组件,而无需具备深厚的 MIME 知识。然而,由于 MIME 是开放且可扩展的,因此提供一种支持任何自定义扩展的简便方法也很重要。以下是此实现的一些特性:

  • 满足 MIME 合规性要求。
  • 用 ANSI C++ 编写,易于移植到 UNIX。
  • 对所有(标准和扩展的)头字段和正文部分进行完全控制。提供原始数据的包装器来解释标准字段。
  • 提供标准的编码机制(Quoted-Printable 和 Base64),并支持所有标准媒体类型。
  • 无缝接口支持扩展媒体类型和编码机制。
  • 提供非 ASCII 文本的标准头编码/解码,以及长行文本的折叠/展开。

使用代码

尽管源文件中包含十多个类,但您需要使用的只有 3 到 4 个类,包括:

CMimeEnvironment 用于管理编码机制和选项的全局环境。
CMimeField 表示 MIME 正文部分头部的字段。提供管理字段名称、值、参数和字符集的函数。
CMimeBody 表示 MIME 消息的正文部分。它派生自 CMimeHeader 类,提供管理正文部分内容和头部字段的函数。
CMimeMessage 表示 MIME 消息。它派生自 CMimeBody 类,提供访问 RFC822 消息头字段(例如 From、To、Date、Subject)的额外功能。

组合消息

本示例演示了如何创建一个复杂的 multipart 消息,其中包含一个文本部分、一个文件附件、一个已附加的消息和一个嵌入的 multipart 实体。

    CMimeMessage mail;

    // Initialize message header
    mail.SetDate(); // set 'Date' field to the current time
    mail.Setversion();
    mail.SetFrom("sender@local.com");
    mail.SetTo("recipient1@server1.com, 
      Nick Name <recipient2@server1.com>, 
      \"Nick Name\" <recipient3@server3.com>");
    mail.SetCc("recipient4@server4.com");
    mail.SetSubject("Test message");
    mail.SetFieldValue("X-Priority", "3 (Normal)"); // extended field
    mail.SetFieldValue("X-My-Field", "My value");   // user-defined field

    // Initialize body part header
    mail.SetContentType("multipart/mixed");
    // generate a boundary delimeter automatically
    // if the parameter is NULL
    mail.SetBoundary(NULL);

    // Add a text body part
    // Content-Type is not specified, so the default
    // value "text/plain" is implicitly used
    // Content-Transfer-Encoding is not specified
    // so the default value "7bit" is implicitly used
    CMimeBody* pBp = mail.CreatePart();
    pBp->SetText("Hi, there");  // set the content of the body part

    // Add a file attachment body part
    pBp = mail.CreatePart();
    pBp->SetContentDescription("enclosed photo");
    pBp->SetTransferEncoding("base64");
    // if Content-Type is not specified, it'll be
    // set to "image/jpeg" by ReadFromFile()
    pBP->ReadFromFile("d:\\myphoto.jpg"); 

    // Generate a simple message
    CMimeMessage mail2;
    mail2.SetFrom("abc@abc.com");
    mail2.SetTo("abc@abc.com");
    mail2.SetSubject("This is an attached message");
    mail2.SetText("Content of attached message.\r\n");

    // Attach the message
    pBp = mail.CreatePart();
    pBp->SetContentDescription("enclosed message");
    pBp->SetTransferEncoding("7bit");
    // if Content-Type is not specified, it'll be
    // set to "message/rfc822" by SetMessage()
    pBp->SetMessage(&mail2); 

    // Add an embeded multipart
    pBp = mail.CreatePart();
    pBp->SetContentType("multipart/alternative");
    pBp->SetBoundary("embeded_multipart_boundary");
    CMimeBody *pBpChild = pBp->CreatePart();
    pBpChild->SetText("Content of Part 1\r\n");
    pBpChild = pBp->CreatePart();
    pBpChild->SetText("Content of Part 2\r\n");

    // Store the message to buffer    
    // fold the long lines in the headers
    CMimeEnvironment::SetAutoFolding(true); 
    int nSize = mail.GetLength();
    char* pBuff = new char[nSize];
    nSize = mail.Store(pBuff, nSize);
    ...
    delete pBuff;

分析消息

分析消息同样简单:调用 Load() 从缓冲区加载消息对象,然后调用 FindFirstPart()/FindNextPart() 函数来迭代其子正文部分。但是,multipart 实体可能包含嵌入式 multipart 实体,这会带来一些复杂性。我们必须对每个子正文部分递归调用 FindFirstPart()/FindNextPart() 来迭代所有后代部分。因此,CMimeBody 类提供了一种替代方法,通过调用 GetBodyPartList() 来检索所有后代正文部分的列表。

    CMimeMessage mail;
    int nLoadedSize = mail.Load(pBuff, nDataSize);

    // Analyze the message header
    const char* pszField;
    pszField = mail.GetSubject();
    if (pszField != NULL)
        printf("Subject: %s\r\n", pszField);
    pszField = mail.GetFrom();
    if (pszField != NULL)
        printf("From: %s\r\n", pszField);
    pszField = mail.GetFieldValue("X-Priority");
    if (pszField != NULL)
        printf("X-Priority: %s\r\n", pszField);

    // Iterate all the descendant body parts
    CMimeBody::CBodyList bodies;
    int nCount = mail.GetBodyPartList(bodies);
    CMimeBody::CBodyList::const_iterator it;
    for (it=bodies.begin(); it!=bodies.end(); it++)
    {
        CMimeBody* pBP = *it;

        // Iterate all the header fields of this body part:
        CMimeHeader::CFieldList& fds = pBP->Fields();
        CMimeHeader::CFieldList::const_iterator itfd;
        for (itfd=fds.begin(); itfd!=fds.end(); itfd++)
        {
            const CMimeField& fd = *itfd;
            printf("%s: %s\r\n", fd.GetName(), fd.GetValue());
        }

        if (pBP->IsText())
        {
            string strText;
            pBP->GetText(strText);
            printf("Content: %s\r\n", strText.c_str());
        }
        else if (pBP->IsAttachment())
        {
            string strName = pBP->GetName();
            printf("File name: %s\r\n", strName.c_str());
            printf("File size: %d\r\n", pBP->GetContentLength());
            strName = "d:\\download\\" + strName;
            pBP->WriteToFile(strName.c_str());
        }
    }

头部编码和折叠

头部编码是将消息头部中的任何非 ASCII 文本数据编码为“encoded-word”。例如,像 "Subject: Bonne Année" 这样的字段将被编码为 "Subject: =?iso-8859-1?Q?Bonne=20Ann=E9e?=",而 "From: René <sender@local.com>" 将被编码为 "From: =?iso-8859-1?Q?Ren=E9?= <sender@local.com>"

在存储期间(调用 CMimeBody::Store() 函数),如果字段包含非 ASCII 文本且已指定了该字段的 'charset',则会自动执行头部编码。字段的 'charset' 可以通过调用 CMimeBody::SetFieldCharset() 来指定,或者通过将 charset 字符串传递给 CMimeBody::SetFieldValue()CMimeMessage::SetSubject()CMimeMessage::SetFrom() 等函数来指定。

与为每个字段重复指定 charset 不同,您可以通过调用 CMimeEnvironment::SetGlobalCharset() 来指定 '全局 charset'。在执行头部编码时,如果字段的 charset 为空,则使用全局 charset。如果全局 charset 也为空,则不执行头部编码。因此,如果您不需要头部编码,可以同时将两个 charset 都留空。

    mail.SetSubject("Bonne Année", "iso-8859-1");
    or:
    mail.SetFieldValue("Content-Description", "carte bonne année");
    CMimeEnvironment::SetGlobalCharset("iso-8859-1");

此外,头部折叠也会自动执行。通常,任何超过 76 字节的头部行都会在空格(SPACE 或 TAB)处插入“\r\n\t”进行折叠。对于地址字段(例如 From、To、CC 等),折叠发生在地址之间,在分隔逗号之后。但是,可以通过调用 CMimeEnvironment::SetAutoFolding() 函数并将 bAutoFolding 参数设置为 true/false 来打开/关闭头部折叠。

支持自定义编码

要支持 Quoted-Printable 和 Base64 以外的任何 Content-Transfer-Encoding 机制,请从 CMimeCodeBase 派生一个类,然后通过调用 REGISTER_MIMECODER() 来注册该类。

class CMyCoder : public CMimeCodeBase
{
    DECLARE_MIMECODER(CMyCoder)
    ...
protected:
    virtual int GetEncodeLength() const;
    virtual int GetDecodeLength() const;
    virtual int Encode(unsigned char* pbOutput, int nMaxSize) const;
    virtual int Decode(unsigned char* pbOutput, int nMaxSize);
};

    CMimeMessage mail;
    ...
    REGISTER_MIMECODER("my_coding_name", CMyCoder);
    mail.Store(pbBuff, nSize);
    mail.Load(pbBuff, nSize);

调用 REGISTER_MIMECODER() 后,对于 Content-Transfer-Encoding 指定为 my_coding_name 的任何正文部分,Store()/Load() 函数将使用 CMyCoder 类来编码/解码内容。

支持扩展媒体类型

扩展媒体类型的用途很少见,但仍然得到支持。与支持自定义编码类似,从 CMimeBody 派生一个类,然后通过调用 REGISTER_MEDIATYPE() 来注册该类。

class CMyBodyPart : public CMimeBody
{
    DECLARE_MEDIATYPE(CMyBodyPart)
    ...
public:
    virtual void Clear();
    virtual int GetLength() const;
    virtual int Store(char* pszData, int nMaxSize) const;
    virtual int Load(const char* pszData, int nDataSize);
};

    CMimeMessage mail;
    ...
    REGISTER_MEDIATYPE("my_media_type", CMyBodyPart);
    CMyBodyPart* pBP = (CMyBodyPart*) mail.CreatePart("my_media_type");
    ...
    mail.Store(pbBuff, nSize);
    mail.Load(pbBuff, nSize);

调用 REGISTER_MEDIATYPE() 后,对于 Content-Type 指定为 my_media_type 的任何正文部分,将创建 CMyBodyPart 对象来表示该正文部分。

历史

  • 2004 年 2 月 17 日 - 更新下载
  • 2004 年 4 月 16 日 - 更新下载
© . All rights reserved.