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