version 0.1.0
[fms.git] / src / nntp / mime / Mime.cpp
diff --git a/src/nntp/mime/Mime.cpp b/src/nntp/mime/Mime.cpp
new file mode 100644 (file)
index 0000000..e79d059
--- /dev/null
@@ -0,0 +1,967 @@
+//////////////////////////////////////////////////////////////////////\r
+//\r
+// MIME message encoding/decoding\r
+//\r
+// Jeff Lee\r
+// Dec 11, 2000\r
+//\r
+//////////////////////////////////////////////////////////////////////\r
+//#include "stdafx.h"\r
+#include "../../../include/nntp/mime/MimeCode.h"\r
+#include "../../../include/nntp/mime/MimeChar.h"\r
+#include "../../../include/nntp/mime/Mime.h"\r
+#include <stdlib.h>\r
+#include <time.h>\r
+\r
+#ifdef _DEBUG\r
+#undef THIS_FILE\r
+static char THIS_FILE[]=__FILE__;\r
+#define new DEBUG_NEW\r
+#endif\r
+\r
+// search for a character in the current line (before CRLF)\r
+static const char* LineFind(const char* pszString, int ch)\r
+{\r
+       ASSERT(pszString != NULL);\r
+       while (*pszString != 0 && *pszString != ch && *pszString != '\r' && *pszString != '\n')\r
+               pszString++;\r
+       return *pszString == ch ? pszString : NULL;\r
+}\r
+\r
+// search for string2 in string1 (strstr)\r
+static const char* FindString(const char* pszStr1, const char* pszStr2, const char* pszEnd)\r
+{\r
+       pszEnd -= ::strlen(pszStr2);\r
+       const char *s1, *s2;\r
+       while (pszStr1 <= pszEnd)\r
+       {\r
+               s1 = pszStr1;\r
+               s2 = pszStr2;\r
+               while (*s1 == *s2 && *s2)\r
+                       s1++, s2++;\r
+               if (!*s2)\r
+                       return pszStr1;\r
+               pszStr1++;\r
+       }\r
+       return NULL;\r
+}\r
+\r
+//////////////////////////////////////////////////////////////////////\r
+// CMimeField class - Represents a field of a MIME body part header\r
+//////////////////////////////////////////////////////////////////////\r
+\r
+void CMimeField::GetValue(string& strValue) const\r
+{\r
+       string::size_type nEnd = m_strValue.find(';');\r
+       if (nEnd != string::npos)\r
+       {\r
+               while (nEnd > 0 && CMimeChar::IsSpace((unsigned char)m_strValue[nEnd-1]))\r
+                       nEnd--;\r
+               strValue.assign(m_strValue.c_str(), nEnd);\r
+       }\r
+       else\r
+               strValue = m_strValue;\r
+}\r
+\r
+// set a parameter (attribute=value) of the field\r
+void CMimeField::SetParameter(const char* pszAttr, const char* pszValue)\r
+{\r
+       int nSize = pszValue ? (int)::strlen(pszValue) : 0;\r
+       string strValue;\r
+       strValue.reserve(nSize+3);\r
+       if (!pszValue || *pszValue != '"')\r
+               strValue = "\"";\r
+       if (pszValue != NULL)\r
+               strValue += pszValue;\r
+       if (nSize < 2 || pszValue[nSize-1] != '"')\r
+               strValue += "\"";\r
+\r
+       int nPos;\r
+       if (!FindParameter(pszAttr, nPos, nSize))       // add new parameter\r
+       {\r
+               m_strValue.reserve(m_strValue.size() + ::strlen(pszAttr) + strValue.size() + 5);\r
+               //if (CMimeEnvironment::AutoFolding())\r
+               //      m_strValue += ";\r\n\t";\r
+               //else\r
+               //      m_strValue += "; ";\r
+               m_strValue += "; ";\r
+               m_strValue += pszAttr;\r
+               m_strValue += '=';\r
+               m_strValue += strValue;\r
+       }\r
+       else                                                    // update existing parameter\r
+               m_strValue.replace(nPos, nSize, strValue);\r
+}\r
+\r
+// get the value of a parameter\r
+bool CMimeField::GetParameter(const char* pszAttr, string& strValue) const\r
+{\r
+       int nPos, nSize;\r
+       if (!FindParameter(pszAttr, nPos, nSize))\r
+       {\r
+               strValue.clear();\r
+               return false;\r
+       }\r
+\r
+       if (m_strValue[nPos] == '"')\r
+       {\r
+               nPos++;\r
+               nSize--;\r
+               if (nSize > 0 && m_strValue[nPos+nSize-1] == '"')\r
+                       nSize--;\r
+       }\r
+       strValue.assign(m_strValue.data()+nPos, nSize);\r
+       return true;\r
+}\r
+\r
+int CMimeField::GetLength() const\r
+{\r
+       int nLength = (int) m_strName.size() + 4;\r
+       CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
+       pCoder->SetCharset(m_strCharset.c_str());\r
+       pCoder->SetInput(m_strValue.c_str(), (int)m_strValue.size(), true);\r
+       nLength += pCoder->GetOutputLength();\r
+       delete pCoder;\r
+       return nLength;\r
+}\r
+\r
+// store a field to string buffer\r
+int CMimeField::Store(char* pszData, int nMaxSize) const\r
+{\r
+       ASSERT(pszData != NULL);\r
+       int nMinSize = (int)m_strName.size() + 4;\r
+       if (nMaxSize < nMinSize)\r
+               return 0;\r
+       ::strcpy(pszData, m_strName.c_str());\r
+       pszData += m_strName.size();\r
+       *pszData++ = ':';\r
+       *pszData++ = ' ';\r
+\r
+       CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
+       pCoder->SetCharset(m_strCharset.c_str());\r
+       pCoder->SetInput(m_strValue.c_str(), (int)m_strValue.size(), true);\r
+       int nEncoded = pCoder->GetOutput((unsigned char*) pszData, nMaxSize-nMinSize);\r
+       delete pCoder;\r
+       pszData += nEncoded;\r
+\r
+       *pszData++ = '\r';\r
+       *pszData++ = '\n';\r
+       return nMinSize + nEncoded;\r
+}\r
+\r
+// load a field from string buffer\r
+int CMimeField::Load(const char* pszData, int nDataSize)\r
+{\r
+       Clear();\r
+       ASSERT(pszData != NULL);\r
+       const char *pszEnd, *pszStart = pszData;\r
+       // find the next field (e.g. "\r\nContent...")\r
+       while (CMimeChar::IsSpace((unsigned char)*pszStart))\r
+       {\r
+               if (*pszStart == '\r')          // end of header ?\r
+                       return 0;\r
+               pszStart = ::FindString(pszStart, "\r\n", pszData+nDataSize);\r
+               if (!pszStart)\r
+                       return 0;\r
+               pszStart += 2;\r
+       }\r
+\r
+       // get the field name\r
+       pszEnd = ::LineFind(pszStart, ':');\r
+       if (pszEnd != NULL)                             // if colon not found, Name would be empty\r
+       {\r
+               m_strName.assign(pszStart, (pszEnd-pszStart));\r
+               pszStart = pszEnd + 1;\r
+       }\r
+\r
+       // find the end of the field\r
+       while (*pszStart == ' ' || *pszStart == '\t')\r
+               pszStart++;\r
+       pszEnd = pszStart;\r
+       do\r
+       {\r
+               pszEnd = ::FindString(pszEnd, "\r\n", pszData+nDataSize);\r
+               if (!pszEnd)\r
+                       return 0;\r
+               pszEnd += 2;\r
+       } while (*pszEnd == '\t' || *pszEnd == ' ');    // linear-white-space\r
+\r
+       // decode and unfold the field value\r
+       CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
+       pCoder->SetInput(pszStart, (int)(pszEnd-pszStart)-2, false);\r
+       m_strValue.resize(pCoder->GetOutputLength());\r
+       int nSize = pCoder->GetOutput((unsigned char*) m_strValue.c_str(), (int) m_strValue.capacity());\r
+       m_strValue.resize(nSize);\r
+       m_strCharset = pCoder->GetCharset();\r
+       delete pCoder;\r
+       return (int) (pszEnd - pszData);\r
+}\r
+\r
+bool CMimeField::FindParameter(const char* pszAttr, int& nPos, int& nSize) const\r
+{\r
+       ASSERT(pszAttr != NULL);\r
+       const char* pszParms = ::strchr(m_strValue.data(), ';');\r
+       int nAttrSize = (int)::strlen(pszAttr);\r
+       while (pszParms != NULL)\r
+       {\r
+               while (CMimeChar::IsSpace((unsigned char)*pszParms) || *pszParms == ';')\r
+                       pszParms++;\r
+\r
+               const char* pszName = pszParms;         // pszName -> attribute\r
+               pszParms = ::strchr(pszParms, '=');\r
+               if (!pszParms)\r
+                       break;\r
+\r
+               pszParms++;                                     // pszParams -> parameter value\r
+               //while (*pszParms == ' ' || *pszParms == '\t')\r
+               //      pszParms++;\r
+\r
+               const char* pszParmEnd = NULL;\r
+               if (*pszParms == '"')           // quoted string\r
+                       pszParmEnd = ::strchr(pszParms+1, '"');\r
+               if (!pszParmEnd)                        // non quoted string\r
+               {\r
+                       pszParmEnd = pszParms;\r
+                       while (CMimeChar::IsToken(*pszParmEnd))\r
+                               pszParmEnd++;\r
+               }\r
+               else  pszParmEnd++;                     // pszParmEnd -> end of parameter value\r
+\r
+               if (!::memicmp(pszAttr, pszName, nAttrSize) &&\r
+                       (CMimeChar::IsSpace((unsigned char)pszName[nAttrSize]) || pszName[nAttrSize] == '='))\r
+               {\r
+                       nPos = (int)(pszParms - m_strValue.data());\r
+                       nSize = (int)(pszParmEnd - pszParms);\r
+                       return true;\r
+               }\r
+\r
+               pszParms = pszParmEnd;\r
+       }\r
+       return false;\r
+}\r
+\r
+//////////////////////////////////////////////////////////////////////\r
+// CMimeHeader class - Represents the header of a MIME body part\r
+//////////////////////////////////////////////////////////////////////\r
+\r
+// Return the media type represented by Content-Type field (see RFC 2046)\r
+CMimeHeader::MediaType CMimeHeader::GetMediaType() const\r
+{\r
+       const char* pszType = GetContentType();\r
+       if (!pszType)\r
+               pszType = "text";\r
+\r
+       int nIndex = 0;\r
+       while (m_TypeTable[nIndex] != NULL &&\r
+               ::memicmp(pszType, m_TypeTable[nIndex], ::strlen(m_TypeTable[nIndex])) != 0)\r
+               nIndex++;\r
+       return (MediaType) nIndex;\r
+}\r
+\r
+// get the top-level media type\r
+string CMimeHeader::GetMainType() const\r
+{\r
+       string strType;\r
+       const char* pszType = GetContentType();\r
+       if (pszType != NULL)\r
+       {\r
+               const char* pszSlash = ::strchr(pszType, '/');\r
+               if (pszSlash != NULL)\r
+                       strType.assign(pszType, pszSlash-pszType);\r
+               else\r
+                       strType = pszType;\r
+       }\r
+       else\r
+               strType = "text";\r
+       return strType;\r
+}\r
+\r
+// get the subtype\r
+string CMimeHeader::GetSubType() const\r
+{\r
+       string strSubType;\r
+       const CMimeField *pfd = GetField(CMimeConst::ContentType());\r
+       if (pfd != NULL)\r
+       {\r
+               string strType;\r
+               pfd->GetValue(strType);\r
+               string::size_type nSlash = strType.find('/');\r
+               if (nSlash > 0)\r
+                       strSubType = strType.substr(nSlash+1);\r
+       }\r
+       else\r
+               strSubType = "plain";\r
+       return strSubType;\r
+}\r
+\r
+// set the 'charset' parameter (for text) of Content-Type field\r
+void CMimeHeader::SetCharset(const char* pszCharset)\r
+{\r
+       CMimeField *pfd = GetField(CMimeConst::ContentType());\r
+       if (!pfd)\r
+       {\r
+               CMimeField fd;\r
+               fd.SetName(CMimeConst::ContentType());\r
+               fd.SetValue("text/plain");\r
+               fd.SetParameter(CMimeConst::Charset(), pszCharset);\r
+               m_listFields.push_back(fd);\r
+       }\r
+       else\r
+               pfd->SetParameter(CMimeConst::Charset(), pszCharset);\r
+}\r
+\r
+// set the 'name' parameter (for attachment) of Content-Type field\r
+void CMimeHeader::SetName(const char* pszName)\r
+{\r
+       CMimeField *pfd = GetField(CMimeConst::ContentType());\r
+       if (!pfd)\r
+       {\r
+               // get the appropriate media-type/subtype according to file extension\r
+               ASSERT(pszName != NULL);\r
+               string strType;\r
+               const char* pszType = "application/octet-stream";\r
+               const char* pszFileExt = ::strrchr(pszName, '.');\r
+               if (pszFileExt != NULL)\r
+               {\r
+                       pszFileExt++;\r
+                       int nIndex = 0;\r
+                       while (m_TypeCvtTable[nIndex].nMediaType != MEDIA_UNKNOWN)\r
+                       {\r
+                               if (!::stricmp(pszFileExt, m_TypeCvtTable[nIndex].pszFileExt))\r
+                               {\r
+                                       strType = m_TypeTable[m_TypeCvtTable[nIndex].nMediaType];\r
+                                       strType += '/';\r
+                                       strType += m_TypeCvtTable[nIndex].pszSubType;\r
+                                       pszType = strType.c_str();\r
+                                       break;\r
+                               }\r
+                               nIndex++;\r
+                       }\r
+               }\r
+\r
+               CMimeField fd;\r
+               fd.SetName(CMimeConst::ContentType());\r
+               fd.SetValue(pszType);\r
+               fd.SetParameter(CMimeConst::Name(), pszName);\r
+               m_listFields.push_back(fd);\r
+       }\r
+       else\r
+               pfd->SetParameter(CMimeConst::Name(), pszName);\r
+}\r
+\r
+// set 'boundary' parameter (for multipart) of Content-Type field\r
+void CMimeHeader::SetBoundary(const char* pszBoundary/*=NULL*/)\r
+{\r
+       static int s_nPartNumber = 0;\r
+       char buf[80];\r
+       if (!pszBoundary)                               // generate a new boundary delimeter\r
+       {\r
+               ::srand(((unsigned)::time(NULL)) ^ (unsigned)this);\r
+               ::sprintf(buf, "__=_Part_Boundary_%03d_%06d.%06d", ++s_nPartNumber, rand(), rand());\r
+               if (s_nPartNumber >= 9)\r
+                       s_nPartNumber = 0;\r
+               pszBoundary = buf;\r
+       }\r
+\r
+       CMimeField *pfd = GetField(CMimeConst::ContentType());\r
+       if (!pfd)\r
+       {\r
+               CMimeField fd;\r
+               fd.SetName(CMimeConst::ContentType());\r
+               fd.SetValue("multipart/mixed");\r
+               fd.SetParameter(CMimeConst::Boundary(), pszBoundary);\r
+               m_listFields.push_back(fd);\r
+       }\r
+       else\r
+       {\r
+               if (::memicmp(pfd->GetValue(), "multipart", 9) != 0)\r
+                       pfd->SetValue("multipart/mixed");\r
+               pfd->SetParameter(CMimeConst::Boundary(), pszBoundary);\r
+       }\r
+}\r
+\r
+void CMimeHeader::Clear()\r
+{\r
+       m_listFields.clear();\r
+}\r
+\r
+// return the length needed to store this header to string buffer\r
+int CMimeHeader::GetLength() const\r
+{\r
+       int nLength = 0;\r
+       list<CMimeField>::const_iterator it;\r
+       for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
+               nLength += (*it).GetLength();\r
+       return nLength + 2;                             // a pair of CRLF indicate the end of header\r
+}\r
+\r
+// store the header to string buffer\r
+int CMimeHeader::Store(char* pszData, int nMaxSize) const\r
+{\r
+       ASSERT(pszData != NULL);\r
+       int nOutput = 0;\r
+       list<CMimeField>::const_iterator it;\r
+       for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
+       {\r
+               const CMimeField& fd = *it;\r
+               int nSize = fd.Store(pszData+nOutput, nMaxSize-nOutput);\r
+               if (nSize <= 0)\r
+                       return nSize;\r
+               nOutput += nSize;\r
+       }\r
+\r
+       pszData[nOutput++] = '\r';              // add CRLF indicating the end of header\r
+       pszData[nOutput++] = '\n';\r
+       return nOutput;\r
+}\r
+\r
+// load a header from string buffer\r
+int CMimeHeader::Load(const char* pszData, int nDataSize)\r
+{\r
+       ASSERT(pszData != NULL);\r
+       int nInput = 0;\r
+       while (pszData[nInput] != 0 && pszData[nInput] != '\r')\r
+       {\r
+               CMimeField fd;\r
+               int nSize = fd.Load(pszData+nInput, nDataSize-nInput);\r
+               if (nSize <= 0)\r
+                       return nSize;\r
+\r
+               nInput += nSize;\r
+               m_listFields.push_back(fd);     // don't use SetField in case of same name fields\r
+       }\r
+\r
+       return nInput + 2;                              // skip the ending CRLF\r
+}\r
+\r
+list<CMimeField>::const_iterator CMimeHeader::FindField(const char* pszFieldName) const\r
+{\r
+       list<CMimeField>::const_iterator it;\r
+       for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
+       {\r
+               const CMimeField& fd = *it;\r
+               if (!::stricmp(fd.GetName(), pszFieldName))\r
+                       break;\r
+       }\r
+       return it;\r
+}\r
+\r
+list<CMimeField>::iterator CMimeHeader::FindField(const char* pszFieldName)\r
+{\r
+       list<CMimeField>::iterator it;\r
+       for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
+       {\r
+               CMimeField& fd = *it;\r
+               if (!::stricmp(fd.GetName(), pszFieldName))\r
+                       break;\r
+       }\r
+       return it;\r
+}\r
+\r
+//////////////////////////////////////////////////////////////////////\r
+// CMimeBody class - Represents a body part in a MIME message\r
+//////////////////////////////////////////////////////////////////////\r
+#include <fcntl.h>\r
+#include <sys/types.h>\r
+#include <sys/stat.h>\r
+#include <io.h>\r
+\r
+// initialize the content with text\r
+int CMimeBody::SetText(const char* pbText, int nLength/*=0*/)\r
+{\r
+       ASSERT(pbText != NULL);\r
+       if (!nLength)\r
+               nLength = (int)::strlen((char*)pbText);\r
+\r
+       if (!AllocateBuffer(nLength+4))\r
+               return -1;\r
+\r
+       ::memcpy(m_pbText, pbText, nLength);\r
+       m_pbText[nLength] = 0;\r
+       m_nTextSize = nLength;\r
+       return nLength;\r
+}\r
+\r
+int CMimeBody::GetText(char* pbText, int nMaxSize)\r
+{\r
+       int nSize = min(nMaxSize, m_nTextSize);\r
+       if (m_pbText != NULL)\r
+               ::memcpy(pbText, m_pbText, nSize);\r
+       return nSize;\r
+}\r
+\r
+int CMimeBody::GetText(string& strText)\r
+{\r
+       if (m_pbText != NULL)\r
+               strText.assign((const char*) m_pbText, m_nTextSize);\r
+       return m_nTextSize;\r
+}\r
+\r
+// initialize the content of this body part with a mail message\r
+bool CMimeBody::SetMessage(const CMimeMessage* pMM)\r
+{\r
+       ASSERT(pMM != NULL);\r
+       int nSize = pMM->GetLength();\r
+       if (!AllocateBuffer(nSize+4))\r
+               return false;\r
+\r
+       nSize = pMM->Store((char*)m_pbText, nSize);\r
+       m_pbText[nSize] = 0;\r
+       m_nTextSize = nSize;\r
+\r
+       const char* pszType = GetContentType();\r
+       if (!pszType || ::memicmp(pszType, "message", 7) != 0)\r
+               SetContentType("message/rfc822");\r
+       //SetTransferEncoding(CMimeConst::EncodingBinary());    // in case the default 7bit cause folding\r
+       return true;\r
+}\r
+\r
+void CMimeBody::GetMessage(CMimeMessage* pMM) const\r
+{\r
+       ASSERT(pMM != NULL);\r
+       ASSERT(m_pbText != NULL);\r
+       pMM->Load((const char*)m_pbText, m_nTextSize);\r
+}\r
+\r
+// initialize the content (attachment) by reading from a file\r
+bool CMimeBody::ReadFromFile(const char* pszFilename)\r
+{\r
+       int hFile = ::open(pszFilename, O_RDONLY | O_BINARY);\r
+       if (hFile < 0)\r
+               return false;\r
+\r
+       try\r
+       {\r
+               int nFileSize = (int)::lseek(hFile, 0L, SEEK_END);      // get file length\r
+               ::lseek(hFile, 0L, SEEK_SET);\r
+\r
+               FreeBuffer();\r
+               if (nFileSize > 0)\r
+               {\r
+                       AllocateBuffer(nFileSize+4);\r
+                       unsigned char* pszData = m_pbText;\r
+\r
+                       for (;;)\r
+                       {\r
+                               int nRead = ::read(hFile, pszData, 512);\r
+                               if (nRead < 0)\r
+                               {\r
+                                       ::close(hFile);\r
+                                       return false;\r
+                               }\r
+                               pszData += nRead;\r
+                               if (nRead < 512)\r
+                                       break;\r
+                       }\r
+                       *pszData = 0;\r
+                       m_nTextSize = nFileSize;\r
+               }\r
+       }\r
+       catch (...)\r
+       {\r
+               ::close(hFile);\r
+               throw;\r
+       }\r
+\r
+       ::close(hFile);\r
+       const char* pszName = ::strrchr(pszFilename, '\\');\r
+       if (!pszName)\r
+               pszName = pszFilename;\r
+       else\r
+               pszName++;\r
+       SetName(pszName);                               // set 'name' parameter:\r
+       return true;\r
+}\r
+\r
+// write the content (attachment) to a file\r
+bool CMimeBody::WriteToFile(const char* pszFilename)\r
+{\r
+       if (!m_nTextSize)\r
+               return true;\r
+       int hFile = ::open(pszFilename, O_CREAT | O_TRUNC | O_RDWR | O_BINARY, S_IREAD | S_IWRITE);\r
+       if (hFile < 0)\r
+               return false;\r
+\r
+       const unsigned char* pszData = m_pbText;\r
+       int nLeft = m_nTextSize;\r
+\r
+       try\r
+       {\r
+               for (;;)\r
+               {\r
+                       int nWritten = ::write(hFile, pszData, min(512, nLeft));\r
+                       if (nWritten <= 0)\r
+                       {\r
+                               ::close(hFile);\r
+                               return false;\r
+                       }\r
+                       pszData += nWritten;\r
+                       nLeft -= nWritten;\r
+                       if (nLeft <= 0)\r
+                               break;\r
+               }\r
+       }\r
+       catch (...)\r
+       {\r
+               ::close(hFile);\r
+               throw;\r
+       }\r
+\r
+       ::close(hFile);\r
+       return true;\r
+}\r
+\r
+// delete all child body parts\r
+void CMimeBody::DeleteAll()\r
+{\r
+       while (!m_listBodies.empty())\r
+       {\r
+               CMimeBody* pBP = m_listBodies.back();\r
+               m_listBodies.pop_back();\r
+               ASSERT(pBP != NULL);\r
+               delete pBP;                                     // surely delete because it was allocated by CreatePart()\r
+       }\r
+}\r
+\r
+// create a new child body part, and add it to body part list\r
+CMimeBody* CMimeBody::CreatePart(const char* pszMediaType/*=NULL*/, CMimeBody* pWhere/*=NULL*/)\r
+{\r
+       CMimeBody* pBP = CMimeEnvironment::CreateBodyPart(pszMediaType);\r
+       ASSERT(pBP != NULL);\r
+       if (pWhere != NULL)\r
+       {\r
+                for (CBodyList::iterator it = m_listBodies.begin(); it != m_listBodies.end(); it++)\r
+                       if (*it == pWhere)\r
+                       {\r
+                               m_listBodies.insert(it, pBP);\r
+                               return pBP;\r
+                       }\r
+       }\r
+       m_listBodies.push_back(pBP);\r
+       return pBP;\r
+}\r
+\r
+// remove and delete a child body part\r
+void CMimeBody::ErasePart(CMimeBody* pBP)\r
+{\r
+       ASSERT(pBP != NULL);\r
+       m_listBodies.remove(pBP);\r
+       delete pBP;\r
+}\r
+\r
+// return a list of all child body parts belong to this body part\r
+int CMimeBody::GetBodyPartList(CBodyList& rList) const\r
+{\r
+       int nCount = 0;\r
+       int nMediaType = GetMediaType();\r
+\r
+       if (MEDIA_MULTIPART != nMediaType)\r
+       {\r
+               rList.push_back((CMimeBody*)this);\r
+               nCount++;\r
+       }\r
+       else\r
+       {\r
+               list<CMimeBody*>::const_iterator it;\r
+               for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
+               {\r
+                       CMimeBody* pBP = *it;\r
+                       ASSERT(pBP != NULL);\r
+                       nCount += pBP->GetBodyPartList(rList);\r
+               }\r
+       }\r
+       return nCount;\r
+}\r
+\r
+// return a list of all attachment body parts belong to this body part\r
+int CMimeBody::GetAttachmentList(CBodyList& rList) const\r
+{\r
+       int nCount = 0;\r
+       int nMediaType = GetMediaType();\r
+\r
+       if (MEDIA_MULTIPART != nMediaType)\r
+       {\r
+               string strName = GetName();\r
+               if (strName.size() > 0)\r
+               {\r
+                       rList.push_back((CMimeBody*)this);\r
+                       nCount++;\r
+               }\r
+       }\r
+       else\r
+       {\r
+               list<CMimeBody*>::const_iterator it;\r
+               for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
+               {\r
+                       CMimeBody* pBP = *it;\r
+                       ASSERT(pBP != NULL);\r
+                       nCount += pBP->GetAttachmentList(rList);\r
+               }\r
+       }\r
+       return nCount;\r
+}\r
+\r
+void CMimeBody::Clear()\r
+{\r
+       DeleteAll();\r
+       m_itFind = m_listBodies.end();\r
+       FreeBuffer();\r
+       CMimeHeader::Clear();\r
+}\r
+\r
+// return the length needed to store this body part to string buffer\r
+int CMimeBody::GetLength() const\r
+{\r
+       int nLength = CMimeHeader::GetLength();\r
+       CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
+       ASSERT(pCoder != NULL);\r
+       pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
+       nLength += pCoder->GetOutputLength();\r
+       delete pCoder;\r
+\r
+       if (m_listBodies.empty())\r
+               return nLength;\r
+\r
+       string strBoundary = GetBoundary();\r
+       int nBoundSize = (int) strBoundary.size();\r
+       list<CMimeBody*>::const_iterator it;\r
+       for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
+       {\r
+               nLength += nBoundSize + 6;      // include 2 leading hyphens and 2 pair of CRLFs\r
+               CMimeBody* pBP = *it;\r
+               ASSERT(pBP != NULL);\r
+               nLength += pBP->GetLength();\r
+       }\r
+       nLength += nBoundSize + 8;              // include 2 leading hyphens, 2 trailng hyphens and 2 pair of CRLFs\r
+       return nLength;\r
+}\r
+\r
+// store the body part to string buffer\r
+int CMimeBody::Store(char* pszData, int nMaxSize) const\r
+{\r
+       // store header fields\r
+       int nSize = CMimeHeader::Store(pszData, nMaxSize);\r
+       if (nSize <= 0)\r
+               return nSize;\r
+\r
+       // store content\r
+       char* pszDataBegin = pszData;   // preserve start position\r
+       pszData += nSize;\r
+       nMaxSize -= nSize;\r
+\r
+       CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
+       ASSERT(pCoder != NULL);\r
+       pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
+       int nOutput = pCoder->GetOutput((unsigned char*)pszData, nMaxSize);\r
+       delete pCoder;\r
+       if (nOutput < 0)\r
+               return nOutput;\r
+\r
+       pszData += nOutput;\r
+       nMaxSize -= nOutput;\r
+       if (m_listBodies.empty())\r
+               return (int)(pszData - pszDataBegin);\r
+\r
+       // store child body parts\r
+       string strBoundary = GetBoundary();\r
+       if (strBoundary.empty())\r
+               return -1;                                      // boundary not be set\r
+\r
+       int nBoundSize = (int)strBoundary.size() + 6;\r
+       for (CBodyList::const_iterator it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
+       {\r
+               if (nMaxSize < nBoundSize)\r
+                       break;\r
+               if (m_listBodies.begin() == it && *(pszData-2) == '\r' && *(pszData-1) == '\n')\r
+               {\r
+                       pszData -= 2;\r
+                       nMaxSize += 2;\r
+               }\r
+               ::sprintf(pszData, "\r\n--%s\r\n", strBoundary.c_str());\r
+               pszData += nBoundSize;\r
+               nMaxSize -= nBoundSize;\r
+\r
+               CMimeBody* pBP = *it;\r
+               ASSERT(pBP != NULL);\r
+               nOutput = pBP->Store(pszData, nMaxSize);\r
+               if (nOutput < 0)\r
+                       return nOutput;\r
+               pszData += nOutput;\r
+               nMaxSize -= nOutput;\r
+       }\r
+\r
+       if (nMaxSize >= nBoundSize+2)   // add closing boundary delimiter\r
+       {\r
+               ::sprintf(pszData, "\r\n--%s--\r\n", strBoundary.c_str());\r
+               pszData += nBoundSize + 2;\r
+       }\r
+       return (int)(pszData - pszDataBegin);\r
+}\r
+\r
+void CMimeBody::Store(std::string &str) const\r
+{\r
+       char *temp=new char[GetLength()+1];\r
+       Store(temp,GetLength());\r
+       temp[GetLength()]='\0';\r
+       str=temp;\r
+       delete [] temp;\r
+}\r
+\r
+// load a body part from string buffer\r
+int CMimeBody::Load(const char* pszData, int nDataSize)\r
+{\r
+       // load header fields\r
+       int nSize = CMimeHeader::Load(pszData, nDataSize);\r
+       if (nSize <= 0)\r
+               return nSize;\r
+\r
+       const char* pszDataBegin = pszData;     // preserve start position\r
+       pszData += nSize;\r
+       nDataSize -= nSize;\r
+       FreeBuffer();\r
+\r
+       // determine the length of the content\r
+       const char* pszEnd = pszData + nDataSize;\r
+       int nMediaType = GetMediaType();\r
+       if (MEDIA_MULTIPART == nMediaType)\r
+       {\r
+               // find the begin boundary\r
+               string strBoundary = GetBoundary();\r
+               if (!strBoundary.empty())\r
+               {\r
+                       strBoundary = "\r\n--" + strBoundary;\r
+                       pszEnd = ::FindString(pszData-2, strBoundary.c_str(), pszEnd);\r
+                       if (!pszEnd)\r
+                               pszEnd = pszData + nDataSize;\r
+                       else\r
+                               pszEnd += 2;\r
+               }\r
+       }\r
+\r
+       // load content\r
+       nSize = (int)(pszEnd - pszData);\r
+       if (nSize > 0)\r
+       {\r
+               CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
+               ASSERT(pCoder != NULL);\r
+               pCoder->SetInput(pszData, nSize, false);\r
+               int nOutput = pCoder->GetOutputLength();\r
+               if (AllocateBuffer(nOutput+4))\r
+                       nOutput = pCoder->GetOutput(m_pbText, nOutput);\r
+               else\r
+                       nOutput = -1;\r
+               delete pCoder;\r
+               if (nOutput < 0)\r
+                       return nOutput;\r
+\r
+               ASSERT(nOutput < m_nTextSize);\r
+               m_pbText[nOutput] = 0;\r
+               m_nTextSize = nOutput;\r
+               pszData += nSize;\r
+               nDataSize -= nSize;\r
+       }\r
+       if (nDataSize <= 0)\r
+               return (int)(pszData - pszDataBegin);\r
+\r
+       // load child body parts\r
+       string strBoundary = GetBoundary();\r
+       ASSERT(strBoundary.size() > 0);\r
+       strBoundary = "\r\n--" + strBoundary;\r
+\r
+       // look for the first boundary (case sensitive)\r
+       pszData -= 2;                                   // go back to CRLF\r
+       nDataSize += 2;\r
+       pszEnd = pszData + nDataSize;\r
+       const char* pszBound1 = ::FindString(pszData, strBoundary.c_str(), pszEnd);\r
+       while (pszBound1 != NULL && pszBound1 < pszEnd)\r
+       {\r
+               const char* pszStart = ::FindString(pszBound1+2, "\r\n", pszEnd);\r
+               if (!pszStart)\r
+                       break;\r
+               pszStart += 2;\r
+               if (pszBound1[strBoundary.size()] == '-' && pszBound1[strBoundary.size()+1] == '-')\r
+                       return (int)(pszStart - pszDataBegin);  // reach the closing boundary\r
+\r
+               // look for the next boundary\r
+               const char* pszBound2 = ::FindString(pszStart, strBoundary.c_str(), pszEnd);\r
+               if (!pszBound2)                         // overflow, boundary may be truncated\r
+                       pszBound2 = pszEnd;\r
+               int nEntitySize = (int) (pszBound2 - pszStart);\r
+\r
+               // find the media type of this body part:\r
+               CMimeHeader header;\r
+               header.Load(pszStart, nEntitySize);\r
+               string strMediaType = header.GetMainType();\r
+               CMimeBody* pBP = CreatePart(strMediaType.c_str());\r
+\r
+               int nInputSize = pBP->Load(pszStart, nEntitySize);\r
+               if (nInputSize < 0)\r
+               {\r
+                       ErasePart(pBP);\r
+                       return nInputSize;\r
+               }\r
+               pszBound1 = pszBound2;\r
+       }\r
+       return (int)(pszEnd - pszDataBegin);\r
+}\r
+\r
+//////////////////////////////////////////////////////////////////////\r
+// CMimeMessage - Represents a MIME message\r
+//////////////////////////////////////////////////////////////////////\r
+\r
+void CMimeMessage::SetDate()\r
+{\r
+       time_t timeNow = ::time(NULL);\r
+       struct tm *ptm = ::localtime(&timeNow);\r
+       SetDate(ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);\r
+}\r
+\r
+void CMimeMessage::SetDate(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond)\r
+{\r
+       static const char* s_MonthNames[] =\r
+               { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };\r
+       static const char* s_DayNames[] =\r
+               { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };\r
+\r
+       struct tm tmDate;\r
+       ::memset(&tmDate, 0, sizeof(tmDate));\r
+       tmDate.tm_year = nYear - 1900;\r
+       tmDate.tm_mon = nMonth - 1;\r
+       tmDate.tm_mday = nDay;\r
+       tmDate.tm_hour = nHour;\r
+       tmDate.tm_min = nMinute;\r
+       tmDate.tm_sec = nSecond;\r
+       tmDate.tm_isdst = -1;\r
+\r
+       time_t timeDate = ::mktime(&tmDate);\r
+       if (timeDate < 0)\r
+       {\r
+               ASSERT(false);\r
+               return;\r
+       }\r
+\r
+       tmDate = *::localtime(&timeDate);                       // adjusted local time\r
+       struct tm *ptmGmt = ::gmtime(&timeDate);        // Greenwich Mean Time\r
+       long nTimeDiff = tmDate.tm_mday - ptmGmt->tm_mday;\r
+       if (nTimeDiff > 1)\r
+               nTimeDiff = -1;\r
+       else if (nTimeDiff < -1)\r
+               nTimeDiff = 1;\r
+       nTimeDiff *= 60 * 24;\r
+       nTimeDiff +=\r
+               (tmDate.tm_hour - ptmGmt->tm_hour) * 60 +\r
+               tmDate.tm_min - ptmGmt->tm_min;\r
+       if (tmDate.tm_isdst > 0)\r
+               nTimeDiff -= 60;\r
+\r
+       char szDate[40];\r
+       ASSERT(tmDate.tm_wday < 7);\r
+       ASSERT(tmDate.tm_mon < 12);\r
+       ::sprintf(szDate, "%s, %d %s %d %02d:%02d:%02d %c%02d%02d",\r
+               s_DayNames[tmDate.tm_wday],\r
+               tmDate.tm_mday, s_MonthNames[tmDate.tm_mon], tmDate.tm_year+1900,\r
+               tmDate.tm_hour, tmDate.tm_min, tmDate.tm_sec,\r
+               (nTimeDiff >= 0 ? '+' : '-'), abs(nTimeDiff / 60), abs(nTimeDiff % 60));\r
+\r
+       SetFieldValue("Date", szDate);\r
+}\r