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
23 #define stricmp strcasecmp
\r
24 #define strnicmp strncasecmp
\r
25 #define memicmp memcmp
\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
38 CMimeEnvironment::CMimeEnvironment()
\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
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
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
61 REGISTER_FIELDCODER("Content-Type", CFieldCodeParameter);
\r
62 REGISTER_FIELDCODER("Content-Disposition", CFieldCodeParameter);
\r
65 void CMimeEnvironment::SetAutoFolding(bool bAutoFolding)
\r
67 m_bAutoFolding = bAutoFolding;
\r
70 DEREGISTER_MIMECODER("7bit");
\r
71 DEREGISTER_MIMECODER("8bit");
\r
75 REGISTER_MIMECODER("7bit", CMimeCode7bit);
\r
76 REGISTER_MIMECODER("8bit", CMimeCode7bit);
\r
80 void CMimeEnvironment::RegisterCoder(const char* pszCodingName, CODER_FACTORY pfnCreateObject/*=NULL*/)
\r
82 ASSERT(pszCodingName != NULL);
\r
83 list<CODER_PAIR>::iterator it = m_listCoders.begin();
\r
84 while (it != m_listCoders.end())
\r
86 list<CODER_PAIR>::iterator it2 = it;
\r
88 if (!::stricmp(pszCodingName, (*it2).first))
\r
89 m_listCoders.erase(it2);
\r
91 if (pfnCreateObject != NULL)
\r
93 CODER_PAIR newPair(pszCodingName, pfnCreateObject);
\r
94 m_listCoders.push_front(newPair);
\r
98 CMimeCodeBase* CMimeEnvironment::CreateCoder(const char* pszCodingName)
\r
100 if (!pszCodingName || !::strlen(pszCodingName))
\r
101 pszCodingName = "7bit";
\r
103 for (list<CODER_PAIR>::iterator it=m_listCoders.begin(); it!=m_listCoders.end(); it++)
\r
105 ASSERT((*it).first != NULL);
\r
106 if (!::stricmp(pszCodingName, (*it).first))
\r
108 CODER_FACTORY pfnCreateObject = (*it).second;
\r
109 ASSERT(pfnCreateObject != NULL);
\r
110 return pfnCreateObject();
\r
113 return new CMimeCodeBase; // default coder for unregistered Content-Transfer-Encoding
\r
117 void CMimeEnvironment::RegisterFieldCoder(const char* pszFieldName, FIELD_CODER_FACTORY pfnCreateObject/*=NULL*/)
\r
119 ASSERT(pszFieldName != NULL);
\r
120 list<FIELD_CODER_PAIR>::iterator it = m_listFieldCoders.begin();
\r
121 while (it != m_listFieldCoders.end())
\r
123 list<FIELD_CODER_PAIR>::iterator it2 = it;
\r
125 if (!::stricmp(pszFieldName, (*it2).first))
\r
126 m_listFieldCoders.erase(it2);
\r
128 if (pfnCreateObject != NULL)
\r
130 FIELD_CODER_PAIR newPair(pszFieldName, pfnCreateObject);
\r
131 m_listFieldCoders.push_front(newPair);
\r
135 CFieldCodeBase* CMimeEnvironment::CreateFieldCoder(const char* pszFieldName)
\r
137 ASSERT(pszFieldName != NULL);
\r
138 for (list<FIELD_CODER_PAIR>::iterator it=m_listFieldCoders.begin(); it!=m_listFieldCoders.end(); it++)
\r
140 ASSERT((*it).first != NULL);
\r
141 if (!::stricmp(pszFieldName, (*it).first))
\r
143 FIELD_CODER_FACTORY pfnCreateObject = (*it).second;
\r
144 ASSERT(pfnCreateObject != NULL);
\r
145 return pfnCreateObject();
\r
148 return new CFieldCodeBase; // default coder for unregistered header fields
\r
151 void CMimeEnvironment::RegisterMediaType(const char* pszMediaType, BODY_PART_FACTORY pfnCreateObject/*=NULL*/)
\r
153 ASSERT(pszMediaType != NULL);
\r
154 list<MEDIA_TYPE_PAIR>::iterator it = m_listMediaTypes.begin();
\r
155 while (it != m_listMediaTypes.end())
\r
157 list<MEDIA_TYPE_PAIR>::iterator it2 = it;
\r
159 if (!::stricmp(pszMediaType, (*it2).first))
\r
160 m_listMediaTypes.erase(it2);
\r
162 if (pfnCreateObject != NULL)
\r
164 MEDIA_TYPE_PAIR newPair(pszMediaType, pfnCreateObject);
\r
165 m_listMediaTypes.push_front(newPair);
\r
169 CMimeBody* CMimeEnvironment::CreateBodyPart(const char* pszMediaType)
\r
171 if (!pszMediaType || !::strlen(pszMediaType))
\r
172 pszMediaType = "text";
\r
174 ASSERT(pszMediaType != NULL);
\r
175 for (list<MEDIA_TYPE_PAIR>::iterator it=m_listMediaTypes.begin(); it!=m_listMediaTypes.end(); it++)
\r
177 ASSERT((*it).first != NULL);
\r
178 if (!::stricmp(pszMediaType, (*it).first))
\r
180 BODY_PART_FACTORY pfnCreateObject = (*it).second;
\r
181 ASSERT(pfnCreateObject != NULL);
\r
182 return pfnCreateObject();
\r
185 return new CMimeBody; // default body part for unregistered media type
\r
188 //////////////////////////////////////////////////////////////////////
\r
189 // CMimeCode7bit - for 7bit/8bit encoding mechanism (fold long line)
\r
190 //////////////////////////////////////////////////////////////////////
\r
191 int CMimeCode7bit::GetEncodeLength() const
\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
203 int CMimeCode7bit::Encode(unsigned char* pbOutput, int nMaxSize) const
\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
211 while (pbData < pbEnd)
\r
213 if (pbOutput >= pbOutEnd)
\r
216 unsigned char ch = *pbData;
\r
217 //if (ch == '.' && pbData-m_pbInput >= 2 && !::memcmp(pbData-2, "\r\n.", 3))
\r
219 // *pbOutput++ = '.'; // avoid confusing with SMTP end flag
\r
223 if (ch == '\r' || ch == '\n')
\r
228 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))
\r
229 pbSpace = pbOutput;
\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
235 int nSize = (int)(pbOutput - pbSpace);
\r
236 ::memmove(pbSpace+2, pbSpace, nSize);
\r
249 return (int)(pbOutput - pbOutStart);
\r
252 //////////////////////////////////////////////////////////////////////
\r
253 // CMimeCodeQP - for quoted-printable encoding mechanism
\r
254 //////////////////////////////////////////////////////////////////////
\r
255 int CMimeCodeQP::GetEncodeLength() const
\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
263 //int nLength = m_nInputSize * 3;
\r
264 nLength += nLength / (MAX_MIME_LINE_LEN - 2) * 6;
\r
268 int CMimeCodeQP::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
270 static const char* s_QPTable = "0123456789ABCDEF";
\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
278 while (pbData < pbEnd)
\r
280 if (pbOutput >= pbOutEnd)
\r
283 unsigned char ch = *pbData;
\r
284 bool bQuote = false, bCopy = false;
\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
290 if (pbData == pbEnd-1 || (!m_bQuoteLineBreak && *(pbData+1) == '\r'))
\r
291 bQuote = true; // quote the SPACE/TAB
\r
293 bCopy = true; // copy the SPACE/TAB
\r
295 pbSpace = (unsigned char*) pbOutput;
\r
297 else if (!m_bQuoteLineBreak && (ch == '\r' || ch == '\n'))
\r
299 bCopy = true; // keep 'hard' line break
\r
303 else if (!m_bQuoteLineBreak && ch == '.')
\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
312 else if (ch < 33 || ch > 126 || ch == '=')
\r
313 bQuote = true; // quote this character
\r
315 bCopy = true; // copy this character
\r
317 if (nLineLen+(bQuote ? 3 : 1) >= MAX_MIME_LINE_LEN && pbOutput+3 <= pbOutEnd)
\r
319 if (pbSpace != NULL && pbSpace < pbOutput)
\r
322 int nSize = (int)(pbOutput - pbSpace);
\r
323 ::memmove(pbSpace+3, pbSpace, nSize);
\r
328 pbSpace = pbOutput;
\r
331 ::memcpy(pbSpace, "=\r\n", 3);
\r
336 if (bQuote && pbOutput+3 <= pbOutEnd)
\r
339 *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];
\r
340 *pbOutput++ = s_QPTable[ch & 0x0f];
\r
345 *pbOutput++ = (char) ch;
\r
352 return (int)(pbOutput - pbOutStart);
\r
355 int CMimeCodeQP::Decode(unsigned char* pbOutput, int nMaxSize)
\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
362 while (pbData < pbEnd)
\r
364 if (pbOutput >= pbOutEnd)
\r
367 unsigned char ch = *pbData++;
\r
370 if (pbData+2 > pbEnd)
\r
371 break; // invalid endcoding
\r
373 if (CMimeChar::IsHexDigit(ch))
\r
375 ch -= ch > '9' ? 0x37 : '0';
\r
376 *pbOutput = ch << 4;
\r
378 ch -= ch > '9' ? 0x37 : '0';
\r
379 *pbOutput++ |= ch & 0x0f;
\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
386 else// if (ch != '\r' && ch != '\n')
\r
390 return (int)(pbOutput - pbOutStart);
\r
393 //////////////////////////////////////////////////////////////////////
\r
394 // CMimeCodeBase64 - for base64 encoding mechanism
\r
395 //////////////////////////////////////////////////////////////////////
\r
396 int CMimeCodeBase64::GetEncodeLength() const
\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
404 int CMimeCodeBase64::GetDecodeLength() const
\r
406 return m_nInputSize * 3 / 4 + 2;
\r
409 int CMimeCodeBase64::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
411 static const char* s_Base64Table = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
\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
418 for (nFrom=0; nFrom<m_nInputSize; nFrom++)
\r
420 if (pbOutput >= pbOutEnd)
\r
423 unsigned char ch = m_pbInput[nFrom];
\r
427 *pbOutput++ = s_Base64Table[ch >> 2];
\r
428 chHigh4bits = (ch << 4) & 0x30;
\r
432 *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 4)];
\r
433 chHigh4bits = (ch << 2) & 0x3c;
\r
437 *pbOutput++ = s_Base64Table[chHigh4bits | (ch >> 6)];
\r
438 if (pbOutput < pbOutEnd)
\r
440 *pbOutput++ = s_Base64Table[ch & 0x3f];
\r
446 if (m_bAddLineBreak && nLineLen >= MAX_MIME_LINE_LEN && pbOutput+2 <= pbOutEnd)
\r
448 *pbOutput++ = '\r';
\r
449 *pbOutput++ = '\n';
\r
454 if (nFrom % 3 != 0 && pbOutput < pbOutEnd) // 76 = 19 * 4, so the padding wouldn't exceed 76
\r
456 *pbOutput++ = s_Base64Table[chHigh4bits];
\r
457 int nPad = 4 - (nFrom % 3) - 1;
\r
458 if (pbOutput+nPad <= pbOutEnd)
\r
460 ::memset(pbOutput, '=', nPad);
\r
464 if (m_bAddLineBreak && nLineLen != 0 && pbOutput+2 <= pbOutEnd) // add CRLF
\r
466 *pbOutput++ = '\r';
\r
467 *pbOutput++ = '\n';
\r
469 return (int)(pbOutput - pbOutStart);
\r
472 int CMimeCodeBase64::Decode(unsigned char* pbOutput, int nMaxSize)
\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
480 unsigned char chHighBits = 0;
\r
482 while (pbData < pbEnd)
\r
484 if (pbOutput >= pbOutEnd)
\r
487 unsigned char ch = *pbData++;
\r
488 if (ch == '\r' || ch == '\n')
\r
490 ch = (unsigned char) DecodeBase64Char(ch);
\r
491 if (ch >= 64) // invalid encoding, or trailing pad '='
\r
494 switch ((nFrom++) % 4)
\r
497 chHighBits = ch << 2;
\r
501 *pbOutput++ = chHighBits | (ch >> 4);
\r
502 chHighBits = ch << 4;
\r
506 *pbOutput++ = chHighBits | (ch >> 2);
\r
507 chHighBits = ch << 6;
\r
511 *pbOutput++ = chHighBits | ch;
\r
515 return (int)(pbOutput - pbOutStart);
\r
518 //////////////////////////////////////////////////////////////////////
\r
519 // CMimeEncodedWord - encoded word for non-ascii text (RFC 2047)
\r
520 //////////////////////////////////////////////////////////////////////
\r
521 int CMimeEncodedWord::GetEncodeLength() const
\r
523 if (!m_nInputSize || m_strCharset.empty())
\r
524 return CMimeCodeBase::GetEncodeLength();
\r
526 int nLength, nCodeLen = (int) m_strCharset.size() + 7;
\r
527 if (tolower(m_nEncoding) == 'b')
\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
537 qp.SetInput((const char*)m_pbInput, m_nInputSize, true);
\r
538 qp.QuoteLineBreak(false);
\r
539 nLength = qp.GetOutputLength();
\r
543 ASSERT(nCodeLen < MAX_ENCODEDWORD_LEN);
\r
544 return (nLength / (MAX_ENCODEDWORD_LEN - nCodeLen) + 1) * nCodeLen + nLength;
\r
547 int CMimeEncodedWord::Encode(unsigned char* pbOutput, int nMaxSize) const
\r
549 if (m_strCharset.empty())
\r
550 return CMimeCodeBase::Encode(pbOutput, nMaxSize);
\r
554 if (tolower(m_nEncoding) == 'b')
\r
555 return BEncode(pbOutput, nMaxSize);
\r
556 return QEncode(pbOutput, nMaxSize);
\r
559 int CMimeEncodedWord::Decode(unsigned char* pbOutput, int nMaxSize)
\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
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
572 pszHeaderEnd = ::strchr(pbData+2, '?');
\r
573 if (pszHeaderEnd != NULL && pszHeaderEnd[2] == '?' && pszHeaderEnd+3 < pbEnd)
\r
575 nCoding = tolower(pszHeaderEnd[1]);
\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
582 if (m_strCharset.empty())
\r
584 m_strCharset.assign(pbData+2, pszHeaderEnd-pbData-5);
\r
585 m_nEncoding = nCoding;
\r
591 if (nCoding == 'b')
\r
593 CMimeCodeBase64 base64;
\r
594 base64.SetInput(pszHeaderEnd, nCodeLen, false);
\r
595 nDecoded = base64.GetOutput(pbOutput, nMaxSize);
\r
597 else if (nCoding == 'q')
\r
600 qp.SetInput(pszHeaderEnd, nCodeLen, false);
\r
601 nDecoded = qp.GetOutput(pbOutput, nMaxSize);
\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
610 const char* pszSpace = pbData;
\r
611 while (CMimeChar::IsSpace((unsigned char)*pszSpace))
\r
613 if (pszSpace == pszCodeEnd) // ignore liner-white-spaces between adjacent encoded words
\r
614 pbData = pszCodeEnd;
\r
616 nDecoded = min((int)(pszCodeEnd - pbData), nMaxSize);
\r
617 ::memcpy(pbOutput, pbData, nDecoded);
\r
620 pbData = pszCodeEnd;
\r
621 pbOutput += nDecoded;
\r
622 nMaxSize -= nDecoded;
\r
627 return (int)(pbOutput - pbOutStart);
\r
630 int CMimeEncodedWord::BEncode(unsigned char* pbOutput, int nMaxSize) const
\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
637 unsigned char* pbOutStart = pbOutput;
\r
641 if (nMaxSize < nCharsetLen+7)
\r
643 *pbOutput++ = '='; // encoded-word header
\r
645 ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);
\r
646 pbOutput += nCharsetLen;
\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
660 nInput += nBlockSize;
\r
661 nMaxSize -= nEncoded + nCharsetLen + 7;
\r
662 if (nInput >= m_nInputSize)
\r
664 *pbOutput++ = ' '; // add a liner-white-space between adjacent encoded words
\r
667 return (int)(pbOutput - pbOutStart);
\r
670 int CMimeEncodedWord::QEncode(unsigned char* pbOutput, int nMaxSize) const
\r
672 static const char* s_QPTable = "0123456789ABCDEF";
\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
681 while (pbData < pbEnd)
\r
683 unsigned char ch = *pbData++;
\r
684 if (ch < 33 || ch > 126 || ch == '=' || ch == '?' || ch == '_')
\r
689 if (nLineLen+nCodeLen > nMaxLine) // add encoded word tailer
\r
691 if (pbOutput+3 > pbOutEnd)
\r
699 if (!nLineLen) // add encoded word header
\r
701 if (pbOutput+nCharsetLen+7 > pbOutEnd)
\r
705 ::memcpy(pbOutput, m_strCharset.c_str(), nCharsetLen);
\r
706 pbOutput += nCharsetLen;
\r
712 nLineLen += nCodeLen;
\r
713 if (pbOutput+nCodeLen > pbOutEnd)
\r
718 *pbOutput++ = s_QPTable[(ch >> 4) & 0x0f];
\r
719 *pbOutput++ = s_QPTable[ch & 0x0f];
\r
725 if (pbOutput+2 <= pbOutEnd)
\r
730 return (int)(pbOutput - pbOutStart);
\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
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
747 const char* pszData = (const char*) m_pbInput;
\r
748 int nInputSize = m_nInputSize;
\r
749 int nNonAsciiChars, nDelimeter = GetDelimeter();
\r
751 // divide the field into syntactic units to calculate the output length
\r
754 int nUnitSize = FindSymbol(pszData, nInputSize, nDelimeter, nNonAsciiChars);
\r
755 if (!nNonAsciiChars || strCharset.empty())
\r
756 nLength += nUnitSize;
\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
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
772 } while (nInputSize > 0);
\r
774 if (CMimeEnvironment::AutoFolding())
\r
775 nLength += nLength / MAX_MIME_LINE_LEN * 6;
\r
779 int CFieldCodeBase::Encode(unsigned char* pbOutput, int nMaxSize) const
\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
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
794 unsigned char* pbSpace = NULL;
\r
796 strUnit.reserve(nInputSize);
\r
798 // divide the field into syntactic units to encode
\r
801 int nUnitSize = FindSymbol(pszInput, nInputSize, nDelimeter, nNonAsciiChars);
\r
802 if (!nNonAsciiChars || strCharset.empty())
\r
803 strUnit.assign(pszInput, nUnitSize);
\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
813 if (nUnitSize < nInputSize)
\r
814 strUnit += pszInput[nUnitSize]; // add the following delimeter (space or special char)
\r
816 // copy the encoded string to target buffer and perform folding if needed
\r
817 if (!CMimeEnvironment::AutoFolding())
\r
819 int nSize = min((int) (pbOutEnd - pbOutput), (int) strUnit.size());
\r
820 ::memcpy(pbOutput, strUnit.c_str(), nSize);
\r
825 const char* pszData = strUnit.c_str();
\r
826 const char* pszEnd = pszData + strUnit.size();
\r
827 while (pszData < pszEnd)
\r
829 char ch = *pszData++;
\r
830 if (ch == '\r' || ch == '\n')
\r
835 else if (nLineLen > 0 && CMimeChar::IsSpace(ch))
\r
836 pbSpace = pbOutput;
\r
838 if (nLineLen >= MAX_MIME_LINE_LEN && pbSpace != NULL &&
\r
839 pbOutput+3 <= pbOutEnd) // fold at the position of the previous space
\r
841 int nSize = (int)(pbOutput - pbSpace);
\r
842 ::memmove(pbSpace+3, pbSpace, nSize);
\r
843 ::memcpy(pbSpace, "\r\n\t", 3);
\r
846 nLineLen = nSize + 1;
\r
848 if (pbOutput < pbOutEnd)
\r
854 pszInput += nUnitSize + 1;
\r
855 nInputSize -= nUnitSize + 1;
\r
856 if (nInputSize <= 0)
\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
862 ::memcpy(pbOutput, "\r\n\t", 3);
\r
866 while (nInputSize > 0 && CMimeChar::IsSpace(*pszInput))
\r
873 return (int) (pbOutput - pbOutBegin);
\r
876 int CFieldCodeBase::Decode(unsigned char* pbOutput, int nMaxSize)
\r
878 CMimeEncodedWord coder;
\r
879 coder.SetInput((const char*)m_pbInput, m_nInputSize, false);
\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
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
894 void CFieldCodeBase::UnfoldField(string& strField) const
\r
898 string::size_type pos = strField.rfind("\r\n");
\r
899 if (pos == string::npos)
\r
902 strField.erase(pos, 2);
\r
903 //if (strField[pos] == '\t')
\r
904 // strField[pos] = ' ';
\r
906 while (CMimeChar::IsSpace((unsigned char)strField[pos+nSpaces]))
\r
908 strField.replace(pos, nSpaces, " ");
\r
912 int CFieldCodeBase::FindSymbol(const char* pszData, int nSize, int& nDelimeter, int& nNonAscChars) const
\r
915 const char* pszDataStart = pszData;
\r
916 const char* pszEnd = pszData + nSize;
\r
918 while (pszData < pszEnd)
\r
920 char ch = *pszData;
\r
921 if (CMimeChar::IsNonAscii((unsigned char)ch))
\r
925 if (ch == (char) nDelimeter)
\r
927 nDelimeter = 0; // stop at any delimeters (space or specials)
\r
931 if (!nDelimeter && CMimeChar::IsDelimiter(ch))
\r
936 nDelimeter = '"'; // quoted-string, delimeter is '"'
\r
939 nDelimeter = ')'; // comment, delimeter is ')'
\r
942 nDelimeter = '>'; // address, delimeter is '>'
\r
951 return (int)(pszData - pszDataStart);
\r