version 0.3.29
[fms.git] / src / nntp / mime / Mime.cpp
1 //////////////////////////////////////////////////////////////////////\r
2 //\r
3 // MIME message encoding/decoding\r
4 //\r
5 // Jeff Lee\r
6 // Dec 11, 2000\r
7 //\r
8 //////////////////////////////////////////////////////////////////////\r
9 //#include "stdafx.h"\r
10 #include "../../../include/nntp/mime/MimeCode.h"\r
11 #include "../../../include/nntp/mime/MimeChar.h"\r
12 #include "../../../include/nntp/mime/Mime.h"\r
13 #include <stdlib.h>\r
14 #include <time.h>\r
15 #include <cstring>\r
16 #include <cstdio>\r
17 \r
18 #ifndef _WIN32\r
19         #define stricmp strcasecmp\r
20         #define strnicmp strncasecmp\r
21         #define memicmp memcmp\r
22 #endif\r
23 \r
24 #ifdef _DEBUG\r
25 #undef THIS_FILE\r
26 static char THIS_FILE[]=__FILE__;\r
27 #define new DEBUG_NEW\r
28 #endif\r
29 \r
30 // search for a character in the current line (before CRLF)\r
31 static const char* LineFind(const char* pszString, int ch)\r
32 {\r
33         ASSERT(pszString != NULL);\r
34         while (*pszString != 0 && *pszString != ch && *pszString != '\r' && *pszString != '\n')\r
35                 pszString++;\r
36         return *pszString == ch ? pszString : NULL;\r
37 }\r
38 \r
39 // search for string2 in string1 (strstr)\r
40 static const char* FindString(const char* pszStr1, const char* pszStr2, const char* pszEnd)\r
41 {\r
42         pszEnd -= ::strlen(pszStr2);\r
43         const char *s1, *s2;\r
44         while (pszStr1 <= pszEnd)\r
45         {\r
46                 s1 = pszStr1;\r
47                 s2 = pszStr2;\r
48                 while (*s1 == *s2 && *s2)\r
49                         s1++, s2++;\r
50                 if (!*s2)\r
51                         return pszStr1;\r
52                 pszStr1++;\r
53         }\r
54         return NULL;\r
55 }\r
56 \r
57 //////////////////////////////////////////////////////////////////////\r
58 // CMimeField class - Represents a field of a MIME body part header\r
59 //////////////////////////////////////////////////////////////////////\r
60 \r
61 void CMimeField::GetValue(string& strValue) const\r
62 {\r
63         string::size_type nEnd = m_strValue.find(';');\r
64         if (nEnd != string::npos)\r
65         {\r
66                 while (nEnd > 0 && CMimeChar::IsSpace((unsigned char)m_strValue[nEnd-1]))\r
67                         nEnd--;\r
68                 strValue.assign(m_strValue.c_str(), nEnd);\r
69         }\r
70         else\r
71                 strValue = m_strValue;\r
72 }\r
73 \r
74 // set a parameter (attribute=value) of the field\r
75 void CMimeField::SetParameter(const char* pszAttr, const char* pszValue)\r
76 {\r
77         int nSize = pszValue ? (int)::strlen(pszValue) : 0;\r
78         string strValue;\r
79         strValue.reserve(nSize+3);\r
80         if (!pszValue || *pszValue != '"')\r
81                 strValue = "\"";\r
82         if (pszValue != NULL)\r
83                 strValue += pszValue;\r
84         if (nSize < 2 || pszValue[nSize-1] != '"')\r
85                 strValue += "\"";\r
86 \r
87         int nPos;\r
88         if (!FindParameter(pszAttr, nPos, nSize))       // add new parameter\r
89         {\r
90                 m_strValue.reserve(m_strValue.size() + ::strlen(pszAttr) + strValue.size() + 5);\r
91                 //if (CMimeEnvironment::AutoFolding())\r
92                 //      m_strValue += ";\r\n\t";\r
93                 //else\r
94                 //      m_strValue += "; ";\r
95                 m_strValue += "; ";\r
96                 m_strValue += pszAttr;\r
97                 m_strValue += '=';\r
98                 m_strValue += strValue;\r
99         }\r
100         else                                                    // update existing parameter\r
101                 m_strValue.replace(nPos, nSize, strValue);\r
102 }\r
103 \r
104 // get the value of a parameter\r
105 bool CMimeField::GetParameter(const char* pszAttr, string& strValue) const\r
106 {\r
107         int nPos, nSize;\r
108         if (!FindParameter(pszAttr, nPos, nSize))\r
109         {\r
110                 strValue.clear();\r
111                 return false;\r
112         }\r
113 \r
114         if (m_strValue[nPos] == '"')\r
115         {\r
116                 nPos++;\r
117                 nSize--;\r
118                 if (nSize > 0 && m_strValue[nPos+nSize-1] == '"')\r
119                         nSize--;\r
120         }\r
121         strValue.assign(m_strValue.data()+nPos, nSize);\r
122         return true;\r
123 }\r
124 \r
125 int CMimeField::GetLength() const\r
126 {\r
127         int nLength = (int) m_strName.size() + 4;\r
128         CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
129         pCoder->SetCharset(m_strCharset.c_str());\r
130         pCoder->SetInput(m_strValue.c_str(), (int)m_strValue.size(), true);\r
131         nLength += pCoder->GetOutputLength();\r
132         delete pCoder;\r
133         return nLength;\r
134 }\r
135 \r
136 // store a field to string buffer\r
137 int CMimeField::Store(char* pszData, int nMaxSize) const\r
138 {\r
139         ASSERT(pszData != NULL);\r
140         int nMinSize = (int)m_strName.size() + 4;\r
141         if (nMaxSize < nMinSize)\r
142                 return 0;\r
143         ::strcpy(pszData, m_strName.c_str());\r
144         pszData += m_strName.size();\r
145         *pszData++ = ':';\r
146         *pszData++ = ' ';\r
147 \r
148         CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
149         pCoder->SetCharset(m_strCharset.c_str());\r
150         pCoder->SetInput(m_strValue.c_str(), (int)m_strValue.size(), true);\r
151         int nEncoded = pCoder->GetOutput((unsigned char*) pszData, nMaxSize-nMinSize);\r
152         delete pCoder;\r
153         pszData += nEncoded;\r
154 \r
155         *pszData++ = '\r';\r
156         *pszData++ = '\n';\r
157         return nMinSize + nEncoded;\r
158 }\r
159 \r
160 // load a field from string buffer\r
161 int CMimeField::Load(const char* pszData, int nDataSize)\r
162 {\r
163         Clear();\r
164         ASSERT(pszData != NULL);\r
165         const char *pszEnd, *pszStart = pszData;\r
166         // find the next field (e.g. "\r\nContent...")\r
167         while (CMimeChar::IsSpace((unsigned char)*pszStart))\r
168         {\r
169                 if (*pszStart == '\r')          // end of header ?\r
170                         return 0;\r
171                 pszStart = ::FindString(pszStart, "\r\n", pszData+nDataSize);\r
172                 if (!pszStart)\r
173                         return 0;\r
174                 pszStart += 2;\r
175         }\r
176 \r
177         // get the field name\r
178         pszEnd = ::LineFind(pszStart, ':');\r
179         if (pszEnd != NULL)                             // if colon not found, Name would be empty\r
180         {\r
181                 m_strName.assign(pszStart, (pszEnd-pszStart));\r
182                 pszStart = pszEnd + 1;\r
183         }\r
184 \r
185         // find the end of the field\r
186         while (*pszStart == ' ' || *pszStart == '\t')\r
187                 pszStart++;\r
188         pszEnd = pszStart;\r
189         do\r
190         {\r
191                 pszEnd = ::FindString(pszEnd, "\r\n", pszData+nDataSize);\r
192                 if (!pszEnd)\r
193                         return 0;\r
194                 pszEnd += 2;\r
195         } while (*pszEnd == '\t' || *pszEnd == ' ');    // linear-white-space\r
196 \r
197         // decode and unfold the field value\r
198         CFieldCodeBase* pCoder = CMimeEnvironment::CreateFieldCoder(GetName());\r
199         pCoder->SetInput(pszStart, (int)(pszEnd-pszStart)-2, false);\r
200         m_strValue.resize(pCoder->GetOutputLength());\r
201         int nSize = pCoder->GetOutput((unsigned char*) m_strValue.c_str(), (int) m_strValue.capacity());\r
202         m_strValue.resize(nSize);\r
203         m_strCharset = pCoder->GetCharset();\r
204         delete pCoder;\r
205         return (int) (pszEnd - pszData);\r
206 }\r
207 \r
208 bool CMimeField::FindParameter(const char* pszAttr, int& nPos, int& nSize) const\r
209 {\r
210         ASSERT(pszAttr != NULL);\r
211         const char* pszParms = ::strchr(m_strValue.data(), ';');\r
212         int nAttrSize = (int)::strlen(pszAttr);\r
213         while (pszParms != NULL)\r
214         {\r
215                 while (CMimeChar::IsSpace((unsigned char)*pszParms) || *pszParms == ';')\r
216                         pszParms++;\r
217 \r
218                 const char* pszName = pszParms;         // pszName -> attribute\r
219                 pszParms = ::strchr(pszParms, '=');\r
220                 if (!pszParms)\r
221                         break;\r
222 \r
223                 pszParms++;                                     // pszParams -> parameter value\r
224                 //while (*pszParms == ' ' || *pszParms == '\t')\r
225                 //      pszParms++;\r
226 \r
227                 const char* pszParmEnd = NULL;\r
228                 if (*pszParms == '"')           // quoted string\r
229                         pszParmEnd = ::strchr(pszParms+1, '"');\r
230                 if (!pszParmEnd)                        // non quoted string\r
231                 {\r
232                         pszParmEnd = pszParms;\r
233                         while (CMimeChar::IsToken(*pszParmEnd))\r
234                                 pszParmEnd++;\r
235                 }\r
236                 else  pszParmEnd++;                     // pszParmEnd -> end of parameter value\r
237 \r
238                 if (!::memicmp(pszAttr, pszName, nAttrSize) &&\r
239                         (CMimeChar::IsSpace((unsigned char)pszName[nAttrSize]) || pszName[nAttrSize] == '='))\r
240                 {\r
241                         nPos = (int)(pszParms - m_strValue.data());\r
242                         nSize = (int)(pszParmEnd - pszParms);\r
243                         return true;\r
244                 }\r
245 \r
246                 pszParms = pszParmEnd;\r
247         }\r
248         return false;\r
249 }\r
250 \r
251 //////////////////////////////////////////////////////////////////////\r
252 // CMimeHeader class - Represents the header of a MIME body part\r
253 //////////////////////////////////////////////////////////////////////\r
254 \r
255 // Return the media type represented by Content-Type field (see RFC 2046)\r
256 CMimeHeader::MediaType CMimeHeader::GetMediaType() const\r
257 {\r
258         const char* pszType = GetContentType();\r
259         if (!pszType)\r
260                 pszType = "text";\r
261 \r
262         int nIndex = 0;\r
263         while (m_TypeTable[nIndex] != NULL &&\r
264                 ::memicmp(pszType, m_TypeTable[nIndex], ::strlen(m_TypeTable[nIndex])) != 0)\r
265                 nIndex++;\r
266         return (MediaType) nIndex;\r
267 }\r
268 \r
269 // get the top-level media type\r
270 string CMimeHeader::GetMainType() const\r
271 {\r
272         string strType;\r
273         const char* pszType = GetContentType();\r
274         if (pszType != NULL)\r
275         {\r
276                 const char* pszSlash = ::strchr(pszType, '/');\r
277                 if (pszSlash != NULL)\r
278                         strType.assign(pszType, pszSlash-pszType);\r
279                 else\r
280                         strType = pszType;\r
281         }\r
282         else\r
283                 strType = "text";\r
284         return strType;\r
285 }\r
286 \r
287 // get the subtype\r
288 string CMimeHeader::GetSubType() const\r
289 {\r
290         string strSubType;\r
291         const CMimeField *pfd = GetField(CMimeConst::ContentType());\r
292         if (pfd != NULL)\r
293         {\r
294                 string strType;\r
295                 pfd->GetValue(strType);\r
296                 string::size_type nSlash = strType.find('/');\r
297                 if (nSlash > 0)\r
298                         strSubType = strType.substr(nSlash+1);\r
299         }\r
300         else\r
301                 strSubType = "plain";\r
302         return strSubType;\r
303 }\r
304 \r
305 // set the 'charset' parameter (for text) of Content-Type field\r
306 void CMimeHeader::SetCharset(const char* pszCharset)\r
307 {\r
308         CMimeField *pfd = GetField(CMimeConst::ContentType());\r
309         if (!pfd)\r
310         {\r
311                 CMimeField fd;\r
312                 fd.SetName(CMimeConst::ContentType());\r
313                 fd.SetValue("text/plain");\r
314                 fd.SetParameter(CMimeConst::Charset(), pszCharset);\r
315                 m_listFields.push_back(fd);\r
316         }\r
317         else\r
318                 pfd->SetParameter(CMimeConst::Charset(), pszCharset);\r
319 }\r
320 \r
321 // set the 'name' parameter (for attachment) of Content-Type field\r
322 void CMimeHeader::SetName(const char* pszName)\r
323 {\r
324         CMimeField *pfd = GetField(CMimeConst::ContentType());\r
325         if (!pfd)\r
326         {\r
327                 // get the appropriate media-type/subtype according to file extension\r
328                 ASSERT(pszName != NULL);\r
329                 string strType;\r
330                 const char* pszType = "application/octet-stream";\r
331                 const char* pszFileExt = ::strrchr(pszName, '.');\r
332                 if (pszFileExt != NULL)\r
333                 {\r
334                         pszFileExt++;\r
335                         int nIndex = 0;\r
336                         while (m_TypeCvtTable[nIndex].nMediaType != MEDIA_UNKNOWN)\r
337                         {\r
338                                 if (!::stricmp(pszFileExt, m_TypeCvtTable[nIndex].pszFileExt))\r
339                                 {\r
340                                         strType = m_TypeTable[m_TypeCvtTable[nIndex].nMediaType];\r
341                                         strType += '/';\r
342                                         strType += m_TypeCvtTable[nIndex].pszSubType;\r
343                                         pszType = strType.c_str();\r
344                                         break;\r
345                                 }\r
346                                 nIndex++;\r
347                         }\r
348                 }\r
349 \r
350                 CMimeField fd;\r
351                 fd.SetName(CMimeConst::ContentType());\r
352                 fd.SetValue(pszType);\r
353                 fd.SetParameter(CMimeConst::Name(), pszName);\r
354                 m_listFields.push_back(fd);\r
355         }\r
356         else\r
357                 pfd->SetParameter(CMimeConst::Name(), pszName);\r
358 }\r
359 \r
360 // set 'boundary' parameter (for multipart) of Content-Type field\r
361 void CMimeHeader::SetBoundary(const char* pszBoundary/*=NULL*/)\r
362 {\r
363         static int s_nPartNumber = 0;\r
364         char buf[80];\r
365         if (!pszBoundary)                               // generate a new boundary delimeter\r
366         {\r
367                 ::srand(((unsigned)::time(NULL)));// ^ reinterpret_cast<unsigned>(this));\r
368                 ::sprintf(buf, "__=_Part_Boundary_%03d_%06d.%06d", ++s_nPartNumber, rand(), rand());\r
369                 if (s_nPartNumber >= 9)\r
370                         s_nPartNumber = 0;\r
371                 pszBoundary = buf;\r
372         }\r
373 \r
374         CMimeField *pfd = GetField(CMimeConst::ContentType());\r
375         if (!pfd)\r
376         {\r
377                 CMimeField fd;\r
378                 fd.SetName(CMimeConst::ContentType());\r
379                 fd.SetValue("multipart/mixed");\r
380                 fd.SetParameter(CMimeConst::Boundary(), pszBoundary);\r
381                 m_listFields.push_back(fd);\r
382         }\r
383         else\r
384         {\r
385                 if (::memicmp(pfd->GetValue(), "multipart", 9) != 0)\r
386                         pfd->SetValue("multipart/mixed");\r
387                 pfd->SetParameter(CMimeConst::Boundary(), pszBoundary);\r
388         }\r
389 }\r
390 \r
391 void CMimeHeader::Clear()\r
392 {\r
393         m_listFields.clear();\r
394 }\r
395 \r
396 // return the length needed to store this header to string buffer\r
397 int CMimeHeader::GetLength() const\r
398 {\r
399         int nLength = 0;\r
400         list<CMimeField>::const_iterator it;\r
401         for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
402                 nLength += (*it).GetLength();\r
403         return nLength + 2;                             // a pair of CRLF indicate the end of header\r
404 }\r
405 \r
406 // store the header to string buffer\r
407 int CMimeHeader::Store(char* pszData, int nMaxSize) const\r
408 {\r
409         ASSERT(pszData != NULL);\r
410         int nOutput = 0;\r
411         list<CMimeField>::const_iterator it;\r
412         for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
413         {\r
414                 const CMimeField& fd = *it;\r
415                 int nSize = fd.Store(pszData+nOutput, nMaxSize-nOutput);\r
416                 if (nSize <= 0)\r
417                         return nSize;\r
418                 nOutput += nSize;\r
419         }\r
420 \r
421         pszData[nOutput++] = '\r';              // add CRLF indicating the end of header\r
422         pszData[nOutput++] = '\n';\r
423         return nOutput;\r
424 }\r
425 \r
426 // load a header from string buffer\r
427 int CMimeHeader::Load(const char* pszData, int nDataSize)\r
428 {\r
429         ASSERT(pszData != NULL);\r
430         int nInput = 0;\r
431         while (pszData[nInput] != 0 && pszData[nInput] != '\r')\r
432         {\r
433                 CMimeField fd;\r
434                 int nSize = fd.Load(pszData+nInput, nDataSize-nInput);\r
435                 if (nSize <= 0)\r
436                         return nSize;\r
437 \r
438                 nInput += nSize;\r
439                 m_listFields.push_back(fd);     // don't use SetField in case of same name fields\r
440         }\r
441 \r
442         return nInput + 2;                              // skip the ending CRLF\r
443 }\r
444 \r
445 list<CMimeField>::const_iterator CMimeHeader::FindField(const char* pszFieldName) const\r
446 {\r
447         list<CMimeField>::const_iterator it;\r
448         for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
449         {\r
450                 const CMimeField& fd = *it;\r
451                 if (!::stricmp(fd.GetName(), pszFieldName))\r
452                         break;\r
453         }\r
454         return it;\r
455 }\r
456 \r
457 list<CMimeField>::iterator CMimeHeader::FindField(const char* pszFieldName)\r
458 {\r
459         list<CMimeField>::iterator it;\r
460         for (it = m_listFields.begin(); it != m_listFields.end(); it++)\r
461         {\r
462                 CMimeField& fd = *it;\r
463                 if (!::stricmp(fd.GetName(), pszFieldName))\r
464                         break;\r
465         }\r
466         return it;\r
467 }\r
468 \r
469 //////////////////////////////////////////////////////////////////////\r
470 // CMimeBody class - Represents a body part in a MIME message\r
471 //////////////////////////////////////////////////////////////////////\r
472 #include <fcntl.h>\r
473 #include <sys/types.h>\r
474 #include <sys/stat.h>\r
475 \r
476 #ifdef _WIN32\r
477         #include <io.h>\r
478 #else\r
479         #if !defined(__APPLE__) && !defined(__DARWIN__)\r
480                 #if !defined(__FreeBSD__) && !defined(solaris) && !defined(__sun)\r
481                         #include <sys/io.h>\r
482                 #else\r
483                         #include <stdio.h>\r
484                 #endif\r
485         #endif\r
486 #endif\r
487 \r
488 #ifndef O_BINARY\r
489         #define O_BINARY 0\r
490 #endif\r
491 \r
492 // initialize the content with text\r
493 int CMimeBody::SetText(const char* pbText, int nLength/*=0*/)\r
494 {\r
495         ASSERT(pbText != NULL);\r
496         if (!nLength)\r
497                 nLength = (int)::strlen((char*)pbText);\r
498 \r
499         if (!AllocateBuffer(nLength+4))\r
500                 return -1;\r
501 \r
502         ::memcpy(m_pbText, pbText, nLength);\r
503         m_pbText[nLength] = 0;\r
504         m_nTextSize = nLength;\r
505         return nLength;\r
506 }\r
507 \r
508 int CMimeBody::GetText(char* pbText, int nMaxSize)\r
509 {\r
510         int nSize = min(nMaxSize, m_nTextSize);\r
511         if (m_pbText != NULL)\r
512                 ::memcpy(pbText, m_pbText, nSize);\r
513         return nSize;\r
514 }\r
515 \r
516 int CMimeBody::GetText(string& strText)\r
517 {\r
518         if (m_pbText != NULL)\r
519                 strText.assign((const char*) m_pbText, m_nTextSize);\r
520         return m_nTextSize;\r
521 }\r
522 \r
523 // initialize the content of this body part with a mail message\r
524 bool CMimeBody::SetMessage(const CMimeMessage* pMM)\r
525 {\r
526         ASSERT(pMM != NULL);\r
527         int nSize = pMM->GetLength();\r
528         if (!AllocateBuffer(nSize+4))\r
529                 return false;\r
530 \r
531         nSize = pMM->Store((char*)m_pbText, nSize);\r
532         m_pbText[nSize] = 0;\r
533         m_nTextSize = nSize;\r
534 \r
535         const char* pszType = GetContentType();\r
536         if (!pszType || ::memicmp(pszType, "message", 7) != 0)\r
537                 SetContentType("message/rfc822");\r
538         //SetTransferEncoding(CMimeConst::EncodingBinary());    // in case the default 7bit cause folding\r
539         return true;\r
540 }\r
541 \r
542 void CMimeBody::GetMessage(CMimeMessage* pMM) const\r
543 {\r
544         ASSERT(pMM != NULL);\r
545         ASSERT(m_pbText != NULL);\r
546         pMM->Load((const char*)m_pbText, m_nTextSize);\r
547 }\r
548 \r
549 // initialize the content (attachment) by reading from a file\r
550 bool CMimeBody::ReadFromFile(const char* pszFilename)\r
551 {\r
552         int hFile = ::open(pszFilename, O_RDONLY | O_BINARY);\r
553         if (hFile < 0)\r
554                 return false;\r
555 \r
556         try\r
557         {\r
558                 int nFileSize = (int)::lseek(hFile, 0L, SEEK_END);      // get file length\r
559                 ::lseek(hFile, 0L, SEEK_SET);\r
560 \r
561                 FreeBuffer();\r
562                 if (nFileSize > 0)\r
563                 {\r
564                         AllocateBuffer(nFileSize+4);\r
565                         unsigned char* pszData = m_pbText;\r
566 \r
567                         for (;;)\r
568                         {\r
569                                 int nRead = ::read(hFile, pszData, 512);\r
570                                 if (nRead < 0)\r
571                                 {\r
572                                         ::close(hFile);\r
573                                         return false;\r
574                                 }\r
575                                 pszData += nRead;\r
576                                 if (nRead < 512)\r
577                                         break;\r
578                         }\r
579                         *pszData = 0;\r
580                         m_nTextSize = nFileSize;\r
581                 }\r
582         }\r
583         catch (...)\r
584         {\r
585                 ::close(hFile);\r
586                 throw;\r
587         }\r
588 \r
589         ::close(hFile);\r
590         const char* pszName = ::strrchr(pszFilename, '\\');\r
591         if (!pszName)\r
592                 pszName = pszFilename;\r
593         else\r
594                 pszName++;\r
595         SetName(pszName);                               // set 'name' parameter:\r
596         return true;\r
597 }\r
598 \r
599 // write the content (attachment) to a file\r
600 bool CMimeBody::WriteToFile(const char* pszFilename)\r
601 {\r
602         if (!m_nTextSize)\r
603                 return true;\r
604         int hFile = ::open(pszFilename, O_CREAT | O_TRUNC | O_RDWR | O_BINARY, S_IREAD | S_IWRITE);\r
605         if (hFile < 0)\r
606                 return false;\r
607 \r
608         const unsigned char* pszData = m_pbText;\r
609         int nLeft = m_nTextSize;\r
610 \r
611         try\r
612         {\r
613                 for (;;)\r
614                 {\r
615                         int nWritten = ::write(hFile, pszData, min(512, nLeft));\r
616                         if (nWritten <= 0)\r
617                         {\r
618                                 ::close(hFile);\r
619                                 return false;\r
620                         }\r
621                         pszData += nWritten;\r
622                         nLeft -= nWritten;\r
623                         if (nLeft <= 0)\r
624                                 break;\r
625                 }\r
626         }\r
627         catch (...)\r
628         {\r
629                 ::close(hFile);\r
630                 throw;\r
631         }\r
632 \r
633         ::close(hFile);\r
634         return true;\r
635 }\r
636 \r
637 // delete all child body parts\r
638 void CMimeBody::DeleteAll()\r
639 {\r
640         while (!m_listBodies.empty())\r
641         {\r
642                 CMimeBody* pBP = m_listBodies.back();\r
643                 m_listBodies.pop_back();\r
644                 ASSERT(pBP != NULL);\r
645                 delete pBP;                                     // surely delete because it was allocated by CreatePart()\r
646         }\r
647 }\r
648 \r
649 // create a new child body part, and add it to body part list\r
650 CMimeBody* CMimeBody::CreatePart(const char* pszMediaType/*=NULL*/, CMimeBody* pWhere/*=NULL*/)\r
651 {\r
652         CMimeBody* pBP = CMimeEnvironment::CreateBodyPart(pszMediaType);\r
653         ASSERT(pBP != NULL);\r
654         if (pWhere != NULL)\r
655         {\r
656                  for (CBodyList::iterator it = m_listBodies.begin(); it != m_listBodies.end(); it++)\r
657                         if (*it == pWhere)\r
658                         {\r
659                                 m_listBodies.insert(it, pBP);\r
660                                 return pBP;\r
661                         }\r
662         }\r
663         m_listBodies.push_back(pBP);\r
664         return pBP;\r
665 }\r
666 \r
667 // remove and delete a child body part\r
668 void CMimeBody::ErasePart(CMimeBody* pBP)\r
669 {\r
670         ASSERT(pBP != NULL);\r
671         m_listBodies.remove(pBP);\r
672         delete pBP;\r
673 }\r
674 \r
675 // return a list of all child body parts belong to this body part\r
676 int CMimeBody::GetBodyPartList(CBodyList& rList) const\r
677 {\r
678         int nCount = 0;\r
679         int nMediaType = GetMediaType();\r
680 \r
681         if (MEDIA_MULTIPART != nMediaType)\r
682         {\r
683                 rList.push_back((CMimeBody*)this);\r
684                 nCount++;\r
685         }\r
686         else\r
687         {\r
688                 list<CMimeBody*>::const_iterator it;\r
689                 for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
690                 {\r
691                         CMimeBody* pBP = *it;\r
692                         ASSERT(pBP != NULL);\r
693                         nCount += pBP->GetBodyPartList(rList);\r
694                 }\r
695         }\r
696         return nCount;\r
697 }\r
698 \r
699 // return a list of all attachment body parts belong to this body part\r
700 int CMimeBody::GetAttachmentList(CBodyList& rList) const\r
701 {\r
702         int nCount = 0;\r
703         int nMediaType = GetMediaType();\r
704 \r
705         if (MEDIA_MULTIPART != nMediaType)\r
706         {\r
707                 string strName = GetName();\r
708                 if (strName.size() > 0)\r
709                 {\r
710                         rList.push_back((CMimeBody*)this);\r
711                         nCount++;\r
712                 }\r
713         }\r
714         else\r
715         {\r
716                 list<CMimeBody*>::const_iterator it;\r
717                 for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
718                 {\r
719                         CMimeBody* pBP = *it;\r
720                         ASSERT(pBP != NULL);\r
721                         nCount += pBP->GetAttachmentList(rList);\r
722                 }\r
723         }\r
724         return nCount;\r
725 }\r
726 \r
727 void CMimeBody::Clear()\r
728 {\r
729         DeleteAll();\r
730         m_itFind = m_listBodies.end();\r
731         FreeBuffer();\r
732         CMimeHeader::Clear();\r
733 }\r
734 \r
735 // return the length needed to store this body part to string buffer\r
736 int CMimeBody::GetLength() const\r
737 {\r
738         int nLength = CMimeHeader::GetLength();\r
739         CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
740         ASSERT(pCoder != NULL);\r
741         pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
742         nLength += pCoder->GetOutputLength();\r
743         delete pCoder;\r
744 \r
745         if (m_listBodies.empty())\r
746                 return nLength;\r
747 \r
748         string strBoundary = GetBoundary();\r
749         int nBoundSize = (int) strBoundary.size();\r
750         list<CMimeBody*>::const_iterator it;\r
751         for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
752         {\r
753                 nLength += nBoundSize + 6;      // include 2 leading hyphens and 2 pair of CRLFs\r
754                 CMimeBody* pBP = *it;\r
755                 ASSERT(pBP != NULL);\r
756                 nLength += pBP->GetLength();\r
757         }\r
758         nLength += nBoundSize + 8;              // include 2 leading hyphens, 2 trailng hyphens and 2 pair of CRLFs\r
759         return nLength;\r
760 }\r
761 \r
762 // store the body part to string buffer\r
763 int CMimeBody::Store(char* pszData, int nMaxSize) const\r
764 {\r
765         // store header fields\r
766         int nSize = CMimeHeader::Store(pszData, nMaxSize);\r
767         if (nSize <= 0)\r
768                 return nSize;\r
769 \r
770         // store content\r
771         char* pszDataBegin = pszData;   // preserve start position\r
772         pszData += nSize;\r
773         nMaxSize -= nSize;\r
774 \r
775         CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
776         ASSERT(pCoder != NULL);\r
777         pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
778         int nOutput = pCoder->GetOutput((unsigned char*)pszData, nMaxSize);\r
779         delete pCoder;\r
780         if (nOutput < 0)\r
781                 return nOutput;\r
782 \r
783         pszData += nOutput;\r
784         nMaxSize -= nOutput;\r
785         if (m_listBodies.empty())\r
786                 return (int)(pszData - pszDataBegin);\r
787 \r
788         // store child body parts\r
789         string strBoundary = GetBoundary();\r
790         if (strBoundary.empty())\r
791                 return -1;                                      // boundary not be set\r
792 \r
793         int nBoundSize = (int)strBoundary.size() + 6;\r
794         for (CBodyList::const_iterator it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
795         {\r
796                 if (nMaxSize < nBoundSize)\r
797                         break;\r
798                 if (m_listBodies.begin() == it && *(pszData-2) == '\r' && *(pszData-1) == '\n')\r
799                 {\r
800                         pszData -= 2;\r
801                         nMaxSize += 2;\r
802                 }\r
803                 ::sprintf(pszData, "\r\n--%s\r\n", strBoundary.c_str());\r
804                 pszData += nBoundSize;\r
805                 nMaxSize -= nBoundSize;\r
806 \r
807                 CMimeBody* pBP = *it;\r
808                 ASSERT(pBP != NULL);\r
809                 nOutput = pBP->Store(pszData, nMaxSize);\r
810                 if (nOutput < 0)\r
811                         return nOutput;\r
812                 pszData += nOutput;\r
813                 nMaxSize -= nOutput;\r
814         }\r
815 \r
816         if (nMaxSize >= nBoundSize+2)   // add closing boundary delimiter\r
817         {\r
818                 ::sprintf(pszData, "\r\n--%s--\r\n", strBoundary.c_str());\r
819                 pszData += nBoundSize + 2;\r
820         }\r
821         return (int)(pszData - pszDataBegin);\r
822 }\r
823 \r
824 void CMimeBody::Store(std::string &str) const\r
825 {\r
826         char *temp=new char[GetLength()+1];\r
827         Store(temp,GetLength());\r
828         temp[GetLength()]='\0';\r
829         str=temp;\r
830         delete [] temp;\r
831 }\r
832 \r
833 // load a body part from string buffer\r
834 int CMimeBody::Load(const char* pszData, int nDataSize)\r
835 {\r
836         // load header fields\r
837         int nSize = CMimeHeader::Load(pszData, nDataSize);\r
838         if (nSize <= 0)\r
839                 return nSize;\r
840 \r
841         const char* pszDataBegin = pszData;     // preserve start position\r
842         pszData += nSize;\r
843         nDataSize -= nSize;\r
844         FreeBuffer();\r
845 \r
846         // determine the length of the content\r
847         const char* pszEnd = pszData + nDataSize;\r
848         int nMediaType = GetMediaType();\r
849         if (MEDIA_MULTIPART == nMediaType)\r
850         {\r
851                 // find the begin boundary\r
852                 string strBoundary = GetBoundary();\r
853                 if (!strBoundary.empty())\r
854                 {\r
855                         strBoundary = "\r\n--" + strBoundary;\r
856                         pszEnd = ::FindString(pszData-2, strBoundary.c_str(), pszEnd);\r
857                         if (!pszEnd)\r
858                                 pszEnd = pszData + nDataSize;\r
859                         else\r
860                                 pszEnd += 2;\r
861                 }\r
862         }\r
863 \r
864         // load content\r
865         nSize = (int)(pszEnd - pszData);\r
866         if (nSize > 0)\r
867         {\r
868                 CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
869                 ASSERT(pCoder != NULL);\r
870                 pCoder->SetInput(pszData, nSize, false);\r
871                 int nOutput = pCoder->GetOutputLength();\r
872                 if (AllocateBuffer(nOutput+4))\r
873                         nOutput = pCoder->GetOutput(m_pbText, nOutput);\r
874                 else\r
875                         nOutput = -1;\r
876                 delete pCoder;\r
877                 if (nOutput < 0)\r
878                         return nOutput;\r
879 \r
880                 ASSERT(nOutput < m_nTextSize);\r
881                 m_pbText[nOutput] = 0;\r
882                 m_nTextSize = nOutput;\r
883                 pszData += nSize;\r
884                 nDataSize -= nSize;\r
885         }\r
886         if (nDataSize <= 0)\r
887                 return (int)(pszData - pszDataBegin);\r
888 \r
889         // load child body parts\r
890         string strBoundary = GetBoundary();\r
891         ASSERT(strBoundary.size() > 0);\r
892         strBoundary = "\r\n--" + strBoundary;\r
893 \r
894         // look for the first boundary (case sensitive)\r
895         pszData -= 2;                                   // go back to CRLF\r
896         nDataSize += 2;\r
897         pszEnd = pszData + nDataSize;\r
898         const char* pszBound1 = ::FindString(pszData, strBoundary.c_str(), pszEnd);\r
899         while (pszBound1 != NULL && pszBound1 < pszEnd)\r
900         {\r
901                 const char* pszStart = ::FindString(pszBound1+2, "\r\n", pszEnd);\r
902                 if (!pszStart)\r
903                         break;\r
904                 pszStart += 2;\r
905                 if (pszBound1[strBoundary.size()] == '-' && pszBound1[strBoundary.size()+1] == '-')\r
906                         return (int)(pszStart - pszDataBegin);  // reach the closing boundary\r
907 \r
908                 // look for the next boundary\r
909                 const char* pszBound2 = ::FindString(pszStart, strBoundary.c_str(), pszEnd);\r
910                 if (!pszBound2)                         // overflow, boundary may be truncated\r
911                         pszBound2 = pszEnd;\r
912                 int nEntitySize = (int) (pszBound2 - pszStart);\r
913 \r
914                 // find the media type of this body part:\r
915                 CMimeHeader header;\r
916                 header.Load(pszStart, nEntitySize);\r
917                 string strMediaType = header.GetMainType();\r
918                 CMimeBody* pBP = CreatePart(strMediaType.c_str());\r
919 \r
920                 int nInputSize = pBP->Load(pszStart, nEntitySize);\r
921                 if (nInputSize < 0)\r
922                 {\r
923                         ErasePart(pBP);\r
924                         return nInputSize;\r
925                 }\r
926                 pszBound1 = pszBound2;\r
927         }\r
928         return (int)(pszEnd - pszDataBegin);\r
929 }\r
930 \r
931 //////////////////////////////////////////////////////////////////////\r
932 // CMimeMessage - Represents a MIME message\r
933 //////////////////////////////////////////////////////////////////////\r
934 \r
935 void CMimeMessage::SetDate()\r
936 {\r
937         time_t timeNow = ::time(NULL);\r
938         struct tm *ptm = ::localtime(&timeNow);\r
939         SetDate(ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);\r
940 }\r
941 \r
942 void CMimeMessage::SetDate(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond)\r
943 {\r
944         static const char* s_MonthNames[] =\r
945                 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };\r
946         static const char* s_DayNames[] =\r
947                 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };\r
948 \r
949         struct tm tmDate;\r
950         ::memset(&tmDate, 0, sizeof(tmDate));\r
951         tmDate.tm_year = nYear - 1900;\r
952         tmDate.tm_mon = nMonth - 1;\r
953         tmDate.tm_mday = nDay;\r
954         tmDate.tm_hour = nHour;\r
955         tmDate.tm_min = nMinute;\r
956         tmDate.tm_sec = nSecond;\r
957         tmDate.tm_isdst = -1;\r
958 \r
959         time_t timeDate = ::mktime(&tmDate);\r
960         if (timeDate < 0)\r
961         {\r
962                 ASSERT(false);\r
963                 return;\r
964         }\r
965 \r
966         tmDate = *::localtime(&timeDate);                       // adjusted local time\r
967         struct tm *ptmGmt = ::gmtime(&timeDate);        // Greenwich Mean Time\r
968         long nTimeDiff = tmDate.tm_mday - ptmGmt->tm_mday;\r
969         if (nTimeDiff > 1)\r
970                 nTimeDiff = -1;\r
971         else if (nTimeDiff < -1)\r
972                 nTimeDiff = 1;\r
973         nTimeDiff *= 60 * 24;\r
974         nTimeDiff +=\r
975                 (tmDate.tm_hour - ptmGmt->tm_hour) * 60 +\r
976                 tmDate.tm_min - ptmGmt->tm_min;\r
977         if (tmDate.tm_isdst > 0)\r
978                 nTimeDiff -= 60;\r
979 \r
980         char szDate[40];\r
981         ASSERT(tmDate.tm_wday < 7);\r
982         ASSERT(tmDate.tm_mon < 12);\r
983         ::sprintf(szDate, "%s, %d %s %d %02d:%02d:%02d %c%02d%02d",\r
984                 s_DayNames[tmDate.tm_wday],\r
985                 tmDate.tm_mday, s_MonthNames[tmDate.tm_mon], tmDate.tm_year+1900,\r
986                 tmDate.tm_hour, tmDate.tm_min, tmDate.tm_sec,\r
987                 (nTimeDiff >= 0 ? '+' : '-'), abs(nTimeDiff / 60), abs(nTimeDiff % 60));\r
988 \r
989         SetFieldValue("Date", szDate);\r
990 }\r