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