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