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