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