You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1102 lines
32 KiB
1102 lines
32 KiB
// --------------------------------------------------------------------------------
|
|
// rfc1522.cpp
|
|
// Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
|
|
// Steven J. Bailey
|
|
// --------------------------------------------------------------------------------
|
|
#include "pch.hxx"
|
|
#include "internat.h"
|
|
#include "dllmain.h"
|
|
#include "inetconv.h"
|
|
#include "strconst.h"
|
|
#include "variantx.h"
|
|
#include "mimeapi.h"
|
|
#include "demand.h"
|
|
#include "shlwapi.h"
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// ISQPESCAPE(_ch)
|
|
// --------------------------------------------------------------------------------
|
|
#define ISQPESCAPE(_ch) \
|
|
(IS_EXTENDED(_ch) || _ch == '?' || _ch == '=' || \
|
|
_ch == '_' || _ch == '"' || \
|
|
_ch == '<' || _ch == '>' || \
|
|
_ch == '(' || _ch == ')' || \
|
|
_ch == '[' || _ch == ']' || \
|
|
_ch == ',')
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// RFC1522OUT
|
|
// --------------------------------------------------------------------------------
|
|
typedef struct tagRFC1522OUT {
|
|
BOOL fWrite;
|
|
CHAR szBuffer[512];
|
|
ULONG iBuffer;
|
|
LPSTREAM pstm;
|
|
} RFC1522OUT, *LPRFC1522OUT;
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522WriteDone
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522WriteDone(LPRFC1522OUT pOut, LPSTR *ppszRfc1522)
|
|
{
|
|
// We better be writing
|
|
Assert(pOut->fWrite && ppszRfc1522);
|
|
|
|
// Init
|
|
*ppszRfc1522 = NULL;
|
|
|
|
// If we haven't created the stream yet, just use the buffer...
|
|
if (NULL == pOut->pstm)
|
|
{
|
|
// No data
|
|
if (0 == pOut->iBuffer)
|
|
return S_OK;
|
|
|
|
// Allocate
|
|
*ppszRfc1522 = PszAllocA(pOut->iBuffer + 1);
|
|
if (NULL == *ppszRfc1522)
|
|
return TrapError(E_OUTOFMEMORY);
|
|
|
|
// Copy data
|
|
CopyMemory(*ppszRfc1522, pOut->szBuffer, pOut->iBuffer);
|
|
|
|
// Null term
|
|
*((*ppszRfc1522) + pOut->iBuffer) = '\0';
|
|
}
|
|
|
|
// Otherwise, do stream
|
|
else
|
|
{
|
|
// Commit final data to stream...
|
|
if (0 != pOut->iBuffer)
|
|
{
|
|
if (FAILED(pOut->pstm->Write(pOut->szBuffer, pOut->iBuffer, NULL)))
|
|
return TrapError(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Commit the stream
|
|
if (FAILED(pOut->pstm->Commit(STGC_DEFAULT)))
|
|
return TrapError(E_OUTOFMEMORY);
|
|
|
|
// Convert the stream to an ANSI string
|
|
*ppszRfc1522 = PszFromANSIStreamA(pOut->pstm);
|
|
Assert(NULL != *ppszRfc1522);
|
|
|
|
// Release the stream
|
|
SafeRelease(pOut->pstm);
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522Write
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522Write(LPRFC1522OUT pOut, UCHAR ch)
|
|
{
|
|
// If not saving data..
|
|
if (!pOut->fWrite)
|
|
return S_OK;
|
|
|
|
// If buffer + 1 is full, dump to stream
|
|
if (pOut->iBuffer + 1 > sizeof(pOut->szBuffer))
|
|
{
|
|
// Do I have a stream yet...
|
|
if (NULL == pOut->pstm)
|
|
{
|
|
// Create stream
|
|
if (FAILED(MimeOleCreateVirtualStream(&pOut->pstm)))
|
|
return TrapError(E_OUTOFMEMORY);
|
|
}
|
|
|
|
// Write buffer to the stream
|
|
if (FAILED(pOut->pstm->Write(pOut->szBuffer, pOut->iBuffer, NULL)))
|
|
return TrapError(E_OUTOFMEMORY);
|
|
|
|
// Reset buffers
|
|
pOut->iBuffer = 0;
|
|
}
|
|
|
|
// Add character to the buffer
|
|
pOut->szBuffer[pOut->iBuffer++] = ch;
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522WriteStr
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522WriteStr(LPRFC1522OUT pOut, LPSTR psz, LONG cb)
|
|
{
|
|
HRESULT hr;
|
|
while(cb)
|
|
{
|
|
hr = HrRfc1522Write(pOut, *psz);
|
|
if (FAILED(hr))
|
|
return hr;
|
|
psz++;
|
|
cb--;
|
|
}
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
inline BOOL IsRfc1522Token(UCHAR ch)
|
|
{
|
|
// 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
|
|
static UINT abToken[] = {0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0, // 000-031
|
|
1,1,1,1,1,1,1,1,0,0,1,1,0,1,0,0,1,1,1,1,1,1,1,1,1,1,0,0,0,0,0,0, // 032-063
|
|
0,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,0,1,0,1,1, // 064-095
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 096-127
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 128-159
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 160-191
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1, // 192-223
|
|
1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1}; // 224-255
|
|
|
|
Assert(sizeof(abToken)/sizeof(abToken[1])==256);
|
|
Assert(abToken['(']==FALSE);
|
|
Assert(abToken[')']==FALSE);
|
|
Assert(abToken['<']==FALSE);
|
|
Assert(abToken['>']==FALSE);
|
|
Assert(abToken['@']==FALSE);
|
|
Assert(abToken[',']==FALSE);
|
|
Assert(abToken[';']==FALSE);
|
|
Assert(abToken[':']==FALSE);
|
|
Assert(abToken['/']==FALSE);
|
|
Assert(abToken['[']==FALSE);
|
|
Assert(abToken[']']==FALSE);
|
|
Assert(abToken['?']==FALSE);
|
|
Assert(abToken['.']==FALSE);
|
|
Assert(abToken['=']==FALSE);
|
|
|
|
return (BOOL) abToken[ch];
|
|
}
|
|
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// PszRfc1522Find
|
|
//
|
|
// Find an RFC1522 word. If the string pointer passed in is NULL, then NULL is
|
|
// returned. If a word is not found, then a pointer to the terminatng NULL is
|
|
// returned. If a word is found, then a pointer to the word is returned, and
|
|
// an output parameter for whether the word was preceeded by non-blank characters
|
|
// is set.
|
|
//
|
|
// If a word is not found, then the output parameter is undefined.
|
|
//
|
|
// The output parameter is optional - it may be NULL, in which case the value
|
|
// will not be stored.
|
|
//
|
|
// --------------------------------------------------------------------------------
|
|
LPSTR PszRfc1522Find(LPSTR psz,
|
|
BOOL *pbNonBlankLeading)
|
|
{
|
|
LPSTR pszCharset;
|
|
|
|
if (!psz || !*psz)
|
|
{
|
|
goto exit;
|
|
}
|
|
|
|
if (pbNonBlankLeading)
|
|
{
|
|
*pbNonBlankLeading = FALSE;
|
|
}
|
|
|
|
// Skip over any leading blanks.
|
|
while (*psz && (*psz == ' ' || *psz == '\t' || *psz == '\r' || *psz == '\n'))
|
|
{
|
|
psz++;
|
|
}
|
|
|
|
if (*psz && (psz[0] != '=' || psz[1] != '?'))
|
|
{
|
|
again:
|
|
// If we end up here (either through the if above or by a goto from below),
|
|
// it means that we have some number of non-blank characters before the
|
|
// RFC1522 word.
|
|
if (pbNonBlankLeading)
|
|
{
|
|
*pbNonBlankLeading = TRUE;
|
|
}
|
|
}
|
|
// Skip until we find an =?.
|
|
while (*psz && (psz[0] != '=' || psz[1] != '?'))
|
|
{
|
|
psz++;
|
|
}
|
|
if (!*psz)
|
|
{
|
|
// End of the string.
|
|
goto exit;
|
|
}
|
|
Assert(psz[0] == '=' && psz[1] == '?');
|
|
|
|
// Parse out the charset.
|
|
pszCharset = psz;
|
|
psz += 2;
|
|
while (IsRfc1522Token(*psz))
|
|
{
|
|
psz++;
|
|
}
|
|
if (!*psz)
|
|
{
|
|
// End of the string.
|
|
goto exit;
|
|
}
|
|
if (*psz != '?')
|
|
{
|
|
// Malformed.
|
|
goto again;
|
|
}
|
|
Assert(*psz == '?');
|
|
|
|
// Parse out the encoding.
|
|
psz++;
|
|
while (IsRfc1522Token(*psz))
|
|
{
|
|
psz++;
|
|
}
|
|
if (!*psz)
|
|
{
|
|
// End of the string.
|
|
goto exit;
|
|
}
|
|
if (*psz != '?')
|
|
{
|
|
// Malformed.
|
|
goto again;
|
|
}
|
|
Assert(*psz == '?');
|
|
|
|
// Parse out the data.
|
|
psz++;
|
|
while (*psz && (psz[0] != '?' || psz[1] != '='))
|
|
{
|
|
psz++;
|
|
}
|
|
if (!*psz)
|
|
{
|
|
// End of the string.
|
|
goto exit;
|
|
}
|
|
Assert(psz[0] == '?' && psz[1] == '=');
|
|
|
|
psz = pszCharset;
|
|
|
|
exit:
|
|
return psz;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// PszRfc1522Decode - *(*ppsz) -> '?'
|
|
// --------------------------------------------------------------------------------
|
|
LPSTR PszRfc1522Decode(LPSTR psz, CHAR chEncoding, LPRFC1522OUT pOut)
|
|
{
|
|
// Check params
|
|
Assert(pOut && psz && *psz == '?');
|
|
Assert(chEncoding == 'B' || chEncoding == 'b' || chEncoding == 'Q' || chEncoding == 'q');
|
|
|
|
// Step over '?'
|
|
psz++;
|
|
|
|
// Done...
|
|
if ('\0' == *psz)
|
|
return psz;
|
|
|
|
// Q encoding
|
|
if ('Q' == chEncoding || 'q' == chEncoding)
|
|
{
|
|
// Locals
|
|
UCHAR uch;
|
|
CHAR ch,
|
|
ch1,
|
|
ch2;
|
|
LPSTR pszMark;
|
|
|
|
// While we have characters and '?=' is not found...
|
|
while(*psz && (psz[0] != '?' || psz[1] != '='))
|
|
{
|
|
// Get next char
|
|
uch = *psz++;
|
|
|
|
// Encoded hex pair i.e. '=1d'
|
|
if (uch == '=')
|
|
{
|
|
// Mark position
|
|
pszMark = psz;
|
|
|
|
// Hex char 1 - If no null...
|
|
if (*psz != '\0') ch1 = ChConvertFromHex (*psz++);
|
|
else ch1 = (char)255;
|
|
|
|
// Hex char 2 - if no null
|
|
if (*psz != '\0') ch2 = ChConvertFromHex (*psz++);
|
|
else ch2 = (char)255;
|
|
|
|
//////////////////////////////////////////////////////////////////
|
|
// raid x5-69640 - Incomplete QP encoded letter causes ÿ
|
|
// This is a sign-extension bug - we need to compare against
|
|
// (char)255 instead of 255...
|
|
//
|
|
// If both are = 255, its an equal sign
|
|
if (ch1 == (char)255 || ch2 == (char)255)
|
|
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, '=')))
|
|
return NULL;
|
|
psz = pszMark;
|
|
}
|
|
|
|
// Otherwise, build character
|
|
else
|
|
{
|
|
ch = ( ch1 << 4 ) | ch2;
|
|
if (FAILED(HrRfc1522Write(pOut, ch)))
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
// _ equals space...
|
|
else if( uch == '_' )
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, ' ')))
|
|
return NULL;
|
|
}
|
|
|
|
// Otherwise, just append the character
|
|
else
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, uch)))
|
|
return NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
// B encoding
|
|
else
|
|
{
|
|
// Locals
|
|
ULONG cbIn=0,
|
|
cbPad=0;
|
|
UCHAR ucIn[4],
|
|
uch;
|
|
|
|
// While we have characters and '?=' is not found...
|
|
while(*psz && (psz[0] != '?' || psz[1] != '='))
|
|
{
|
|
// Gets 4 legal Base64 characters, ignores if illegal
|
|
uch = *psz++;
|
|
|
|
// Decode base64 character
|
|
ucIn[cbIn] = DECODE64(uch) ;
|
|
|
|
// Inc count
|
|
if (ucIn[cbIn] < 64 || (uch == '=' && cbIn > 1))
|
|
cbIn++;
|
|
|
|
// Pad it
|
|
if (uch == '=' && cbIn > 1)
|
|
cbPad++;
|
|
|
|
// Outputs when 4 legal Base64 characters are in the buffer
|
|
if (cbIn == 4)
|
|
{
|
|
if (cbPad < 3)
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, (ucIn[0] << 2) | (ucIn[1] >> 4))))
|
|
return NULL;
|
|
}
|
|
if (cbPad < 2)
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, (ucIn[1] << 4) | (ucIn[2] >> 2))))
|
|
return NULL;
|
|
}
|
|
if (cbPad < 1)
|
|
{
|
|
if (FAILED(HrRfc1522Write(pOut, (ucIn[2] << 6) | (ucIn[3]))))
|
|
return NULL;
|
|
}
|
|
cbIn = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Finish stepping
|
|
if ('?' == *psz)
|
|
psz++;
|
|
if ('=' == *psz)
|
|
psz++;
|
|
|
|
// Done
|
|
return psz;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// PszRfc1522GetEncoding - *(*ppsz) -> '?'
|
|
//
|
|
// Returns NULL if '?X?' is not found... or B b Q q is not found
|
|
// --------------------------------------------------------------------------------
|
|
LPSTR PszRfc1522GetEncoding(LPSTR psz, CHAR *pchEncoding)
|
|
{
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Should be pointing to '='
|
|
if ('?' != *psz)
|
|
return NULL;
|
|
|
|
// Next character
|
|
psz++;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Save encoding...
|
|
*pchEncoding = *psz;
|
|
|
|
// Step over encoding character
|
|
psz++;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Should be pointing to '='
|
|
if ('?' != *psz)
|
|
return NULL;
|
|
|
|
// Invalid encoding
|
|
if ('B' != *pchEncoding && 'b' != *pchEncoding && 'Q' != *pchEncoding && 'q' != *pchEncoding)
|
|
return NULL;
|
|
|
|
// Continue
|
|
return psz;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// PszRfc1522GetCset - *(*ppsz) -> '='
|
|
//
|
|
// Returns NULL if '=?CHARSET?' is not found
|
|
// --------------------------------------------------------------------------------
|
|
LPSTR PszRfc1522GetCset(LPSTR psz, LPSTR pszCharset, ULONG cchmax)
|
|
{
|
|
// Locals
|
|
LPSTR pszStart, pszEnd;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Should be pointing to '='
|
|
if ('=' != *psz)
|
|
return NULL;
|
|
|
|
// Next character
|
|
psz++;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Should be pointing to '?'
|
|
if ('?' != *psz)
|
|
return NULL;
|
|
|
|
// Step over '?'
|
|
psz++;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Save Start
|
|
pszStart = psz;
|
|
|
|
// Seek to next '?'
|
|
while(*psz && *psz != '?')
|
|
psz++;
|
|
|
|
// Done
|
|
if ('\0' == *psz)
|
|
return NULL;
|
|
|
|
// Save end
|
|
pszEnd = psz;
|
|
Assert(*pszEnd == '?');
|
|
|
|
// Charset name is too large...
|
|
if ((ULONG)(pszEnd - pszStart) > cchmax)
|
|
return NULL;
|
|
|
|
// Copy charset
|
|
*pszEnd = '\0';
|
|
StrCpyNA(pszCharset, pszStart, cchmax);
|
|
*pszEnd = '?';
|
|
|
|
// Continue
|
|
return psz;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522EncodeBase64
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522EncodeBase64(UCHAR *pb, ULONG cb, LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
BYTE rgbEncoded[1024];
|
|
ULONG cbEncoded=0;
|
|
ULONG i;
|
|
UCHAR uch[3];
|
|
|
|
// Encodes 3 characters at a time
|
|
for (i=0; i<cb; i+=3)
|
|
{
|
|
// Setup Buffer
|
|
uch[0] = pb[i];
|
|
uch[1] = (i + 1 < cb) ? pb[i + 1] : '\0';
|
|
uch[2] = (i + 2 < cb) ? pb[i + 2] : '\0';
|
|
|
|
// Encode first tow
|
|
rgbEncoded[cbEncoded++] = g_rgchEncodeBase64[(uch[0] >> 2) & 0x3F];
|
|
rgbEncoded[cbEncoded++] = g_rgchEncodeBase64[(uch[0] << 4 | uch[1] >> 4) & 0x3F];
|
|
|
|
// Encode Next
|
|
if (i + 1 < cb)
|
|
rgbEncoded[cbEncoded++] = g_rgchEncodeBase64[(uch[1] << 2 | uch[2] >> 6) & 0x3F];
|
|
else
|
|
rgbEncoded[cbEncoded++] = '=';
|
|
|
|
// Encode Net
|
|
if (i + 2 < cb)
|
|
rgbEncoded[cbEncoded++] = g_rgchEncodeBase64[(uch[2]) & 0x3F];
|
|
else
|
|
rgbEncoded[cbEncoded++] = '=';
|
|
}
|
|
|
|
// Write rgbEncoded
|
|
CHECKHR(hr = pStream->Write(rgbEncoded, cbEncoded, NULL));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522EncodeQP
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522EncodeQP(UCHAR *pb, ULONG cb, LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
BYTE rgbEncoded[1024];
|
|
ULONG cbEncoded=0;
|
|
ULONG i;
|
|
|
|
// Loop through buffer
|
|
for (i=0; i<cb; i++)
|
|
{
|
|
// Replace spaces with underscore - this is a more portable character
|
|
if (pb[i] == ' ')
|
|
rgbEncoded[cbEncoded++] = '_';
|
|
|
|
// Otherwise, if this is an escapeable character
|
|
else if (ISQPESCAPE(pb[i]))
|
|
{
|
|
// Write equal sign (start of an encodedn QP character
|
|
rgbEncoded[cbEncoded++] = '=';
|
|
rgbEncoded[cbEncoded++] = g_rgchHex[pb[i] >> 4];
|
|
rgbEncoded[cbEncoded++] = g_rgchHex[pb[i] & 0x0F];
|
|
}
|
|
|
|
// Otherwise, just write the char as is
|
|
else
|
|
rgbEncoded[cbEncoded++] = pb[i];
|
|
}
|
|
|
|
// Write rgbEncoded
|
|
CHECKHR(hr = pStream->Write(rgbEncoded, cbEncoded, NULL));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// FContainsExtended
|
|
// --------------------------------------------------------------------------------
|
|
BOOL FContainsExtended(LPPROPSTRINGA pStringA, ULONG *pcExtended)
|
|
{
|
|
// Invalid Arg
|
|
Assert(ISVALIDSTRINGA(pStringA) && pcExtended);
|
|
|
|
// Init
|
|
*pcExtended = 0;
|
|
|
|
// Look for an extended character
|
|
for (ULONG cch=0; cch<pStringA->cchVal; cch++)
|
|
{
|
|
// Is this an extended char
|
|
if (IS_EXTENDED(pStringA->pszVal[cch]))
|
|
{
|
|
// Count
|
|
(*pcExtended)++;
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return ((*pcExtended) > 0) ? TRUE : FALSE;
|
|
}
|
|
|
|
#define IS_EXTENDED_W(wch) \
|
|
((wch > 126 || wch < 32) && wch != L'\t' && wch != L'\n' && wch != L'\r')
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// FContainsExtendedW
|
|
// --------------------------------------------------------------------------------
|
|
BOOL FContainsExtendedW(LPPROPSTRINGW pStringW, ULONG *pcExtended)
|
|
{
|
|
// Invalid Arg
|
|
Assert(ISVALIDSTRINGW(pStringW) && pcExtended);
|
|
|
|
// Init
|
|
*pcExtended = 0;
|
|
|
|
// Look for an extended character
|
|
for (ULONG cch=0; cch<pStringW->cchVal; cch++)
|
|
{
|
|
// Is this an extended char
|
|
if (IS_EXTENDED_W(pStringW->pszVal[cch]))
|
|
{
|
|
// Count
|
|
(*pcExtended)++;
|
|
}
|
|
}
|
|
|
|
// Done
|
|
return ((*pcExtended) > 0) ? TRUE : FALSE;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522Encode
|
|
// --------------------------------------------------------------------------------
|
|
MIMEOLEAPI MimeOleRfc1522Encode(
|
|
/* in */ LPCSTR pszValue,
|
|
/* in */ HCHARSET hCharset,
|
|
/* out */ LPSTR *ppszEncoded)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPINETCSETINFO pCharset;
|
|
MIMEVARIANT rSource;
|
|
MIMEVARIANT rDest;
|
|
CODEPAGEID cpiSource;
|
|
CODEPAGEID cpiDest;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pszValue || NULL == hCharset || NULL == ppszEncoded)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init rDest
|
|
ZeroMemory(&rDest, sizeof(MIMEVARIANT));
|
|
|
|
// Setup rSource
|
|
rSource.type = MVT_STRINGA;
|
|
rSource.rStringA.pszVal = (LPSTR)pszValue;
|
|
rSource.rStringA.cchVal = lstrlen(pszValue);
|
|
|
|
// Open the Character Set
|
|
CHECKHR(hr = g_pInternat->HrOpenCharset(hCharset, &pCharset));
|
|
|
|
// Setup rDest
|
|
rDest.type = MVT_STRINGA;
|
|
|
|
// Convert the String
|
|
CHECKHR(hr = g_pInternat->HrConvertString(pCharset->cpiWindows, pCharset->cpiInternet, &rSource, &rDest));
|
|
|
|
// Setup Source and Dest Charset
|
|
cpiSource = pCharset->cpiWindows;
|
|
cpiDest = pCharset->cpiInternet;
|
|
|
|
// Adjust the Codepages
|
|
CHECKHR(hr = g_pInternat->HrValidateCodepages(&rSource, &rDest, NULL, NULL, &cpiSource, &cpiDest));
|
|
|
|
// 1522 Encode this dude
|
|
CHECKHR(hr = HrRfc1522Encode(&rSource, &rDest, cpiSource, cpiDest, pCharset->szName, ppszEncoded));
|
|
|
|
exit:
|
|
// Cleanup
|
|
MimeVariantFree(&rDest);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// HrRfc1522Encode
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT HrRfc1522Encode(LPMIMEVARIANT pSource, LPMIMEVARIANT pDest, CODEPAGEID cpiSource,
|
|
CODEPAGEID cpiDest, LPCSTR pszCharset, LPSTR *ppszEncoded)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
CByteStream cStream;
|
|
CHAR chEncoding;
|
|
ULONG cExtended=0;
|
|
LPBYTE pb;
|
|
ULONG cb;
|
|
ULONG i=0;
|
|
ULONG cbFirstTry;
|
|
ULONG cbRead;
|
|
BLOB rBlobSource;
|
|
BLOB rBlobCset;
|
|
ULONG cTrys;
|
|
CHAR szEncoding[1];
|
|
ULONG iBefore;
|
|
ULONG iAfter;
|
|
ULONG cbExtra;
|
|
ULARGE_INTEGER uli;
|
|
LARGE_INTEGER li;
|
|
//IStream *pStream=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pSource && pDest && pszCharset && ppszEncoded);
|
|
Assert(MVT_STRINGW == pSource->type ? CP_UNICODE == cpiSource : CP_UNICODE != cpiSource);
|
|
Assert(cpiDest != CP_UNICODE && MVT_STRINGA == pDest->type);
|
|
|
|
// Init
|
|
*ppszEncoded = NULL;
|
|
uli.HighPart = 0;
|
|
li.HighPart = 0;
|
|
rBlobCset.pBlobData = NULL;
|
|
|
|
// Raid-50014: Will will always rfc1522 encode utf encodings
|
|
// Raid-50788: Cannot post UTF news messages
|
|
if (MVT_STRINGW != pSource->type)
|
|
{
|
|
// If it does not contain 8bit, then no rfc1522 encoding is needed
|
|
if (FALSE == FContainsExtended(&pSource->rStringA, &cExtended))
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// We should be converting to UTF...
|
|
else
|
|
{
|
|
// Must be encoding into utf
|
|
Assert(65000 == cpiDest || 65001 == cpiDest);
|
|
|
|
// If it does not contain 8bit, then no rfc1522 encoding is needed
|
|
if (FALSE == FContainsExtendedW(&pSource->rStringW, &cExtended))
|
|
{
|
|
hr = E_FAIL;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Create a Stream
|
|
// CHECKHR(hr = CreateStreamOnHGlobal(NULL, TRUE, &pStream));
|
|
|
|
// Compute Encoding...
|
|
chEncoding = (((cExtended * 100) / pSource->rStringA.cchVal) >= 17) ? 'B' : 'Q';
|
|
|
|
// RAID-21673: If DBCS source codepage, then, encode the entire string so that I don't fragment the character set encoding.
|
|
if (IsDBCSCodePage(cpiSource))
|
|
chEncoding = 'B';
|
|
|
|
// Set szEncoding
|
|
szEncoding[0] = chEncoding;
|
|
|
|
// Setup Encoding Loop
|
|
pb = (MVT_STRINGW == pSource->type) ? (LPBYTE)pSource->rStringW.pszVal : (LPBYTE)pSource->rStringA.pszVal;
|
|
cb = (MVT_STRINGW == pSource->type) ? (pSource->rStringW.cchVal * sizeof(WCHAR)) : pSource->rStringA.cchVal;
|
|
|
|
// Adjust pszCharset
|
|
if (CP_JAUTODETECT == cpiDest || 50222 == cpiDest || 50221 == cpiDest)
|
|
pszCharset = (LPSTR)c_szISO2022JP;
|
|
|
|
// Compute cbExtra - =?cchCharset?1?<cbFirstTry>?=
|
|
cbExtra = 2 + lstrlen(pszCharset) + 3 + 2;
|
|
|
|
// Compute cbFirstTry
|
|
cbFirstTry = 76 - cbExtra;
|
|
|
|
// cbFirstTry needs to be even if we are encoding unicode
|
|
cbFirstTry -= (cbFirstTry % 2);
|
|
|
|
// Adjust cbFirstTry if its greater than cb
|
|
cbFirstTry = min(cb, cbFirstTry);
|
|
|
|
// Make sure cbFirstTry is even ... That's a good starting point,
|
|
// but we need to make sure that we're not breaking on a leading byte
|
|
// On Korean (and other DBCS locales) we can have a mix of
|
|
// SBCs and MBCs, so just being even isn't enough
|
|
if(pb && (MVT_STRINGW != pSource->type))
|
|
{
|
|
LPCSTR pszEnd = (LPCSTR)&pb[cbFirstTry],
|
|
pszNewEnd = NULL;
|
|
|
|
pszNewEnd = CharPrevExA((WORD)cpiSource, (LPCSTR)pb, pszEnd, 0);
|
|
if(pszNewEnd && (pszNewEnd == (pszEnd - 1)) && IsDBCSLeadByteEx(cpiSource, *pszNewEnd))
|
|
cbFirstTry-= (ULONG)(pszEnd - pszNewEnd);
|
|
}
|
|
|
|
// Loop until we have encoded the entire string
|
|
while (i < cb)
|
|
{
|
|
// Write Prefix
|
|
CHECKHR(hr = cStream.Write("=?", 2, NULL));
|
|
CHECKHR(hr = cStream.Write(pszCharset, lstrlen(pszCharset), NULL));
|
|
CHECKHR(hr = cStream.Write("?", 1, NULL));
|
|
CHECKHR(hr = cStream.Write(szEncoding, 1, NULL));
|
|
CHECKHR(hr = cStream.Write("?", 1, NULL));
|
|
|
|
// Compute Try Amount
|
|
rBlobSource.cbSize = min(cb - i, cbFirstTry);
|
|
rBlobSource.pBlobData = (LPBYTE)(pb + i);
|
|
|
|
// Get Index
|
|
CHECKHR(hr = HrGetStreamPos(&cStream, &iBefore));
|
|
|
|
// Encoded blocks until we get one at a good length
|
|
for (cTrys=0;;cTrys++)
|
|
{
|
|
// Too many Trys ?
|
|
if (cTrys > 100)
|
|
{
|
|
AssertSz(FALSE, "Too many rfc1522 encode buffer reduction attemps, failing (No rfc1522 encoding will be applied).");
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Memory Leak
|
|
Assert(NULL == rBlobCset.pBlobData);
|
|
|
|
// Convert Block
|
|
CHECKHR(hr = g_pInternat->ConvertBuffer(cpiSource, cpiDest, &rBlobSource, &rBlobCset, &cbRead));
|
|
|
|
// Problem
|
|
if (cbRead == 0)
|
|
{
|
|
AssertSz(FALSE, "Bad buffer conversion");
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Validate
|
|
Assert(cbRead <= rBlobSource.cbSize);
|
|
|
|
// 'B' Encoding
|
|
if ('B' == chEncoding)
|
|
{
|
|
// ApplyBase64
|
|
CHECKHR(hr = HrRfc1522EncodeBase64(rBlobCset.pBlobData, rBlobCset.cbSize, &cStream));
|
|
}
|
|
else
|
|
{
|
|
// ApplyQP
|
|
CHECKHR(hr = HrRfc1522EncodeQP(rBlobCset.pBlobData, rBlobCset.cbSize, &cStream));
|
|
}
|
|
|
|
// Get Index
|
|
CHECKHR(hr = HrGetStreamPos(&cStream, &iAfter));
|
|
|
|
// Validate
|
|
Assert(iAfter > iBefore);
|
|
|
|
// Too big ?
|
|
if ((iAfter - iBefore) + cbExtra <= 76)
|
|
break;
|
|
|
|
// Problem
|
|
if (rBlobSource.cbSize <= 5)
|
|
{
|
|
Assert(FALSE);
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Cleanup
|
|
SafeMemFree(rBlobCset.pBlobData);
|
|
|
|
// Seek Back to iBefore
|
|
uli.LowPart = iBefore;
|
|
cStream.SetSize(uli);
|
|
|
|
// Seek Backwards
|
|
li.LowPart = iBefore;
|
|
cStream.Seek(li, STREAM_SEEK_SET, NULL);
|
|
|
|
// Compute Inflation Rate
|
|
if (0 == cTrys)
|
|
rBlobSource.cbSize = (((76 - cbExtra) * rBlobSource.cbSize) / (iAfter - iBefore));
|
|
|
|
// Otherwise, start taking off 5 bytes
|
|
else
|
|
rBlobSource.cbSize -= 5;
|
|
|
|
// Make sure it is even ... That's a good starting point,
|
|
// but we need to make sure that we're not breaking on a leading byte
|
|
// On Korean (and other DBCS locales) we can have a mix of
|
|
// SBCs and MBCs, so just being even isn't enough
|
|
rBlobSource.cbSize -= (rBlobSource.cbSize % 2);
|
|
if(rBlobSource.pBlobData && (MVT_STRINGW != pSource->type))
|
|
{
|
|
LPCSTR pszEnd = (LPCSTR)&rBlobSource.pBlobData[rBlobSource.cbSize],
|
|
pszNewEnd = NULL;
|
|
|
|
pszNewEnd = CharPrevExA((WORD)cpiSource, (LPCSTR)rBlobSource.pBlobData, pszEnd, 0);
|
|
if(pszNewEnd && (pszNewEnd == (pszEnd - 1)) && IsDBCSLeadByteEx(cpiSource, *pszNewEnd))
|
|
rBlobSource.cbSize-= (ULONG)(pszEnd - pszNewEnd);
|
|
}
|
|
|
|
// Should be less than cb
|
|
Assert(rBlobSource.cbSize < cb);
|
|
}
|
|
|
|
// Write termination
|
|
CHECKHR(hr = cStream.Write("?=", 2, NULL));
|
|
|
|
// Increment i
|
|
i += cbRead;
|
|
|
|
// Cleanup
|
|
SafeMemFree(rBlobCset.pBlobData);
|
|
|
|
// Write folding
|
|
if (i < cb)
|
|
{
|
|
// Write Fold
|
|
CHECKHR(hr = cStream.Write(c_szCRLFTab, lstrlen(c_szCRLFTab), NULL));
|
|
}
|
|
}
|
|
|
|
// Return the encoded string
|
|
CHECKHR(hr = cStream.HrAcquireStringA(&cb, ppszEncoded, ACQ_DISPLACE));
|
|
//cStream.Commit(STGC_DEFAULT);
|
|
//CHECKALLOC(*ppszEncoded = PszFromANSIStreamA(pStream));
|
|
|
|
exit:
|
|
// Cleanup
|
|
//ReleaseObj(pStream);
|
|
g_pMalloc->Free(rBlobCset.pBlobData);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// MimeOleRfc1522Decode
|
|
// --------------------------------------------------------------------------------
|
|
MIMEOLEAPI MimeOleRfc1522Decode(LPCSTR pszValue, LPSTR pszCharset, ULONG cchmax, LPSTR *ppszDecoded)
|
|
{
|
|
// Locals
|
|
HRESULT hrEncoded=E_FAIL,
|
|
hrCsetFound=E_FAIL;
|
|
RFC1522OUT rOut;
|
|
LPSTR psz=(LPSTR)pszValue,
|
|
pszNew;
|
|
CHAR szCset[CCHMAX_CSET_NAME],
|
|
chEncoding;
|
|
BOOL bNonBlankLeading;
|
|
|
|
// check params
|
|
if (NULL == pszValue)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init out structure
|
|
ZeroMemory(&rOut, sizeof(RFC1522OUT));
|
|
|
|
// Save data..
|
|
if (ppszDecoded)
|
|
rOut.fWrite = TRUE;
|
|
|
|
// Start decoding loop...
|
|
while(psz && *psz)
|
|
{
|
|
// Seek to start of 1522 encoding...
|
|
pszNew = PszRfc1522Find(psz, &bNonBlankLeading);
|
|
Assert(pszNew!=NULL);
|
|
|
|
if (bNonBlankLeading || psz == pszValue || !*psz)
|
|
{
|
|
// Either we found non-blank characters before the word,
|
|
// or this is the first word on the line, or we didn't
|
|
// find a word. Whatever, we need to write all of the
|
|
// data before the word.
|
|
if (FAILED(HrRfc1522WriteStr(&rOut, psz, (LONG) (pszNew-psz))))
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
// If didn't find start.. were done
|
|
if (!*pszNew)
|
|
break;
|
|
|
|
// Set psz to new position
|
|
psz = pszNew;
|
|
|
|
// Get charset
|
|
pszNew = PszRfc1522GetCset(psz, szCset, ARRAYSIZE(szCset));
|
|
|
|
// If didn't parse charset correctly, continue
|
|
if (NULL == pszNew)
|
|
{
|
|
psz++;
|
|
continue;
|
|
}
|
|
|
|
// Character set was found
|
|
hrCsetFound = S_OK;
|
|
|
|
// Was caller just looking for the charset...
|
|
if (NULL == ppszDecoded)
|
|
break;
|
|
|
|
// Otherwise, parse encoding
|
|
pszNew = PszRfc1522GetEncoding(pszNew, &chEncoding);
|
|
|
|
// If didn't parse charset correctly, continue
|
|
if (NULL == pszNew)
|
|
{
|
|
psz++;
|
|
continue;
|
|
}
|
|
|
|
// Decode the text to the end - THIS SHOULD NEVER FAIL...
|
|
psz = PszRfc1522Decode(pszNew, chEncoding, &rOut);
|
|
|
|
// It is a valid encoded string
|
|
if (psz)
|
|
hrEncoded = S_OK;
|
|
}
|
|
|
|
// Were we actually decoding...
|
|
if (ppszDecoded && hrEncoded == S_OK)
|
|
{
|
|
// Commit the stream
|
|
if (FAILED(HrRfc1522WriteDone(&rOut, ppszDecoded)))
|
|
{
|
|
*ppszDecoded = NULL;
|
|
hrEncoded = S_FALSE;
|
|
}
|
|
}
|
|
|
|
// Otherwise, return charset...
|
|
if (pszCharset && hrCsetFound == S_OK)
|
|
StrCpyN(pszCharset, szCset, cchmax);
|
|
|
|
// Cleanup
|
|
SafeRelease(rOut.pstm);
|
|
|
|
// Done
|
|
return ppszDecoded ? hrEncoded : hrCsetFound;
|
|
}
|