version 0.1.1
[fms.git] / src / nntp / mime / MimeCode.cpp
1 //////////////////////////////////////////////////////////////////////\r
2 //\r
3 // MIME Encoding/Decoding:\r
4 //      Quoted-printable and Base64 for content encoding;\r
5 //      Encoded-word for header field encoding.\r
6 //\r
7 // Jeff Lee\r
8 // Dec 11, 2000\r
9 //\r
10 //////////////////////////////////////////////////////////////////////\r
11 //#include "stdafx.h"\r
12 #include "../../../include/nntp/mime/MimeCode.h"\r
13 #include "../../../include/nntp/mime/MimeChar.h"\r
14 #include "../../../include/nntp/mime/Mime.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 #ifndef _WIN32\r
23         #define stricmp strcasecmp\r
24         #define strnicmp strncasecmp\r
25         #define memicmp memcmp\r
26 #endif\r
27 \r
28 //////////////////////////////////////////////////////////////////////\r
29 // CMimeEnvironment - global environment to manage encoding/decoding\r
30 //////////////////////////////////////////////////////////////////////\r
31 bool CMimeEnvironment::m_bAutoFolding = false;\r
32 string CMimeEnvironment::m_strCharset;\r
33 list<CMimeEnvironment::CODER_PAIR> CMimeEnvironment::m_listCoders;\r
34 list<CMimeEnvironment::FIELD_CODER_PAIR> CMimeEnvironment::m_listFieldCoders;\r
35 list<CMimeEnvironment::MEDIA_TYPE_PAIR> CMimeEnvironment::m_listMediaTypes;\r
36 CMimeEnvironment CMimeEnvironment::m_globalMgr;\r
37 \r
38 CMimeEnvironment::CMimeEnvironment()\r
39 {\r
40         // initialize transfer encoding\r
41         //REGISTER_MIMECODER("7bit", CMimeCode7bit);\r
42         //REGISTER_MIMECODER("8bit", CMimeCode7bit);\r
43         REGISTER_MIMECODER("quoted-printable", CMimeCodeQP);\r
44         REGISTER_MIMECODER("base64", CMimeCodeBase64);\r
45 \r
46         // initialize header fields encoding\r
47         REGISTER_FIELDCODER("Subject", CFieldCodeText);\r
48         REGISTER_FIELDCODER("Comments", CFieldCodeText);\r
49         REGISTER_FIELDCODER("Content-Description", CFieldCodeText);\r
50 \r
51         REGISTER_FIELDCODER("From", CFieldCodeAddress);\r
52         REGISTER_FIELDCODER("To", CFieldCodeAddress);\r
53         REGISTER_FIELDCODER("Resent-To", CFieldCodeAddress);\r
54         REGISTER_FIELDCODER("Cc", CFieldCodeAddress);\r
55         REGISTER_FIELDCODER("Resent-Cc", CFieldCodeAddress);\r
56         REGISTER_FIELDCODER("Bcc", CFieldCodeAddress);\r
57         REGISTER_FIELDCODER("Resent-Bcc", CFieldCodeAddress);\r
58         REGISTER_FIELDCODER("Reply-To", CFieldCodeAddress);\r
59         REGISTER_FIELDCODER("Resent-Reply-To", CFieldCodeAddress);\r
60 \r
61         REGISTER_FIELDCODER("Content-Type", CFieldCodeParameter);\r
62         REGISTER_FIELDCODER("Content-Disposition", CFieldCodeParameter);\r
63 }\r
64 \r
65 void CMimeEnvironment::SetAutoFolding(bool bAutoFolding)\r
66 {\r
67         m_bAutoFolding = bAutoFolding;\r
68         if (!bAutoFolding)\r
69         {\r
70                 DEREGISTER_MIMECODER("7bit");\r
71                 DEREGISTER_MIMECODER("8bit");\r
72         }\r
73         else\r
74         {\r
75                 REGISTER_MIMECODER("7bit", CMimeCode7bit);\r
76                 REGISTER_MIMECODER("8bit", CMimeCode7bit);\r
77         }\r
78 }\r
79 \r
80 void CMimeEnvironment::RegisterCoder(const char* pszCodingName, CODER_FACTORY pfnCreateObject/*=NULL*/)\r
81 {\r
82         ASSERT(pszCodingName != NULL);\r
83         list<CODER_PAIR>::iterator it = m_listCoders.begin();\r
84         while (it != m_listCoders.end())\r
85         {\r
86                 list<CODER_PAIR>::iterator it2 = it;\r
87                 it++;\r
88                 if (!::stricmp(pszCodingName, (*it2).first))\r
89                         m_listCoders.erase(it2);\r
90         }\r
91         if (pfnCreateObject != NULL)\r
92         {\r
93                 CODER_PAIR newPair(pszCodingName, pfnCreateObject);\r
94                 m_listCoders.push_front(newPair);\r
95         }\r
96 }\r
97 \r
98 CMimeCodeBase* CMimeEnvironment::CreateCoder(const char* pszCodingName)\r
99 {\r
100         if (!pszCodingName || !::strlen(pszCodingName))\r
101                 pszCodingName = "7bit";\r
102 \r
103         for (list<CODER_PAIR>::iterator it=m_listCoders.begin(); it!=m_listCoders.end(); it++)\r
104         {\r
105                 ASSERT((*it).first != NULL);\r
106                 if (!::stricmp(pszCodingName, (*it).first))\r
107                 {\r
108                         CODER_FACTORY pfnCreateObject = (*it).second;\r
109                         ASSERT(pfnCreateObject != NULL);\r
110                         return pfnCreateObject();\r
111                 }\r
112         }\r
113         return new CMimeCodeBase;               // default coder for unregistered Content-Transfer-Encoding\r
114 }\r
115 \r
116 \r
117 void CMimeEnvironment::RegisterFieldCoder(const char* pszFieldName, FIELD_CODER_FACTORY pfnCreateObject/*=NULL*/)\r
118 {\r
119         ASSERT(pszFieldName != NULL);\r
120         list<FIELD_CODER_PAIR>::iterator it = m_listFieldCoders.begin();\r
121         while (it != m_listFieldCoders.end())\r
122         {\r
123                 list<FIELD_CODER_PAIR>::iterator it2 = it;\r
124                 it++;\r
125                 if (!::stricmp(pszFieldName, (*it2).first))\r
126                         m_listFieldCoders.erase(it2);\r
127         }\r
128         if (pfnCreateObject != NULL)\r
129         {\r
130                 FIELD_CODER_PAIR newPair(pszFieldName, pfnCreateObject);\r
131                 m_listFieldCoders.push_front(newPair);\r
132         }\r
133 }\r
134 \r
135 CFieldCodeBase* CMimeEnvironment::CreateFieldCoder(const char* pszFieldName)\r
136 {\r
137         ASSERT(pszFieldName != NULL);\r
138         for (list<FIELD_CODER_PAIR>::iterator it=m_listFieldCoders.begin(); it!=m_listFieldCoders.end(); it++)\r
139         {\r
140                 ASSERT((*it).first != NULL);\r
141                 if (!::stricmp(pszFieldName, (*it).first))\r
142                 {\r
143                         FIELD_CODER_FACTORY pfnCreateObject = (*it).second;\r
144                         ASSERT(pfnCreateObject != NULL);\r
145                         return pfnCreateObject();\r
146                 }\r
147         }\r
148         return new CFieldCodeBase;              // default coder for unregistered header fields\r
149 }\r
150 \r
151 void CMimeEnvironment::RegisterMediaType(const char* pszMediaType, BODY_PART_FACTORY pfnCreateObject/*=NULL*/)\r
152 {\r
153         ASSERT(pszMediaType != NULL);\r
154         list<MEDIA_TYPE_PAIR>::iterator it = m_listMediaTypes.begin();\r
155         while (it != m_listMediaTypes.end())\r
156         {\r
157                 list<MEDIA_TYPE_PAIR>::iterator it2 = it;\r
158                 it++;\r
159                 if (!::stricmp(pszMediaType, (*it2).first))\r
160                         m_listMediaTypes.erase(it2);\r
161         }\r
162         if (pfnCreateObject != NULL)\r
163         {\r
164                 MEDIA_TYPE_PAIR newPair(pszMediaType, pfnCreateObject);\r
165                 m_listMediaTypes.push_front(newPair);\r
166         }\r
167 }\r
168 \r
169 CMimeBody* CMimeEnvironment::CreateBodyPart(const char* pszMediaType)\r
170 {\r
171         if (!pszMediaType || !::strlen(pszMediaType))\r
172                 pszMediaType = "text";\r
173 \r
174         ASSERT(pszMediaType != NULL);\r
175         for (list<MEDIA_TYPE_PAIR>::iterator it=m_listMediaTypes.begin(); it!=m_listMediaTypes.end(); it++)\r
176         {\r
177                 ASSERT((*it).first != NULL);\r
178                 if (!::stricmp(pszMediaType, (*it).first))\r
179                 {\r
180                         BODY_PART_FACTORY pfnCreateObject = (*it).second;\r
181                         ASSERT(pfnCreateObject != NULL);\r
182                         return pfnCreateObject();\r
183                 }\r
184         }\r
185         return new CMimeBody;                   // default body part for unregistered media type\r
186 }\r
187 \r
188 //////////////////////////////////////////////////////////////////////\r
189 // CMimeCode7bit - for 7bit/8bit encoding mechanism (fold long line)\r
190 //////////////////////////////////////////////////////////////////////\r
191 int CMimeCode7bit::GetEncodeLength() const\r
192 {\r
193         int nSize = m_nInputSize + m_nInputSize / MAX_MIME_LINE_LEN * 4;\r
194         //const unsigned char* pbData = m_pbInput;\r
195         //const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
196         //while (++pbData < pbEnd)\r
197         //      if (*pbData == '.' && *(pbData-1) == '\n')\r
198         //              nSize++;\r
199         nSize += 4;\r
200         return nSize;\r
201 }\r
202 \r
203 int CMimeCode7bit::Encode(unsigned char* pbOutput, int nMaxSize) const\r
204 {\r
205         const unsigned char* pbData = m_pbInput;\r
206         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
207         unsigned char* pbOutStart = pbOutput;\r
208         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
209         unsigned char* pbSpace = NULL;\r
210         int nLineLen = 0;\r
211         while (pbData < pbEnd)\r
212         {\r
213                 if (pbOutput >= pbOutEnd)\r
214                         break;\r
215 \r
216                 unsigned char ch = *pbData;\r
217                 //if (ch == '.' && pbData-m_pbInput >= 2 && !::memcmp(pbData-2, "\r\n.", 3))\r
218                 //{\r
219                 //      *pbOutput++ = '.';              // avoid confusing with SMTP end flag\r
220                 //      nLineLen++;\r
221                 //}\r
222 \r
223                 if (ch == '\r' || ch == '\n')\r
224                 {\r
225                         nLineLen = -1;\r
226                         pbSpace = NULL;\r
227                 }\r
228                 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))\r
229                         pbSpace = pbOutput;\r
230 \r
231                 // fold the line if it's longer than 76\r
232                 if (nLineLen >= MAX_MIME_LINE_LEN && pbSpace != NULL &&\r
233                         pbOutput+2 <= pbOutEnd)\r
234                 {\r
235                         int nSize = (int)(pbOutput - pbSpace);\r
236                         ::memmove(pbSpace+2, pbSpace, nSize);\r
237                         *pbSpace++ = '\r';\r
238                         *pbSpace = '\n';\r
239                         pbSpace = NULL;\r
240                         nLineLen = nSize;\r
241                         pbOutput += 2;\r
242                 }\r
243 \r
244                 *pbOutput++ = ch;\r
245                 pbData++;\r
246                 nLineLen++;\r
247         }\r
248 \r
249         return (int)(pbOutput - pbOutStart);\r
250 }\r
251 \r
252 //////////////////////////////////////////////////////////////////////\r
253 // CMimeCodeQP - for quoted-printable encoding mechanism\r
254 //////////////////////////////////////////////////////////////////////\r
255 int CMimeCodeQP::GetEncodeLength() const\r
256 {\r
257         int nLength = m_nInputSize;\r
258         const unsigned char* pbData = m_pbInput;\r
259         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
260         while (pbData < pbEnd)\r
261                 if (!CMimeChar::IsPrintable(*pbData++))\r
262                         nLength += 2;\r
263         //int nLength = m_nInputSize * 3;\r
264         nLength += nLength / (MAX_MIME_LINE_LEN - 2) * 6;\r
265         return nLength;\r
266 }\r
267 \r
268 int CMimeCodeQP::Encode(unsigned char* pbOutput, int nMaxSize) const\r
269 {\r
270         static const char* s_QPTable = "0123456789ABCDEF";\r
271 \r
272         const unsigned char* pbData = m_pbInput;\r
273         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
274         unsigned char* pbOutStart = pbOutput;\r
275         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
276         unsigned char* pbSpace = NULL;\r
277         int nLineLen = 0;\r
278         while (pbData < pbEnd)\r
279         {\r
280                 if (pbOutput >= pbOutEnd)\r
281                         break;\r
282 \r
283                 unsigned char ch = *pbData;\r
284                 bool bQuote = false, bCopy = false;\r
285 \r
286                 // According to RFC 2045, TAB and SPACE MAY be represented as the ASCII characters.\r
287                 // But it MUST NOT be so represented at the end of an encoded line.\r
288                 if (ch == '\t' || ch == ' ')\r
289                 {\r
290                         if (pbData == pbEnd-1 || (!m_bQuoteLineBreak && *(pbData+1) == '\r'))\r
291                                 bQuote = true;          // quote the SPACE/TAB\r
292                         else\r
293                                 bCopy = true;           // copy the SPACE/TAB\r
294                         if (nLineLen > 0)\r
295                                 pbSpace = (unsigned char*) pbOutput;\r
296                 }\r
297                 else if (!m_bQuoteLineBreak && (ch == '\r' || ch == '\n'))\r
298                 {\r
299                         bCopy = true;                   // keep 'hard' line break\r
300                         nLineLen = -1;\r
301                         pbSpace = NULL;\r
302                 }\r
303                 else if (!m_bQuoteLineBreak && ch == '.')\r
304                 {\r
305                         if (pbData-m_pbInput >= 2 &&\r
306                                 *(pbData-2) == '\r' && *(pbData-1) == '\n' &&\r
307                                 *(pbData+1) == '\r' && *(pbData+2) == '\n')\r
308                                 bQuote = true;          // avoid confusing with SMTP's message end flag\r
309                         else\r
310                                 bCopy = true;\r
311                 }\r
312                 else if (ch < 33 || ch > 126 || ch == '=')\r
313                         bQuote = true;                  // quote this character\r
314                 else\r
315                         bCopy = true;                   // copy this character\r
316 \r
317                 if (nLineLen+(bQuote ? 3 : 1) >= MAX_MIME_LINE_LEN && pbOutput+3 <= pbOutEnd)\r
318                 {\r
319                         if (pbSpace != NULL && pbSpace < pbOutput)\r
320                         {\r
321                                 pbSpace++;\r
322                                 int nSize = (int)(pbOutput - pbSpace);\r
323                                 ::memmove(pbSpace+3, pbSpace, nSize);\r
324                                 nLineLen = nSize;\r
325                         }\r
326                         else\r
327                         {\r
328                                 pbSpace = pbOutput;\r
329                                 nLineLen = 0;\r
330                         }\r
331                         ::memcpy(pbSpace, "=\r\n", 3);\r
332                         pbOutput += 3;\r
333                         pbSpace = NULL;\r
334                 }\r
335 \r
336                 if (bQuote && pbOutput+3 <= pbOutEnd)\r
337                 {\r
338                         *pbOutput++ = '=';\r
339                         *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];\r
340                         *pbOutput++ = s_QPTable[ch & 0x0f];\r
341                         nLineLen += 3;\r
342                 }\r
343                 else if (bCopy)\r
344                 {\r
345                         *pbOutput++ = (char) ch;\r
346                         nLineLen++;\r
347                 }\r
348 \r
349                 pbData++;\r
350         }\r
351 \r
352         return (int)(pbOutput - pbOutStart);\r
353 }\r
354 \r
355 int CMimeCodeQP::Decode(unsigned char* pbOutput, int nMaxSize)\r
356 {\r
357         const unsigned char* pbData = m_pbInput;\r
358         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
359         unsigned char* pbOutStart = pbOutput;\r
360         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
361 \r
362         while (pbData < pbEnd)\r
363         {\r
364                 if (pbOutput >= pbOutEnd)\r
365                         break;\r
366 \r
367                 unsigned char ch = *pbData++;\r
368                 if (ch == '=')\r
369                 {\r
370                         if (pbData+2 > pbEnd)\r
371                                 break;                          // invalid endcoding\r
372                         ch = *pbData++;\r
373                         if (CMimeChar::IsHexDigit(ch))\r
374                         {\r
375                                 ch -= ch > '9' ? 0x37 : '0';\r
376                                 *pbOutput = ch << 4;\r
377                                 ch = *pbData++;\r
378                                 ch -= ch > '9' ? 0x37 : '0';\r
379                                 *pbOutput++ |= ch & 0x0f;\r
380                         }\r
381                         else if (ch == '\r' && *pbData == '\n')\r
382                                 pbData++;                       // Soft Line Break, eat it\r
383                         else                                    // invalid endcoding, let it go\r
384                                 *pbOutput++ = ch;\r
385                 }\r
386                 else// if (ch != '\r' && ch != '\n')\r
387                         *pbOutput++ = ch;\r
388         }\r
389 \r
390         return (int)(pbOutput - pbOutStart);\r
391 }\r
392 \r
393 //////////////////////////////////////////////////////////////////////\r
394 // CMimeCodeBase64 - for base64 encoding mechanism\r
395 //////////////////////////////////////////////////////////////////////\r
396 int CMimeCodeBase64::GetEncodeLength() const\r
397 {\r
398         int nLength = (m_nInputSize + 2) / 3 * 4;\r
399         if (m_bAddLineBreak)\r
400                 nLength += (nLength / MAX_MIME_LINE_LEN + 1) * 2;\r
401         return nLength;\r
402 }\r
403 \r
404 int CMimeCodeBase64::GetDecodeLength() const\r
405 {\r
406         return m_nInputSize * 3 / 4 + 2;\r
407 }\r
408 \r
409 int CMimeCodeBase64::Encode(unsigned char* pbOutput, int nMaxSize) const\r
410 {\r
411         static const char* s_Base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";\r
412 \r
413         unsigned char* pbOutStart = pbOutput;\r
414         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
415         int nFrom, nLineLen = 0;\r
416         unsigned char chHigh4bits = 0;\r
417 \r
418         for (nFrom=0; nFrom<m_nInputSize; nFrom++)\r
419         {\r
420                 if (pbOutput >= pbOutEnd)\r
421                         break;\r
422 \r
423                 unsigned char ch = m_pbInput[nFrom];\r
424                 switch (nFrom % 3)\r
425                 {\r
426                 case 0:\r
427                         *pbOutput++ = s_Base64Table[ch >> 2];\r
428                         chHigh4bits = (ch << 4) & 0x30;\r
429                         break;\r
430 \r
431                 case 1:\r
432                         *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 4)];\r
433                         chHigh4bits = (ch << 2) & 0x3c;\r
434                         break;\r
435 \r
436                 default:\r
437                         *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 6)];\r
438                         if (pbOutput < pbOutEnd)\r
439                         {\r
440                                 *pbOutput++ = s_Base64Table[ch & 0x3f];\r
441                                 nLineLen++;\r
442                         }\r
443                 }\r
444 \r
445                 nLineLen++;\r
446                 if (m_bAddLineBreak && nLineLen >= MAX_MIME_LINE_LEN && pbOutput+2 <= pbOutEnd)\r
447                 {\r
448                         *pbOutput++ = '\r';\r
449                         *pbOutput++ = '\n';\r
450                         nLineLen = 0;\r
451                 }\r
452         }\r
453 \r
454         if (nFrom % 3 != 0 && pbOutput < pbOutEnd)      // 76 = 19 * 4, so the padding wouldn't exceed 76\r
455         {\r
456                 *pbOutput++ = s_Base64Table[chHigh4bits];\r
457                 int nPad = 4 - (nFrom % 3) - 1;\r
458                 if (pbOutput+nPad <= pbOutEnd)\r
459                 {\r
460                         ::memset(pbOutput, '=', nPad);\r
461                         pbOutput += nPad;\r
462                 }\r
463         }\r
464         if (m_bAddLineBreak && nLineLen != 0 && pbOutput+2 <= pbOutEnd) // add CRLF\r
465         {\r
466                 *pbOutput++ = '\r';\r
467                 *pbOutput++ = '\n';\r
468         }\r
469         return (int)(pbOutput - pbOutStart);\r
470 }\r
471 \r
472 int CMimeCodeBase64::Decode(unsigned char* pbOutput, int nMaxSize)\r
473 {\r
474         const unsigned char* pbData = m_pbInput;\r
475         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
476         unsigned char* pbOutStart = pbOutput;\r
477         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
478 \r
479         int nFrom = 0;\r
480         unsigned char chHighBits = 0;\r
481 \r
482         while (pbData < pbEnd)\r
483         {\r
484                 if (pbOutput >= pbOutEnd)\r
485                         break;\r
486 \r
487                 unsigned char ch = *pbData++;\r
488                 if (ch == '\r' || ch == '\n')\r
489                         continue;\r
490                 ch = (unsigned char) DecodeBase64Char(ch);\r
491                 if (ch >= 64)                           // invalid encoding, or trailing pad '='\r
492                         break;\r
493 \r
494                 switch ((nFrom++) % 4)\r
495                 {\r
496                 case 0:\r
497                         chHighBits = ch << 2;\r
498                         break;\r
499 \r
500                 case 1:\r
501                         *pbOutput++ = chHighBits | (ch >> 4);\r
502                         chHighBits = ch << 4;\r
503                         break;\r
504 \r
505                 case 2:\r
506                         *pbOutput++ = chHighBits | (ch >> 2);\r
507                         chHighBits = ch << 6;\r
508                         break;\r
509 \r
510                 default:\r
511                         *pbOutput++ = chHighBits | ch;\r
512                 }\r
513         }\r
514 \r
515         return (int)(pbOutput - pbOutStart);\r
516 }\r
517 \r
518 //////////////////////////////////////////////////////////////////////\r
519 // CMimeEncodedWord - encoded word for non-ascii text (RFC 2047)\r
520 //////////////////////////////////////////////////////////////////////\r
521 int CMimeEncodedWord::GetEncodeLength() const\r
522 {\r
523         if (!m_nInputSize || m_strCharset.empty())\r
524                 return CMimeCodeBase::GetEncodeLength();\r
525 \r
526         int nLength, nCodeLen = (int) m_strCharset.size() + 7;\r
527         if (tolower(m_nEncoding) == 'b')\r
528         {\r
529                 CMimeCodeBase64 base64;\r
530                 base64.SetInput((const char*)m_pbInput, m_nInputSize, true);\r
531                 base64.AddLineBreak(false);\r
532                 nLength = base64.GetOutputLength();\r
533         }\r
534         else\r
535         {\r
536                 CMimeCodeQP qp;\r
537                 qp.SetInput((const char*)m_pbInput, m_nInputSize, true);\r
538                 qp.QuoteLineBreak(false);\r
539                 nLength = qp.GetOutputLength();\r
540         }\r
541 \r
542         nCodeLen += 4;\r
543         ASSERT(nCodeLen < MAX_ENCODEDWORD_LEN);\r
544         return (nLength / (MAX_ENCODEDWORD_LEN - nCodeLen) + 1) * nCodeLen + nLength;\r
545 }\r
546 \r
547 int CMimeEncodedWord::Encode(unsigned char* pbOutput, int nMaxSize) const\r
548 {\r
549         if (m_strCharset.empty())\r
550                 return CMimeCodeBase::Encode(pbOutput, nMaxSize);\r
551 \r
552         if (!m_nInputSize)\r
553                 return 0;\r
554         if (tolower(m_nEncoding) == 'b')\r
555                 return BEncode(pbOutput, nMaxSize);\r
556         return QEncode(pbOutput, nMaxSize);\r
557 }\r
558 \r
559 int CMimeEncodedWord::Decode(unsigned char* pbOutput, int nMaxSize)\r
560 {\r
561         m_strCharset.clear();\r
562         const char* pbData = (const char*) m_pbInput;\r
563         const char* pbEnd = pbData + m_nInputSize;\r
564         unsigned char* pbOutStart = pbOutput;\r
565         while (pbData < pbEnd)\r
566         {\r
567                 const char* pszHeaderEnd = pbData;\r
568                 const char* pszCodeEnd = pbEnd;\r
569                 int nCoding = 0, nCodeLen = (int)(pbEnd - pbData);\r
570                 if (pbData[0] == '=' && pbData[1] == '?')       // it might be an encoded-word\r
571                 {\r
572                         pszHeaderEnd = ::strchr(pbData+2, '?');\r
573                         if (pszHeaderEnd != NULL && pszHeaderEnd[2] == '?' && pszHeaderEnd+3 < pbEnd)\r
574                         {\r
575                                 nCoding = tolower(pszHeaderEnd[1]);\r
576                                 pszHeaderEnd += 3;\r
577                                 pszCodeEnd = ::strstr(pszHeaderEnd, "?=");      // look for the tailer\r
578                                 if (!pszCodeEnd || pszCodeEnd >= pbEnd)\r
579                                         pszCodeEnd = pbEnd;\r
580                                 nCodeLen = (int)(pszCodeEnd - pszHeaderEnd);\r
581                                 pszCodeEnd += 2;\r
582                                 if (m_strCharset.empty())\r
583                                 {\r
584                                         m_strCharset.assign(pbData+2, pszHeaderEnd-pbData-5);\r
585                                         m_nEncoding = nCoding;\r
586                                 }\r
587                         }\r
588                 }\r
589 \r
590                 int nDecoded;\r
591                 if (nCoding == 'b')\r
592                 {\r
593                         CMimeCodeBase64 base64;\r
594                         base64.SetInput(pszHeaderEnd, nCodeLen, false);\r
595                         nDecoded = base64.GetOutput(pbOutput, nMaxSize);\r
596                 }\r
597                 else if (nCoding == 'q')\r
598                 {\r
599                         CMimeCodeQP qp;\r
600                         qp.SetInput(pszHeaderEnd, nCodeLen, false);\r
601                         nDecoded = qp.GetOutput(pbOutput, nMaxSize);\r
602                 }\r
603                 else\r
604                 {\r
605                         pszCodeEnd = ::strstr(pbData+1, "=?");  // find the next encoded-word\r
606                         if (!pszCodeEnd || pszCodeEnd >= pbEnd)\r
607                                 pszCodeEnd = pbEnd;\r
608                         else if (pbData > (const char*) m_pbInput)\r
609                         {\r
610                                 const char* pszSpace = pbData;\r
611                                 while (CMimeChar::IsSpace((unsigned char)*pszSpace))\r
612                                         pszSpace++;\r
613                                 if (pszSpace == pszCodeEnd)     // ignore liner-white-spaces between adjacent encoded words\r
614                                         pbData = pszCodeEnd;\r
615                         }\r
616                         nDecoded = min((int)(pszCodeEnd - pbData), nMaxSize);\r
617                         ::memcpy(pbOutput, pbData, nDecoded);\r
618                 }\r
619 \r
620                 pbData = pszCodeEnd;\r
621                 pbOutput += nDecoded;\r
622                 nMaxSize -= nDecoded;\r
623                 if (nMaxSize <= 0)\r
624                         break;\r
625         }\r
626 \r
627         return (int)(pbOutput - pbOutStart);\r
628 }\r
629 \r
630 int CMimeEncodedWord::BEncode(unsigned char* pbOutput, int nMaxSize) const\r
631 {\r
632         int nCharsetLen = (int)m_strCharset.size();\r
633         int nBlockSize = MAX_ENCODEDWORD_LEN - nCharsetLen - 7; // a single encoded-word cannot exceed 75 bytes\r
634         nBlockSize = nBlockSize / 4 * 3;\r
635         ASSERT(nBlockSize > 0);\r
636 \r
637         unsigned char* pbOutStart = pbOutput;\r
638         int nInput = 0;\r
639         for (;;)\r
640         {\r
641                 if (nMaxSize < nCharsetLen+7)\r
642                         break;\r
643                 *pbOutput++ = '=';                      // encoded-word header\r
644                 *pbOutput++ = '?';\r
645                 ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);\r
646                 pbOutput += nCharsetLen;\r
647                 *pbOutput++ = '?';\r
648                 *pbOutput++ = 'B';\r
649                 *pbOutput++ = '?';\r
650 \r
651                 nMaxSize -= nCharsetLen + 7;\r
652                 CMimeCodeBase64 base64;\r
653                 base64.SetInput((const char*)m_pbInput+nInput, min(m_nInputSize-nInput, nBlockSize), true);\r
654                 base64.AddLineBreak(false);\r
655                 int nEncoded = base64.GetOutput(pbOutput, nMaxSize);\r
656                 pbOutput += nEncoded;\r
657                 *pbOutput++ = '?';                      // encoded-word tailer\r
658                 *pbOutput++ = '=';\r
659 \r
660                 nInput += nBlockSize;\r
661                 nMaxSize -= nEncoded + nCharsetLen + 7;\r
662                 if (nInput >= m_nInputSize)\r
663                         break;\r
664                 *pbOutput++ = ' ';                      // add a liner-white-space between adjacent encoded words\r
665                 nMaxSize--;\r
666         }\r
667         return (int)(pbOutput - pbOutStart);\r
668 }\r
669 \r
670 int CMimeEncodedWord::QEncode(unsigned char* pbOutput, int nMaxSize) const\r
671 {\r
672         static const char* s_QPTable = "0123456789ABCDEF";\r
673 \r
674         const unsigned char* pbData = m_pbInput;\r
675         const unsigned char* pbEnd = m_pbInput + m_nInputSize;\r
676         unsigned char* pbOutStart = pbOutput;\r
677         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
678         int nCodeLen, nCharsetLen = (int)m_strCharset.size();\r
679         int nLineLen = 0, nMaxLine = MAX_ENCODEDWORD_LEN - nCharsetLen - 7;\r
680 \r
681         while (pbData < pbEnd)\r
682         {\r
683                 unsigned char ch = *pbData++;\r
684                 if (ch < 33 || ch > 126 || ch == '=' || ch == '?' || ch == '_')\r
685                         nCodeLen = 3;\r
686                 else\r
687                         nCodeLen = 1;\r
688 \r
689                 if (nLineLen+nCodeLen > nMaxLine)       // add encoded word tailer\r
690                 {\r
691                         if (pbOutput+3 > pbOutEnd)\r
692                                 break;\r
693                         *pbOutput++ = '?';\r
694                         *pbOutput++ = '=';\r
695                         *pbOutput++ = ' ';\r
696                         nLineLen = 0;\r
697                 }\r
698 \r
699                 if (!nLineLen)                          // add encoded word header\r
700                 {\r
701                         if (pbOutput+nCharsetLen+7 > pbOutEnd)\r
702                                 break;\r
703                         *pbOutput++ = '=';\r
704                         *pbOutput++ = '?';\r
705                         ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);\r
706                         pbOutput += nCharsetLen;\r
707                         *pbOutput++ = '?';\r
708                         *pbOutput++ = 'Q';\r
709                         *pbOutput++ = '?';\r
710                 }\r
711 \r
712                 nLineLen += nCodeLen;\r
713                 if (pbOutput+nCodeLen > pbOutEnd)\r
714                         break;\r
715                 if (nCodeLen > 1)\r
716                 {\r
717                         *pbOutput++ = '=';\r
718                         *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];\r
719                         *pbOutput++ = s_QPTable[ch & 0x0f];\r
720                 }\r
721                 else\r
722                         *pbOutput++ = ch;\r
723         }\r
724 \r
725         if (pbOutput+2 <= pbOutEnd)\r
726         {\r
727                 *pbOutput++ = '?';\r
728                 *pbOutput++ = '=';\r
729         }\r
730         return (int)(pbOutput - pbOutStart);\r
731 }\r
732 \r
733 //////////////////////////////////////////////////////////////////////\r
734 // CFieldCodeBase - base class to encode/decode header fields\r
735 // default coder for any unregistered fields\r
736 //////////////////////////////////////////////////////////////////////\r
737 int CFieldCodeBase::GetEncodeLength() const\r
738 {\r
739         // use the global charset if there's no specified charset\r
740         string strCharset = m_strCharset;\r
741         if (strCharset.empty())\r
742                 strCharset = CMimeEnvironment::GetGlobalCharset();\r
743         if (strCharset.empty() && !CMimeEnvironment::AutoFolding())\r
744                 return CMimeCodeBase::GetEncodeLength();\r
745 \r
746         int nLength = 0;\r
747         const char* pszData = (const char*) m_pbInput;\r
748         int nInputSize = m_nInputSize;\r
749         int nNonAsciiChars, nDelimeter = GetDelimeter();\r
750 \r
751         // divide the field into syntactic units to calculate the output length\r
752         do\r
753         {\r
754                 int nUnitSize = FindSymbol(pszData, nInputSize, nDelimeter, nNonAsciiChars);\r
755                 if (!nNonAsciiChars || strCharset.empty())\r
756                         nLength += nUnitSize;\r
757                 else\r
758                 {\r
759                         CMimeEncodedWord coder;\r
760                         coder.SetEncoding(SelectEncoding(nUnitSize, nNonAsciiChars), strCharset.c_str());\r
761                         coder.SetInput(pszData, nUnitSize, true);\r
762                         nLength += coder.GetOutputLength();\r
763                 }\r
764 \r
765                 pszData += nUnitSize;\r
766                 nInputSize -= nUnitSize;\r
767                 if (IsFoldingChar(*pszData))    // the char follows the unit is a delimeter (space or special char)\r
768                         nLength += 3;\r
769                 nLength++;\r
770                 pszData++;\r
771                 nInputSize--;\r
772         } while (nInputSize > 0);\r
773 \r
774         if (CMimeEnvironment::AutoFolding())\r
775                 nLength += nLength / MAX_MIME_LINE_LEN * 6;\r
776         return nLength;\r
777 }\r
778 \r
779 int CFieldCodeBase::Encode(unsigned char* pbOutput, int nMaxSize) const\r
780 {\r
781         // use the global charset if there's no specified charset\r
782         string strCharset = m_strCharset;\r
783         if (strCharset.empty())\r
784                 strCharset = CMimeEnvironment::GetGlobalCharset();\r
785         if (strCharset.empty() && !CMimeEnvironment::AutoFolding())\r
786                 return CMimeCodeBase::Encode(pbOutput, nMaxSize);\r
787 \r
788         unsigned char* pbOutBegin = pbOutput;\r
789         unsigned char* pbOutEnd = pbOutput + nMaxSize;\r
790         const char* pszInput = (const char*) m_pbInput;\r
791         int nInputSize = m_nInputSize;\r
792         int nNonAsciiChars, nDelimeter = GetDelimeter();\r
793         int nLineLen = 0;\r
794         unsigned char* pbSpace = NULL;\r
795         string strUnit;\r
796         strUnit.reserve(nInputSize);\r
797 \r
798         // divide the field into syntactic units to encode\r
799         for (;;)\r
800         {\r
801                 int nUnitSize = FindSymbol(pszInput, nInputSize, nDelimeter, nNonAsciiChars);\r
802                 if (!nNonAsciiChars || strCharset.empty())\r
803                         strUnit.assign(pszInput, nUnitSize);\r
804                 else\r
805                 {\r
806                         CMimeEncodedWord coder;\r
807                         coder.SetEncoding(SelectEncoding(nUnitSize, nNonAsciiChars), strCharset.c_str());\r
808                         coder.SetInput(pszInput, nUnitSize, true);\r
809                         strUnit.resize(coder.GetOutputLength());\r
810                         int nEncoded = coder.GetOutput((unsigned char*) strUnit.c_str(), (int) strUnit.capacity());\r
811                         strUnit.resize(nEncoded);\r
812                 }\r
813                 if (nUnitSize < nInputSize)\r
814                         strUnit += pszInput[nUnitSize];         // add the following delimeter (space or special char)\r
815 \r
816                 // copy the encoded string to target buffer and perform folding if needed\r
817                 if (!CMimeEnvironment::AutoFolding())\r
818                 {\r
819                         int nSize = min((int) (pbOutEnd - pbOutput), (int) strUnit.size());\r
820                         ::memcpy(pbOutput, strUnit.c_str(), nSize);\r
821                         pbOutput += nSize;\r
822                 }\r
823                 else\r
824                 {\r
825                         const char* pszData = strUnit.c_str();\r
826                         const char* pszEnd = pszData + strUnit.size();\r
827                         while (pszData < pszEnd)\r
828                         {\r
829                                 char ch = *pszData++;\r
830                                 if (ch == '\r' || ch == '\n')\r
831                                 {\r
832                                         nLineLen = -1;\r
833                                         pbSpace = NULL;\r
834                                 }\r
835                                 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))\r
836                                         pbSpace = pbOutput;\r
837 \r
838                                 if (nLineLen >= MAX_MIME_LINE_LEN && pbSpace != NULL &&\r
839                                         pbOutput+3 <= pbOutEnd)         // fold at the position of the previous space\r
840                                 {\r
841                                         int nSize = (int)(pbOutput - pbSpace);\r
842                                         ::memmove(pbSpace+3, pbSpace, nSize);\r
843                                         ::memcpy(pbSpace, "\r\n\t", 3);\r
844                                         pbOutput += 3;\r
845                                         pbSpace = NULL;\r
846                                         nLineLen = nSize + 1;\r
847                                 }\r
848                                 if (pbOutput < pbOutEnd)\r
849                                         *pbOutput++ = ch;\r
850                                 nLineLen++;\r
851                         }\r
852                 }\r
853 \r
854                 pszInput += nUnitSize + 1;\r
855                 nInputSize -= nUnitSize + 1;\r
856                 if (nInputSize <= 0)\r
857                         break;\r
858 \r
859                 // fold at the position of the specific char and eat the following spaces\r
860                 if (IsFoldingChar(pszInput[-1]) && pbOutput+3 <= pbOutEnd)\r
861                 {\r
862                         ::memcpy(pbOutput, "\r\n\t", 3);\r
863                         pbOutput += 3;\r
864                         pbSpace = NULL;\r
865                         nLineLen = 1;\r
866                         while (nInputSize > 0 && CMimeChar::IsSpace(*pszInput))\r
867                         {\r
868                                 pszInput++;\r
869                                 nInputSize--;\r
870                         }\r
871                 }\r
872         }\r
873         return (int) (pbOutput - pbOutBegin);\r
874 }\r
875 \r
876 int CFieldCodeBase::Decode(unsigned char* pbOutput, int nMaxSize)\r
877 {\r
878         CMimeEncodedWord coder;\r
879         coder.SetInput((const char*)m_pbInput, m_nInputSize, false);\r
880 \r
881         string strField;\r
882         strField.resize(coder.GetOutputLength());\r
883         int nDecoded = coder.GetOutput((unsigned char*) strField.c_str(), (int) strField.capacity());\r
884         strField.resize(nDecoded);\r
885         m_strCharset = coder.GetCharset();\r
886 \r
887         if (CMimeEnvironment::AutoFolding())\r
888                 UnfoldField(strField);\r
889         int nSize = min((int)strField.size(), nMaxSize);\r
890         ::memcpy(pbOutput, strField.c_str(), nSize);\r
891         return nSize;\r
892 }\r
893 \r
894 void CFieldCodeBase::UnfoldField(string& strField) const\r
895 {\r
896         for (;;)\r
897         {\r
898                 string::size_type pos = strField.rfind("\r\n");\r
899                 if (pos == string::npos)\r
900                         break;\r
901 \r
902                 strField.erase(pos, 2);\r
903                 //if (strField[pos] == '\t')\r
904                 //      strField[pos] = ' ';\r
905                 int nSpaces = 0;\r
906                 while (CMimeChar::IsSpace((unsigned char)strField[pos+nSpaces]))\r
907                         nSpaces++;\r
908                 strField.replace(pos, nSpaces, " ");\r
909         }\r
910 }\r
911 \r
912 int CFieldCodeBase::FindSymbol(const char* pszData, int nSize, int& nDelimeter, int& nNonAscChars) const\r
913 {\r
914         nNonAscChars = 0;\r
915         const char* pszDataStart = pszData;\r
916         const char* pszEnd = pszData + nSize;\r
917 \r
918         while (pszData < pszEnd)\r
919         {\r
920                 char ch = *pszData;\r
921                 if (CMimeChar::IsNonAscii((unsigned char)ch))\r
922                         nNonAscChars++;\r
923                 else\r
924                 {\r
925                         if (ch == (char) nDelimeter)\r
926                         {\r
927                                 nDelimeter = 0;         // stop at any delimeters (space or specials)\r
928                                 break;\r
929                         }\r
930 \r
931                         if (!nDelimeter && CMimeChar::IsDelimiter(ch))\r
932                         {\r
933                                 switch (ch)\r
934                                 {\r
935                                 case '"':\r
936                                         nDelimeter = '"';       // quoted-string, delimeter is '"'\r
937                                         break;\r
938                                 case '(':\r
939                                         nDelimeter = ')';       // comment, delimeter is ')'\r
940                                         break;\r
941                                 case '<':\r
942                                         nDelimeter = '>';       // address, delimeter is '>'\r
943                                         break;\r
944                                 }\r
945                                 break;\r
946                         }\r
947                 }\r
948                 pszData++;\r
949         }\r
950 \r
951         return (int)(pszData - pszDataStart);\r
952 }\r