1 //////////////////////////////////////////////////////////////////////
\r
3 // MIME Encoding/Decoding:
\r
4 // Quoted-printable and Base64 for content encoding;
\r
5 // Encoded-word for header field encoding.
\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
18 static char THIS_FILE[]=__FILE__;
\r
19 #define new DEBUG_NEW
\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
32 CMimeEnvironment::CMimeEnvironment()
\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
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
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
55 REGISTER_FIELDCODER("Content-Type", CFieldCodeParameter);
\r
56 REGISTER_FIELDCODER("Content-Disposition", CFieldCodeParameter);
\r
59 void CMimeEnvironment::SetAutoFolding(bool bAutoFolding)
\r
61 m_bAutoFolding = bAutoFolding;
\r
64 DEREGISTER_MIMECODER("7bit");
\r
65 DEREGISTER_MIMECODER("8bit");
\r
69 REGISTER_MIMECODER("7bit", CMimeCode7bit);
\r
70 REGISTER_MIMECODER("8bit", CMimeCode7bit);
\r
74 void CMimeEnvironment::RegisterCoder(const char* pszCodingName, CODER_FACTORY pfnCreateObject/*=NULL*/)
\r
76 ASSERT(pszCodingName != NULL);
\r
77 list<CODER_PAIR>::iterator it = m_listCoders.begin();
\r
78 while (it != m_listCoders.end())
\r
80 list<CODER_PAIR>::iterator it2 = it;
\r
82 if (!::stricmp(pszCodingName, (*it2).first))
\r
83 m_listCoders.erase(it2);
\r
85 if (pfnCreateObject != NULL)
\r
87 CODER_PAIR newPair(pszCodingName, pfnCreateObject);
\r
88 m_listCoders.push_front(newPair);
\r
92 CMimeCodeBase* CMimeEnvironment::CreateCoder(const char* pszCodingName)
\r
94 if (!pszCodingName || !::strlen(pszCodingName))
\r
95 pszCodingName = "7bit";
\r
97 for (list<CODER_PAIR>::iterator it=m_listCoders.begin(); it!=m_listCoders.end(); it++)
\r
99 ASSERT((*it).first != NULL);
\r
100 if (!::stricmp(pszCodingName, (*it).first))
\r
102 CODER_FACTORY pfnCreateObject = (*it).second;
\r
103 ASSERT(pfnCreateObject != NULL);
\r
104 return pfnCreateObject();
\r
107 return new CMimeCodeBase; // default coder for unregistered Content-Transfer-Encoding
\r
111 void CMimeEnvironment::RegisterFieldCoder(const char* pszFieldName, FIELD_CODER_FACTORY pfnCreateObject/*=NULL*/)
\r
113 ASSERT(pszFieldName != NULL);
\r
114 list<FIELD_CODER_PAIR>::iterator it = m_listFieldCoders.begin();
\r
115 while (it != m_listFieldCoders.end())
\r
117 list<FIELD_CODER_PAIR>::iterator it2 = it;
\r
119 if (!::stricmp(pszFieldName, (*it2).first))
\r
120 m_listFieldCoders.erase(it2);
\r
122 if (pfnCreateObject != NULL)
\r
124 FIELD_CODER_PAIR newPair(pszFieldName, pfnCreateObject);
\r
125 m_listFieldCoders.push_front(newPair);
\r
129 CFieldCodeBase* CMimeEnvironment::CreateFieldCoder(const char* pszFieldName)
\r
131 ASSERT(pszFieldName != NULL);
\r
132 for (list<FIELD_CODER_PAIR>::iterator it=m_listFieldCoders.begin(); it!=m_listFieldCoders.end(); it++)
\r
134 ASSERT((*it).first != NULL);
\r
135 if (!::stricmp(pszFieldName, (*it).first))
\r
137 FIELD_CODER_FACTORY pfnCreateObject = (*it).second;
\r
138 ASSERT(pfnCreateObject != NULL);
\r
139 return pfnCreateObject();
\r
142 return new CFieldCodeBase; // default coder for unregistered header fields
\r
145 void CMimeEnvironment::RegisterMediaType(const char* pszMediaType, BODY_PART_FACTORY pfnCreateObject/*=NULL*/)
\r
147 ASSERT(pszMediaType != NULL);
\r
148 list<MEDIA_TYPE_PAIR>::iterator it = m_listMediaTypes.begin();
\r
149 while (it != m_listMediaTypes.end())
\r
151 list<MEDIA_TYPE_PAIR>::iterator it2 = it;
\r
153 if (!::stricmp(pszMediaType, (*it2).first))
\r
154 m_listMediaTypes.erase(it2);
\r
156 if (pfnCreateObject != NULL)
\r
158 MEDIA_TYPE_PAIR newPair(pszMediaType, pfnCreateObject);
\r
159 m_listMediaTypes.push_front(newPair);
\r
163 CMimeBody* CMimeEnvironment::CreateBodyPart(const char* pszMediaType)
\r
165 if (!pszMediaType || !::strlen(pszMediaType))
\r
166 pszMediaType = "text";
\r
168 ASSERT(pszMediaType != NULL);
\r
169 for (list<MEDIA_TYPE_PAIR>::iterator it=m_listMediaTypes.begin(); it!=m_listMediaTypes.end(); it++)
\r
171 ASSERT((*it).first != NULL);
\r
172 if (!::stricmp(pszMediaType, (*it).first))
\r
174 BODY_PART_FACTORY pfnCreateObject = (*it).second;
\r
175 ASSERT(pfnCreateObject != NULL);
\r
176 return pfnCreateObject();
\r
179 return new CMimeBody; // default body part for unregistered media type
\r
182 //////////////////////////////////////////////////////////////////////
\r
183 // CMimeCode7bit - for 7bit/8bit encoding mechanism (fold long line)
\r
184 //////////////////////////////////////////////////////////////////////
\r
185 int CMimeCode7bit::GetEncodeLength() const
\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
197 int CMimeCode7bit::Encode(unsigned char* pbOutput, int nMaxSize) const
\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
205 while (pbData < pbEnd)
\r
207 if (pbOutput >= pbOutEnd)
\r
210 unsigned char ch = *pbData;
\r
211 //if (ch == '.' && pbData-m_pbInput >= 2 && !::memcmp(pbData-2, "\r\n.", 3))
\r
213 // *pbOutput++ = '.'; // avoid confusing with SMTP end flag
\r
217 if (ch == '\r' || ch == '\n')
\r
222 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))
\r
223 pbSpace = pbOutput;
\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
229 int nSize = (int)(pbOutput - pbSpace);
\r
230 ::memmove(pbSpace+2, pbSpace, nSize);
\r
243 return (int)(pbOutput - pbOutStart);
\r
246 //////////////////////////////////////////////////////////////////////
\r
247 // CMimeCodeQP - for quoted-printable encoding mechanism
\r
248 //////////////////////////////////////////////////////////////////////
\r
249 int CMimeCodeQP::GetEncodeLength() const
\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
257 //int nLength = m_nInputSize * 3;
\r
258 nLength += nLength / (MAX_MIME_LINE_LEN - 2) * 6;
\r
262 int CMimeCodeQP::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
264 static const char* s_QPTable = "0123456789ABCDEF";
\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
272 while (pbData < pbEnd)
\r
274 if (pbOutput >= pbOutEnd)
\r
277 unsigned char ch = *pbData;
\r
278 bool bQuote = false, bCopy = false;
\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
284 if (pbData == pbEnd-1 || (!m_bQuoteLineBreak && *(pbData+1) == '\r'))
\r
285 bQuote = true; // quote the SPACE/TAB
\r
287 bCopy = true; // copy the SPACE/TAB
\r
289 pbSpace = (unsigned char*) pbOutput;
\r
291 else if (!m_bQuoteLineBreak && (ch == '\r' || ch == '\n'))
\r
293 bCopy = true; // keep 'hard' line break
\r
297 else if (!m_bQuoteLineBreak && ch == '.')
\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
306 else if (ch < 33 || ch > 126 || ch == '=')
\r
307 bQuote = true; // quote this character
\r
309 bCopy = true; // copy this character
\r
311 if (nLineLen+(bQuote ? 3 : 1) >= MAX_MIME_LINE_LEN && pbOutput+3 <= pbOutEnd)
\r
313 if (pbSpace != NULL && pbSpace < pbOutput)
\r
316 int nSize = (int)(pbOutput - pbSpace);
\r
317 ::memmove(pbSpace+3, pbSpace, nSize);
\r
322 pbSpace = pbOutput;
\r
325 ::memcpy(pbSpace, "=\r\n", 3);
\r
330 if (bQuote && pbOutput+3 <= pbOutEnd)
\r
333 *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];
\r
334 *pbOutput++ = s_QPTable[ch & 0x0f];
\r
339 *pbOutput++ = (char) ch;
\r
346 return (int)(pbOutput - pbOutStart);
\r
349 int CMimeCodeQP::Decode(unsigned char* pbOutput, int nMaxSize)
\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
356 while (pbData < pbEnd)
\r
358 if (pbOutput >= pbOutEnd)
\r
361 unsigned char ch = *pbData++;
\r
364 if (pbData+2 > pbEnd)
\r
365 break; // invalid endcoding
\r
367 if (CMimeChar::IsHexDigit(ch))
\r
369 ch -= ch > '9' ? 0x37 : '0';
\r
370 *pbOutput = ch << 4;
\r
372 ch -= ch > '9' ? 0x37 : '0';
\r
373 *pbOutput++ |= ch & 0x0f;
\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
380 else// if (ch != '\r' && ch != '\n')
\r
384 return (int)(pbOutput - pbOutStart);
\r
387 //////////////////////////////////////////////////////////////////////
\r
388 // CMimeCodeBase64 - for base64 encoding mechanism
\r
389 //////////////////////////////////////////////////////////////////////
\r
390 int CMimeCodeBase64::GetEncodeLength() const
\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
398 int CMimeCodeBase64::GetDecodeLength() const
\r
400 return m_nInputSize * 3 / 4 + 2;
\r
403 int CMimeCodeBase64::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
405 static const char* s_Base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
\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
412 for (nFrom=0; nFrom<m_nInputSize; nFrom++)
\r
414 if (pbOutput >= pbOutEnd)
\r
417 unsigned char ch = m_pbInput[nFrom];
\r
421 *pbOutput++ = s_Base64Table[ch >> 2];
\r
422 chHigh4bits = (ch << 4) & 0x30;
\r
426 *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 4)];
\r
427 chHigh4bits = (ch << 2) & 0x3c;
\r
431 *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 6)];
\r
432 if (pbOutput < pbOutEnd)
\r
434 *pbOutput++ = s_Base64Table[ch & 0x3f];
\r
440 if (m_bAddLineBreak && nLineLen >= MAX_MIME_LINE_LEN && pbOutput+2 <= pbOutEnd)
\r
442 *pbOutput++ = '\r';
\r
443 *pbOutput++ = '\n';
\r
448 if (nFrom % 3 != 0 && pbOutput < pbOutEnd) // 76 = 19 * 4, so the padding wouldn't exceed 76
\r
450 *pbOutput++ = s_Base64Table[chHigh4bits];
\r
451 int nPad = 4 - (nFrom % 3) - 1;
\r
452 if (pbOutput+nPad <= pbOutEnd)
\r
454 ::memset(pbOutput, '=', nPad);
\r
458 if (m_bAddLineBreak && nLineLen != 0 && pbOutput+2 <= pbOutEnd) // add CRLF
\r
460 *pbOutput++ = '\r';
\r
461 *pbOutput++ = '\n';
\r
463 return (int)(pbOutput - pbOutStart);
\r
466 int CMimeCodeBase64::Decode(unsigned char* pbOutput, int nMaxSize)
\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
474 unsigned char chHighBits = 0;
\r
476 while (pbData < pbEnd)
\r
478 if (pbOutput >= pbOutEnd)
\r
481 unsigned char ch = *pbData++;
\r
482 if (ch == '\r' || ch == '\n')
\r
484 ch = (unsigned char) DecodeBase64Char(ch);
\r
485 if (ch >= 64) // invalid encoding, or trailing pad '='
\r
488 switch ((nFrom++) % 4)
\r
491 chHighBits = ch << 2;
\r
495 *pbOutput++ = chHighBits | (ch >> 4);
\r
496 chHighBits = ch << 4;
\r
500 *pbOutput++ = chHighBits | (ch >> 2);
\r
501 chHighBits = ch << 6;
\r
505 *pbOutput++ = chHighBits | ch;
\r
509 return (int)(pbOutput - pbOutStart);
\r
512 //////////////////////////////////////////////////////////////////////
\r
513 // CMimeEncodedWord - encoded word for non-ascii text (RFC 2047)
\r
514 //////////////////////////////////////////////////////////////////////
\r
515 int CMimeEncodedWord::GetEncodeLength() const
\r
517 if (!m_nInputSize || m_strCharset.empty())
\r
518 return CMimeCodeBase::GetEncodeLength();
\r
520 int nLength, nCodeLen = (int) m_strCharset.size() + 7;
\r
521 if (tolower(m_nEncoding) == 'b')
\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
531 qp.SetInput((const char*)m_pbInput, m_nInputSize, true);
\r
532 qp.QuoteLineBreak(false);
\r
533 nLength = qp.GetOutputLength();
\r
537 ASSERT(nCodeLen < MAX_ENCODEDWORD_LEN);
\r
538 return (nLength / (MAX_ENCODEDWORD_LEN - nCodeLen) + 1) * nCodeLen + nLength;
\r
541 int CMimeEncodedWord::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
543 if (m_strCharset.empty())
\r
544 return CMimeCodeBase::Encode(pbOutput, nMaxSize);
\r
548 if (tolower(m_nEncoding) == 'b')
\r
549 return BEncode(pbOutput, nMaxSize);
\r
550 return QEncode(pbOutput, nMaxSize);
\r
553 int CMimeEncodedWord::Decode(unsigned char* pbOutput, int nMaxSize)
\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
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
566 pszHeaderEnd = ::strchr(pbData+2, '?');
\r
567 if (pszHeaderEnd != NULL && pszHeaderEnd[2] == '?' && pszHeaderEnd+3 < pbEnd)
\r
569 nCoding = tolower(pszHeaderEnd[1]);
\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
576 if (m_strCharset.empty())
\r
578 m_strCharset.assign(pbData+2, pszHeaderEnd-pbData-5);
\r
579 m_nEncoding = nCoding;
\r
585 if (nCoding == 'b')
\r
587 CMimeCodeBase64 base64;
\r
588 base64.SetInput(pszHeaderEnd, nCodeLen, false);
\r
589 nDecoded = base64.GetOutput(pbOutput, nMaxSize);
\r
591 else if (nCoding == 'q')
\r
594 qp.SetInput(pszHeaderEnd, nCodeLen, false);
\r
595 nDecoded = qp.GetOutput(pbOutput, nMaxSize);
\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
604 const char* pszSpace = pbData;
\r
605 while (CMimeChar::IsSpace((unsigned char)*pszSpace))
\r
607 if (pszSpace == pszCodeEnd) // ignore liner-white-spaces between adjacent encoded words
\r
608 pbData = pszCodeEnd;
\r
610 nDecoded = min((int)(pszCodeEnd - pbData), nMaxSize);
\r
611 ::memcpy(pbOutput, pbData, nDecoded);
\r
614 pbData = pszCodeEnd;
\r
615 pbOutput += nDecoded;
\r
616 nMaxSize -= nDecoded;
\r
621 return (int)(pbOutput - pbOutStart);
\r
624 int CMimeEncodedWord::BEncode(unsigned char* pbOutput, int nMaxSize) const
\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
631 unsigned char* pbOutStart = pbOutput;
\r
635 if (nMaxSize < nCharsetLen+7)
\r
637 *pbOutput++ = '='; // encoded-word header
\r
639 ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);
\r
640 pbOutput += nCharsetLen;
\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
654 nInput += nBlockSize;
\r
655 nMaxSize -= nEncoded + nCharsetLen + 7;
\r
656 if (nInput >= m_nInputSize)
\r
658 *pbOutput++ = ' '; // add a liner-white-space between adjacent encoded words
\r
661 return (int)(pbOutput - pbOutStart);
\r
664 int CMimeEncodedWord::QEncode(unsigned char* pbOutput, int nMaxSize) const
\r
666 static const char* s_QPTable = "0123456789ABCDEF";
\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
675 while (pbData < pbEnd)
\r
677 unsigned char ch = *pbData++;
\r
678 if (ch < 33 || ch > 126 || ch == '=' || ch == '?' || ch == '_')
\r
683 if (nLineLen+nCodeLen > nMaxLine) // add encoded word tailer
\r
685 if (pbOutput+3 > pbOutEnd)
\r
693 if (!nLineLen) // add encoded word header
\r
695 if (pbOutput+nCharsetLen+7 > pbOutEnd)
\r
699 ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);
\r
700 pbOutput += nCharsetLen;
\r
706 nLineLen += nCodeLen;
\r
707 if (pbOutput+nCodeLen > pbOutEnd)
\r
712 *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];
\r
713 *pbOutput++ = s_QPTable[ch & 0x0f];
\r
719 if (pbOutput+2 <= pbOutEnd)
\r
724 return (int)(pbOutput - pbOutStart);
\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
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
741 const char* pszData = (const char*) m_pbInput;
\r
742 int nInputSize = m_nInputSize;
\r
743 int nNonAsciiChars, nDelimeter = GetDelimeter();
\r
745 // divide the field into syntactic units to calculate the output length
\r
748 int nUnitSize = FindSymbol(pszData, nInputSize, nDelimeter, nNonAsciiChars);
\r
749 if (!nNonAsciiChars || strCharset.empty())
\r
750 nLength += nUnitSize;
\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
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
766 } while (nInputSize > 0);
\r
768 if (CMimeEnvironment::AutoFolding())
\r
769 nLength += nLength / MAX_MIME_LINE_LEN * 6;
\r
773 int CFieldCodeBase::Encode(unsigned char* pbOutput, int nMaxSize) const
\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
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
788 unsigned char* pbSpace = NULL;
\r
790 strUnit.reserve(nInputSize);
\r
792 // divide the field into syntactic units to encode
\r
795 int nUnitSize = FindSymbol(pszInput, nInputSize, nDelimeter, nNonAsciiChars);
\r
796 if (!nNonAsciiChars || strCharset.empty())
\r
797 strUnit.assign(pszInput, nUnitSize);
\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
807 if (nUnitSize < nInputSize)
\r
808 strUnit += pszInput[nUnitSize]; // add the following delimeter (space or special char)
\r
810 // copy the encoded string to target buffer and perform folding if needed
\r
811 if (!CMimeEnvironment::AutoFolding())
\r
813 int nSize = min((int) (pbOutEnd - pbOutput), (int) strUnit.size());
\r
814 ::memcpy(pbOutput, strUnit.c_str(), nSize);
\r
819 const char* pszData = strUnit.c_str();
\r
820 const char* pszEnd = pszData + strUnit.size();
\r
821 while (pszData < pszEnd)
\r
823 char ch = *pszData++;
\r
824 if (ch == '\r' || ch == '\n')
\r
829 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))
\r
830 pbSpace = pbOutput;
\r
832 if (nLineLen >= MAX_MIME_LINE_LEN && pbSpace != NULL &&
\r
833 pbOutput+3 <= pbOutEnd) // fold at the position of the previous space
\r
835 int nSize = (int)(pbOutput - pbSpace);
\r
836 ::memmove(pbSpace+3, pbSpace, nSize);
\r
837 ::memcpy(pbSpace, "\r\n\t", 3);
\r
840 nLineLen = nSize + 1;
\r
842 if (pbOutput < pbOutEnd)
\r
848 pszInput += nUnitSize + 1;
\r
849 nInputSize -= nUnitSize + 1;
\r
850 if (nInputSize <= 0)
\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
856 ::memcpy(pbOutput, "\r\n\t", 3);
\r
860 while (nInputSize > 0 && CMimeChar::IsSpace(*pszInput))
\r
867 return (int) (pbOutput - pbOutBegin);
\r
870 int CFieldCodeBase::Decode(unsigned char* pbOutput, int nMaxSize)
\r
872 CMimeEncodedWord coder;
\r
873 coder.SetInput((const char*)m_pbInput, m_nInputSize, false);
\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
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
888 void CFieldCodeBase::UnfoldField(string& strField) const
\r
892 string::size_type pos = strField.rfind("\r\n");
\r
893 if (pos == string::npos)
\r
896 strField.erase(pos, 2);
\r
897 //if (strField[pos] == '\t')
\r
898 // strField[pos] = ' ';
\r
900 while (CMimeChar::IsSpace((unsigned char)strField[pos+nSpaces]))
\r
902 strField.replace(pos, nSpaces, " ");
\r
906 int CFieldCodeBase::FindSymbol(const char* pszData, int nSize, int& nDelimeter, int& nNonAscChars) const
\r
909 const char* pszDataStart = pszData;
\r
910 const char* pszEnd = pszData + nSize;
\r
912 while (pszData < pszEnd)
\r
914 char ch = *pszData;
\r
915 if (CMimeChar::IsNonAscii((unsigned char)ch))
\r
919 if (ch == (char) nDelimeter)
\r
921 nDelimeter = 0; // stop at any delimeters (space or specials)
\r
925 if (!nDelimeter && CMimeChar::IsDelimiter(ch))
\r
930 nDelimeter = '"'; // quoted-string, delimeter is '"'
\r
933 nDelimeter = ')'; // comment, delimeter is ')'
\r
936 nDelimeter = '>'; // address, delimeter is '>'
\r
945 return (int)(pszData - pszDataStart);
\r