version 0.1.10
[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)));// ^ reinterpret_cast<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         #if !defined(__APPLE__) && !defined(__DARWIN__)\r
478                 #ifndef __FreeBSD__\r
479                         #include <sys/io.h>\r
480                 #else\r
481                         #include <stdio.h>\r
482                 #endif\r
483         #endif\r
484 #endif\r
485 \r
486 #ifndef O_BINARY\r
487         #define O_BINARY 0\r
488 #endif\r
489 \r
490 // initialize the content with text\r
491 int CMimeBody::SetText(const char* pbText, int nLength/*=0*/)\r
492 {\r
493         ASSERT(pbText != NULL);\r
494         if (!nLength)\r
495                 nLength = (int)::strlen((char*)pbText);\r
496 \r
497         if (!AllocateBuffer(nLength+4))\r
498                 return -1;\r
499 \r
500         ::memcpy(m_pbText, pbText, nLength);\r
501         m_pbText[nLength] = 0;\r
502         m_nTextSize = nLength;\r
503         return nLength;\r
504 }\r
505 \r
506 int CMimeBody::GetText(char* pbText, int nMaxSize)\r
507 {\r
508         int nSize = min(nMaxSize, m_nTextSize);\r
509         if (m_pbText != NULL)\r
510                 ::memcpy(pbText, m_pbText, nSize);\r
511         return nSize;\r
512 }\r
513 \r
514 int CMimeBody::GetText(string& strText)\r
515 {\r
516         if (m_pbText != NULL)\r
517                 strText.assign((const char*) m_pbText, m_nTextSize);\r
518         return m_nTextSize;\r
519 }\r
520 \r
521 // initialize the content of this body part with a mail message\r
522 bool CMimeBody::SetMessage(const CMimeMessage* pMM)\r
523 {\r
524         ASSERT(pMM != NULL);\r
525         int nSize = pMM->GetLength();\r
526         if (!AllocateBuffer(nSize+4))\r
527                 return false;\r
528 \r
529         nSize = pMM->Store((char*)m_pbText, nSize);\r
530         m_pbText[nSize] = 0;\r
531         m_nTextSize = nSize;\r
532 \r
533         const char* pszType = GetContentType();\r
534         if (!pszType || ::memicmp(pszType, "message", 7) != 0)\r
535                 SetContentType("message/rfc822");\r
536         //SetTransferEncoding(CMimeConst::EncodingBinary());    // in case the default 7bit cause folding\r
537         return true;\r
538 }\r
539 \r
540 void CMimeBody::GetMessage(CMimeMessage* pMM) const\r
541 {\r
542         ASSERT(pMM != NULL);\r
543         ASSERT(m_pbText != NULL);\r
544         pMM->Load((const char*)m_pbText, m_nTextSize);\r
545 }\r
546 \r
547 // initialize the content (attachment) by reading from a file\r
548 bool CMimeBody::ReadFromFile(const char* pszFilename)\r
549 {\r
550         int hFile = ::open(pszFilename, O_RDONLY | O_BINARY);\r
551         if (hFile < 0)\r
552                 return false;\r
553 \r
554         try\r
555         {\r
556                 int nFileSize = (int)::lseek(hFile, 0L, SEEK_END);      // get file length\r
557                 ::lseek(hFile, 0L, SEEK_SET);\r
558 \r
559                 FreeBuffer();\r
560                 if (nFileSize > 0)\r
561                 {\r
562                         AllocateBuffer(nFileSize+4);\r
563                         unsigned char* pszData = m_pbText;\r
564 \r
565                         for (;;)\r
566                         {\r
567                                 int nRead = ::read(hFile, pszData, 512);\r
568                                 if (nRead < 0)\r
569                                 {\r
570                                         ::close(hFile);\r
571                                         return false;\r
572                                 }\r
573                                 pszData += nRead;\r
574                                 if (nRead < 512)\r
575                                         break;\r
576                         }\r
577                         *pszData = 0;\r
578                         m_nTextSize = nFileSize;\r
579                 }\r
580         }\r
581         catch (...)\r
582         {\r
583                 ::close(hFile);\r
584                 throw;\r
585         }\r
586 \r
587         ::close(hFile);\r
588         const char* pszName = ::strrchr(pszFilename, '\\');\r
589         if (!pszName)\r
590                 pszName = pszFilename;\r
591         else\r
592                 pszName++;\r
593         SetName(pszName);                               // set 'name' parameter:\r
594         return true;\r
595 }\r
596 \r
597 // write the content (attachment) to a file\r
598 bool CMimeBody::WriteToFile(const char* pszFilename)\r
599 {\r
600         if (!m_nTextSize)\r
601                 return true;\r
602         int hFile = ::open(pszFilename, O_CREAT | O_TRUNC | O_RDWR | O_BINARY, S_IREAD | S_IWRITE);\r
603         if (hFile < 0)\r
604                 return false;\r
605 \r
606         const unsigned char* pszData = m_pbText;\r
607         int nLeft = m_nTextSize;\r
608 \r
609         try\r
610         {\r
611                 for (;;)\r
612                 {\r
613                         int nWritten = ::write(hFile, pszData, min(512, nLeft));\r
614                         if (nWritten <= 0)\r
615                         {\r
616                                 ::close(hFile);\r
617                                 return false;\r
618                         }\r
619                         pszData += nWritten;\r
620                         nLeft -= nWritten;\r
621                         if (nLeft <= 0)\r
622                                 break;\r
623                 }\r
624         }\r
625         catch (...)\r
626         {\r
627                 ::close(hFile);\r
628                 throw;\r
629         }\r
630 \r
631         ::close(hFile);\r
632         return true;\r
633 }\r
634 \r
635 // delete all child body parts\r
636 void CMimeBody::DeleteAll()\r
637 {\r
638         while (!m_listBodies.empty())\r
639         {\r
640                 CMimeBody* pBP = m_listBodies.back();\r
641                 m_listBodies.pop_back();\r
642                 ASSERT(pBP != NULL);\r
643                 delete pBP;                                     // surely delete because it was allocated by CreatePart()\r
644         }\r
645 }\r
646 \r
647 // create a new child body part, and add it to body part list\r
648 CMimeBody* CMimeBody::CreatePart(const char* pszMediaType/*=NULL*/, CMimeBody* pWhere/*=NULL*/)\r
649 {\r
650         CMimeBody* pBP = CMimeEnvironment::CreateBodyPart(pszMediaType);\r
651         ASSERT(pBP != NULL);\r
652         if (pWhere != NULL)\r
653         {\r
654                  for (CBodyList::iterator it = m_listBodies.begin(); it != m_listBodies.end(); it++)\r
655                         if (*it == pWhere)\r
656                         {\r
657                                 m_listBodies.insert(it, pBP);\r
658                                 return pBP;\r
659                         }\r
660         }\r
661         m_listBodies.push_back(pBP);\r
662         return pBP;\r
663 }\r
664 \r
665 // remove and delete a child body part\r
666 void CMimeBody::ErasePart(CMimeBody* pBP)\r
667 {\r
668         ASSERT(pBP != NULL);\r
669         m_listBodies.remove(pBP);\r
670         delete pBP;\r
671 }\r
672 \r
673 // return a list of all child body parts belong to this body part\r
674 int CMimeBody::GetBodyPartList(CBodyList& rList) const\r
675 {\r
676         int nCount = 0;\r
677         int nMediaType = GetMediaType();\r
678 \r
679         if (MEDIA_MULTIPART != nMediaType)\r
680         {\r
681                 rList.push_back((CMimeBody*)this);\r
682                 nCount++;\r
683         }\r
684         else\r
685         {\r
686                 list<CMimeBody*>::const_iterator it;\r
687                 for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
688                 {\r
689                         CMimeBody* pBP = *it;\r
690                         ASSERT(pBP != NULL);\r
691                         nCount += pBP->GetBodyPartList(rList);\r
692                 }\r
693         }\r
694         return nCount;\r
695 }\r
696 \r
697 // return a list of all attachment body parts belong to this body part\r
698 int CMimeBody::GetAttachmentList(CBodyList& rList) const\r
699 {\r
700         int nCount = 0;\r
701         int nMediaType = GetMediaType();\r
702 \r
703         if (MEDIA_MULTIPART != nMediaType)\r
704         {\r
705                 string strName = GetName();\r
706                 if (strName.size() > 0)\r
707                 {\r
708                         rList.push_back((CMimeBody*)this);\r
709                         nCount++;\r
710                 }\r
711         }\r
712         else\r
713         {\r
714                 list<CMimeBody*>::const_iterator it;\r
715                 for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
716                 {\r
717                         CMimeBody* pBP = *it;\r
718                         ASSERT(pBP != NULL);\r
719                         nCount += pBP->GetAttachmentList(rList);\r
720                 }\r
721         }\r
722         return nCount;\r
723 }\r
724 \r
725 void CMimeBody::Clear()\r
726 {\r
727         DeleteAll();\r
728         m_itFind = m_listBodies.end();\r
729         FreeBuffer();\r
730         CMimeHeader::Clear();\r
731 }\r
732 \r
733 // return the length needed to store this body part to string buffer\r
734 int CMimeBody::GetLength() const\r
735 {\r
736         int nLength = CMimeHeader::GetLength();\r
737         CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
738         ASSERT(pCoder != NULL);\r
739         pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
740         nLength += pCoder->GetOutputLength();\r
741         delete pCoder;\r
742 \r
743         if (m_listBodies.empty())\r
744                 return nLength;\r
745 \r
746         string strBoundary = GetBoundary();\r
747         int nBoundSize = (int) strBoundary.size();\r
748         list<CMimeBody*>::const_iterator it;\r
749         for (it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
750         {\r
751                 nLength += nBoundSize + 6;      // include 2 leading hyphens and 2 pair of CRLFs\r
752                 CMimeBody* pBP = *it;\r
753                 ASSERT(pBP != NULL);\r
754                 nLength += pBP->GetLength();\r
755         }\r
756         nLength += nBoundSize + 8;              // include 2 leading hyphens, 2 trailng hyphens and 2 pair of CRLFs\r
757         return nLength;\r
758 }\r
759 \r
760 // store the body part to string buffer\r
761 int CMimeBody::Store(char* pszData, int nMaxSize) const\r
762 {\r
763         // store header fields\r
764         int nSize = CMimeHeader::Store(pszData, nMaxSize);\r
765         if (nSize <= 0)\r
766                 return nSize;\r
767 \r
768         // store content\r
769         char* pszDataBegin = pszData;   // preserve start position\r
770         pszData += nSize;\r
771         nMaxSize -= nSize;\r
772 \r
773         CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
774         ASSERT(pCoder != NULL);\r
775         pCoder->SetInput((const char*)m_pbText, m_nTextSize, true);\r
776         int nOutput = pCoder->GetOutput((unsigned char*)pszData, nMaxSize);\r
777         delete pCoder;\r
778         if (nOutput < 0)\r
779                 return nOutput;\r
780 \r
781         pszData += nOutput;\r
782         nMaxSize -= nOutput;\r
783         if (m_listBodies.empty())\r
784                 return (int)(pszData - pszDataBegin);\r
785 \r
786         // store child body parts\r
787         string strBoundary = GetBoundary();\r
788         if (strBoundary.empty())\r
789                 return -1;                                      // boundary not be set\r
790 \r
791         int nBoundSize = (int)strBoundary.size() + 6;\r
792         for (CBodyList::const_iterator it=m_listBodies.begin(); it!=m_listBodies.end(); it++)\r
793         {\r
794                 if (nMaxSize < nBoundSize)\r
795                         break;\r
796                 if (m_listBodies.begin() == it && *(pszData-2) == '\r' && *(pszData-1) == '\n')\r
797                 {\r
798                         pszData -= 2;\r
799                         nMaxSize += 2;\r
800                 }\r
801                 ::sprintf(pszData, "\r\n--%s\r\n", strBoundary.c_str());\r
802                 pszData += nBoundSize;\r
803                 nMaxSize -= nBoundSize;\r
804 \r
805                 CMimeBody* pBP = *it;\r
806                 ASSERT(pBP != NULL);\r
807                 nOutput = pBP->Store(pszData, nMaxSize);\r
808                 if (nOutput < 0)\r
809                         return nOutput;\r
810                 pszData += nOutput;\r
811                 nMaxSize -= nOutput;\r
812         }\r
813 \r
814         if (nMaxSize >= nBoundSize+2)   // add closing boundary delimiter\r
815         {\r
816                 ::sprintf(pszData, "\r\n--%s--\r\n", strBoundary.c_str());\r
817                 pszData += nBoundSize + 2;\r
818         }\r
819         return (int)(pszData - pszDataBegin);\r
820 }\r
821 \r
822 void CMimeBody::Store(std::string &str) const\r
823 {\r
824         char *temp=new char[GetLength()+1];\r
825         Store(temp,GetLength());\r
826         temp[GetLength()]='\0';\r
827         str=temp;\r
828         delete [] temp;\r
829 }\r
830 \r
831 // load a body part from string buffer\r
832 int CMimeBody::Load(const char* pszData, int nDataSize)\r
833 {\r
834         // load header fields\r
835         int nSize = CMimeHeader::Load(pszData, nDataSize);\r
836         if (nSize <= 0)\r
837                 return nSize;\r
838 \r
839         const char* pszDataBegin = pszData;     // preserve start position\r
840         pszData += nSize;\r
841         nDataSize -= nSize;\r
842         FreeBuffer();\r
843 \r
844         // determine the length of the content\r
845         const char* pszEnd = pszData + nDataSize;\r
846         int nMediaType = GetMediaType();\r
847         if (MEDIA_MULTIPART == nMediaType)\r
848         {\r
849                 // find the begin boundary\r
850                 string strBoundary = GetBoundary();\r
851                 if (!strBoundary.empty())\r
852                 {\r
853                         strBoundary = "\r\n--" + strBoundary;\r
854                         pszEnd = ::FindString(pszData-2, strBoundary.c_str(), pszEnd);\r
855                         if (!pszEnd)\r
856                                 pszEnd = pszData + nDataSize;\r
857                         else\r
858                                 pszEnd += 2;\r
859                 }\r
860         }\r
861 \r
862         // load content\r
863         nSize = (int)(pszEnd - pszData);\r
864         if (nSize > 0)\r
865         {\r
866                 CMimeCodeBase* pCoder = CMimeEnvironment::CreateCoder(GetTransferEncoding());\r
867                 ASSERT(pCoder != NULL);\r
868                 pCoder->SetInput(pszData, nSize, false);\r
869                 int nOutput = pCoder->GetOutputLength();\r
870                 if (AllocateBuffer(nOutput+4))\r
871                         nOutput = pCoder->GetOutput(m_pbText, nOutput);\r
872                 else\r
873                         nOutput = -1;\r
874                 delete pCoder;\r
875                 if (nOutput < 0)\r
876                         return nOutput;\r
877 \r
878                 ASSERT(nOutput < m_nTextSize);\r
879                 m_pbText[nOutput] = 0;\r
880                 m_nTextSize = nOutput;\r
881                 pszData += nSize;\r
882                 nDataSize -= nSize;\r
883         }\r
884         if (nDataSize <= 0)\r
885                 return (int)(pszData - pszDataBegin);\r
886 \r
887         // load child body parts\r
888         string strBoundary = GetBoundary();\r
889         ASSERT(strBoundary.size() > 0);\r
890         strBoundary = "\r\n--" + strBoundary;\r
891 \r
892         // look for the first boundary (case sensitive)\r
893         pszData -= 2;                                   // go back to CRLF\r
894         nDataSize += 2;\r
895         pszEnd = pszData + nDataSize;\r
896         const char* pszBound1 = ::FindString(pszData, strBoundary.c_str(), pszEnd);\r
897         while (pszBound1 != NULL && pszBound1 < pszEnd)\r
898         {\r
899                 const char* pszStart = ::FindString(pszBound1+2, "\r\n", pszEnd);\r
900                 if (!pszStart)\r
901                         break;\r
902                 pszStart += 2;\r
903                 if (pszBound1[strBoundary.size()] == '-' && pszBound1[strBoundary.size()+1] == '-')\r
904                         return (int)(pszStart - pszDataBegin);  // reach the closing boundary\r
905 \r
906                 // look for the next boundary\r
907                 const char* pszBound2 = ::FindString(pszStart, strBoundary.c_str(), pszEnd);\r
908                 if (!pszBound2)                         // overflow, boundary may be truncated\r
909                         pszBound2 = pszEnd;\r
910                 int nEntitySize = (int) (pszBound2 - pszStart);\r
911 \r
912                 // find the media type of this body part:\r
913                 CMimeHeader header;\r
914                 header.Load(pszStart, nEntitySize);\r
915                 string strMediaType = header.GetMainType();\r
916                 CMimeBody* pBP = CreatePart(strMediaType.c_str());\r
917 \r
918                 int nInputSize = pBP->Load(pszStart, nEntitySize);\r
919                 if (nInputSize < 0)\r
920                 {\r
921                         ErasePart(pBP);\r
922                         return nInputSize;\r
923                 }\r
924                 pszBound1 = pszBound2;\r
925         }\r
926         return (int)(pszEnd - pszDataBegin);\r
927 }\r
928 \r
929 //////////////////////////////////////////////////////////////////////\r
930 // CMimeMessage - Represents a MIME message\r
931 //////////////////////////////////////////////////////////////////////\r
932 \r
933 void CMimeMessage::SetDate()\r
934 {\r
935         time_t timeNow = ::time(NULL);\r
936         struct tm *ptm = ::localtime(&timeNow);\r
937         SetDate(ptm->tm_year+1900, ptm->tm_mon+1, ptm->tm_mday, ptm->tm_hour, ptm->tm_min, ptm->tm_sec);\r
938 }\r
939 \r
940 void CMimeMessage::SetDate(int nYear, int nMonth, int nDay, int nHour, int nMinute, int nSecond)\r
941 {\r
942         static const char* s_MonthNames[] =\r
943                 { "Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec" };\r
944         static const char* s_DayNames[] =\r
945                 { "Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat" };\r
946 \r
947         struct tm tmDate;\r
948         ::memset(&tmDate, 0, sizeof(tmDate));\r
949         tmDate.tm_year = nYear - 1900;\r
950         tmDate.tm_mon = nMonth - 1;\r
951         tmDate.tm_mday = nDay;\r
952         tmDate.tm_hour = nHour;\r
953         tmDate.tm_min = nMinute;\r
954         tmDate.tm_sec = nSecond;\r
955         tmDate.tm_isdst = -1;\r
956 \r
957         time_t timeDate = ::mktime(&tmDate);\r
958         if (timeDate < 0)\r
959         {\r
960                 ASSERT(false);\r
961                 return;\r
962         }\r
963 \r
964         tmDate = *::localtime(&timeDate);                       // adjusted local time\r
965         struct tm *ptmGmt = ::gmtime(&timeDate);        // Greenwich Mean Time\r
966         long nTimeDiff = tmDate.tm_mday - ptmGmt->tm_mday;\r
967         if (nTimeDiff > 1)\r
968                 nTimeDiff = -1;\r
969         else if (nTimeDiff < -1)\r
970                 nTimeDiff = 1;\r
971         nTimeDiff *= 60 * 24;\r
972         nTimeDiff +=\r
973                 (tmDate.tm_hour - ptmGmt->tm_hour) * 60 +\r
974                 tmDate.tm_min - ptmGmt->tm_min;\r
975         if (tmDate.tm_isdst > 0)\r
976                 nTimeDiff -= 60;\r
977 \r
978         char szDate[40];\r
979         ASSERT(tmDate.tm_wday < 7);\r
980         ASSERT(tmDate.tm_mon < 12);\r
981         ::sprintf(szDate, "%s, %d %s %d %02d:%02d:%02d %c%02d%02d",\r
982                 s_DayNames[tmDate.tm_wday],\r
983                 tmDate.tm_mday, s_MonthNames[tmDate.tm_mon], tmDate.tm_year+1900,\r
984                 tmDate.tm_hour, tmDate.tm_min, tmDate.tm_sec,\r
985                 (nTimeDiff >= 0 ? '+' : '-'), abs(nTimeDiff / 60), abs(nTimeDiff % 60));\r
986 \r
987         SetFieldValue("Date", szDate);\r
988 }\r