MIME 消息组合器/分析器






4.89/5 (76投票s)
2004年1月4日
4分钟阅读

644085

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 日 - 更新下载