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.
10472 lines
310 KiB
10472 lines
310 KiB
// --------------------------------------------------------------------------------
|
|
// BookTree.cpp
|
|
// Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved
|
|
// Steven J. Bailey
|
|
// --------------------------------------------------------------------------------
|
|
#include "pch.hxx"
|
|
#include <stddef.h>
|
|
#include "dllmain.h"
|
|
#include "booktree.h"
|
|
#include "stmlock.h"
|
|
#include "ibdylock.h"
|
|
#include "resource.h"
|
|
#include "vstream.h"
|
|
#include "ixputil.h"
|
|
#include "olealloc.h"
|
|
#include "smime.h"
|
|
#include "objheap.h"
|
|
#include "internat.h"
|
|
#include "icoint.h"
|
|
#include "ibdystm.h"
|
|
#include "symcache.h"
|
|
#include "urlmon.h"
|
|
#include "mhtmlurl.h"
|
|
#include "shlwapi.h"
|
|
#include "shlwapip.h"
|
|
#include "inetstm.h"
|
|
#include "imnxport.h"
|
|
#include "bookbody.h"
|
|
#include "mimeapi.h"
|
|
#include "strconst.h"
|
|
#include "bindstm.h"
|
|
#include "enriched.h"
|
|
#include "webpage.h"
|
|
#include "demand.h"
|
|
|
|
//#define TRACEPARSE 1
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// _IsMultiPart
|
|
// --------------------------------------------------------------------------------
|
|
inline BOOL _IsMultiPart(LPTREENODEINFO pNode)
|
|
{
|
|
return pNode->pContainer->IsContentType(STR_CNT_MULTIPART, NULL) == S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// BINDASSERTARGS
|
|
// --------------------------------------------------------------------------------
|
|
#define BINDASSERTARGS(_bindstate, _fBoundary) \
|
|
Assert(m_pBindNode && m_pBindNode->pBody && m_pBindNode->pContainer && _bindstate == m_pBindNode->bindstate && (FALSE == _fBoundary || ISVALIDSTRINGA(&m_pBindNode->rBoundary)))
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Array of Bind Parsing States to Functions
|
|
// --------------------------------------------------------------------------------
|
|
const PFNBINDPARSER CMessageTree::m_rgBindStates[BINDSTATE_LAST] = {
|
|
NULL, // BINDSTATE_COMPLETE
|
|
(PFNBINDPARSER)CMessageTree::_HrBindParsingHeader, // BINDSTATE_PARSING_HEADER
|
|
(PFNBINDPARSER)CMessageTree::_HrBindFindingMimeFirst, // BINDSTATE_FINDING_MIMEFIRST
|
|
(PFNBINDPARSER)CMessageTree::_HrBindFindingMimeNext, // BINDSTATE_FINDING_MIMENEXT
|
|
(PFNBINDPARSER)CMessageTree::_HrBindFindingUuencodeBegin, // BINDSTATE_FINDING_UUBEGIN
|
|
(PFNBINDPARSER)CMessageTree::_HrBindFindingUuencodeEnd, // BINDSTATE_FINDING_UUEND
|
|
(PFNBINDPARSER)CMessageTree::_HrBindRfc1154, // BINDSTATE_PARSING_RFC1154
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Used in IMimeMessageTree::ToMultipart
|
|
// --------------------------------------------------------------------------------
|
|
static LPCSTR g_rgszToMultipart[] = {
|
|
PIDTOSTR(PID_HDR_CNTTYPE),
|
|
PIDTOSTR(PID_HDR_CNTDESC),
|
|
PIDTOSTR(PID_HDR_CNTDISP),
|
|
PIDTOSTR(PID_HDR_CNTXFER),
|
|
PIDTOSTR(PID_HDR_CNTID),
|
|
PIDTOSTR(PID_HDR_CNTBASE),
|
|
PIDTOSTR(PID_HDR_CNTLOC)
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Used in IMimeMessage::AttachObject IID_IMimeBody
|
|
// --------------------------------------------------------------------------------
|
|
static LPCSTR g_rgszAttachBody[] = {
|
|
PIDTOSTR(PID_HDR_CNTTYPE),
|
|
PIDTOSTR(PID_HDR_CNTDESC),
|
|
PIDTOSTR(PID_HDR_CNTDISP),
|
|
PIDTOSTR(PID_HDR_CNTXFER),
|
|
PIDTOSTR(PID_HDR_CNTID),
|
|
PIDTOSTR(PID_HDR_CNTBASE),
|
|
PIDTOSTR(PID_HDR_CNTLOC)
|
|
};
|
|
|
|
static const WEBPAGEOPTIONS g_rDefWebPageOpt = {
|
|
sizeof(WEBPAGEOPTIONS), // cbsize
|
|
WPF_NOMETACHARSET | WPF_HTML | WPF_AUTOINLINE, // dwFlags
|
|
3000, // dwDelay
|
|
NULL // wchQuote
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Default Tree Options
|
|
// --------------------------------------------------------------------------------
|
|
static const TREEOPTIONS g_rDefTreeOptions = {
|
|
DEF_CLEANUP_TREE_ON_SAVE, // OID_CLEANUP_TREE_ON_SAVE
|
|
DEF_HIDE_TNEF_ATTACHMENTS, // OID_HIDE_TNEF_ATTACHMENTS
|
|
DEF_ALLOW_8BIT_HEADER, // OID_ALLOW_8BIT_HEADER
|
|
DEF_GENERATE_MESSAGE_ID, // OID_GENERATE_MESSAGE_ID
|
|
DEF_WRAP_BODY_TEXT, // OID_WRAP_BODY_TEXT
|
|
DEF_CBMAX_HEADER_LINE, // OID_CBMAX_HEADER_LINE
|
|
DEF_CBMAX_BODY_LINE, // OID_CBMAX_BODY_LINE
|
|
SAVE_RFC1521, // OID_SAVE_FORMAT
|
|
NULL, // hCharset
|
|
CSET_APPLY_UNTAGGED, // csetapply
|
|
DEF_TRANSMIT_TEXT_ENCODING, // OID_TRANSMIT_TEXT_ENCODING
|
|
DEF_XMIT_PLAIN_TEXT_ENCODING, // OID_XMIT_PLAIN_TEXT_ENCODING
|
|
DEF_XMIT_HTML_TEXT_ENCODING, // OID_XMIT_HTML_TEXT_ENCODING
|
|
0, // OID_SECURITY_ENCODE_FLAGS
|
|
DEF_HEADER_RELOAD_TYPE_TREE, // OID_HEADER_REALOD_TYPE
|
|
DEF_CAN_INLINE_TEXT_BODIES, // OID_CAN_INLINE_TEXT_BODIES
|
|
DEF_SHOW_MACBINARY, // OID_SHOW_MACBINARY
|
|
DEF_SAVEBODY_KEEPBOUNDARY, // OID_SAVEBODY_KEEPBOUNDARY
|
|
FALSE, // OID_LOAD_USE_BIND_FILE
|
|
DEF_HANDSOFF_ONSAVE, // OID_HANDSOFF_ONSAVE
|
|
DEF_SUPPORT_EXTERNAL_BODY, // OID_SUPPORT_EXTERNAL_BODY
|
|
DEF_DECODE_RFC1154 // OID_DECODE_RFC1154
|
|
};
|
|
|
|
extern BOOL FIsMsasn1Loaded();
|
|
|
|
#ifdef DEBUG
|
|
// --------------------------------------------------------------------------------
|
|
// These booleans determine if the tree is dumped to the output window
|
|
// --------------------------------------------------------------------------------
|
|
static BOOL s_fWriteMessageDump = 0;
|
|
static BOOL s_fKeepBoundary = 0;
|
|
static BOOL s_fDumpMessage = 0;
|
|
static BOOL s_fWriteXClient = 0;
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// This writes the message X-Mailer or X-Newsreader
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::DebugWriteXClient()
|
|
{
|
|
if (s_fWriteXClient)
|
|
{
|
|
LPSTR pszX;
|
|
if (SUCCEEDED(m_pRootNode->pContainer->GetProp(SYM_HDR_XMAILER, &pszX)) && pszX)
|
|
{
|
|
DebugTrace("X-Mailer: %s\n", pszX);
|
|
MemFree(pszX);
|
|
}
|
|
else if (SUCCEEDED(m_pRootNode->pContainer->GetProp(SYM_HDR_XNEWSRDR, &pszX)) && pszX)
|
|
{
|
|
DebugTrace("X-Newsreader: %s\n", pszX);
|
|
MemFree(pszX);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// This dumps the current tree to debug output window
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::DebugDumpTree(LPSTR pszfunc, BOOL fWrite)
|
|
{
|
|
if (TRUE == fWrite)
|
|
{
|
|
DebugTrace("---------------------------------------------------------------------------\n");
|
|
DebugTrace("CMessageTree::%s\n", pszfunc);
|
|
}
|
|
DebugDumpTree(m_pRootNode, 0, fWrite);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// This macros writes _pstm to a file
|
|
// --------------------------------------------------------------------------------
|
|
#define DEBUGMESSAGEOUT "c:\\lastmsg.txt"
|
|
void DebugWriteMsg(LPSTREAM pstm)
|
|
{
|
|
if (TRUE == s_fDumpMessage)
|
|
{
|
|
LPSTREAM pstmFile;
|
|
if (SUCCEEDED(OpenFileStream(DEBUGMESSAGEOUT, CREATE_ALWAYS, GENERIC_WRITE, &pstmFile)))
|
|
{
|
|
HrRewindStream(pstm);
|
|
HrCopyStream(pstm, pstmFile, NULL);
|
|
pstmFile->Commit(STGC_DEFAULT);
|
|
pstmFile->Release();
|
|
}
|
|
}
|
|
}
|
|
|
|
#else // DEBUG
|
|
|
|
#define DebugDumpTree 1 ? (void)0 : (void)
|
|
#define DebugWriteMsg 1 ? (void)0 : (void)
|
|
#define DebugAssertNotLinked 1 ? (void)0 : (void)
|
|
#define DebugIsRootContainer 1 ? (void)0 : (void)
|
|
|
|
#endif // DEBUG
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// WebBookContentTree_CreateInstance
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT WebBookContentTree_CreateInstance(IUnknown* pUnkOuter, IUnknown** ppUnknown)
|
|
{
|
|
// Invalid Arg
|
|
Assert(ppUnknown);
|
|
|
|
// Initialize
|
|
*ppUnknown = NULL;
|
|
|
|
// Create me
|
|
CMessageTree *pNew = new CMessageTree(pUnkOuter);
|
|
if (NULL == pNew)
|
|
return TrapError(E_OUTOFMEMORY);
|
|
|
|
// Return the Innter
|
|
*ppUnknown = pNew->GetInner();
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// Text Type Information Array
|
|
// --------------------------------------------------------------------------------
|
|
static const TEXTTYPEINFO g_rgTextInfo[] = {
|
|
{ TXT_PLAIN, STR_SUB_PLAIN, 0 },
|
|
{ TXT_HTML, STR_SUB_HTML, 5 }
|
|
};
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CMessageTree
|
|
// --------------------------------------------------------------------------------
|
|
CMessageTree::CMessageTree(IUnknown *pUnkOuter) : CPrivateUnknown(pUnkOuter)
|
|
{
|
|
DllAddRef();
|
|
m_dwState = 0;
|
|
m_pCallback = NULL;
|
|
m_pBindNode = NULL;
|
|
m_pRootStm = NULL;
|
|
m_pInternet = NULL;
|
|
m_pStmBind = NULL;
|
|
m_pBinding = NULL;
|
|
m_pMoniker = NULL;
|
|
m_pBC = NULL;
|
|
m_cbMessage = 0;
|
|
m_pStmLock = NULL;
|
|
m_pRootNode = NULL;
|
|
m_pwszFilePath = NULL;
|
|
m_hrBind = S_OK;
|
|
m_pPending = NULL;
|
|
m_pComplete = NULL;
|
|
m_wTag = LOWORD(GetTickCount());
|
|
m_pActiveUrl = NULL;
|
|
m_pWebPage = NULL;
|
|
m_fApplySaveSecurity = FALSE;
|
|
m_pBT1154 = NULL;
|
|
while(m_wTag == 0 || m_wTag == 0xffff) m_wTag++;
|
|
ZeroMemory(&m_rRootUrl, sizeof(PROPSTRINGA));
|
|
ZeroMemory(&m_rTree, sizeof(TREENODETABLE));
|
|
CopyMemory(&m_rWebPageOpt, &g_rDefWebPageOpt, sizeof(WEBPAGEOPTIONS));
|
|
CopyMemory(&m_rOptions, &g_rDefTreeOptions, sizeof(TREEOPTIONS));
|
|
InitializeCriticalSection(&m_cs);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::~CMessageTree
|
|
// --------------------------------------------------------------------------------
|
|
CMessageTree::~CMessageTree(void)
|
|
{
|
|
if(m_pActiveUrl && g_pUrlCache)
|
|
{
|
|
//Bug #101348 - free CActiveUrl leaked to the CMimeActiveUrlCache
|
|
g_pUrlCache->RemoveUrl(m_pActiveUrl);
|
|
m_pActiveUrl = NULL;
|
|
}
|
|
|
|
// Reset the Object
|
|
_ResetObject(BOOKTREE_RESET_DECONSTRUCT);
|
|
SafeRelease(m_pStmLock);
|
|
|
|
|
|
// Kill the Critical Section
|
|
DeleteCriticalSection(&m_cs);
|
|
|
|
// Releaes the Dll
|
|
DllRelease();
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::PrivateQueryInterface
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::PrivateQueryInterface(REFIID riid, LPVOID *ppv)
|
|
{
|
|
// check params
|
|
if (ppv == NULL)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Interface Map
|
|
if (IID_IPersist == riid)
|
|
*ppv = (IPersist *)(IPersistStreamInit *)this;
|
|
else if (IID_IPersistStreamInit == riid)
|
|
*ppv = (IPersistStreamInit *)this;
|
|
else if (IID_IMimeMessage == riid)
|
|
*ppv = (IMimeMessage *)this;
|
|
else if (IID_IMimeMessageW == riid)
|
|
*ppv = (IMimeMessageW *)this;
|
|
else if (IID_IMimeMessageTree == riid)
|
|
*ppv = (IMimeMessageTree *)this;
|
|
else if (IID_IDataObject == riid)
|
|
*ppv = (IDataObject *)this;
|
|
else if (IID_IPersistFile == riid)
|
|
*ppv = (IPersistFile *)this;
|
|
else if (IID_IBindStatusCallback == riid)
|
|
*ppv = (IBindStatusCallback *)this;
|
|
else if (IID_IServiceProvider == riid)
|
|
*ppv = (IServiceProvider *)this;
|
|
else if (IID_CMessageTree == riid)
|
|
*ppv = (CMessageTree *)this;
|
|
else if (IID_IPersistMoniker == riid)
|
|
*ppv = (IPersistMoniker *)this;
|
|
#ifdef SMIME_V3
|
|
else if (IID_IMimeSecurity2 == riid)
|
|
*ppv = (IMimeSecurity2 *) this;
|
|
#endif // SMIME_V3
|
|
|
|
// E_NOINTERFACE
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return TrapError(E_NOINTERFACE);
|
|
}
|
|
|
|
// AddRef It
|
|
((IUnknown *)*ppv)->AddRef();
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DebugDumpTree
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::DebugDumpTree(LPTREENODEINFO pParent, ULONG ulLevel, BOOL fVerbose)
|
|
{
|
|
// Locals
|
|
LPSTR pszPriType=NULL,
|
|
pszEncoding=NULL,
|
|
pszFileName=NULL;
|
|
LPTREENODEINFO pChild,
|
|
pPrev,
|
|
pNext;
|
|
ULONG cChildren;
|
|
LONG lRendered=-1;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Get Content Type
|
|
if (fVerbose)
|
|
{
|
|
Assert(pParent->pContainer->GetProp(SYM_HDR_CNTTYPE, &pszPriType) == S_OK);
|
|
Assert(pParent->pContainer->GetProp(SYM_HDR_CNTXFER, &pszEncoding) == S_OK);
|
|
Assert(pParent->pContainer->GetProp(SYM_ATT_GENFNAME, &pszFileName) == S_OK);
|
|
|
|
rVariant.vt = VT_UI4;
|
|
if (SUCCEEDED(pParent->pContainer->GetProp(PIDTOSTR(PID_ATT_RENDERED), 0, &rVariant)))
|
|
lRendered = (LONG)rVariant.ulVal;
|
|
|
|
for (ULONG i=0; i<ulLevel; i++)
|
|
DebugTrace(" ");
|
|
DebugTrace("%0x == > %s (%s - %s) Rendered: %ld\n", pParent->hBody, pszPriType, pszFileName, pszEncoding, lRendered);
|
|
}
|
|
|
|
// IsMultiPart
|
|
if (_IsMultiPart(pParent))
|
|
{
|
|
// Count Children
|
|
cChildren = 0;
|
|
pPrev = NULL;
|
|
|
|
// Increment the level
|
|
ulLevel++;
|
|
|
|
// Loop Chilren
|
|
for (pChild=pParent->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Verify Handle
|
|
Assert(_FIsValidHandle(pChild->hBody));
|
|
|
|
// Check Parent
|
|
AssertSz(pChild->pParent == pParent, "Parent is wrong");
|
|
|
|
// Check pParent Child Head
|
|
if (NULL == pChild->pPrev)
|
|
Assert(pParent->pChildHead == pChild);
|
|
|
|
// Check pParent Child Tail
|
|
if (NULL == pChild->pNext)
|
|
Assert(pParent->pChildTail == pChild);
|
|
|
|
// Valid Prev
|
|
Assert(pChild->pPrev == pPrev);
|
|
|
|
// Dump This Child
|
|
DebugDumpTree(pChild, ulLevel, fVerbose);
|
|
|
|
// Count Children
|
|
cChildren++;
|
|
|
|
// Set Previous
|
|
pPrev = pChild;
|
|
}
|
|
|
|
// Verify Children
|
|
Assert(pParent->cChildren == cChildren);
|
|
}
|
|
|
|
// Cleanup
|
|
SafeMemFree(pszPriType);
|
|
SafeMemFree(pszEncoding);
|
|
SafeMemFree(pszFileName);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DebugAssertNotLinked
|
|
// This insures that pNode is not referenced by the tree
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::DebugAssertNotLinked(LPTREENODEINFO pNode)
|
|
{
|
|
// Better not be the root
|
|
Assert(m_pRootNode != pNode);
|
|
|
|
// Loop through bodies
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Check if linked to pBody
|
|
Assert(m_rTree.prgpNode[i]->pParent != pNode);
|
|
Assert(m_rTree.prgpNode[i]->pChildHead != pNode);
|
|
Assert(m_rTree.prgpNode[i]->pChildTail != pNode);
|
|
Assert(m_rTree.prgpNode[i]->pNext != pNode);
|
|
Assert(m_rTree.prgpNode[i]->pPrev != pNode);
|
|
}
|
|
}
|
|
|
|
#endif // DEBUG
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::IsState
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::IsState(DWORD dwState)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
HRESULT hr = (ISFLAGSET(m_dwState, dwState)) ? S_OK : S_FALSE;
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetRootMoniker (This Function will die soon)
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetRootMoniker(LPMONIKER *ppmk)
|
|
{
|
|
Assert(FALSE);
|
|
return E_FAIL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CreateWebPage
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::CreateWebPage(IStream *pStmRoot, LPWEBPAGEOPTIONS pOptions,
|
|
IMimeMessageCallback *pCallback, IMoniker **ppMoniker)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPWSTR pwszRootUrl=NULL;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppMoniker)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// If an options structure was passed in, is it the right size ?
|
|
if (pOptions && sizeof(WEBPAGEOPTIONS) != pOptions->cbSize)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init
|
|
*ppMoniker = NULL;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Release Current BindRoot Stream
|
|
SafeRelease(m_pRootStm);
|
|
SafeRelease(m_pWebPage);
|
|
|
|
// Null pStream is allowed
|
|
if (pStmRoot)
|
|
{
|
|
// Save Root Stream
|
|
m_pRootStm = pStmRoot;
|
|
m_pRootStm->AddRef();
|
|
}
|
|
|
|
// Otherwise, we can inline text bodies...
|
|
else
|
|
{
|
|
// Change Option
|
|
m_rOptions.fCanInlineText = TRUE;
|
|
}
|
|
|
|
// Release current webpage callback
|
|
SafeRelease(m_pCallback);
|
|
|
|
// Setup the new webpage callback
|
|
if (pCallback)
|
|
{
|
|
m_pCallback = pCallback;
|
|
m_pCallback->AddRef();
|
|
}
|
|
|
|
// Save WebPageOptions
|
|
if (pOptions)
|
|
CopyMemory(&m_rWebPageOpt, pOptions, sizeof(WEBPAGEOPTIONS));
|
|
else
|
|
CopyMemory(&m_rWebPageOpt, &g_rDefWebPageOpt, sizeof(WEBPAGEOPTIONS));
|
|
|
|
// Already have a Base Url from IMimeMessageTree::IPersitMoniker::Load
|
|
if (NULL == m_rRootUrl.pszVal)
|
|
{
|
|
// Locals
|
|
CHAR szRootUrl[CCHMAX_MID + 8];
|
|
|
|
// Build MessageID
|
|
m_rRootUrl.cchVal = wnsprintf(szRootUrl, ARRAYSIZE(szRootUrl), "mhtml:mid://%08d/", DwCounterNext());
|
|
|
|
// Allocate
|
|
CHECKALLOC(m_rRootUrl.pszVal = (LPSTR)g_pMalloc->Alloc(m_rRootUrl.cchVal + 1));
|
|
|
|
// Copy memory
|
|
CopyMemory((LPBYTE)m_rRootUrl.pszVal, (LPBYTE)szRootUrl, m_rRootUrl.cchVal + 1);
|
|
|
|
// Register this object in the list of active objects
|
|
Assert(g_pUrlCache);
|
|
CHECKHR(hr = g_pUrlCache->RegisterActiveObject(m_rRootUrl.pszVal, this));
|
|
|
|
// We shuould have a m_pActiveUrl now
|
|
Assert(m_pActiveUrl != NULL);
|
|
|
|
// Set some flags on the activeurl
|
|
m_pActiveUrl->SetFlag(ACTIVEURL_ISFAKEURL);
|
|
|
|
// Is valid
|
|
Assert(ISVALIDSTRINGA(&m_rRootUrl));
|
|
}
|
|
|
|
// Convert Url to Wide
|
|
CHECKALLOC(pwszRootUrl = PszToUnicode(CP_ACP, m_rRootUrl.pszVal));
|
|
|
|
// Create a dummy moniker
|
|
CHECKHR(hr = CreateURLMoniker(NULL, pwszRootUrl, ppMoniker));
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pwszRootUrl);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CMessageTree::SetActiveUrl
|
|
// ---------------------------------------------------------------------------
|
|
HRESULT CMessageTree::SetActiveUrl(CActiveUrl *pActiveUrl)
|
|
{
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// NULL ?
|
|
if (NULL == pActiveUrl)
|
|
{
|
|
Assert(m_pActiveUrl);
|
|
SafeRelease(m_pActiveUrl);
|
|
}
|
|
else
|
|
{
|
|
Assert(NULL == m_pActiveUrl);
|
|
m_pActiveUrl = pActiveUrl;
|
|
m_pActiveUrl->AddRef();
|
|
}
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CompareRootUrl
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::CompareRootUrl(LPCSTR pszUrl)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid ARg
|
|
Assert(pszUrl);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// No Root Url
|
|
if (NULL == m_rRootUrl.pszVal)
|
|
{
|
|
Assert(FALSE);
|
|
hr = S_FALSE;
|
|
goto exit;
|
|
}
|
|
|
|
// This url must start with mhtml:
|
|
Assert(StrCmpNI(m_rRootUrl.pszVal, "mhtml:", 6) == 0);
|
|
|
|
// Compare
|
|
hr = MimeOleCompareUrl(m_rRootUrl.pszVal + 6, FALSE, pszUrl, FALSE);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::Load
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Load(BOOL fFullyAvailable, IMoniker *pMoniker, IBindCtx *pBindCtx, DWORD grfMode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
IStream *pStream=NULL;
|
|
ULONG cb;
|
|
LPOLESTR pwszUrl=NULL;
|
|
LPSTR pszUrl=NULL;
|
|
ULONG cchUrl;
|
|
BOOL fReSynchronize;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pMoniker)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Remember if TREESTATE_RESYNCHRONIZE is set...
|
|
fReSynchronize = ISFLAGSET(m_dwState, TREESTATE_RESYNCHRONIZE);
|
|
|
|
// InitNew
|
|
CHECKHR(hr = _HrLoadInitNew());
|
|
|
|
// Reset pragma no cache
|
|
if (fReSynchronize)
|
|
{
|
|
// Reset
|
|
FLAGSET(m_dwState, TREESTATE_RESYNCHRONIZE);
|
|
}
|
|
|
|
// We better have a tree
|
|
Assert(NULL == m_pMoniker);
|
|
|
|
// Assume the Moniker
|
|
m_pMoniker = pMoniker;
|
|
m_pMoniker->AddRef();
|
|
|
|
// No Bind Context was given ?
|
|
if (NULL == pBindCtx)
|
|
{
|
|
// Create me a BindContext
|
|
CHECKHR(hr = CreateBindCtx(0, &pBindCtx));
|
|
}
|
|
|
|
// Otherwise, assume the Bind Context Passed Into me
|
|
else
|
|
pBindCtx->AddRef();
|
|
|
|
Assert (m_pBC==NULL);
|
|
m_pBC = pBindCtx; // released in OnStopBinding
|
|
|
|
// Get the Url from this dude
|
|
CHECKHR(hr = m_pMoniker->GetDisplayName(NULL, NULL, &pwszUrl));
|
|
|
|
// Save as Root Url
|
|
CHECKALLOC(pszUrl = PszToANSI(CP_ACP, pwszUrl));
|
|
|
|
// Unescape inplace
|
|
CHECKHR(hr = UrlUnescapeA(pszUrl, NULL, NULL, URL_UNESCAPE_INPLACE));
|
|
|
|
// Raid-2508: Comment tag ( <! comment> ) doesn't work in mhtml
|
|
ReplaceChars(pszUrl, '!', '_');
|
|
|
|
// Better not have mhtml: on it
|
|
Assert(StrCmpNI(pszUrl, "mhtml:", 6) != 0);
|
|
|
|
// Get the length of pszUrl
|
|
cchUrl = lstrlen(pszUrl);
|
|
|
|
// Create "mhtml://" + pszUrl + '/' + '\0'
|
|
DWORD cchSize = (10 + cchUrl);
|
|
CHECKALLOC(m_rRootUrl.pszVal = (LPSTR)g_pMalloc->Alloc(cchSize));
|
|
|
|
// Format the string
|
|
SideAssert(wnsprintf(m_rRootUrl.pszVal, cchSize, "%s%s", c_szMHTMLColon, pszUrl) <= (LONG)(10 + cchUrl));
|
|
|
|
// Register my bind status callback in the bind context
|
|
CHECKHR(hr = RegisterBindStatusCallback(pBindCtx, (IBindStatusCallback *)this, NULL, 0));
|
|
|
|
// Assume the Bind has Finished
|
|
FLAGCLEAR(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// I only support share deny none
|
|
FLAGSET(m_dwState, TREESTATE_BINDUSEFILE);
|
|
|
|
// I was loaded by a moniker
|
|
FLAGSET(m_dwState, TREESTATE_LOADEDBYMONIKER);
|
|
|
|
// This better be synchronous
|
|
hr = m_pMoniker->BindToStorage(pBindCtx, NULL, IID_IStream, (LPVOID *)&pStream);
|
|
if (FAILED(hr) || MK_S_ASYNCHRONOUS == hr)
|
|
{
|
|
TrapError(hr);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pStream);
|
|
SafeMemFree(pwszUrl);
|
|
SafeMemFree(pszUrl);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::GetCurMoniker
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetCurMoniker(IMoniker **ppMoniker)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppMoniker)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// No Data
|
|
if (NULL == m_pMoniker)
|
|
{
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Return It
|
|
*ppMoniker = m_pMoniker;
|
|
(*ppMoniker)->AddRef();
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::GetCurFile
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetCurFile(LPOLESTR *ppszFileName)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppszFileName)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Return File Name
|
|
if (NULL == m_pwszFilePath)
|
|
{
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Dup and return
|
|
CHECKALLOC(*ppszFileName = PszDupW(m_pwszFilePath));
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::Load
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Load(LPCOLESTR pszFileName, DWORD dwMode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
IStream *pstmFile=NULL;
|
|
DWORD dwAccess=GENERIC_READ;
|
|
DWORD dwShare=FILE_SHARE_READ|FILE_SHARE_WRITE;
|
|
BOOL fBindUseFile;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pszFileName)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Determine Access
|
|
if (ISFLAGSET(dwMode, STGM_WRITE))
|
|
FLAGSET(dwAccess, GENERIC_WRITE);
|
|
if (ISFLAGSET(dwMode, STGM_READWRITE))
|
|
FLAGSET(dwAccess, GENERIC_READ | GENERIC_WRITE);
|
|
|
|
// Determine Share Mode
|
|
dwMode &= 0x00000070; // the STGM_SHARE_* flags are not individual bits
|
|
if (STGM_SHARE_DENY_NONE == dwMode)
|
|
dwShare = FILE_SHARE_READ | FILE_SHARE_WRITE;
|
|
else if (STGM_SHARE_DENY_READ == dwMode)
|
|
dwShare = FILE_SHARE_WRITE;
|
|
else if (STGM_SHARE_DENY_WRITE == dwMode)
|
|
dwShare = FILE_SHARE_READ;
|
|
else if (STGM_SHARE_EXCLUSIVE == dwMode)
|
|
dwShare = 0;
|
|
|
|
// Save Option
|
|
fBindUseFile = m_rOptions.fBindUseFile;
|
|
|
|
// If the user wants file sharing on this file, then I need to put this into my own file
|
|
if (ISFLAGSET(dwShare, FILE_SHARE_WRITE))
|
|
m_rOptions.fBindUseFile = TRUE;
|
|
|
|
// Open File Stream
|
|
CHECKHR(hr = OpenFileStreamShareW((LPWSTR)pszFileName, OPEN_EXISTING, dwAccess, dwShare, &pstmFile));
|
|
|
|
// Bind the message
|
|
CHECKHR(hr = Load(pstmFile));
|
|
|
|
// Reset Option
|
|
m_rOptions.fBindUseFile = fBindUseFile;
|
|
|
|
// Free Current File
|
|
SafeMemFree(m_pwszFilePath);
|
|
|
|
// Assume new file
|
|
CHECKALLOC(m_pwszFilePath = PszDupW(pszFileName));
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmFile);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::Save
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Save(LPCOLESTR pszFileName, BOOL fRemember)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
IStream *pstmFile=NULL,
|
|
*pstmSource=NULL;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pszFileName)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Open File Stream
|
|
CHECKHR(hr = OpenFileStreamW((LPWSTR)pszFileName, CREATE_ALWAYS, GENERIC_READ | GENERIC_WRITE, &pstmFile));
|
|
|
|
// If Remember
|
|
if (fRemember)
|
|
{
|
|
// Bind the message
|
|
CHECKHR(hr = Save(pstmFile, TRUE));
|
|
}
|
|
|
|
// Otherwise, get message source, and copy...
|
|
else
|
|
{
|
|
// Get Message Source
|
|
CHECKHR(hr = GetMessageSource(&pstmSource, COMMIT_ONLYIFDIRTY));
|
|
|
|
// Copy...
|
|
CHECKHR(hr = HrCopyStream(pstmSource, pstmFile, NULL));
|
|
}
|
|
|
|
// Commit
|
|
CHECKHR(hr = pstmFile->Commit(STGC_DEFAULT));
|
|
|
|
// If Remember
|
|
if (fRemember)
|
|
{
|
|
// Free Current File
|
|
SafeMemFree(m_pwszFilePath);
|
|
|
|
// Assume new file
|
|
CHECKALLOC(m_pwszFilePath = PszDupW(pszFileName));
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmFile);
|
|
SafeRelease(pstmSource);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::SaveCompleted
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SaveCompleted(LPCOLESTR pszFileName)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::GetClassID
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetClassID(CLSID *pClassID)
|
|
{
|
|
// Invalid Arg
|
|
if (NULL == pClassID)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Copy Class Id
|
|
CopyMemory(pClassID, &IID_IMimeMessageTree, sizeof(CLSID));
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::GetSizeMax
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetSizeMax(ULARGE_INTEGER* pcbSize)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cbSize;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pcbSize)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// INit
|
|
pcbSize->QuadPart = 0;
|
|
|
|
// Get Message Size
|
|
CHECKHR(hr = GetMessageSize(&cbSize, COMMIT_ONLYIFDIRTY));
|
|
|
|
// Set Size
|
|
pcbSize->QuadPart = cbSize;
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::_FIsValidHandle
|
|
// ----------------------------------------------------------------------------
|
|
BOOL CMessageTree::_FIsValidHandle(HBODY hBody)
|
|
{
|
|
// Its Valid
|
|
if ((WORD)HBODYTAG(hBody) == m_wTag &&
|
|
HBODYINDEX(hBody) < m_rTree.cNodes &&
|
|
m_rTree.prgpNode[HBODYINDEX(hBody)] &&
|
|
m_rTree.prgpNode[HBODYINDEX(hBody)]->hBody == hBody)
|
|
return TRUE;
|
|
|
|
// Not Valid
|
|
return FALSE;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::_PNodeFromHBody
|
|
// ----------------------------------------------------------------------------
|
|
LPTREENODEINFO CMessageTree::_PNodeFromHBody(HBODY hBody)
|
|
{
|
|
Assert(_FIsValidHandle(hBody));
|
|
return m_rTree.prgpNode[HBODYINDEX(hBody)];
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetMessageSize
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetMessageSize(ULONG *pcbSize, DWORD dwFlags)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pstmSource=NULL;
|
|
|
|
// Invalid Arg
|
|
if (pcbSize == NULL)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init
|
|
*pcbSize = 0;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get the message source
|
|
CHECKHR(hr = GetMessageSource(&pstmSource, dwFlags));
|
|
|
|
// Get the stream Size
|
|
CHECKHR(hr = HrGetStreamSize(pstmSource, pcbSize));
|
|
|
|
// If you hit this assert, please let me know. t-erikne
|
|
// I'm trying to see if we have to call HrGetStreamSize here.
|
|
Assert(m_cbMessage == *pcbSize);
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmSource);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CMessageTree::_ApplyOptionToAllBodies
|
|
// ---------------------------------------------------------------------------
|
|
void CMessageTree::_ApplyOptionToAllBodies(const TYPEDID oid, LPCPROPVARIANT pValue)
|
|
{
|
|
// Loop through bodies and set on each body
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Check if deleted
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Dirty Header...
|
|
m_rTree.prgpNode[i]->pBody->SetOption(oid, pValue);
|
|
}
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CMessageTree::SetOption
|
|
// ---------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SetOption(const TYPEDID oid, LPCPROPVARIANT pValue)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// check params
|
|
if (NULL == pValue)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Handle Optid
|
|
switch(oid)
|
|
{
|
|
case OID_HANDSOFF_ONSAVE:
|
|
m_rOptions.fHandsOffOnSave = pValue->boolVal ? TRUE : FALSE;
|
|
break;
|
|
|
|
case OID_SUPPORT_EXTERNAL_BODY:
|
|
_ApplyOptionToAllBodies(oid, pValue);
|
|
break;
|
|
|
|
case OID_SHOW_MACBINARY:
|
|
if (m_rOptions.fShowMacBin != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
m_rOptions.fShowMacBin = pValue->boolVal ? TRUE : FALSE;
|
|
_ApplyOptionToAllBodies(oid, pValue);
|
|
}
|
|
break;
|
|
|
|
case OID_HEADER_RELOAD_TYPE:
|
|
if (pValue->ulVal > RELOAD_HEADER_REPLACE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.ReloadType != (RELOADTYPE)pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.ReloadType = (RELOADTYPE)pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_LOAD_USE_BIND_FILE:
|
|
m_rOptions.fBindUseFile = pValue->boolVal ? TRUE : FALSE;
|
|
break;
|
|
|
|
case OID_CLEANUP_TREE_ON_SAVE:
|
|
m_rOptions.fCleanupTree = pValue->boolVal ? TRUE : FALSE;
|
|
break;
|
|
|
|
case OID_SAVEBODY_KEEPBOUNDARY:
|
|
if (m_rOptions.fKeepBoundary != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.fKeepBoundary = pValue->boolVal ? TRUE : FALSE;
|
|
}
|
|
break;
|
|
|
|
case OID_CAN_INLINE_TEXT_BODIES:
|
|
if (m_rOptions.fCanInlineText != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.fCanInlineText = pValue->boolVal ? TRUE : FALSE;
|
|
}
|
|
break;
|
|
|
|
case OID_HIDE_TNEF_ATTACHMENTS:
|
|
if (m_rOptions.fHideTnef != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
m_rOptions.fHideTnef = pValue->boolVal ? TRUE : FALSE;
|
|
_ApplyOptionToAllBodies(oid, pValue);
|
|
}
|
|
break;
|
|
|
|
case OID_ALLOW_8BIT_HEADER:
|
|
if (m_rOptions.fAllow8bitHeader != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.fAllow8bitHeader = pValue->boolVal ? TRUE : FALSE;
|
|
}
|
|
break;
|
|
|
|
case OID_CBMAX_HEADER_LINE:
|
|
if (pValue->ulVal < MIN_CBMAX_HEADER_LINE || pValue->ulVal > MAX_CBMAX_HEADER_LINE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.cchMaxHeaderLine != pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.cchMaxHeaderLine = pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_SAVE_FORMAT:
|
|
if (SAVE_RFC822 != pValue->ulVal && SAVE_RFC1521 != pValue->ulVal)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.savetype != (MIMESAVETYPE)pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.savetype = (MIMESAVETYPE)pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_TRANSMIT_TEXT_ENCODING:
|
|
if (FALSE == FIsValidBodyEncoding((ENCODINGTYPE)pValue->ulVal))
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.ietTextXmit != (ENCODINGTYPE)pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.ietTextXmit = (ENCODINGTYPE)pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_XMIT_PLAIN_TEXT_ENCODING:
|
|
if (FALSE == FIsValidBodyEncoding((ENCODINGTYPE)pValue->ulVal))
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.ietPlainXmit != (ENCODINGTYPE)pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.ietPlainXmit = (ENCODINGTYPE)pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_XMIT_HTML_TEXT_ENCODING:
|
|
if (FALSE == FIsValidBodyEncoding((ENCODINGTYPE)pValue->ulVal))
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.ietHtmlXmit != (ENCODINGTYPE)pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.ietHtmlXmit = (ENCODINGTYPE)pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_WRAP_BODY_TEXT:
|
|
if (m_rOptions.fWrapBodyText != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.fWrapBodyText = pValue->boolVal ? TRUE : FALSE;
|
|
}
|
|
break;
|
|
|
|
case OID_CBMAX_BODY_LINE:
|
|
if (pValue->ulVal < MIN_CBMAX_BODY_LINE || pValue->ulVal > MAX_CBMAX_BODY_LINE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_OPTION_VALUE);
|
|
goto exit;
|
|
}
|
|
if (m_rOptions.cchMaxBodyLine != pValue->ulVal)
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.cchMaxBodyLine = pValue->ulVal;
|
|
}
|
|
break;
|
|
|
|
case OID_GENERATE_MESSAGE_ID:
|
|
if (m_rOptions.fGenMessageId != (pValue->boolVal ? TRUE : FALSE))
|
|
{
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
m_rOptions.fGenMessageId = pValue->boolVal ? TRUE : FALSE;
|
|
}
|
|
break;
|
|
|
|
case OID_SECURITY_ENCODE_FLAGS:
|
|
m_rOptions.ulSecIgnoreMask = pValue->ulVal;
|
|
break;
|
|
|
|
case OID_DECODE_RFC1154:
|
|
m_rOptions.fDecodeRfc1154 = pValue->boolVal ? TRUE : FALSE;
|
|
break;
|
|
|
|
default:
|
|
hr = TrapError(MIME_E_INVALID_OPTION_ID);
|
|
break;
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// ---------------------------------------------------------------------------
|
|
// CMessageTree::GetOption
|
|
// ---------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetOption(const TYPEDID oid, LPPROPVARIANT pValue)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// check params
|
|
if (NULL == pValue)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
pValue->vt = TYPEDID_TYPE(oid);
|
|
|
|
// Handle Optid
|
|
switch(oid)
|
|
{
|
|
case OID_HANDSOFF_ONSAVE:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fHandsOffOnSave;
|
|
break;
|
|
|
|
case OID_LOAD_USE_BIND_FILE:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fBindUseFile;
|
|
break;
|
|
|
|
case OID_SHOW_MACBINARY:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fShowMacBin;
|
|
break;
|
|
|
|
case OID_HEADER_RELOAD_TYPE:
|
|
pValue->ulVal = m_rOptions.ReloadType;
|
|
break;
|
|
|
|
case OID_CAN_INLINE_TEXT_BODIES:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fCanInlineText;
|
|
break;
|
|
|
|
case OID_CLEANUP_TREE_ON_SAVE:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fCleanupTree;
|
|
break;
|
|
|
|
case OID_SAVEBODY_KEEPBOUNDARY:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fKeepBoundary;
|
|
break;
|
|
|
|
case OID_HIDE_TNEF_ATTACHMENTS:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fHideTnef;
|
|
break;
|
|
|
|
case OID_ALLOW_8BIT_HEADER:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fAllow8bitHeader;
|
|
break;
|
|
|
|
case OID_WRAP_BODY_TEXT:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fWrapBodyText;
|
|
break;
|
|
|
|
case OID_CBMAX_HEADER_LINE:
|
|
pValue->ulVal = m_rOptions.cchMaxHeaderLine;
|
|
break;
|
|
|
|
case OID_SAVE_FORMAT:
|
|
pValue->ulVal = (ULONG)m_rOptions.savetype;
|
|
break;
|
|
|
|
case OID_TRANSMIT_TEXT_ENCODING:
|
|
pValue->ulVal = (ULONG)m_rOptions.ietTextXmit;
|
|
break;
|
|
|
|
case OID_XMIT_PLAIN_TEXT_ENCODING:
|
|
pValue->ulVal = (ULONG)m_rOptions.ietPlainXmit;
|
|
break;
|
|
|
|
case OID_XMIT_HTML_TEXT_ENCODING:
|
|
pValue->ulVal = (ULONG)m_rOptions.ietHtmlXmit;
|
|
break;
|
|
|
|
case OID_CBMAX_BODY_LINE:
|
|
pValue->ulVal = m_rOptions.cchMaxBodyLine;
|
|
break;
|
|
|
|
case OID_GENERATE_MESSAGE_ID:
|
|
pValue->boolVal = m_rOptions.fGenMessageId;
|
|
break;
|
|
|
|
case OID_SECURITY_ENCODE_FLAGS:
|
|
pValue->ulVal = m_rOptions.ulSecIgnoreMask;
|
|
break;
|
|
|
|
case OID_DECODE_RFC1154:
|
|
pValue->boolVal = (VARIANT_BOOL) !!m_rOptions.fDecodeRfc1154;
|
|
break;
|
|
|
|
default:
|
|
hr = TrapError(MIME_E_INVALID_OPTION_ID);
|
|
break;
|
|
}
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_ResetObject
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_ResetObject(BOOKTREERESET ResetType)
|
|
{
|
|
// Load InitNew
|
|
if (BOOKTREE_RESET_LOADINITNEW == ResetType)
|
|
{
|
|
// There has to be a root (look at impl of ::_HrLoadInitNew)
|
|
Assert(m_pRootNode);
|
|
|
|
// Don't Crash
|
|
if (m_pRootNode)
|
|
{
|
|
// Delete all bodies, except for the root, if there is one...
|
|
if (m_pRootNode->pBody->IsType(IBT_EMPTY) == S_FALSE || m_pRootNode->pContainer->CountProps() > 0)
|
|
{
|
|
// Delete the root body, this simply removed properties and empties the body, but leave the root body
|
|
DeleteBody(m_pRootNode->hBody, 0);
|
|
}
|
|
|
|
// Lighweight FreeTree Node Info
|
|
_FreeTreeNodeInfo(m_pRootNode, FALSE);
|
|
|
|
// Validate
|
|
Assert(m_pRootNode->cChildren == 0);
|
|
Assert(m_pRootNode->pParent == NULL);
|
|
Assert(m_pRootNode->pNext == NULL);
|
|
Assert(m_pRootNode->pPrev == NULL);
|
|
Assert(m_pRootNode->pChildHead == NULL);
|
|
Assert(m_pRootNode->pChildTail == NULL);
|
|
Assert(m_pRootNode->pBody);
|
|
Assert(m_pRootNode->pContainer);
|
|
|
|
// Quick Reset
|
|
TREENODEINFO rTemp;
|
|
CopyMemory(&rTemp, m_pRootNode, sizeof(TREENODEINFO));
|
|
ZeroMemory(m_pRootNode, sizeof(TREENODEINFO));
|
|
m_pRootNode->pBody = rTemp.pBody;
|
|
m_pRootNode->pContainer = rTemp.pContainer;
|
|
m_pRootNode->hBody = rTemp.hBody;
|
|
|
|
// Set OID_RELOAD_HEADER_TYPE
|
|
PROPVARIANT rOption;
|
|
rOption.vt = VT_UI4;
|
|
rOption.ulVal = (ULONG)m_rOptions.ReloadType;
|
|
m_pRootNode->pContainer->SetOption(OID_HEADER_RELOAD_TYPE, &rOption);
|
|
}
|
|
}
|
|
|
|
// Free All Elements
|
|
else
|
|
_FreeNodeTableElements();
|
|
|
|
// Free Bind Request Table
|
|
_ReleaseUrlRequestList(&m_pPending);
|
|
_ReleaseUrlRequestList(&m_pComplete);
|
|
|
|
// Free and Release Objects
|
|
SafeRelease(m_pCallback);
|
|
SafeRelease(m_pWebPage);
|
|
SafeMemFree(m_pwszFilePath);
|
|
SafeRelease(m_pBinding);
|
|
SafeRelease(m_pMoniker);
|
|
SafeRelease(m_pBC);
|
|
SafeRelease(m_pInternet);
|
|
SafeRelease(m_pStmBind);
|
|
SafeRelease(m_pRootStm);
|
|
SafeMemFree(m_rRootUrl.pszVal);
|
|
SafeMemFree(m_pBT1154);
|
|
|
|
// Clear Current BindNode
|
|
m_pBindNode = NULL;
|
|
|
|
// Orphan CStreamLockBytes
|
|
if (m_pStmLock)
|
|
{
|
|
m_pStmLock->HrHandsOffStorage();
|
|
m_pStmLock->Release();
|
|
m_pStmLock = NULL;
|
|
}
|
|
|
|
// If Deconstructing
|
|
if (BOOKTREE_RESET_DECONSTRUCT == ResetType)
|
|
{
|
|
// Release the body table array
|
|
SafeMemFree(m_rTree.prgpNode);
|
|
|
|
// If I'm registered as a Url
|
|
if (m_pActiveUrl)
|
|
m_pActiveUrl->RevokeWebBook(this);
|
|
|
|
// Better not have an active Url
|
|
Assert(NULL == m_pActiveUrl);
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrLoadInitNew
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrLoadInitNew(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// If there is not root body, normal InitNew
|
|
if (NULL == m_pRootNode || RELOAD_HEADER_RESET == m_rOptions.ReloadType)
|
|
{
|
|
// InitNew
|
|
CHECKHR(hr = InitNew());
|
|
}
|
|
|
|
// Otherwise, smart init new, allowing root header merge
|
|
else
|
|
{
|
|
// Reset the Object
|
|
_ResetObject(BOOKTREE_RESET_LOADINITNEW);
|
|
|
|
// Reset Vars
|
|
m_cbMessage = 0;
|
|
m_dwState = 0;
|
|
|
|
// Assume the Bind has Finished
|
|
FLAGSET(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// Reset charset to system charset
|
|
m_rOptions.pCharset = CIntlGlobals::GetDefBodyCset();
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_InitNewWithoutRoot
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_InitNewWithoutRoot(void)
|
|
{
|
|
// Reset the Object
|
|
_ResetObject(BOOKTREE_RESET_INITNEW);
|
|
|
|
// Reset Vars
|
|
m_cbMessage = 0;
|
|
m_dwState = 0;
|
|
m_wTag++;
|
|
|
|
// Invalid Tag Numbers
|
|
while(m_wTag == 0 || m_wTag == 0xffff)
|
|
m_wTag++;
|
|
|
|
// Assume the Bind has Finished
|
|
FLAGSET(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// Reset charset to system charset
|
|
m_rOptions.pCharset = CIntlGlobals::GetDefBodyCset();
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::InitNew
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::InitNew(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// _InitNewWithoutRoot
|
|
_InitNewWithoutRoot();
|
|
|
|
// Init the Root Body...
|
|
CHECKHR(hr = InsertBody(IBL_ROOT, NULL, NULL));
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::IsDirty
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::IsDirty(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_FALSE;
|
|
ULONG i;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// If Dirty...
|
|
if (ISFLAGSET(m_dwState, TREESTATE_DIRTY))
|
|
{
|
|
hr = S_OK;
|
|
goto exit;
|
|
}
|
|
|
|
// Loop through bodies and ask IMimeHeader's and IMimeBody's
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Better have it
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Dirty Header...
|
|
if (m_rTree.prgpNode[i]->pBody->IsDirty() == S_OK)
|
|
{
|
|
hr = S_OK;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_RecursiveGetFlags
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_RecursiveGetFlags(LPTREENODEINFO pNode, LPDWORD pdwFlags, BOOL fInRelated)
|
|
{
|
|
// Locals
|
|
DWORD dw;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode && pdwFlags);
|
|
|
|
// $$WARNING$$ Don't use pNode->pContainer here, that will circumvent CMimeBody's chance to set some flags
|
|
dw = pNode->pBody->DwGetFlags(m_rOptions.fHideTnef);
|
|
|
|
// If in related, clear IMF_ATTACHMENTS
|
|
if (fInRelated)
|
|
FLAGCLEAR(dw, IMF_ATTACHMENTS);
|
|
|
|
// Raid-44446: not getting paperclip icon in listview on pegasus messages w/ text attach
|
|
// If dw has text and no attachments and pdwFlags has text and no attachments, add attachments
|
|
//
|
|
// Raid-11617: OE: GetAttachmentCount should not include vcards
|
|
if (ISFLAGSET(dw, IMF_TEXT) && !ISFLAGSET(dw, IMF_HASVCARD) && ISFLAGSET(*pdwFlags, IMF_TEXT) && !ISFLAGSET(dw, IMF_ATTACHMENTS) && !ISFLAGSET(*pdwFlags, IMF_ATTACHMENTS))
|
|
{
|
|
// As long as pNode is not in an alternative section
|
|
if (NULL == pNode->pParent || pNode->pParent->pContainer->IsContentType(STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_FALSE)
|
|
{
|
|
// This message must have text attachments
|
|
FLAGSET(*pdwFlags, IMF_ATTACHMENTS);
|
|
}
|
|
}
|
|
|
|
// Add in Flags
|
|
FLAGSET(*pdwFlags, dw);
|
|
|
|
// Partial...
|
|
if (ISFLAGSET(pNode->dwType, NODETYPE_INCOMPLETE))
|
|
FLAGSET(*pdwFlags, IMF_PARTIAL);
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Sub-multipart
|
|
FLAGSET(*pdwFlags, IMF_SUBMULTIPART);
|
|
|
|
// If fInRelated == FALSE...
|
|
if (FALSE == fInRelated)
|
|
fInRelated = (S_OK == pNode->pContainer->IsContentType(NULL, STR_SUB_RELATED) ? TRUE : FALSE);
|
|
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Get the flags for this child node
|
|
_RecursiveGetFlags(pChild, pdwFlags, fInRelated);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DwGetFlags
|
|
// --------------------------------------------------------------------------------
|
|
DWORD CMessageTree::DwGetFlags(void)
|
|
{
|
|
// Locals
|
|
DWORD dwFlags=0;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Recurse the tree
|
|
if (m_pRootNode && m_pRootNode->pBody->IsType(IBT_EMPTY) == S_FALSE)
|
|
_RecursiveGetFlags(m_pRootNode, &dwFlags, (S_OK == m_pRootNode->pContainer->IsContentType(NULL, STR_SUB_RELATED) ? TRUE : FALSE));
|
|
|
|
if (m_pRootNode && ISFLAGSET(m_pRootNode->dwType, NODETYPE_RFC1154_ROOT))
|
|
FLAGSET(dwFlags, IMF_RFC1154);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return dwFlags;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::GetFlags
|
|
// ----------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetFlags(DWORD *pdwFlags)
|
|
{
|
|
// Invalid Arg
|
|
if (NULL == pdwFlags)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// dwgetflags has a critsec
|
|
*pdwFlags = DwGetFlags();
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::_FreeTreeNodeInfo
|
|
// ----------------------------------------------------------------------------
|
|
void CMessageTree::_FreeTreeNodeInfo(LPTREENODEINFO pNode, BOOL fFull /* TRUE */)
|
|
{
|
|
// Invalid
|
|
Assert(pNode);
|
|
|
|
// Free Boundary info
|
|
if (!ISFLAGSET(pNode->dwState, NODESTATE_BOUNDNOFREE))
|
|
SafeMemFree(pNode->rBoundary.pszVal);
|
|
|
|
// Full Free
|
|
if (TRUE == fFull)
|
|
{
|
|
// Release the Container
|
|
SafeRelease(pNode->pContainer);
|
|
|
|
// Revoke the TreeNode from the body
|
|
if (pNode->pBody)
|
|
{
|
|
// Revoke pNode
|
|
pNode->pBody->RevokeTreeNode();
|
|
|
|
// Release the body object
|
|
SafeRelease(pNode->pBody);
|
|
|
|
// Null It
|
|
pNode->pBody = NULL;
|
|
}
|
|
}
|
|
|
|
// Orphan the lockbytes
|
|
if (pNode->pLockBytes)
|
|
{
|
|
// Orhpan It
|
|
pNode->pLockBytes->HrHandsOffStorage();
|
|
|
|
// Release Body Lock Bytes
|
|
SafeRelease(pNode->pLockBytes);
|
|
}
|
|
|
|
// Free Bind Request List
|
|
if (pNode->pResolved)
|
|
_ReleaseUrlRequestList(&pNode->pResolved);
|
|
|
|
// Free the node
|
|
if (fFull)
|
|
g_pMalloc->Free(pNode);
|
|
}
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// CMessageTree::_FreeNodeTableElements
|
|
// ----------------------------------------------------------------------------
|
|
void CMessageTree::_FreeNodeTableElements(void)
|
|
{
|
|
// Release all of the headers
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Better have a bindinfo
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Free the node info
|
|
_FreeTreeNodeInfo(m_rTree.prgpNode[i]);
|
|
}
|
|
|
|
// Zero
|
|
m_rTree.cNodes = 0;
|
|
m_rTree.cEmpty = 0;
|
|
|
|
// No Root Body...
|
|
m_pRootNode = NULL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrAllocateTreeNode
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrAllocateTreeNode(ULONG ulIndex)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// Check Params
|
|
Assert(ulIndex < m_rTree.cAlloc);
|
|
|
|
// Allocate a TREENODEINFO Object
|
|
CHECKALLOC(pNode = (LPTREENODEINFO)g_pMalloc->Alloc(sizeof(TREENODEINFO)));
|
|
|
|
// ZeroInit
|
|
ZeroMemory(pNode, sizeof(TREENODEINFO));
|
|
|
|
// Allocate the body
|
|
CHECKALLOC(pNode->pBody = new CMessageBody(pNode));
|
|
|
|
// InitNew
|
|
CHECKHR(hr = pNode->pBody->InitNew());
|
|
|
|
// Pass Down Some Inherited Options
|
|
if (m_rOptions.fExternalBody != DEF_SUPPORT_EXTERNAL_BODY)
|
|
{
|
|
// Locals
|
|
PROPVARIANT Variant;
|
|
|
|
// Initialize the Variant
|
|
Variant.vt = VT_BOOL;
|
|
Variant.boolVal = (VARIANT_BOOL) !!m_rOptions.fExternalBody;
|
|
|
|
// Set the Option
|
|
SideAssert(SUCCEEDED(pNode->pBody->SetOption(OID_SUPPORT_EXTERNAL_BODY, &Variant)));
|
|
}
|
|
|
|
// Get the Container
|
|
SideAssert(SUCCEEDED(pNode->pBody->BindToObject(IID_CMimePropertyContainer, (LPVOID *)&pNode->pContainer)));
|
|
|
|
// Create hBody
|
|
pNode->hBody = HBODYMAKE(ulIndex);
|
|
|
|
// Readability
|
|
m_rTree.prgpNode[ulIndex] = pNode;
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::LoadOffsetTable
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::LoadOffsetTable(LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
CACHEINFOV2 rInfo;
|
|
LPCACHENODEV2 prgNode=NULL;
|
|
ULONG cbNodes,
|
|
i;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == pStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init New
|
|
_InitNewWithoutRoot();
|
|
|
|
// Free the root
|
|
Assert(NULL == m_pRootNode && 0 == m_rTree.cNodes);
|
|
|
|
// Read Header...
|
|
CHECKHR(hr = pStream->Read(&rInfo, sizeof(CACHEINFOV2), NULL));
|
|
|
|
// Current Version...
|
|
if (VER_BODYTREEV2 == rInfo.wVersion)
|
|
{
|
|
// Save Message Size
|
|
m_cbMessage = rInfo.cbMessage;
|
|
|
|
// Are there bodies...
|
|
Assert(rInfo.cNodes >= 1);
|
|
|
|
// Better have a root
|
|
if (FVerifySignedNode(rInfo, rInfo.iRoot) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Compute sizeof Nodes
|
|
cbNodes = sizeof(CACHENODEV2) * rInfo.cNodes;
|
|
Assert(cbNodes % 4 == 0);
|
|
|
|
// Allocate prgNode array
|
|
CHECKHR(hr = HrAlloc((LPVOID *)&prgNode, cbNodes));
|
|
|
|
// Read Nodes...
|
|
CHECKHR(hr = pStream->Read(prgNode, cbNodes, NULL));
|
|
|
|
// Set body count
|
|
m_rTree.cNodes = rInfo.cNodes;
|
|
m_rTree.cAlloc = m_rTree.cNodes + 5;
|
|
|
|
// Build Body Table
|
|
CHECKHR(hr = HrRealloc((LPVOID *)&m_rTree.prgpNode, sizeof(LPTREENODEINFO) * m_rTree.cAlloc));
|
|
|
|
// Zero Init
|
|
ZeroMemory(m_rTree.prgpNode, sizeof(LPTREENODEINFO) * m_rTree.cAlloc);
|
|
|
|
// Build bodies
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Allocate LPBINDINFO
|
|
CHECKHR(hr = _HrAllocateTreeNode(i));
|
|
}
|
|
|
|
// Link Body Table
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
pNode = m_rTree.prgpNode[i];
|
|
Assert(pNode);
|
|
|
|
// Flags
|
|
pNode->dwType = prgNode[i].dwType;
|
|
|
|
// Number of Children
|
|
pNode->cChildren = prgNode[i].cChildren;
|
|
|
|
// Valid Boundary
|
|
if (prgNode[i].dwBoundary >= BOUNDARY_LAST || 2 == prgNode[i].dwBoundary)
|
|
pNode->boundary = BOUNDARY_NONE;
|
|
else
|
|
pNode->boundary = (BOUNDARYTYPE)prgNode[i].dwBoundary;
|
|
|
|
// Offset
|
|
pNode->cbBoundaryStart = prgNode[i].cbBoundaryStart;
|
|
pNode->cbHeaderStart = prgNode[i].cbHeaderStart;
|
|
pNode->cbBodyStart = prgNode[i].cbBodyStart;
|
|
pNode->cbBodyEnd = prgNode[i].cbBodyEnd;
|
|
|
|
// Parent
|
|
if (prgNode[i].iParent)
|
|
{
|
|
// Validate the handle with the signature
|
|
if (FVerifySignedNode(rInfo, prgNode[i].iParent) == FALSE)
|
|
{
|
|
AssertSz(FALSE, "MIME_E_CORRUPT_CACHE_TREE");
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Get the parent
|
|
pNode->pParent = PNodeFromSignedNode(prgNode[i].iParent);
|
|
}
|
|
|
|
// Next
|
|
if (prgNode[i].iNext)
|
|
{
|
|
// Validate the handle with the signature
|
|
if (FVerifySignedNode(rInfo, prgNode[i].iNext) == FALSE)
|
|
{
|
|
AssertSz(FALSE, "MIME_E_CORRUPT_CACHE_TREE");
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Get the Next
|
|
pNode->pNext = PNodeFromSignedNode(prgNode[i].iNext);
|
|
}
|
|
|
|
// Prev
|
|
if (prgNode[i].iPrev)
|
|
{
|
|
// Validate the handle with the signature
|
|
if (FVerifySignedNode(rInfo, prgNode[i].iPrev) == FALSE)
|
|
{
|
|
AssertSz(FALSE, "MIME_E_CORRUPT_CACHE_TREE");
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Get the Prev
|
|
pNode->pPrev = PNodeFromSignedNode(prgNode[i].iPrev);
|
|
}
|
|
|
|
// First Child
|
|
if (prgNode[i].iChildHead)
|
|
{
|
|
// Validate the handle with the signature
|
|
if (FVerifySignedNode(rInfo, prgNode[i].iChildHead) == FALSE)
|
|
{
|
|
AssertSz(FALSE, "MIME_E_CORRUPT_CACHE_TREE");
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Get the first child
|
|
pNode->pChildHead = PNodeFromSignedNode(prgNode[i].iChildHead);
|
|
}
|
|
|
|
// Tail
|
|
if (prgNode[i].iChildTail)
|
|
{
|
|
// Validate the handle with the signature
|
|
if (FVerifySignedNode(rInfo, prgNode[i].iChildTail) == FALSE)
|
|
{
|
|
AssertSz(FALSE, "MIME_E_CORRUPT_CACHE_TREE");
|
|
hr = TrapError(MIME_E_CORRUPT_CACHE_TREE);
|
|
goto exit;
|
|
}
|
|
|
|
// Get the last child
|
|
pNode->pChildTail = PNodeFromSignedNode(prgNode[i].iChildTail);
|
|
}
|
|
}
|
|
|
|
// Save Root Handle
|
|
Assert(NULL == m_pRootNode);
|
|
m_pRootNode = PNodeFromSignedNode(rInfo.iRoot);
|
|
}
|
|
|
|
// Otherwise, bad version...
|
|
else
|
|
{
|
|
hr = TrapError(MIME_E_UNKNOWN_BODYTREE_VERSION);
|
|
goto exit;
|
|
}
|
|
|
|
// Tree Loaded
|
|
FLAGSET(m_dwState, TREESTATE_LOADED);
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(prgNode);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SaveOffsetTable
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SaveOffsetTable(LPSTREAM pStream, DWORD dwFlags)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG i,
|
|
cbNodes=0,
|
|
iNode;
|
|
LPTREENODEINFO pNode;
|
|
CACHEINFOV2 rInfo;
|
|
LPCACHENODEV2 prgNode=NULL;
|
|
|
|
// check params
|
|
if (NULL == pStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// We better have some bodies (we always have a root)
|
|
Assert(m_rTree.cNodes >= 1);
|
|
|
|
// If Dirty, SaveMessage needs to be called first...
|
|
if (ISFLAGSET(dwFlags, COMMIT_ONLYIFDIRTY) && IsDirty() == S_OK)
|
|
{
|
|
// Commit it
|
|
CHECKHR(hr = Commit(dwFlags));
|
|
}
|
|
|
|
// I removed this check because of the addition of OID_HANDSOFF_ONSAVE option
|
|
// I need to be able to save the offsettable even if i don't have m_pStmLock
|
|
Assert(NULL == m_pStmLock ? S_FALSE == IsDirty() : TRUE);
|
|
#if 0
|
|
if (NULL == m_pStmLock)
|
|
{
|
|
hr = TrapError(MIME_E_NOTHING_TO_SAVE);
|
|
goto exit;
|
|
}
|
|
#endif
|
|
|
|
// Init rHeader
|
|
ZeroMemory(&rInfo, sizeof(CACHEINFOV2));
|
|
|
|
// Loop bodies
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
if (m_rTree.prgpNode[i])
|
|
m_rTree.prgpNode[i]->iCacheNode = rInfo.cNodes++;
|
|
}
|
|
|
|
// Version
|
|
rInfo.wVersion = VER_BODYTREEV2;
|
|
rInfo.wSignature = m_wTag;
|
|
rInfo.cbMessage = m_cbMessage;
|
|
|
|
// Better have a root
|
|
Assert(m_pRootNode);
|
|
|
|
// Compute sizeof Nodes
|
|
cbNodes = sizeof(CACHENODEV2) * rInfo.cNodes;
|
|
Assert(cbNodes % 4 == 0);
|
|
|
|
// Allocate prgNode array
|
|
CHECKHR(hr = HrAlloc((LPVOID *)&prgNode, cbNodes));
|
|
|
|
// Zero the array
|
|
ZeroMemory(prgNode, cbNodes);
|
|
|
|
// Loop bodies
|
|
for (i=0, iNode=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
pNode = m_rTree.prgpNode[i];
|
|
if (NULL == pNode)
|
|
continue;
|
|
|
|
// Validate this node
|
|
Assert(pNode->hBody == HBODYMAKE(i));
|
|
Assert(pNode->iCacheNode == iNode);
|
|
|
|
// Is this the root
|
|
if (pNode == m_pRootNode)
|
|
{
|
|
Assert(0 == rInfo.iRoot);
|
|
rInfo.iRoot = DwSignNode(rInfo, pNode->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, rInfo.iRoot));
|
|
}
|
|
|
|
// Copy Offset Information
|
|
prgNode[iNode].dwBoundary = pNode->boundary;
|
|
prgNode[iNode].cbBoundaryStart = pNode->cbBoundaryStart;
|
|
prgNode[iNode].cbHeaderStart = pNode->cbHeaderStart;
|
|
prgNode[iNode].cbBodyStart = pNode->cbBodyStart;
|
|
prgNode[iNode].cbBodyEnd = pNode->cbBodyEnd;
|
|
|
|
// Bitmask of NODETYPE_xxx describing this body
|
|
prgNode[iNode].dwType = pNode->dwType;
|
|
|
|
// Number of children
|
|
prgNode[iNode].cChildren = pNode->cChildren;
|
|
|
|
// Parent
|
|
if (pNode->pParent)
|
|
{
|
|
prgNode[iNode].iParent = DwSignNode(rInfo, pNode->pParent->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, prgNode[iNode].iParent));
|
|
}
|
|
|
|
// ChildHead
|
|
if (pNode->pChildHead)
|
|
{
|
|
prgNode[iNode].iChildHead = DwSignNode(rInfo, pNode->pChildHead->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, prgNode[iNode].iChildHead));
|
|
}
|
|
|
|
// ChildTail
|
|
if (pNode->pChildTail)
|
|
{
|
|
prgNode[iNode].iChildTail = DwSignNode(rInfo, pNode->pChildTail->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, prgNode[iNode].iChildTail));
|
|
}
|
|
|
|
// Next
|
|
if (pNode->pNext)
|
|
{
|
|
prgNode[iNode].iNext = DwSignNode(rInfo, pNode->pNext->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, prgNode[iNode].iNext));
|
|
}
|
|
|
|
// Prev
|
|
if (pNode->pPrev)
|
|
{
|
|
prgNode[iNode].iPrev = DwSignNode(rInfo, pNode->pPrev->iCacheNode);
|
|
Assert(FVerifySignedNode(rInfo, prgNode[iNode].iPrev));
|
|
}
|
|
|
|
// Increment iNode
|
|
iNode++;
|
|
}
|
|
|
|
// Write the header...
|
|
Assert(sizeof(CACHEINFOV2) % 4 == 0 && rInfo.iRoot);
|
|
CHECKHR(hr = pStream->Write(&rInfo, sizeof(CACHEINFOV2), NULL));
|
|
|
|
// Write the nodes
|
|
CHECKHR(hr = pStream->Write(prgNode, cbNodes, NULL));
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(prgNode);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::Commit
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Commit(DWORD dwFlags)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pStream=NULL;
|
|
ULARGE_INTEGER uli;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Not Dirty and it has been saved into m_pStmLock
|
|
if (IsDirty() == S_FALSE && m_pStmLock)
|
|
goto exit;
|
|
|
|
// Reuse Storage
|
|
if (ISFLAGSET(dwFlags, COMMIT_REUSESTORAGE) && ISFLAGSET(m_dwState, TREESTATE_HANDSONSTORAGE) && m_pStmLock)
|
|
{
|
|
// Get the current stream from m_pStmLock
|
|
m_pStmLock->GetCurrentStream(&pStream);
|
|
|
|
// Hands off of current storage
|
|
CHECKHR(hr = HandsOffStorage());
|
|
|
|
// Rewind the stream
|
|
CHECKHR(hr = HrRewindStream(pStream));
|
|
|
|
// SetSize to Zero
|
|
INT64SET(&uli, 0);
|
|
pStream->SetSize(uli);
|
|
|
|
// Call Save Message
|
|
CHECKHR(hr = _HrWriteMessage(pStream, TRUE, FALSE, FALSE));
|
|
}
|
|
|
|
// Otherwise, I'll create my own storage
|
|
else
|
|
{
|
|
// Create a new stream
|
|
CHECKALLOC(pStream = new CVirtualStream);
|
|
|
|
// Call Save Message
|
|
CHECKHR(hr = _HrWriteMessage(pStream, TRUE, FALSE,
|
|
!!(dwFlags & COMMIT_SMIMETRANSFERENCODE)));
|
|
|
|
// Hands are off..
|
|
FLAGCLEAR(m_dwState, TREESTATE_HANDSONSTORAGE);
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pStream);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::Save
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Save(IStream *pStream, BOOL fClearDirty)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
|
|
// check params
|
|
if (pStream == NULL)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Not dirty, and we have a stream $$$INFO$$ should be using m_pLockBytes here if we have one
|
|
if (IsDirty() == S_FALSE && m_pStmLock)
|
|
{
|
|
// Copy Lock Bytes to Stream
|
|
CHECKHR(hr = HrCopyLockBytesToStream(m_pStmLock, pStream, NULL));
|
|
|
|
// Commit
|
|
CHECKHR(hr = pStream->Commit(STGC_DEFAULT));
|
|
|
|
// Raid-33985: MIMEOLE: CMessageTree:Save does not respect fHandsOffOnSave == FALSE if the message is not dirty
|
|
if (FALSE == m_rOptions.fHandsOffOnSave)
|
|
{
|
|
// Replace internal stream
|
|
m_pStmLock->ReplaceInternalStream(pStream);
|
|
|
|
// Hands are on..
|
|
FLAGSET(m_dwState, TREESTATE_HANDSONSTORAGE);
|
|
}
|
|
|
|
// Were Done
|
|
goto exit;
|
|
}
|
|
|
|
// Write the message
|
|
CHECKHR(hr = _HrWriteMessage(pStream, fClearDirty, m_rOptions.fHandsOffOnSave, FALSE));
|
|
|
|
// Return Warnings
|
|
if (S_OK != hr)
|
|
hrWarnings = TrapError(hr);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrWriteMessage
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrWriteMessage(IStream *pStream, BOOL fClearDirty, BOOL fHandsOffOnSave, BOOL fSMimeCTE)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
MIMEPROPINFO rPropInfo;
|
|
DWORD dwSaveFlags;
|
|
INETCSETINFO rCharset;
|
|
LPINETCSETINFO pOriginal=NULL;
|
|
|
|
// This Function is re-entrant when saving a message that is signed and/or encrypted
|
|
if (FALSE == m_fApplySaveSecurity)
|
|
{
|
|
// Character Set Fixup
|
|
if (m_rOptions.pCharset)
|
|
{
|
|
// RAID-25300 - FE-J:Athena: Newsgroup article and mail sent with charset=_autodetect Internet Encoded and Windows Encoding are CPI_AUTODETECT
|
|
if (CP_JAUTODETECT == m_rOptions.pCharset->cpiInternet)
|
|
{
|
|
// Save Current Charset
|
|
pOriginal = m_rOptions.pCharset;
|
|
|
|
// Find ISO-2022-JP
|
|
SideAssert(SUCCEEDED(g_pInternat->HrOpenCharset(c_szISO2022JP, &m_rOptions.pCharset)));
|
|
}
|
|
|
|
// Raid-8436: OE: non standard MIME header is composed when send from Send as Unicode dialog, if UTF-7 or UTF-8, and not saving as mime...
|
|
else if (SAVE_RFC822 == m_rOptions.savetype && (CP_UTF7 == m_rOptions.pCharset->cpiInternet || CP_UTF8 == m_rOptions.pCharset->cpiInternet))
|
|
{
|
|
// Save Current Charset
|
|
pOriginal = m_rOptions.pCharset;
|
|
|
|
// Get the default body charset
|
|
if (FAILED(g_pInternat->HrOpenCharset(GetACP(), CHARSET_BODY, &m_rOptions.pCharset)))
|
|
m_rOptions.pCharset = NULL;
|
|
}
|
|
}
|
|
|
|
// State
|
|
m_fApplySaveSecurity = TRUE;
|
|
|
|
// Do Message Save Security
|
|
hr = _HrApplySaveSecurity();
|
|
|
|
// Not in Apply Save security
|
|
m_fApplySaveSecurity = FALSE;
|
|
|
|
// Failure
|
|
if (FAILED(hr))
|
|
goto exit;
|
|
}
|
|
|
|
// Cleanup the message (i.e. remove empty multiparts, multiparts that have a single child that is a multipart, TNEF)
|
|
if (TRUE == m_rOptions.fCleanupTree)
|
|
{
|
|
// Call Espiranza and have her do the cleaning
|
|
CHECKHR(hr = _HrCleanupMessageTree(m_pRootNode));
|
|
}
|
|
|
|
// Generate Message Id...
|
|
if (m_rOptions.fGenMessageId)
|
|
{
|
|
// Set the message Id
|
|
_HrSetMessageId(m_pRootNode);
|
|
}
|
|
|
|
// Determine if we are saving a News Message
|
|
rPropInfo.dwMask = 0;
|
|
if (SUCCEEDED(m_pRootNode->pContainer->GetPropInfo(PIDTOSTR(PID_HDR_XNEWSRDR), &rPropInfo)) ||
|
|
SUCCEEDED(m_pRootNode->pContainer->GetPropInfo(PIDTOSTR(PID_HDR_NEWSGROUPS), &rPropInfo)))
|
|
FLAGSET(m_dwState, TREESTATE_SAVENEWS);
|
|
|
|
// Set MIME Version
|
|
CHECKHR(hr = m_pRootNode->pContainer->SetProp(PIDTOSTR(PID_HDR_MIMEVER), c_szMimeVersion));
|
|
|
|
// X-MimeOLE Version
|
|
CHECKHR(hr = m_pRootNode->pContainer->SetProp(STR_HDR_XMIMEOLE, STR_MIMEOLE_VERSION));
|
|
|
|
// Remove Types...
|
|
m_pRootNode->pContainer->DeleteProp(STR_HDR_ENCODING);
|
|
|
|
// Root
|
|
m_pRootNode->boundary = BOUNDARY_ROOT;
|
|
m_pRootNode->cbBoundaryStart = 0;
|
|
|
|
// Set SaveBody Flags
|
|
dwSaveFlags = SAVEBODY_UPDATENODES;
|
|
if (m_rOptions.fKeepBoundary)
|
|
FLAGSET(dwSaveFlags, SAVEBODY_KEEPBOUNDARY);
|
|
|
|
if (fSMimeCTE)
|
|
FLAGSET(dwSaveFlags, SAVEBODY_SMIMECTE);
|
|
|
|
// Save Root body
|
|
CHECKHR(hr = _HrSaveBody(fClearDirty, dwSaveFlags, pStream, m_pRootNode, 0));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Commit
|
|
CHECKHR(hr = pStream->Commit(STGC_DEFAULT));
|
|
|
|
// Hands Off On Save ?
|
|
if (FALSE == fHandsOffOnSave)
|
|
{
|
|
// Reset message size
|
|
CHECKHR(hr = HrSafeGetStreamSize(pStream, &m_cbMessage));
|
|
|
|
// Save this new stream
|
|
SafeRelease(m_pStmLock);
|
|
|
|
// Create a new Stream Lock Bytes Wrapper
|
|
CHECKALLOC(m_pStmLock = new CStreamLockBytes(pStream));
|
|
|
|
// Hands are on the storage
|
|
FLAGSET(m_dwState, TREESTATE_HANDSONSTORAGE);
|
|
}
|
|
|
|
// Debug to temp file...
|
|
DebugWriteMsg(pStream);
|
|
|
|
// Clear Dirty
|
|
if (fClearDirty)
|
|
ClearDirty();
|
|
|
|
exit:
|
|
// Reset Original Charset
|
|
if (pOriginal)
|
|
m_rOptions.pCharset = pOriginal;
|
|
|
|
// Remove state flag the tells us to reuse multipart/signed boundaries
|
|
FLAGCLEAR(m_dwState, TREESTATE_REUSESIGNBOUND);
|
|
|
|
// Reset
|
|
FLAGCLEAR(m_dwState, TREESTATE_SAVENEWS);
|
|
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrApplySaveSecurity
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrApplySaveSecurity(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPVARIANT var;
|
|
CSMime *pSMime=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(m_pRootNode);
|
|
|
|
m_pRootNode->pBody->GetOption(OID_NOSECURITY_ONSAVE, &var);
|
|
if (var.boolVal) goto exit;
|
|
|
|
// Query the root body for secure status
|
|
m_pRootNode->pBody->GetOption(OID_SECURITY_TYPE, &var);
|
|
if (MST_NONE != var.ulVal)
|
|
{
|
|
// Create the object
|
|
CHECKALLOC(pSMime = new CSMime);
|
|
|
|
// Initialize the object
|
|
CHECKHR(hr = pSMime->InitNew());
|
|
|
|
// Set state flag the tells us to reuse multipart/signed boundaries
|
|
FLAGSET(m_dwState, TREESTATE_REUSESIGNBOUND);
|
|
|
|
// Encode the message
|
|
CHECKHR(hr = pSMime->EncodeMessage(this, m_rOptions.ulSecIgnoreMask));
|
|
}
|
|
|
|
exit:
|
|
ReleaseObj(pSMime);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrCleanupMessageTree
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrCleanupMessageTree(LPTREENODEINFO pParent)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
ULONG i;
|
|
BOOL fKeepOnTruckin=TRUE;
|
|
|
|
// check params
|
|
Assert(pParent);
|
|
|
|
// This could require multiple passes
|
|
while(fKeepOnTruckin)
|
|
{
|
|
// Assume we will not have to do another pass
|
|
fKeepOnTruckin = FALSE;
|
|
|
|
// Loop through bodies
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
pNode = m_rTree.prgpNode[i];
|
|
if (NULL == pNode)
|
|
continue;
|
|
|
|
// Hiding TNEF Attachments ?
|
|
if (TRUE == m_rOptions.fHideTnef && pNode->pContainer->IsContentType(STR_CNT_APPLICATION, STR_SUB_MSTNEF) == S_OK)
|
|
{
|
|
// Remove this TNEF attachment
|
|
CHECKHR(hr = DeleteBody(pNode->hBody, 0));
|
|
|
|
// Lets Stop right here, and start another pass
|
|
fKeepOnTruckin = TRUE;
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
|
|
// Empty multipart... and not the root... ?
|
|
else if (_IsMultiPart(pNode))
|
|
{
|
|
// No Children ?
|
|
if (0 == pNode->cChildren)
|
|
{
|
|
// If this is the root...simply change the content type
|
|
if (m_pRootNode == pNode)
|
|
{
|
|
// Make the body empty
|
|
pNode->pBody->EmptyData();
|
|
|
|
// text/plain
|
|
pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN);
|
|
}
|
|
|
|
// Otherwise, delete the body
|
|
else
|
|
{
|
|
// Delete delete the body
|
|
CHECKHR(hr = DeleteBody(pNode->hBody, 0));
|
|
|
|
// Lets Stop right here, and start another pass
|
|
fKeepOnTruckin = TRUE;
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Otherwise, Multipart with a single child...
|
|
else if (pNode->cChildren == 1)
|
|
{
|
|
// Do a ReplaceBody
|
|
CHECKHR(hr = DeleteBody(pNode->hBody, DELETE_PROMOTE_CHILDREN));
|
|
|
|
// Lets Stop right here, and start another pass
|
|
fKeepOnTruckin = TRUE;
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SaveBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SaveBody(HBODY hBody, DWORD dwFlags, IStream *pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// Invalid ARg
|
|
if (NULL == pStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Save From This Body On Down
|
|
CHECKHR(hr = _HrSaveBody(TRUE, dwFlags, pStream, pNode, 0));
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSaveBody
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSaveBody(BOOL fClearDirty, DWORD dwFlags, IStream *pStream,
|
|
LPTREENODEINFO pNode, ULONG ulLevel)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
TREENODEINFO rOriginal;
|
|
BOOL fWeSetSaveBoundary=FALSE;
|
|
|
|
// Parameters
|
|
Assert(pStream && pNode);
|
|
|
|
if (ISFLAGSET(dwFlags, SAVEBODY_KEEPBOUNDARY))
|
|
{
|
|
if (!ISFLAGSET(m_dwState, TREESTATE_REUSESIGNBOUND))
|
|
{
|
|
fWeSetSaveBoundary = TRUE;
|
|
FLAGSET(m_dwState, TREESTATE_REUSESIGNBOUND);
|
|
}
|
|
}
|
|
|
|
// Save the Current Node
|
|
if (!ISFLAGSET(dwFlags, SAVEBODY_UPDATENODES))
|
|
CopyMemory(&rOriginal, pNode, sizeof(TREENODEINFO));
|
|
|
|
// Override Options
|
|
_HrBodyInheritOptions(pNode);
|
|
|
|
// Starting Boundary pNode->boundary and pNode->cbBoundaryStart are expected to be set on entry
|
|
pNode->cbHeaderStart = 0;
|
|
pNode->cbBodyStart = 0;
|
|
pNode->cbBodyEnd = 0;
|
|
|
|
// If this is a multipart content item, lets read its child
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Save Multipart Children
|
|
CHECKHR(hr = _HrSaveMultiPart(fClearDirty, dwFlags, pStream, pNode, ulLevel));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
}
|
|
|
|
#ifdef SMIME_V3
|
|
// OID content types are saved by just copying the body into the save
|
|
// location.
|
|
else if (pNode->pContainer->IsContentType("OID", NULL) == S_OK)
|
|
{
|
|
CHECKHR(hr = pNode->pBody->GetDataHere(IET_BINARY, pStream));
|
|
if (hr != S_OK)
|
|
{
|
|
hrWarnings = TrapError(hr);
|
|
}
|
|
}
|
|
#endif // SMIME_V3
|
|
|
|
// Otherwise, parse single part
|
|
else
|
|
{
|
|
// Save SinglePart Children
|
|
CHECKHR(hr = _HrSaveSinglePart(fClearDirty, dwFlags, pStream, pNode, ulLevel));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
}
|
|
|
|
// Reset the Node
|
|
if (!ISFLAGSET(dwFlags, SAVEBODY_UPDATENODES))
|
|
CopyMemory(pNode, &rOriginal, sizeof(TREENODEINFO));
|
|
|
|
exit:
|
|
if (fWeSetSaveBoundary)
|
|
FLAGCLEAR(m_dwState, TREESTATE_REUSESIGNBOUND);
|
|
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSetMessageId
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSetMessageId(LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr= S_OK;
|
|
CHAR szMessageId[CCHMAX_MID];
|
|
FILETIME ft;
|
|
SYSTEMTIME st;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode);
|
|
|
|
// Get Current Time
|
|
GetSystemTime(&st);
|
|
SystemTimeToFileTime(&st, &ft);
|
|
|
|
// Build MessageID
|
|
CHECKHR(hr = MimeOleGenerateMID(szMessageId, sizeof(szMessageId), FALSE));
|
|
|
|
// Write the message Id
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_MESSAGEID, szMessageId));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_GenerateBoundary
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_GenerateBoundary(LPSTR pszBoundary, DWORD cchSize, ULONG ulLevel)
|
|
{
|
|
// Locals
|
|
SYSTEMTIME stNow;
|
|
FILETIME ftNow;
|
|
WORD wCounter;
|
|
|
|
// Get Local Time
|
|
GetLocalTime(&stNow);
|
|
SystemTimeToFileTime(&stNow, &ftNow);
|
|
|
|
// Format the string
|
|
wnsprintfA(pszBoundary, cchSize, "----=_NextPart_%03d_%04X_%08.8lX.%08.8lX", ulLevel, DwCounterNext(), ftNow.dwHighDateTime, ftNow.dwLowDateTime);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrWriteBoundary
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrWriteBoundary(LPSTREAM pStream, LPSTR pszBoundary, BOUNDARYTYPE boundary,
|
|
LPDWORD pcboffStart, LPDWORD pcboffEnd)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD cbBoundaryStart;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream && pszBoundary);
|
|
|
|
// Header body CRLF
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// Starting Boundary Start
|
|
if (pcboffStart)
|
|
CHECKHR(hr = HrGetStreamPos(pStream, pcboffStart));
|
|
|
|
// --
|
|
CHECKHR(hr = pStream->Write(c_szDoubleDash, lstrlen(c_szDoubleDash), NULL));
|
|
|
|
// Write the boundary
|
|
CHECKHR(hr = pStream->Write(pszBoundary, lstrlen(pszBoundary), NULL));
|
|
|
|
// If end
|
|
if (BOUNDARY_MIMEEND == boundary)
|
|
{
|
|
// Write ending double dash
|
|
CHECKHR(hr = pStream->Write(c_szDoubleDash, lstrlen(c_szDoubleDash), NULL));
|
|
}
|
|
|
|
// Otherwise, set pNode->cbBoundaryStart
|
|
else
|
|
Assert(BOUNDARY_MIMENEXT == boundary);
|
|
|
|
// Emit Line Break;
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// BUG 38411: to be complient with RFC 1847 we have to include
|
|
// the last CRLF in the hash of a signed message. The S/MIME
|
|
// code relies on cbBodyEnd, so place this after the CRLF emit.
|
|
|
|
// Ending offset
|
|
if (pcboffEnd)
|
|
CHECKHR(hr = HrGetStreamPos(pStream, pcboffEnd));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrComputeBoundary
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrComputeBoundary(LPTREENODEINFO pNode, ULONG ulLevel, LPSTR pszBoundary, LONG cchMax)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
BOOL fGenerate=TRUE;
|
|
LPSTR pszCurrent=NULL;
|
|
|
|
// If reusing tree boundaries...
|
|
if (ISFLAGSET(m_dwState, TREESTATE_REUSESIGNBOUND))
|
|
{
|
|
// (this is required for multipart/signed -- t-erikne)
|
|
if (SUCCEEDED(pNode->pContainer->GetProp(SYM_PAR_BOUNDARY, &pszCurrent)))
|
|
{
|
|
// Better fit into cchMax
|
|
if (lstrlen(pszCurrent) <= cchMax - 1)
|
|
{
|
|
// Copy it to the out param
|
|
StrCpyN(pszBoundary, pszCurrent, cchMax);
|
|
|
|
// Don't generate
|
|
fGenerate = FALSE;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Generate a boundary ?
|
|
if (TRUE == fGenerate)
|
|
{
|
|
// Generate boundary
|
|
_GenerateBoundary(pszBoundary, cchMax, ulLevel);
|
|
|
|
// Set the boundary property...
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_PAR_BOUNDARY, pszBoundary));
|
|
}
|
|
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszCurrent);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSaveMultiPart
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSaveMultiPart(BOOL fClearDirty, DWORD dwFlags, LPSTREAM pStream,
|
|
LPTREENODEINFO pNode, ULONG ulLevel)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
CHAR szRes[100];
|
|
CHAR szBoundary[CCHMAX_BOUNDARY];
|
|
LPTREENODEINFO pChild;
|
|
LPSTR pszBoundary=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream && pNode);
|
|
|
|
// MIME
|
|
if (SAVE_RFC1521 == m_rOptions.savetype)
|
|
{
|
|
// Remove Fake Multipart flag, its a real multipart now...
|
|
FLAGCLEAR(pNode->dwType, NODETYPE_FAKEMULTIPART);
|
|
FLAGCLEAR(pNode->dwType, NODETYPE_RFC1154_ROOT);
|
|
FLAGCLEAR(pNode->dwType, NODETYPE_RFC1154_BINHEX);
|
|
|
|
// HrComputeBoundary
|
|
CHECKHR(hr = _HrComputeBoundary(pNode, ulLevel, szBoundary, ARRAYSIZE(szBoundary)));
|
|
|
|
// Delete any charset information (doesn't make sense on a multipart)
|
|
pNode->pContainer->DeleteProp(SYM_PAR_CHARSET);
|
|
|
|
// Write the header
|
|
CHECKHR(hr = _HrWriteHeader(fClearDirty, pStream, pNode));
|
|
|
|
// Remove SMIME_CTE for Multipart/signed
|
|
if ((pNode->pContainer->IsContentType(STR_CNT_MULTIPART, STR_SUB_SIGNED) == S_OK) &&
|
|
(pNode->cChildren == 2))
|
|
{
|
|
FLAGCLEAR(dwFlags, SAVEBODY_SMIMECTE);
|
|
FLAGSET(dwFlags, SAVEBODY_REUSECTE);
|
|
}
|
|
|
|
// Multipart-Preamble
|
|
if (0 == ulLevel)
|
|
{
|
|
LoadString(g_hLocRes, IDS_MULTIPARTPROLOG, szRes, ARRAYSIZE(szRes));
|
|
CHECKHR(hr = pStream->Write(szRes, lstrlen(szRes), NULL));
|
|
}
|
|
|
|
// Increment Level
|
|
ulLevel++;
|
|
|
|
// Loop Chilren
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Set Boundary
|
|
pChild->boundary = BOUNDARY_MIMENEXT;
|
|
|
|
// Write Boundary
|
|
CHECKHR(hr = _HrWriteBoundary(pStream, szBoundary, BOUNDARY_MIMENEXT, &pChild->cbBoundaryStart, NULL));
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrSaveBody(fClearDirty, dwFlags, pStream, pChild, ulLevel));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
}
|
|
|
|
// Write Ending Boundary
|
|
CHECKHR(hr = _HrWriteBoundary(pStream, szBoundary, BOUNDARY_MIMEEND, NULL, &pNode->cbBodyEnd));
|
|
}
|
|
|
|
// Otherwise, SAVE_RFC822
|
|
else
|
|
{
|
|
// Only write UUENCODED root header...
|
|
if (0 == ulLevel)
|
|
{
|
|
// Write the header
|
|
CHECKHR(hr = _HrWriteHeader(fClearDirty, pStream, pNode));
|
|
}
|
|
|
|
// Increment Level
|
|
ulLevel++;
|
|
|
|
// Now its a fakemultipart...
|
|
FLAGSET(pNode->dwType, NODETYPE_FAKEMULTIPART);
|
|
|
|
// Loop Chilren
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrSaveBody(fClearDirty, dwFlags, pStream, pChild, ulLevel));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
}
|
|
|
|
// Body Start...
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyEnd));
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrWriteHeader
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrWriteHeader(BOOL fClearDirty, IStream *pStream, LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream && pNode);
|
|
|
|
// Better be the root
|
|
Assert(pNode->boundary == BOUNDARY_ROOT || pNode->boundary == BOUNDARY_MIMENEXT ||
|
|
pNode->boundary == BOUNDARY_NONE);
|
|
|
|
// Get current stream position
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbHeaderStart));
|
|
|
|
// Write the header...
|
|
CHECKHR(hr = pNode->pContainer->Save(pStream, fClearDirty));
|
|
|
|
// Header body CRLF
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// Get Header End
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyStart));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_GetContentTransferEncoding
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_GetContentTransferEncoding(LPTREENODEINFO pNode, BOOL fText,
|
|
BOOL fPlain, BOOL fMessage, BOOL fAttachment, DWORD dwFlags,
|
|
ENCODINGTYPE *pietEncoding)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
TRANSMITINFO rXmitInfo;
|
|
PROPVARIANT rOption;
|
|
|
|
*pietEncoding=IET_UNKNOWN;
|
|
|
|
if (ISFLAGSET(dwFlags, SAVEBODY_REUSECTE))
|
|
{
|
|
pNode->pBody->GetPreviousEncoding(pietEncoding);
|
|
if (*pietEncoding != IET_UNKNOWN)
|
|
goto exit;
|
|
}
|
|
|
|
|
|
// If mesage/*, always use 7bit
|
|
if (fMessage)
|
|
{
|
|
// Don't Wrap It
|
|
rOption.vt = VT_BOOL;
|
|
rOption.boolVal = FALSE;
|
|
pNode->pBody->SetOption(OID_WRAP_BODY_TEXT, &rOption);
|
|
|
|
// Set Encoding
|
|
*pietEncoding = (SAVE_RFC1521 == m_rOptions.savetype) ? IET_7BIT : IET_UUENCODE;
|
|
|
|
// Done
|
|
goto smimeExit;
|
|
}
|
|
|
|
// Use Option for text transmit format
|
|
if (fText && !fAttachment)
|
|
{
|
|
// Default to plain text encoding first
|
|
if (IET_UNKNOWN != m_rOptions.ietTextXmit)
|
|
*pietEncoding = m_rOptions.ietTextXmit;
|
|
|
|
// Plain
|
|
if (IET_UNKNOWN != m_rOptions.ietPlainXmit && pNode->pContainer->IsContentType(NULL, STR_SUB_PLAIN) == S_OK)
|
|
*pietEncoding = m_rOptions.ietPlainXmit;
|
|
|
|
// Html
|
|
else if (IET_UNKNOWN != m_rOptions.ietHtmlXmit && pNode->pContainer->IsContentType(NULL, STR_SUB_HTML) == S_OK)
|
|
*pietEncoding = m_rOptions.ietHtmlXmit;
|
|
}
|
|
|
|
// Not known yet, using body option...
|
|
if (IET_UNKNOWN == *pietEncoding)
|
|
{
|
|
// Try to get the body option
|
|
if (SUCCEEDED(pNode->pBody->GetOption(OID_TRANSMIT_BODY_ENCODING, &rOption)) && IET_UNKNOWN != rOption.ulVal)
|
|
*pietEncoding = (ENCODINGTYPE)rOption.ulVal;
|
|
}
|
|
|
|
// Save as MIME
|
|
if (SAVE_RFC1521 == m_rOptions.savetype)
|
|
{
|
|
// Get the current encoding of the body..
|
|
if (IET_UNKNOWN == *pietEncoding)
|
|
pNode->pBody->GetCurrentEncoding(pietEncoding);
|
|
|
|
// If CTE is IET_QP or IET_BASE64 or IET_UUENCODE, were done
|
|
if (IET_QP == *pietEncoding || IET_BASE64 == *pietEncoding || IET_UUENCODE == *pietEncoding)
|
|
goto exit;
|
|
|
|
// Ask the pody to suggest an ietEncoding
|
|
hr = pNode->pBody->GetTransmitInfo(&rXmitInfo);
|
|
if (SUCCEEDED(hr) )
|
|
{
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Must not need wrapping
|
|
if (IET_7BIT == rXmitInfo.ietXmitMime)
|
|
{
|
|
rOption.vt = VT_BOOL;
|
|
rOption.boolVal = FALSE;
|
|
pNode->pBody->SetOption(OID_WRAP_BODY_TEXT, &rOption);
|
|
}
|
|
|
|
// If IET_7BIT and there are 8bit chars, bump upto 8bit
|
|
if (IET_7BIT == *pietEncoding || IET_8BIT == *pietEncoding)
|
|
{
|
|
// 8bit
|
|
*pietEncoding = (rXmitInfo.cExtended > 0) ? IET_8BIT : IET_7BIT;
|
|
}
|
|
|
|
// Just use the suggested mime cte from GetTransmitInfo
|
|
else
|
|
*pietEncoding = rXmitInfo.ietXmitMime;
|
|
}
|
|
|
|
// Transmit ietEncoding still unknown
|
|
else
|
|
*pietEncoding = (IET_UNKNOWN == *pietEncoding) ? (fText ? IET_QP : IET_BASE64) : *pietEncoding;
|
|
}
|
|
|
|
// Save a non-MIME
|
|
else
|
|
{
|
|
// If I already know this body is TREENODE_INCOMPLETE, it will be 7bit...
|
|
if (ISFLAGSET(pNode->dwType, NODETYPE_INCOMPLETE))
|
|
{
|
|
// No Encoding
|
|
*pietEncoding = IET_7BIT;
|
|
|
|
// Tell the body that its 7bit
|
|
pNode->pBody->SetCurrentEncoding(IET_7BIT);
|
|
}
|
|
|
|
// Raid 41599 - lost/munged attachments on forward/uuencode - Text attachments were not
|
|
// getting encoded when: *pietEncoding = (fText && fPlain) ? IET_7BIT : IET_UUENCODE;
|
|
else
|
|
*pietEncoding = (fText && fPlain && !fAttachment) ? IET_7BIT : IET_UUENCODE;
|
|
}
|
|
|
|
// If we are doing S/MIME at this point, we need to make sure that the
|
|
// content encoding rules for S/MIME are followed. Specifically we
|
|
// want to make sure that binary and 8bit are not allowed.
|
|
smimeExit:
|
|
if (ISFLAGSET(dwFlags, SAVEBODY_SMIMECTE))
|
|
{
|
|
if (*pietEncoding == IET_8BIT)
|
|
*pietEncoding = IET_QP;
|
|
if (*pietEncoding == IET_BINARY)
|
|
*pietEncoding = IET_BASE64;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrWriteUUFileName
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrWriteUUFileName(IStream *pStream, LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPVARIANT rFileName;
|
|
|
|
// Init rFileName
|
|
ZeroMemory(&rFileName, sizeof(PROPVARIANT));
|
|
rFileName.vt = VT_LPSTR;
|
|
|
|
// RAID-22479: FE-J:Athena:SJIS is used on file name on the message source with Uuencode/JIS.
|
|
if (FAILED(pNode->pContainer->GetProp(PIDTOSTR(PID_ATT_GENFNAME), PDF_ENCODED, &rFileName)))
|
|
{
|
|
// Write the filename
|
|
CHECKHR(hr = pStream->Write(c_szUUENCODE_DAT, lstrlen(c_szUUENCODE_DAT), NULL));
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Write the filename
|
|
CHECKHR(hr = pStream->Write(rFileName.pszVal, lstrlen(rFileName.pszVal), NULL));
|
|
|
|
exit:
|
|
// Cleanup
|
|
MimeOleVariantFree(&rFileName);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSaveSinglePart
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSaveSinglePart(BOOL fClearDirty, DWORD dwFlags, LPSTREAM pStream,
|
|
LPTREENODEINFO pNode, ULONG ulLevel)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
LPSTR pszFileName=NULL;
|
|
BOOL fText=FALSE;
|
|
BOOL fMessage=FALSE;
|
|
BOOL fAttachment=FALSE;
|
|
ENCODINGTYPE ietEncoding;
|
|
BOOL fPlain=FALSE;
|
|
PROPVARIANT val;
|
|
LPINETCSETINFO pTaggedCset=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream && pNode);
|
|
|
|
// Text/Plain
|
|
if (pNode->pContainer->IsContentType(STR_CNT_TEXT, STR_SUB_PLAIN) == S_OK)
|
|
fText = fPlain = TRUE;
|
|
|
|
// Text Body
|
|
else if (pNode->pContainer->IsContentType(STR_CNT_TEXT, NULL) == S_OK)
|
|
fText = TRUE;
|
|
|
|
// Message/*
|
|
else if (pNode->pContainer->IsContentType(STR_CNT_MESSAGE, NULL) == S_OK)
|
|
{
|
|
// We have a message
|
|
fMessage = TRUE;
|
|
fAttachment = TRUE;
|
|
}
|
|
|
|
// fAttachment has not been set yet
|
|
if (!fAttachment)
|
|
{
|
|
fAttachment = (pNode->pContainer->QueryProp(SYM_HDR_CNTDISP, STR_DIS_ATTACHMENT, FALSE, FALSE) == S_OK ||
|
|
pNode->pContainer->IsPropSet(PIDTOSTR(PID_PAR_FILENAME)) == S_OK ||
|
|
pNode->pContainer->IsPropSet(PIDTOSTR(PID_PAR_NAME)) == S_OK);
|
|
}
|
|
|
|
// Get Content Transfer Encoding
|
|
hr = _GetContentTransferEncoding(pNode, fText, fPlain, fMessage, fAttachment,
|
|
dwFlags, &ietEncoding);
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Sanity Check
|
|
Assert(ietEncoding != IET_UNKNOWN && (SAVE_RFC1521 == m_rOptions.savetype || SAVE_RFC822 == m_rOptions.savetype));
|
|
|
|
// Set Content-Transfer-Encoding...
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTXFER, g_rgEncodingMap[ietEncoding].pszName));
|
|
pNode->pBody->SetPreviousEncoding(ietEncoding);
|
|
|
|
// Compute Character Set for the message...
|
|
if (m_rOptions.pCharset && TRUE == fText && (FALSE == fAttachment || S_OK == pNode->pBody->IsType(IBT_CSETTAGGED)))
|
|
{
|
|
|
|
|
|
#if 0 // Raid-69667: OE5: Kor: Only the charset, euc-kr, is used for news message
|
|
// ISO-2022-KR -> EUC-KR for News text/plain
|
|
if (ISFLAGSET(m_dwState, TREESTATE_SAVENEWS) && 949 == m_rOptions.pCharset->cpiWindows && pNode->pContainer->IsContentType(NULL, STR_SUB_PLAIN) == S_OK)
|
|
{
|
|
// Locals
|
|
LPINETCSETINFO pEUCKR;
|
|
|
|
// Find EUC-KR
|
|
if (SUCCEEDED(g_pInternat->HrOpenCharset("EUC-KR", &pEUCKR)))
|
|
pNode->pBody->SetCharset(pEUCKR->hCharset, CSET_APPLY_UNTAGGED);
|
|
}
|
|
|
|
// Otherwise, use current charset
|
|
else
|
|
#endif // Raid-69667: OE5: Kor: Only the charset, euc-kr, is used for news message
|
|
|
|
// Store the Character set
|
|
pNode->pBody->SetCharset(m_rOptions.pCharset->hCharset, m_rOptions.csetapply);
|
|
|
|
// Get original charset
|
|
if (fAttachment && S_OK == pNode->pBody->IsType(IBT_CSETTAGGED))
|
|
{
|
|
// Get the tagged charset
|
|
pTaggedCset = pNode->pBody->PGetTaggedCset();
|
|
|
|
// Set the charset property
|
|
pNode->pContainer->SetProp(PIDTOSTR(PID_PAR_CHARSET), pTaggedCset->szName);
|
|
|
|
// Remove the CSETTAGGED state, and then reset it after we write the body
|
|
// This will keep the body from being character set converted
|
|
pNode->pBody->ClearState(BODYSTATE_CSETTAGGED);
|
|
}
|
|
}
|
|
|
|
// Otherwise, remove the charset parameter, we don't encode attachments in a character set
|
|
else
|
|
{
|
|
// Remove the CharacterSet parameter from the body
|
|
pNode->pContainer->DeleteProp(SYM_PAR_CHARSET);
|
|
}
|
|
|
|
// Write the header...
|
|
if (SAVE_RFC1521 == m_rOptions.savetype || 0 == ulLevel)
|
|
{
|
|
// Write the header
|
|
CHECKHR(hr = _HrWriteHeader(fClearDirty, pStream, pNode));
|
|
}
|
|
|
|
// Determine the send ietEncoding
|
|
if (SAVE_RFC1521 == m_rOptions.savetype)
|
|
{
|
|
// Write body data into the stream
|
|
CHECKHR(hr = pNode->pBody->GetDataHere(ietEncoding, pStream));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Body End...
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyEnd));
|
|
}
|
|
|
|
// Otherwise, SAVE_RFC822
|
|
else if (SAVE_RFC822 == m_rOptions.savetype && IET_UUENCODE == ietEncoding)
|
|
{
|
|
// Starting boundary/header
|
|
if (ulLevel > 0)
|
|
pNode->boundary = BOUNDARY_UUBEGIN;
|
|
|
|
// Start new line
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// Get Boundary Start
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBoundaryStart));
|
|
|
|
// Header Start and boundary start are the same
|
|
if (ulLevel > 0)
|
|
pNode->cbHeaderStart = pNode->cbBoundaryStart;
|
|
|
|
// Write begin
|
|
CHECKHR(hr = pStream->Write(c_szUUENCODE_BEGIN, lstrlen(c_szUUENCODE_BEGIN), NULL));
|
|
|
|
// Write the file permission
|
|
CHECKHR(hr = pStream->Write(c_szUUENCODE_666, lstrlen(c_szUUENCODE_666), NULL));
|
|
|
|
// Write UU File Name
|
|
CHECKHR(hr = _HrWriteUUFileName(pStream, pNode));
|
|
|
|
// Start new line
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// Get Header End
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyStart));
|
|
|
|
// Write the data
|
|
CHECKHR(hr = pNode->pBody->GetDataHere(IET_UUENCODE, pStream));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Body End...
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyEnd));
|
|
|
|
// Write end
|
|
CHECKHR(hr = pStream->Write(c_szUUENCODE_END, lstrlen(c_szUUENCODE_END), NULL));
|
|
}
|
|
|
|
// Otherwise, SAVE_RFC822 and IET_7BIT
|
|
else if (SAVE_RFC822 == m_rOptions.savetype && IET_7BIT == ietEncoding)
|
|
{
|
|
// Get Boundary Start....
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyStart));
|
|
|
|
// Starting boundary/header
|
|
if (ulLevel > 0)
|
|
{
|
|
// No Boundary
|
|
pNode->boundary = BOUNDARY_NONE;
|
|
|
|
// Boundoff
|
|
pNode->cbBoundaryStart = pNode->cbBodyStart;
|
|
|
|
// Same as header start
|
|
pNode->cbHeaderStart = pNode->cbBoundaryStart;
|
|
}
|
|
|
|
// Write the data
|
|
CHECKHR(hr = pNode->pBody->GetDataHere(IET_7BIT, pStream));
|
|
if ( S_OK != hr )
|
|
hrWarnings = TrapError(hr);
|
|
|
|
// Write final crlf
|
|
CHECKHR(hr = pStream->Write(c_szCRLF, lstrlen(c_szCRLF), NULL));
|
|
|
|
// Body End...
|
|
CHECKHR(hr = HrGetStreamPos(pStream, &pNode->cbBodyEnd));
|
|
}
|
|
|
|
// Otherwise...
|
|
else
|
|
AssertSz(FALSE, "A body is about to be lost. Contact sbailey at x32553 NOW!!!");
|
|
|
|
exit:
|
|
// Try to fixup the body
|
|
if (pTaggedCset)
|
|
{
|
|
pNode->pBody->SetCharset(pTaggedCset->hCharset, CSET_APPLY_UNTAGGED);
|
|
pNode->pBody->SetState(BODYSTATE_CSETTAGGED);
|
|
}
|
|
|
|
// Free BodyInfo
|
|
SafeMemFree(pszFileName);
|
|
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBodyInheritOptions
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBodyInheritOptions(LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPVARIANT rValue;
|
|
|
|
// Invalid ARg
|
|
Assert(pNode);
|
|
|
|
// Allow 8bit in header
|
|
rValue.boolVal = m_rOptions.fAllow8bitHeader;
|
|
CHECKHR(hr = pNode->pBody->SetOption(OID_ALLOW_8BIT_HEADER, &rValue));
|
|
|
|
// Wrap Body Text
|
|
rValue.boolVal = m_rOptions.fWrapBodyText;
|
|
CHECKHR(hr = pNode->pBody->SetOption(OID_WRAP_BODY_TEXT, &rValue));
|
|
|
|
// Max Header Line
|
|
rValue.ulVal = m_rOptions.cchMaxHeaderLine;
|
|
CHECKHR(hr = pNode->pBody->SetOption(OID_CBMAX_HEADER_LINE, &rValue));
|
|
|
|
// Persist Type
|
|
rValue.ulVal = (ULONG)m_rOptions.savetype;
|
|
CHECKHR(hr = pNode->pBody->SetOption(OID_SAVE_FORMAT, &rValue));
|
|
|
|
// Max Body Line
|
|
rValue.ulVal = m_rOptions.cchMaxBodyLine;
|
|
CHECKHR(hr = pNode->pBody->SetOption(OID_CBMAX_BODY_LINE, &rValue));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::Load
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::Load(IStream *pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrWarnings=S_OK;
|
|
ULONG i;
|
|
HCHARSET hCharset;
|
|
PROPVARIANT rVersion;
|
|
STGMEDIUM rMedium;
|
|
|
|
// check params
|
|
if (NULL == pStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Assume the Bind has Finished
|
|
FLAGCLEAR(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// Release m_pStmLock
|
|
SafeRelease(m_pStmLock);
|
|
|
|
// Do I have a tree already...
|
|
if (!ISFLAGSET(m_dwState, TREESTATE_LOADED) || FAILED(_HrBindOffsetTable(pStream, &m_pStmLock)))
|
|
{
|
|
// InitNew
|
|
CHECKHR(hr = _HrLoadInitNew());
|
|
|
|
// Use file
|
|
if (m_rOptions.fBindUseFile)
|
|
FLAGSET(m_dwState, TREESTATE_BINDUSEFILE);
|
|
|
|
// If this fails, I assume the clients stream is already rewound and they don't support this
|
|
HrRewindStream(pStream);
|
|
|
|
// Fake OnStartBinding
|
|
OnStartBinding(0, NULL);
|
|
|
|
// Setup the Storage Medium
|
|
ZeroMemory(&rMedium, sizeof(STGMEDIUM));
|
|
rMedium.tymed = TYMED_ISTREAM;
|
|
rMedium.pstm = pStream;
|
|
|
|
// Fake OnDataAvailable
|
|
OnDataAvailable(BSCF_LASTDATANOTIFICATION, 0, NULL, &rMedium);
|
|
|
|
// Fake OnStartBinding
|
|
OnStopBinding(S_OK, NULL);
|
|
|
|
// If bind failed, return warnings
|
|
if (FAILED(m_hrBind))
|
|
hrWarnings = MIME_S_INVALID_MESSAGE;
|
|
}
|
|
|
|
// Otherwise, we are finished binding
|
|
else
|
|
{
|
|
// HandleCanInlineTextOption
|
|
_HandleCanInlineTextOption();
|
|
|
|
// Bind Finished
|
|
FLAGSET(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// DispatchBindRequest
|
|
_HrProcessPendingUrlRequests();
|
|
}
|
|
|
|
// Assume the stream
|
|
Assert(m_pStmLock);
|
|
|
|
// Allow for zero-byte stream to be Loaded
|
|
if (m_cbMessage)
|
|
{
|
|
// Is MIME ?
|
|
rVersion.vt = VT_UI4;
|
|
if (SUCCEEDED(m_pRootNode->pContainer->GetProp(PIDTOSTR(PID_HDR_MIMEVER), 0, &rVersion)))
|
|
{
|
|
// Its a Mime Message
|
|
m_rOptions.savetype = SAVE_RFC1521;
|
|
|
|
// Invalid Version
|
|
if (rVersion.ulVal != TREE_MIMEVERSION)
|
|
hrWarnings = MIME_S_MIME_VERSION;
|
|
}
|
|
|
|
// Otherwise, savetype should default to rfc822
|
|
else
|
|
m_rOptions.savetype = SAVE_RFC822;
|
|
|
|
// Detect Partials and Set FileName/Encoding Correctly
|
|
_FuzzyPartialRecognition(m_rOptions.savetype == SAVE_RFC822 ? FALSE : TRUE);
|
|
|
|
// Bind All Bodies to the Tree
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability - Should not have any deleted bodies yet
|
|
if (m_rTree.prgpNode[i] && !ISFLAGSET(m_rTree.prgpNode[i]->dwState, NODESTATE_BOUNDTOTREE))
|
|
{
|
|
// BindState is done
|
|
m_rTree.prgpNode[i]->bindstate = BINDSTATE_COMPLETE;
|
|
|
|
// Bind to the tree
|
|
CHECKHR(hr = m_rTree.prgpNode[i]->pBody->HrBindToTree(m_pStmLock, m_rTree.prgpNode[i]));
|
|
}
|
|
}
|
|
|
|
// Determine the dominent charcter set of the message
|
|
if (SUCCEEDED(_HrGetCharsetTree(m_pRootNode, &hCharset)) && hCharset)
|
|
{
|
|
// Apply Charset to Untagged bodies
|
|
SetCharset(hCharset, CSET_APPLY_UNTAGGED);
|
|
}
|
|
}
|
|
|
|
#ifdef DEBUG
|
|
// Write X-Mailer or X-NewsReader
|
|
DebugWriteXClient();
|
|
#endif
|
|
|
|
// My hands are on the storage
|
|
FLAGSET(m_dwState, TREESTATE_HANDSONSTORAGE);
|
|
|
|
// Dirty
|
|
ClearDirty();
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return (hr == S_OK) ? hrWarnings : hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HandleCanInlineTextOption
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_HandleCanInlineTextOption(void)
|
|
{
|
|
// Locals
|
|
FINDBODY rFind;
|
|
HBODY hMixed;
|
|
LPTREENODEINFO pNode;
|
|
LPTREENODEINFO pChild;
|
|
LPTREENODEINFO pText=NULL;
|
|
|
|
// Only do this if the client doesn't support inlining multiple text bodies, such as Outlook Express
|
|
if (TRUE == m_rOptions.fCanInlineText)
|
|
return;
|
|
|
|
// Raid 53456: mail: We should be displaying the plain text portion and making enriched text an attachment for attached msg
|
|
// Raid 53470: mail: We are not forwarding the attachment in the attached message
|
|
// I am going to find the first multipart/mixed section, then find the first text/plain body, and then
|
|
// mark all of the text/*, non-attachment bodies after that as attachments
|
|
ZeroMemory(&rFind, sizeof(FINDBODY));
|
|
rFind.pszPriType = (LPSTR)STR_CNT_MULTIPART;
|
|
rFind.pszSubType = (LPSTR)STR_SUB_MIXED;
|
|
|
|
// Find First
|
|
if (FAILED(FindFirst(&rFind, &hMixed)))
|
|
goto exit;
|
|
|
|
// Get node for hMixed
|
|
pNode = _PNodeFromHBody(hMixed);
|
|
|
|
// Loop
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Not an attachment
|
|
if (S_FALSE == pChild->pBody->IsType(IBT_ATTACHMENT))
|
|
{
|
|
// Is text/plain
|
|
if (S_OK == pChild->pContainer->IsContentType(STR_CNT_TEXT, STR_SUB_PLAIN) ||
|
|
S_OK == pChild->pContainer->IsContentType(STR_CNT_TEXT, STR_SUB_HTML))
|
|
{
|
|
pText = pChild;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If we found a text body
|
|
if (NULL == pText)
|
|
goto exit;
|
|
|
|
// Loop through the children again
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Is text/*
|
|
if (pChild != pText && S_OK == pChild->pContainer->IsContentType(STR_CNT_TEXT, NULL) && S_FALSE == pChild->pBody->IsType(IBT_ATTACHMENT))
|
|
{
|
|
// Mark as attachment
|
|
pChild->pContainer->SetProp(PIDTOSTR(PID_HDR_CNTDISP), STR_DIS_ATTACHMENT);
|
|
|
|
// Set a special flag to denote it was converted to an attachment
|
|
FLAGSET(pChild->dwState, NODESTATE_AUTOATTACH);
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindOffsetTable
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindOffsetTable(IStream *pStream, CStreamLockBytes **ppStmLock)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cb;
|
|
CInternetStream cInternet;
|
|
|
|
// Init
|
|
*ppStmLock = NULL;
|
|
|
|
// Get Sizeof Stream
|
|
CHECKHR(hr = HrSafeGetStreamSize(pStream, &cb));
|
|
|
|
// Otherwise bind the body table
|
|
if (cb != m_cbMessage)
|
|
{
|
|
hr = TrapError(MIME_E_MSG_SIZE_DIFF);
|
|
goto exit;
|
|
}
|
|
|
|
// Init the Internet Stream
|
|
CHECKHR(hr = cInternet.HrInitNew(pStream));
|
|
|
|
// Fast Parse Body
|
|
CHECKHR(hr = _HrFastParseBody(&cInternet, m_pRootNode));
|
|
|
|
// Success, get the lockbytes from the internet stream
|
|
cInternet.GetLockBytes(ppStmLock);
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetBodyOffsets
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetBodyOffsets(HBODY hBody, LPBODYOFFSETS pOffsets)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// Invalid Arg
|
|
if (NULL == hBody || NULL == pOffsets)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// No Data ?
|
|
CHECKHR(hr = pNode->pBody->GetOffsets(pOffsets));
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::ClearDirty
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::ClearDirty(void)
|
|
{
|
|
// If Dirty...
|
|
FLAGCLEAR(m_dwState, TREESTATE_DIRTY);
|
|
|
|
// Loop through bodies and ask IMimeHeader's and IMimeBody's
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// If NULL...
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Dirty Header...
|
|
m_rTree.prgpNode[i]->pBody->ClearDirty();
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetCharset
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetCharset(LPHCHARSET phCharset)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HCHARSET hCharset;
|
|
|
|
// Check Params
|
|
if (NULL == phCharset)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
*phCharset = NULL;
|
|
|
|
// Recurse the current tree
|
|
if (NULL == m_rOptions.pCharset && m_pRootNode)
|
|
{
|
|
// Get charset
|
|
if (SUCCEEDED(_HrGetCharsetTree(m_pRootNode, &hCharset)))
|
|
{
|
|
// Get Pointer to Charset
|
|
SideAssert(SUCCEEDED(g_pInternat->HrOpenCharset(hCharset, &m_rOptions.pCharset)));
|
|
}
|
|
}
|
|
|
|
// No Charset
|
|
if (NULL == m_rOptions.pCharset)
|
|
{
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Set Return
|
|
*phCharset = m_rOptions.pCharset->hCharset;
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrGetCharsetTree
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrGetCharsetTree(LPTREENODEINFO pNode, LPHCHARSET phCharset)
|
|
{
|
|
// Locals
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode && phCharset && m_rOptions.pCharset);
|
|
|
|
// Init
|
|
*phCharset = NULL;
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
if (SUCCEEDED(_HrGetCharsetTree(pChild, phCharset)) && *phCharset)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the Header was tagged with a charset, use that charset
|
|
else if (pNode->pContainer->IsState(COSTATE_CSETTAGGED) == S_OK)
|
|
{
|
|
// Get Internal Character Set
|
|
if (SUCCEEDED(pNode->pContainer->GetCharset(phCharset)) && *phCharset)
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return (*phCharset) ? S_OK : E_FAIL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SetCharset
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SetCharset(HCHARSET hCharset, CSETAPPLYTYPE applytype)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Check Params
|
|
if (NULL == hCharset)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Lookiup Charset Info
|
|
if (FALSE == g_pInternat->FIsValidHandle(hCharset))
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Save the charset
|
|
SideAssert(SUCCEEDED(g_pInternat->HrOpenCharset(hCharset, &m_rOptions.pCharset)));
|
|
|
|
// Save apply type
|
|
m_rOptions.csetapply = applytype;
|
|
|
|
// If we have a root body
|
|
if (m_pRootNode)
|
|
{
|
|
// Recurse all bodies and set the charset
|
|
CHECKHR(hr = _HrSetCharsetTree(m_pRootNode, m_rOptions.pCharset->hCharset, m_rOptions.csetapply));
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSetCharsetTree
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSetCharsetTree(LPTREENODEINFO pNode, HCHARSET hCharset, CSETAPPLYTYPE applytype)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode);
|
|
|
|
// Raid-22662: OExpress: if content-type on fileattach does not have charset should apply same as message body
|
|
pNode->pBody->SetCharset(hCharset, applytype);
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrSetCharsetTree(pChild, hCharset, applytype));
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrValidateOffsets
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrValidateOffsets(LPTREENODEINFO pNode)
|
|
{
|
|
// Invalid Arg
|
|
Assert(pNode);
|
|
|
|
// Validate the offsets
|
|
if (pNode->cbBodyStart > m_cbMessage || pNode->cbBodyEnd > m_cbMessage ||
|
|
pNode->cbHeaderStart > m_cbMessage || pNode->cbBoundaryStart > m_cbMessage)
|
|
{
|
|
Assert(FALSE);
|
|
return TrapError(MIME_E_BODYTREE_OUT_OF_SYNC);
|
|
}
|
|
|
|
// Validate the offsets
|
|
if (pNode->cbBodyStart > pNode->cbBodyEnd || pNode->cbBoundaryStart > pNode->cbHeaderStart ||
|
|
pNode->cbHeaderStart > pNode->cbBodyStart || pNode->cbBoundaryStart > pNode->cbBodyEnd)
|
|
{
|
|
Assert(FALSE);
|
|
return TrapError(MIME_E_BODYTREE_OUT_OF_SYNC);
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrValidateStartBoundary
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrValidateStartBoundary(CInternetStream *pInternet, LPTREENODEINFO pNode,
|
|
LPSTR *ppszFileName)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPSTRINGA rLine;
|
|
|
|
// Is there a boundary to read...
|
|
if (BOUNDARY_MIMENEXT == pNode->boundary)
|
|
{
|
|
// Seek to the boundary start..
|
|
pInternet->Seek(pNode->cbBoundaryStart);
|
|
|
|
// Readline and verify the header
|
|
CHECKHR(hr = pInternet->HrReadLine(&rLine));
|
|
|
|
// Read and verify the boundary...
|
|
if (!ISVALIDSTRINGA(&pNode->rBoundary) || BOUNDARY_MIMENEXT != _GetMimeBoundaryType(&rLine, &pNode->rBoundary))
|
|
{
|
|
AssertSz(FALSE, "MIME_E_BODYTREE_OUT_OF_SYNC");
|
|
hr = TrapError(MIME_E_BODYTREE_OUT_OF_SYNC);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Otherwise, verify UU boundary
|
|
else if (BOUNDARY_UUBEGIN == pNode->boundary)
|
|
{
|
|
// Seek to the boundary start..
|
|
pInternet->Seek(pNode->cbBoundaryStart);
|
|
|
|
// Readline and verify the header
|
|
CHECKHR(hr = pInternet->HrReadLine(&rLine));
|
|
|
|
// Read and verify the boundary...
|
|
if (FALSE == _FIsUuencodeBegin(&rLine, ppszFileName))
|
|
{
|
|
AssertSz(FALSE, "MIME_E_BODYTREE_OUT_OF_SYNC");
|
|
hr = TrapError(MIME_E_BODYTREE_OUT_OF_SYNC);
|
|
goto exit;
|
|
}
|
|
|
|
// FileName..
|
|
AssertSz(!ISFLAGSET(pNode->dwType, NODETYPE_FAKEMULTIPART), "pszFileName is not going to get set.");
|
|
}
|
|
|
|
// Otherwise, header start should be same as boundary start
|
|
else
|
|
Assert(BOUNDARY_ROOT == pNode->boundary ? TRUE : pNode->cbBoundaryStart == pNode->cbHeaderStart);
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrFastParseBody
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrFastParseBody(CInternetStream *pInternet, LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
MIMEVARIANT rVariant;
|
|
LPSTR pszFileName=NULL;
|
|
LPTREENODEINFO pChild,
|
|
pTemp;
|
|
|
|
// check params
|
|
Assert(pInternet && pNode);
|
|
|
|
// Validate Offset
|
|
CHECKHR(hr = _HrValidateOffsets(pNode));
|
|
|
|
// Validate Start Boundary
|
|
CHECKHR(hr = _HrValidateStartBoundary(pInternet, pNode, &pszFileName));
|
|
|
|
// Is there a header to read...
|
|
if (BOUNDARY_MIMENEXT == pNode->boundary || BOUNDARY_ROOT == pNode->boundary)
|
|
{
|
|
// Load the header
|
|
Assert(pNode->boundary == BOUNDARY_ROOT ? m_pRootNode == pNode : TRUE);
|
|
|
|
// Seek to the boundary start..
|
|
pInternet->Seek(pNode->cbHeaderStart);
|
|
|
|
// Load the Header
|
|
CHECKHR(hr = pNode->pContainer->Load(pInternet));
|
|
|
|
// Raid-38646: Mimeole: Multipart/Digest not being parsed correctly initially, but save fine
|
|
// Message In a Message
|
|
if (pNode->pContainer->IsContentType(STR_CNT_MESSAGE, NULL) == S_OK)
|
|
{
|
|
// We are parsing a message attachment
|
|
FLAGSET(pNode->dwState, NODESTATE_MESSAGE);
|
|
}
|
|
|
|
// Otherwise, if parent and parent is a multipart/digest
|
|
else if (pNode->pParent && pNode->pParent->pContainer->IsContentType(STR_CNT_MULTIPART, STR_SUB_DIGEST) == S_OK &&
|
|
pNode->pContainer->IsPropSet(PIDTOSTR(PID_HDR_CNTTYPE)) == S_FALSE)
|
|
{
|
|
// Change the Content Type
|
|
pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_MSG_RFC822);
|
|
|
|
// This is a message
|
|
FLAGSET(pNode->dwState, NODESTATE_MESSAGE);
|
|
}
|
|
}
|
|
|
|
// Encoding
|
|
else if (!ISFLAGSET(pNode->dwType, NODETYPE_FAKEMULTIPART))
|
|
{
|
|
// ComputeDefaultContent
|
|
CHECKHR(hr = _HrComputeDefaultContent(pNode, pszFileName));
|
|
}
|
|
|
|
// Fake Multipart...
|
|
if (ISFLAGSET(pNode->dwType, NODETYPE_FAKEMULTIPART))
|
|
{
|
|
// Application/octet-stream
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_MPART_MIXED));
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_7BIT));
|
|
|
|
// Loop children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check pChild
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrFastParseBody(pInternet, pChild));
|
|
}
|
|
}
|
|
|
|
// If Multipart...cruise through the children
|
|
else if (_IsMultiPart(pNode))
|
|
{
|
|
// Get the boundary from the header
|
|
rVariant.type = MVT_STRINGA;
|
|
if (FAILED(pNode->pContainer->GetProp(SYM_PAR_BOUNDARY, 0, &rVariant)))
|
|
{
|
|
hr = TrapError(MIME_E_NO_MULTIPART_BOUNDARY);
|
|
goto exit;
|
|
}
|
|
|
|
// But the Boundary into pNode->rBoundary
|
|
pNode->rBoundary.pszVal = rVariant.rStringA.pszVal;
|
|
pNode->rBoundary.cchVal = rVariant.rStringA.cchVal;
|
|
|
|
// Free this boundary later
|
|
FLAGCLEAR(pNode->dwState, NODESTATE_BOUNDNOFREE);
|
|
|
|
// Loop children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check pChild
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Put the Boundary into pChild
|
|
pChild->rBoundary.pszVal = rVariant.rStringA.pszVal;
|
|
pChild->rBoundary.cchVal = rVariant.rStringA.cchVal;
|
|
|
|
// Done free the boundary
|
|
FLAGSET(pChild->dwState, NODESTATE_BOUNDNOFREE);
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrFastParseBody(pInternet, pChild));
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszFileName);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_FuzzyPartialRecognition
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_FuzzyPartialRecognition(BOOL fIsMime)
|
|
{
|
|
// Locals
|
|
CHAR szFile[MAX_PATH];
|
|
ULONG ulTotal;
|
|
ULONG ulPart;
|
|
BOOL fCntTypeSet=FALSE;
|
|
LPSTR pszContentType=NULL;
|
|
CHAR szExt[_MAX_EXT];
|
|
|
|
// Better have a Root
|
|
Assert(m_pRootNode);
|
|
|
|
// Only if this is the
|
|
if (fIsMime || m_pRootNode->cChildren || m_pRootNode->pContainer->IsContentType(STR_CNT_TEXT, STR_SUB_PLAIN) == S_FALSE)
|
|
goto exit;
|
|
|
|
// Extract FileName and part/total from the subject
|
|
if (FAILED(MimeOleGetSubjectFileName(m_pRootNode->pBody, &ulPart, &ulTotal, szFile, MAX_PATH)))
|
|
goto exit;
|
|
|
|
// Mark as Partial
|
|
FLAGSET(m_pRootNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// A Little Debugging
|
|
DebugTrace("FuzzyPartialRecognition - FileName = '%s', Part = %d, Total = %d\n", szFile, ulPart, ulTotal);
|
|
|
|
// Store the FileName
|
|
if (FAILED(m_pRootNode->pContainer->SetProp(SYM_ATT_FILENAME, szFile)))
|
|
goto exit;
|
|
|
|
// Get File Extesion
|
|
if (SUCCEEDED(MimeOleGetFileExtension(szFile, szExt, sizeof(szExt))))
|
|
{
|
|
// GetExtContentType
|
|
if (SUCCEEDED(MimeOleGetExtContentType(szExt, &pszContentType)))
|
|
{
|
|
// Set Content Type
|
|
m_pRootNode->pContainer->SetProp(SYM_HDR_CNTTYPE, pszContentType);
|
|
|
|
// We Set the content type
|
|
fCntTypeSet = TRUE;
|
|
}
|
|
}
|
|
|
|
// Default to Application/octet-stream
|
|
if (FALSE == fCntTypeSet)
|
|
m_pRootNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_APPL_STREAM);
|
|
|
|
// Set Encoding
|
|
m_pRootNode->pContainer->SetProp(SYM_HDR_CNTDISP, STR_DIS_ATTACHMENT);
|
|
|
|
// I Should Actualy do some detection...
|
|
if (FALSE == fIsMime)
|
|
m_pRootNode->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_UUENCODE);
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszContentType);
|
|
|
|
// Done
|
|
return;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrComputeDefaultContent
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrComputeDefaultContent(LPTREENODEINFO pNode, LPCSTR pszFileName)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
CHAR szExt[256];
|
|
LPSTR pszContentType=NULL;
|
|
LPSTR pszDecoded=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode);
|
|
|
|
// Otherwise, lets get the content type
|
|
if (pszFileName)
|
|
{
|
|
// Set File Name
|
|
PROPVARIANT rVariant;
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)pszFileName;
|
|
|
|
// Set the file name
|
|
CHECKHR(hr = pNode->pContainer->SetProp(PIDTOSTR(PID_ATT_FILENAME), PDF_ENCODED, &rVariant));
|
|
|
|
// Get the filename back out so that its decoded...
|
|
CHECKHR(hr = pNode->pContainer->GetProp(PIDTOSTR(PID_ATT_FILENAME), &pszDecoded));
|
|
|
|
// Test for winmail.dat
|
|
if (lstrcmpi(pszDecoded, c_szWinmailDotDat) == 0)
|
|
{
|
|
// Make sure the stream is really TNEF
|
|
FLAGSET(pNode->dwState, NODESTATE_VERIFYTNEF);
|
|
}
|
|
|
|
// Get File Extesion
|
|
if (SUCCEEDED(MimeOleGetFileExtension(pszDecoded, szExt, sizeof(szExt))))
|
|
{
|
|
// GetExtContentType
|
|
if (SUCCEEDED(MimeOleGetExtContentType(szExt, &pszContentType)))
|
|
{
|
|
// Set Content Type
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, pszContentType));
|
|
}
|
|
else
|
|
{
|
|
// Set Content Type
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_APPL_STREAM));
|
|
}
|
|
}
|
|
|
|
// Set Encoding
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTDISP, STR_DIS_ATTACHMENT));
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Default to text/plain
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN));
|
|
}
|
|
|
|
// Boundary Was UUencode
|
|
if (BOUNDARY_UUBEGIN == pNode->boundary)
|
|
{
|
|
// Set Encoding
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_UUENCODE));
|
|
}
|
|
|
|
else if (ISFLAGSET(pNode->dwType,NODETYPE_RFC1154_BINHEX))
|
|
{
|
|
// This is BINHEX from RFC1154
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTDISP, STR_DIS_ATTACHMENT));
|
|
CHECKHR(hr = pNode->pContainer->SetProp(PIDTOSTR(PID_ATT_PRITYPE), STR_CNT_APPLICATION));
|
|
CHECKHR(hr = pNode->pContainer->SetProp(PIDTOSTR(PID_ATT_SUBTYPE), STR_SUB_BINHEX));
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Set Encoding
|
|
CHECKHR(hr = pNode->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_7BIT));
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszContentType);
|
|
SafeMemFree(pszDecoded);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::HandsOffStorage
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::HandsOffStorage(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pstmNew=NULL;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// No Internal Stream...
|
|
if (NULL == m_pStmLock)
|
|
goto exit;
|
|
|
|
// I own the stream
|
|
if (!ISFLAGSET(m_dwState, TREESTATE_HANDSONSTORAGE))
|
|
goto exit;
|
|
|
|
// Copy m_pStmLock to a local place...
|
|
CHECKALLOC(pstmNew = new CVirtualStream);
|
|
|
|
// Go through m_pLockBytes to continue to provide thread safety to m_pStmLock
|
|
CHECKHR(hr = HrCopyLockBytesToStream(m_pStmLock, pstmNew, NULL));
|
|
|
|
// Rewind and commit
|
|
CHECKHR(hr = pstmNew->Commit(STGC_DEFAULT));
|
|
|
|
// Replace internal stream
|
|
m_pStmLock->ReplaceInternalStream(pstmNew);
|
|
|
|
// Hands are off..
|
|
FLAGCLEAR(m_dwState, TREESTATE_HANDSONSTORAGE);
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmNew);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetMessageSource
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetMessageSource(IStream **ppStream, DWORD dwFlags)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
IStream *pStream=NULL;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init
|
|
*ppStream = NULL;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// If Dirty
|
|
if (ISFLAGSET(dwFlags, COMMIT_ONLYIFDIRTY) && IsDirty() == S_OK && FALSE == m_rOptions.fHandsOffOnSave)
|
|
{
|
|
// Commit
|
|
CHECKHR(hr = Commit(dwFlags));
|
|
}
|
|
|
|
// Raid-19644: MIMEOLE: GetMessageSource fails with MIME_E_NO_DATA (because of OID_HANDSOFF_ONSAVE = TRUE)
|
|
if (NULL == m_pStmLock || TRUE == m_rOptions.fHandsOffOnSave)
|
|
{
|
|
// Create a new stream
|
|
CHECKALLOC(pStream = new CVirtualStream);
|
|
|
|
// Call Save Message
|
|
CHECKHR(hr = _HrWriteMessage(pStream, FALSE, m_rOptions.fHandsOffOnSave, FALSE));
|
|
|
|
// All good
|
|
*ppStream = pStream;
|
|
|
|
// Null pStream
|
|
pStream = NULL;
|
|
}
|
|
|
|
// Otherwise, just wrap m_pStmLock
|
|
else if (m_pStmLock)
|
|
{
|
|
// Locked Stream
|
|
CHECKALLOC(*ppStream = (IStream *)new CLockedStream(m_pStmLock, m_cbMessage));
|
|
}
|
|
|
|
// Otherwise, failure
|
|
else
|
|
{
|
|
hr = TrapError(MIME_E_NO_DATA);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Cleanup
|
|
SafeRelease(pStream);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::QueryService
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::QueryService(REFGUID rsid, REFIID riid, void **ppvObject) /* IServiceProvider */
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppvObject)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// IID_IBindMessageStream
|
|
if (IID_IBindMessageStream == riid)
|
|
{
|
|
// We should not have a lock bytes yet
|
|
Assert(NULL == m_pStmLock);
|
|
|
|
// Create a Virtual Stream
|
|
CHECKHR(hr = MimeOleCreateVirtualStream((IStream **)ppvObject));
|
|
}
|
|
|
|
// IID_IBinding
|
|
else if (IID_IBinding == riid)
|
|
{
|
|
// No Bind Context Yet
|
|
if (NULL == m_pBinding)
|
|
{
|
|
hr = TrapError(E_UNEXPECTED);
|
|
goto exit;
|
|
}
|
|
|
|
// Return It
|
|
(*ppvObject) = m_pBinding;
|
|
((IUnknown *)*ppvObject)->AddRef();
|
|
}
|
|
|
|
// IID_IMoniker
|
|
else if (IID_IMoniker == riid)
|
|
{
|
|
// No Bind Context Yet
|
|
if (NULL == m_pMoniker)
|
|
{
|
|
hr = TrapError(E_UNEXPECTED);
|
|
goto exit;
|
|
}
|
|
|
|
// Return It
|
|
(*ppvObject) = m_pMoniker;
|
|
((IUnknown *)*ppvObject)->AddRef();
|
|
}
|
|
|
|
// Otherwise, no object
|
|
else
|
|
{
|
|
hr = TrapError(E_NOINTERFACE);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::BinToObject
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::BindToObject(const HBODY hBody, REFIID riid, void **ppvObject)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == ppvObject)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// BindToObject on the body
|
|
CHECKHR(hr = pNode->pBody->BindToObject(riid, ppvObject));
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_PoseCreateTreeNode
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_PostCreateTreeNode(HRESULT hrResult, LPTREENODEINFO pNode)
|
|
{
|
|
// Failure...
|
|
if (FAILED(hrResult) && pNode)
|
|
{
|
|
// Set Index
|
|
ULONG ulIndex = HBODYINDEX(pNode->hBody);
|
|
|
|
// This body better be here
|
|
Assert(m_rTree.prgpNode[ulIndex] == pNode);
|
|
|
|
// Lets make sure nobody else is referencing this node...
|
|
#ifdef DEBUG
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
if (m_rTree.prgpNode[i])
|
|
{
|
|
AssertSz(m_rTree.prgpNode[i]->pPrev != pNode, "Killing a linked node is not good");
|
|
AssertSz(m_rTree.prgpNode[i]->pNext != pNode, "Killing a linked node is not good");
|
|
AssertSz(m_rTree.prgpNode[i]->pParent != pNode, "Killing a linked node is not good");
|
|
AssertSz(m_rTree.prgpNode[i]->pChildHead != pNode, "Killing a linked node is not good");
|
|
AssertSz(m_rTree.prgpNode[i]->pChildTail != pNode, "Killing a linked node is not good");
|
|
}
|
|
}
|
|
#endif
|
|
|
|
// This node should not have been linked yet...
|
|
AssertSz(pNode->pPrev == NULL, "Killing a linked node is not good");
|
|
AssertSz(pNode->pNext == NULL, "Killing a linked node is not good");
|
|
AssertSz(pNode->pParent == NULL, "Killing a linked node is not good");
|
|
AssertSz(pNode->pChildHead == NULL, "Killing a linked node is not good");
|
|
AssertSz(pNode->pChildTail == NULL, "Killing a linked node is not good");
|
|
AssertSz(pNode->cChildren == 0, "Deleting a node with children");
|
|
|
|
// Free It
|
|
_FreeTreeNodeInfo(pNode);
|
|
|
|
// Reset entry in table
|
|
m_rTree.prgpNode[ulIndex] = NULL;
|
|
|
|
// If Index is last item
|
|
if (ulIndex + 1 == m_rTree.cNodes)
|
|
m_rTree.cNodes--;
|
|
|
|
// Otherwise, Increment Empty Count...
|
|
else
|
|
m_rTree.cEmpty++;
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrCreateTreeNode
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrCreateTreeNode(LPTREENODEINFO *ppNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG i=0;
|
|
BOOL fUsingEmpty=FALSE;
|
|
|
|
// Invalid Arg
|
|
Assert(ppNode);
|
|
|
|
// Use Empty Cell
|
|
if (m_rTree.cEmpty)
|
|
{
|
|
// Find First Empty Cell..
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Empty ?
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
{
|
|
fUsingEmpty = TRUE;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If not using empty
|
|
if (FALSE == fUsingEmpty)
|
|
{
|
|
// Lets grow the table first...
|
|
if (m_rTree.cNodes + 1 > m_rTree.cAlloc)
|
|
{
|
|
// Grow my current property value array
|
|
CHECKHR(hr = HrRealloc((LPVOID *)&m_rTree.prgpNode, sizeof(LPTREENODEINFO) * (m_rTree.cAlloc + 10)));
|
|
|
|
// Increment alloc size
|
|
m_rTree.cAlloc += 10;
|
|
}
|
|
|
|
// Index to use
|
|
i = m_rTree.cNodes;
|
|
}
|
|
|
|
// Set to empty
|
|
m_rTree.prgpNode[i] = NULL;
|
|
|
|
// Allocate this node...
|
|
CHECKHR(hr = _HrAllocateTreeNode(i));
|
|
|
|
// Return It
|
|
*ppNode = m_rTree.prgpNode[i];
|
|
|
|
// If not using empty cell, increment body count
|
|
if (FALSE == fUsingEmpty)
|
|
m_rTree.cNodes++;
|
|
|
|
// Otherwise, decrement number of empty cells
|
|
else
|
|
m_rTree.cEmpty--;
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::InsertBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::InsertBody(BODYLOCATION location, HBODY hPivot, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode=NULL;
|
|
LPTREENODEINFO pPivot=NULL;
|
|
LPTREENODEINFO pPrev;
|
|
LPTREENODEINFO pNext;
|
|
LPTREENODEINFO pParent;
|
|
|
|
// Invalid Arg
|
|
if (IBL_PARENT == location)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
|
|
// Handle Body Type
|
|
if (IBL_ROOT == location)
|
|
{
|
|
// Currently No root
|
|
if (NULL == m_pRootNode)
|
|
{
|
|
// Create Object
|
|
CHECKHR(hr = _HrCreateTreeNode(&pNode));
|
|
|
|
// Better not be any bodies
|
|
Assert(m_rTree.cNodes == 1);
|
|
|
|
// Set as root
|
|
m_pRootNode = pNode;
|
|
}
|
|
|
|
// Otherwise, re-use the root
|
|
else
|
|
{
|
|
hr = TrapError(MIME_E_CANT_RESET_ROOT);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// All non-root inserts
|
|
else
|
|
{
|
|
// Get Pivot
|
|
if (_FIsValidHandle(hPivot) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Cast it..
|
|
pPivot = _PNodeFromHBody(hPivot);
|
|
|
|
// Create Object
|
|
CHECKHR(hr = _HrCreateTreeNode(&pNode));
|
|
|
|
// First or Last Child
|
|
if (IBL_LAST == location || IBL_FIRST == location)
|
|
{
|
|
// Better be a multipart
|
|
if (!_IsMultiPart(pPivot))
|
|
{
|
|
hr = TrapError(MIME_E_NOT_MULTIPART);
|
|
goto exit;
|
|
}
|
|
|
|
// DON'T FAIL FROM HERE TO END OF FUNCTION
|
|
// No Children on pPivot
|
|
if (NULL == pPivot->pChildHead)
|
|
{
|
|
Assert(pPivot->pChildTail == NULL);
|
|
pPivot->pChildHead = pNode;
|
|
pPivot->pChildTail = pNode;
|
|
pNode->pParent = pPivot;
|
|
}
|
|
|
|
// IBL_LAST
|
|
else if (IBL_LAST == location)
|
|
{
|
|
pPrev = pPivot->pChildTail;
|
|
pNode->pPrev = pPrev;
|
|
pPrev->pNext = pNode;
|
|
pPivot->pChildTail = pNode;
|
|
pNode->pParent = pPivot;
|
|
}
|
|
|
|
// IBL_FIRST
|
|
else if (IBL_FIRST == location)
|
|
{
|
|
pNext = pPivot->pChildHead;
|
|
pNode->pNext = pNext;
|
|
pNext->pPrev = pNode;
|
|
pPivot->pChildHead = pNode;
|
|
pNode->pParent = pPivot;
|
|
}
|
|
|
|
// Increment Count
|
|
pPivot->cChildren++;
|
|
}
|
|
|
|
// Otherwise
|
|
else if (IBL_NEXT == location || IBL_PREVIOUS == location)
|
|
{
|
|
// Need a parent
|
|
pParent = pPivot->pParent;
|
|
|
|
// No Parent
|
|
if (NULL == pParent)
|
|
{
|
|
hr = TrapError(MIME_E_NOT_MULTIPART);
|
|
goto exit;
|
|
}
|
|
|
|
// DON'T FAIL FROM HERE TO END OF FUNCTION
|
|
// Parent Better be a multipart
|
|
Assert(_IsMultiPart(pParent));
|
|
|
|
// Set Parent
|
|
pNode->pParent = pParent;
|
|
|
|
// IBL_NEXT
|
|
if (IBL_NEXT == location)
|
|
{
|
|
// Set Previous
|
|
pPrev = pPivot;
|
|
|
|
// Append to the end
|
|
if (NULL == pPrev->pNext)
|
|
{
|
|
pPrev->pNext = pNode;
|
|
pNode->pPrev = pPrev;
|
|
pParent->pChildTail = pNode;
|
|
}
|
|
|
|
// Otherwise, inserting between two nodes
|
|
else
|
|
{
|
|
pNext = pPrev->pNext;
|
|
pNode->pPrev = pPrev;
|
|
pNode->pNext = pNext;
|
|
pPrev->pNext = pNode;
|
|
pNext->pPrev = pNode;
|
|
}
|
|
}
|
|
|
|
// IBL_PREVIOUS
|
|
else if (IBL_PREVIOUS == location)
|
|
{
|
|
// Set Previous
|
|
pNext = pPivot;
|
|
|
|
// Append to the end
|
|
if (NULL == pNext->pPrev)
|
|
{
|
|
pNext->pPrev = pNode;
|
|
pNode->pNext = pNext;
|
|
pParent->pChildHead = pNode;
|
|
}
|
|
|
|
// Otherwise, inserting between two nodes
|
|
else
|
|
{
|
|
pPrev = pNext->pPrev;
|
|
pNode->pNext = pNext;
|
|
pNode->pPrev = pPrev;
|
|
pPrev->pNext = pNode;
|
|
pNext->pPrev = pNode;
|
|
}
|
|
}
|
|
|
|
// Increment Count
|
|
pParent->cChildren++;
|
|
}
|
|
|
|
// Otherwise bad body location
|
|
else
|
|
{
|
|
hr = TrapError(MIME_E_BAD_BODY_LOCATION);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Set Return
|
|
if (phBody)
|
|
*phBody = pNode->hBody;
|
|
|
|
// Dirty
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
|
|
exit:
|
|
// Failure
|
|
_PostCreateTreeNode(hr, pNode);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetBody(BODYLOCATION location, HBODY hPivot, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pPivot, pCurr;
|
|
|
|
// check params
|
|
if (NULL == phBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
*phBody = NULL;
|
|
|
|
// Handle Root Case
|
|
if (IBL_ROOT == location)
|
|
{
|
|
if (m_pRootNode)
|
|
*phBody = m_pRootNode->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Validate handle
|
|
if (_FIsValidHandle(hPivot) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Cast It
|
|
pPivot = _PNodeFromHBody(hPivot);
|
|
|
|
// Handle Get Type
|
|
switch(location)
|
|
{
|
|
// ----------------------------------------------
|
|
case IBL_PARENT:
|
|
if (pPivot->pParent)
|
|
*phBody = pPivot->pParent->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
break;
|
|
|
|
// ----------------------------------------------
|
|
case IBL_FIRST:
|
|
if (pPivot->pChildHead)
|
|
*phBody = pPivot->pChildHead->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
break;
|
|
|
|
// ----------------------------------------------
|
|
case IBL_LAST:
|
|
if (pPivot->pChildTail)
|
|
*phBody = pPivot->pChildTail->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
break;
|
|
|
|
// ----------------------------------------------
|
|
case IBL_NEXT:
|
|
if (pPivot->pNext)
|
|
*phBody = pPivot->pNext->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
break;
|
|
|
|
// ----------------------------------------------
|
|
case IBL_PREVIOUS:
|
|
if (pPivot->pPrev)
|
|
*phBody = pPivot->pPrev->hBody;
|
|
else
|
|
hr = MIME_E_NOT_FOUND;
|
|
break;
|
|
|
|
// ----------------------------------------------
|
|
default:
|
|
hr = TrapError(MIME_E_BAD_BODY_LOCATION);
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DeleteBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::DeleteBody(HBODY hBody, DWORD dwFlags)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
BOOL fMultipart;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Validate handle
|
|
if (_FIsValidHandle(hBody) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Cast
|
|
pNode = _PNodeFromHBody(hBody);
|
|
|
|
// Free Children...
|
|
fMultipart = (_IsMultiPart(pNode)) ? TRUE :FALSE;
|
|
|
|
// Promote Children ?
|
|
if (TRUE == fMultipart && ISFLAGSET(dwFlags, DELETE_PROMOTE_CHILDREN) && pNode->cChildren > 0)
|
|
{
|
|
// Call Helper
|
|
CHECKHR(hr = _HrDeletePromoteChildren(pNode));
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// If multipart, delete children
|
|
if (fMultipart && pNode->cChildren > 0)
|
|
{
|
|
// Remove the children
|
|
_DeleteChildren(pNode);
|
|
}
|
|
|
|
// If Not Children Only
|
|
if (!ISFLAGSET(dwFlags, DELETE_CHILDREN_ONLY))
|
|
{
|
|
// Was this the root
|
|
if (pNode == m_pRootNode)
|
|
{
|
|
// Delete the content type
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTBASE);
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTLOC);
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTID);
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTTYPE);
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTXFER);
|
|
m_pRootNode->pContainer->DeleteProp(SYM_HDR_CNTDISP);
|
|
|
|
// Empty the body
|
|
m_pRootNode->pBody->EmptyData();
|
|
}
|
|
|
|
// Otherwise, not deleting the root
|
|
else
|
|
{
|
|
// Unlink the node
|
|
_UnlinkTreeNode(pNode);
|
|
|
|
// Fix up the table
|
|
m_rTree.prgpNode[HBODYINDEX(hBody)] = NULL;
|
|
|
|
// Increment Empty Count
|
|
m_rTree.cEmpty++;
|
|
|
|
// Free this node
|
|
_FreeTreeNodeInfo(pNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dirty
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDeletePromoteChildren
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDeletePromoteChildren(LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pParent, pChild, pNext, pPrev;
|
|
|
|
// Get Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Single Child...
|
|
if (1 == pNode->cChildren)
|
|
{
|
|
// Promote the child up one level...
|
|
Assert(pNode->pChildHead && pNode->pChildHead && pNode->pChildHead == pNode->pChildTail);
|
|
|
|
// Get Child
|
|
pChild = pNode->pChildHead;
|
|
Assert(pChild->pNext == NULL && pChild->pPrev == NULL && pChild->pParent == pNode);
|
|
|
|
// Replace pBody with pChild
|
|
pChild->pParent = pNode->pParent;
|
|
pChild->pNext = pNode->pNext;
|
|
pChild->pPrev = pNode->pPrev;
|
|
|
|
// Is there a parent ?
|
|
if (pParent)
|
|
{
|
|
// Fixup pChildHead and pChildTail
|
|
if (pParent->pChildHead == pNode)
|
|
pParent->pChildHead = pChild;
|
|
if (pParent->pChildTail == pNode)
|
|
pParent->pChildTail = pChild;
|
|
}
|
|
|
|
// pNode's next and Previous
|
|
LPTREENODEINFO pNext = pNode->pNext;
|
|
LPTREENODEINFO pPrev = pNode->pPrev;
|
|
|
|
// Fixup Next and Previuos
|
|
if (pNext)
|
|
pNext->pPrev = pChild;
|
|
if (pPrev)
|
|
pPrev->pNext = pChild;
|
|
|
|
// pNode Basically does not have any children now
|
|
Assert(pNode->cChildren == 1);
|
|
pNode->cChildren = 0;
|
|
|
|
// Was this the root ?
|
|
if (m_pRootNode == pNode)
|
|
{
|
|
// OE5 Raid: 51543
|
|
if(S_OK == pChild->pContainer->IsContentType(STR_CNT_TEXT, STR_SUB_PLAIN))
|
|
{
|
|
pChild->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN);
|
|
}
|
|
|
|
// Raid 41595 - Athena: Reply to a message includes the body of the message being replied to as an attachment
|
|
CHECKHR(hr = pChild->pContainer->MoveProps(0, NULL, m_pRootNode->pBody));
|
|
|
|
// Reset Header on pChild
|
|
pChild->pBody->SwitchContainers(m_pRootNode->pBody);
|
|
|
|
// Copy Options from p and tell m_pRootNode->pBody
|
|
m_pRootNode->pBody->CopyOptionsTo(pChild->pBody, TRUE);
|
|
|
|
// New root
|
|
m_pRootNode = pChild;
|
|
}
|
|
|
|
// We have now totally unlinked pNode
|
|
DebugAssertNotLinked(pNode);
|
|
|
|
// Fix up the table
|
|
m_rTree.prgpNode[HBODYINDEX(pNode->hBody)] = NULL;
|
|
|
|
// Increment Empty Count
|
|
m_rTree.cEmpty++;
|
|
|
|
// Free this node
|
|
_FreeTreeNodeInfo(pNode);
|
|
}
|
|
|
|
// Or parent is a multipart
|
|
else
|
|
{
|
|
// No parent or not multipart
|
|
if (NULL == pParent || FALSE == _IsMultiPart(pParent))
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_DELETE_TYPE);
|
|
goto exit;
|
|
}
|
|
|
|
// Set Previous
|
|
pPrev = pParent->pChildTail;
|
|
|
|
// Walk children of pBody and append as children of pParent
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// pPrev
|
|
pChild->pPrev = pPrev;
|
|
|
|
// pNext
|
|
pChild->pNext = NULL;
|
|
|
|
// pPrev->pNext
|
|
if (pPrev)
|
|
pPrev->pNext = pChild;
|
|
|
|
// pChildTail
|
|
pParent->pChildTail = pChild;
|
|
|
|
// Set Parent
|
|
pChild->pParent = pParent;
|
|
|
|
// Increment pParent child count
|
|
pParent->cChildren++;
|
|
|
|
// Save pPrev
|
|
pPrev = pChild;
|
|
}
|
|
|
|
// Unlink the node
|
|
_UnlinkTreeNode(pNode);
|
|
|
|
// Fix up the table
|
|
m_rTree.prgpNode[HBODYINDEX(pNode->hBody)] = NULL;
|
|
|
|
// Increment Empty Count
|
|
m_rTree.cEmpty++;
|
|
|
|
// Free this node
|
|
_FreeTreeNodeInfo(pNode);
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_DeleteChildren
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_DeleteChildren(LPTREENODEINFO pParent)
|
|
{
|
|
// Locals
|
|
ULONG i;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
Assert(pParent);
|
|
|
|
// Loop through bodies
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
pNode = m_rTree.prgpNode[i];
|
|
|
|
// Could be null if I already deleted it
|
|
if (NULL == pNode)
|
|
continue;
|
|
|
|
// pBody is Parent...
|
|
if (pParent == pNode->pParent)
|
|
{
|
|
// Free Children...
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Delete Children
|
|
_DeleteChildren(pNode);
|
|
}
|
|
|
|
// Unlink the node
|
|
_UnlinkTreeNode(pNode);
|
|
|
|
// Free this node
|
|
_FreeTreeNodeInfo(pNode);
|
|
|
|
// Fix up the table
|
|
m_rTree.prgpNode[i] = NULL;
|
|
|
|
// Increment Empty Count
|
|
m_rTree.cEmpty++;
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::MoveBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::MoveBody(HBODY hBody, BODYLOCATION location)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
LPTREENODEINFO pPrev;
|
|
LPTREENODEINFO pNext;
|
|
LPTREENODEINFO pParent;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Validate handle
|
|
if (_FIsValidHandle(hBody) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Cast
|
|
pNode = _PNodeFromHBody(hBody);
|
|
|
|
// Handle Location Type
|
|
switch(location)
|
|
{
|
|
// ------------------------------------------------------------------------------------
|
|
case IBL_PARENT:
|
|
// Root already
|
|
AssertSz(FALSE, "UNTESTED - PLEASE CALL SBAILEY AT X32553");
|
|
if (NULL == pNode->pParent || NULL == pNode->pParent->pParent)
|
|
{
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
}
|
|
|
|
// Set Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Parent better be a multipart
|
|
Assert(_IsMultiPart(pParent) && _IsMultiPart(pNode->pParent));
|
|
|
|
// Unlink from tree
|
|
_UnlinkTreeNode(pNode);
|
|
|
|
// Get the current first child
|
|
pPrev = pParent->pChildTail;
|
|
|
|
// Fixup pCurrent
|
|
pNode->pPrev = pPrev;
|
|
|
|
if (pPrev)
|
|
{
|
|
// Fixup pPrev
|
|
pPrev->pNext = pNode;
|
|
}
|
|
|
|
// Fixup Tail
|
|
pParent->pChildTail = pNode;
|
|
|
|
// Increment child count
|
|
pParent->cChildren++;
|
|
|
|
// Done
|
|
break;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// This is a swap of two nodes in a doubly-linked list
|
|
case IBL_NEXT:
|
|
// No Next ?
|
|
AssertSz(FALSE, "UNTESTED - PLEASE CALL SBAILEY AT X32553");
|
|
if (NULL == pNode->pNext)
|
|
{
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
}
|
|
|
|
// Setup for move
|
|
pPrev = pNode->pPrev;
|
|
pNext = pNode->pNext;
|
|
|
|
// Set pNext up...
|
|
Assert(pNext->pPrev == pNode);
|
|
pNext->pPrev = pPrev;
|
|
|
|
// Setup pPrev
|
|
if (pPrev)
|
|
{
|
|
Assert(pPrev->pNext == pNode);
|
|
pPrev->pNext = pNext;
|
|
}
|
|
|
|
// Setup pNode->pNext
|
|
pNode->pNext = pNext->pNext;
|
|
if (pNode->pNext)
|
|
{
|
|
Assert(pNode->pNext->pPrev == pNext);
|
|
pNode->pNext->pPrev = pNode;
|
|
}
|
|
pNext->pNext = pNode;
|
|
|
|
// Setup pNode->pPrev
|
|
pNode->pPrev = pNext;
|
|
|
|
// Get Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Adjust Child and Tail...
|
|
if (pNode == pParent->pChildHead)
|
|
pParent->pChildHead = pNext;
|
|
if (pNext == pParent->pChildTail)
|
|
pParent->pChildTail = pNode;
|
|
|
|
// Done
|
|
break;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// This is a swap of two nodes in a doubly-linked list (reverse of IBL_NEXT)
|
|
case IBL_PREVIOUS:
|
|
// No pPrev ?
|
|
AssertSz(FALSE, "UNTESTED - PLEASE CALL SBAILEY AT X32553");
|
|
if (NULL == pNode->pPrev)
|
|
{
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
}
|
|
|
|
// Setup for move
|
|
pPrev = pNode->pPrev;
|
|
pNext = pNode->pNext;
|
|
|
|
// Set pNext
|
|
Assert(pPrev->pNext == pNode);
|
|
pPrev->pNext = pNext;
|
|
|
|
// Setup pPrev
|
|
pPrev->pPrev = pNode;
|
|
|
|
// Fixup Net
|
|
if (pNext)
|
|
{
|
|
Assert(pNext->pPrev == pNode);
|
|
pNext->pPrev = pPrev;
|
|
}
|
|
|
|
// Setup pNode->pNext
|
|
pNode->pNext = pPrev;
|
|
|
|
// Setup pNode->pPrev
|
|
pNode->pPrev = pPrev->pPrev;
|
|
|
|
// Setup two(prev)->next
|
|
if (pNode->pPrev)
|
|
{
|
|
Assert(pNode->pPrev->pNext == pPrev);
|
|
pNode->pPrev->pNext = pNode;
|
|
}
|
|
|
|
// Get Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Adjust Child and Tail...
|
|
if (pNode == pParent->pChildTail)
|
|
pParent->pChildTail = pPrev;
|
|
if (pPrev == pParent->pChildHead)
|
|
pParent->pChildHead = pNode;
|
|
|
|
// Done
|
|
break;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
case IBL_FIRST:
|
|
// No Parent ?
|
|
if (NULL == pNode->pParent)
|
|
{
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
}
|
|
|
|
// Set Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Better be first child
|
|
if (NULL == pNode->pPrev)
|
|
{
|
|
Assert(pNode == pParent->pChildHead);
|
|
goto exit;
|
|
}
|
|
|
|
// Unlink this body
|
|
pPrev = pNode->pPrev;
|
|
pNext = pNode->pNext;
|
|
|
|
// If pPrev
|
|
pPrev->pNext = pNext;
|
|
|
|
// If pNext or pChildTail
|
|
if (pNext)
|
|
{
|
|
Assert(pNext->pPrev == pNode);
|
|
pNext->pPrev = pPrev;
|
|
}
|
|
else if (pParent)
|
|
{
|
|
Assert(pParent->pChildTail == pNode);
|
|
pParent->pChildTail = pPrev;
|
|
}
|
|
|
|
// Setup pNode
|
|
pNode->pNext = pParent->pChildHead;
|
|
pParent->pChildHead->pPrev = pNode;
|
|
pNode->pPrev = NULL;
|
|
pParent->pChildHead = pNode;
|
|
|
|
// Done
|
|
break;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
case IBL_LAST:
|
|
// No Parent ?
|
|
AssertSz(FALSE, "UNTESTED - PLEASE CALL SBAILEY AT X32553");
|
|
if (NULL == pNode->pParent)
|
|
{
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
}
|
|
|
|
// Set Parent
|
|
pParent = pNode->pParent;
|
|
|
|
// Better be first child
|
|
if (NULL == pNode->pNext)
|
|
{
|
|
Assert(pNode == pParent->pChildTail);
|
|
goto exit;
|
|
}
|
|
|
|
// Unlink this body
|
|
pPrev = pNode->pPrev;
|
|
pNext = pNode->pNext;
|
|
|
|
// If pPrev
|
|
pNext->pPrev = pPrev;
|
|
|
|
// If pNext or pChildTail
|
|
if (pPrev)
|
|
{
|
|
Assert(pPrev->pNext == pNode);
|
|
pPrev->pNext = pNext;
|
|
}
|
|
else if (pParent)
|
|
{
|
|
Assert(pParent->pChildHead == pNode);
|
|
pParent->pChildHead = pNext;
|
|
}
|
|
|
|
// Setup pNode
|
|
pNode->pPrev = pParent->pChildTail;
|
|
pNode->pNext = NULL;
|
|
pParent->pChildTail = pNode;
|
|
|
|
// Done
|
|
break;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
case IBL_ROOT:
|
|
hr = TrapError(MIME_E_CANT_MOVE_BODY);
|
|
goto exit;
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
default:
|
|
hr = TrapError(MIME_E_BAD_BODY_LOCATION);
|
|
goto exit;
|
|
}
|
|
|
|
// Dirty
|
|
FLAGSET(m_dwState, TREESTATE_DIRTY);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
#ifndef WIN16
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_UnlinkTreeNode
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_UnlinkTreeNode(LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
LPTREENODEINFO pPrev;
|
|
LPTREENODEINFO pNext;
|
|
LPTREENODEINFO pParent;
|
|
|
|
// Check Params
|
|
Assert(pNode);
|
|
|
|
// Set Next and Previous
|
|
pParent = pNode->pParent;
|
|
pPrev = pNode->pPrev;
|
|
pNext = pNode->pNext;
|
|
|
|
// If pPrev
|
|
if (pPrev)
|
|
pPrev->pNext = pNext;
|
|
else if (pParent)
|
|
pParent->pChildHead = pNext;
|
|
|
|
// If pNext
|
|
if (pNext)
|
|
pNext->pPrev = pPrev;
|
|
else if (pParent)
|
|
pParent->pChildTail = pPrev;
|
|
|
|
// Delete Children on Parent
|
|
if (pParent)
|
|
pParent->cChildren--;
|
|
|
|
// Cleanup pNode
|
|
pNode->pParent = NULL;
|
|
pNode->pNext = NULL;
|
|
pNode->pPrev = NULL;
|
|
pNode->pChildHead = NULL;
|
|
pNode->pChildTail = NULL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CountBodies
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::CountBodies(HBODY hParent, boolean fRecurse, ULONG *pcBodies)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == pcBodies)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
*pcBodies = 0;
|
|
|
|
// No Parent ?
|
|
if (NULL == hParent || HBODY_ROOT == hParent)
|
|
{
|
|
// Is there a root..
|
|
if (NULL == m_pRootNode)
|
|
goto exit;
|
|
|
|
// Use Root
|
|
pNode = m_pRootNode;
|
|
}
|
|
|
|
// Otherwise, get parent...
|
|
else
|
|
{
|
|
// Validate handle
|
|
if (_FIsValidHandle(hParent) == FALSE)
|
|
{
|
|
hr = TrapError(MIME_E_INVALID_HANDLE);
|
|
goto exit;
|
|
}
|
|
|
|
// Cast
|
|
pNode = _PNodeFromHBody(hParent);
|
|
}
|
|
|
|
// Include the root
|
|
(*pcBodies)++;
|
|
|
|
// Count the children...
|
|
_CountChildrenInt(pNode, fRecurse, pcBodies);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_CountChildrenInt
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_CountChildrenInt(LPTREENODEINFO pParent, BOOL fRecurse, ULONG *pcChildren)
|
|
{
|
|
// Locals
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
Assert(pParent && pcChildren);
|
|
|
|
// Loop through bodies
|
|
for (ULONG i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Readability
|
|
pNode = m_rTree.prgpNode[i];
|
|
|
|
// Empty..
|
|
if (NULL == pNode)
|
|
continue;
|
|
|
|
// pNode is Parent...
|
|
if (pParent == pNode->pParent)
|
|
{
|
|
// Increment Count
|
|
(*pcChildren)++;
|
|
|
|
// Free Children...
|
|
if (fRecurse && _IsMultiPart(pNode))
|
|
_CountChildrenInt(pNode, fRecurse, pcChildren);
|
|
}
|
|
}
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::FindFirst
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::FindFirst(LPFINDBODY pFindBody, LPHBODY phBody)
|
|
{
|
|
// Invalid Arg
|
|
if (NULL == pFindBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init Find
|
|
pFindBody->dwReserved = 0;
|
|
|
|
// Find Next
|
|
return FindNext(pFindBody, phBody);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::FindNext
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::FindNext(LPFINDBODY pFindBody, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG i;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == pFindBody || NULL == phBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
*phBody = NULL;
|
|
|
|
// Loop
|
|
for (i=pFindBody->dwReserved; i<m_rTree.cNodes; i++)
|
|
{
|
|
// If delete
|
|
pNode = m_rTree.prgpNode[i];
|
|
|
|
// Empty
|
|
if (NULL == pNode)
|
|
continue;
|
|
|
|
// Compare content type
|
|
if (pNode->pContainer->IsContentType(pFindBody->pszPriType, pFindBody->pszSubType) == S_OK)
|
|
{
|
|
// Save Index of next item to search
|
|
pFindBody->dwReserved = i + 1;
|
|
*phBody = pNode->hBody;
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Error
|
|
pFindBody->dwReserved = m_rTree.cNodes;
|
|
hr = MIME_E_NOT_FOUND;
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::ToMultipart
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::ToMultipart(HBODY hBody, LPCSTR pszSubType, LPHBODY phMultipart)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
LPTREENODEINFO pNew=NULL;
|
|
LPTREENODEINFO pParent;
|
|
LPTREENODEINFO pNext;
|
|
LPTREENODEINFO pPrev;
|
|
|
|
// check params
|
|
if (NULL == hBody || NULL == pszSubType)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phMultipart)
|
|
*phMultipart = NULL;
|
|
|
|
// Get the body from hBody
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// We better have a root
|
|
Assert(m_pRootNode);
|
|
|
|
// If pNode does not have a parent...
|
|
if (NULL == pNode->pParent)
|
|
{
|
|
// pNode must be the root ?
|
|
Assert(m_pRootNode == pNode);
|
|
|
|
// Create Object
|
|
//N duplicated
|
|
CHECKHR(hr = _HrCreateTreeNode(&pNew));
|
|
|
|
// Set pNode First and Last...
|
|
pNew->pChildHead = m_pRootNode;
|
|
pNew->pChildTail = m_pRootNode;
|
|
m_pRootNode->pParent = pNew;
|
|
|
|
// Set Children Count
|
|
pNew->cChildren = 1;
|
|
|
|
// Set new root
|
|
m_pRootNode = pNew;
|
|
|
|
// Return New Multipart Handle
|
|
if (phMultipart)
|
|
*phMultipart = pNew->hBody;
|
|
|
|
// Swap Property Sets...
|
|
Assert(m_pRootNode != pNode);
|
|
m_pRootNode->pBody->SwitchContainers(pNode->pBody);
|
|
|
|
// Copy Some Props Across
|
|
CHECKHR(hr = m_pRootNode->pBody->MoveProps(ARRAYSIZE(g_rgszToMultipart), g_rgszToMultipart, pNode->pBody));
|
|
}
|
|
|
|
// Otherwise, create a body that takes the place of pNode
|
|
else
|
|
{
|
|
// Create a body object
|
|
CHECKHR(hr = _HrCreateTreeNode(&pNew));
|
|
|
|
// DON'T FAIL FROM HERE TO END OF FUNCTION
|
|
// Return New Multipart Handle
|
|
if (phMultipart)
|
|
*phMultipart = pNew->hBody;
|
|
|
|
// Assume the position of pNode
|
|
pNew->pParent = pNode->pParent;
|
|
pNew->pPrev = pNode->pPrev;
|
|
pNew->pNext = pNode->pNext;
|
|
pNew->pChildHead = pNode;
|
|
pNew->pChildTail = pNode;
|
|
pNew->cChildren = 1;
|
|
|
|
// Set pParnet
|
|
pParent = pNode->pParent;
|
|
|
|
// Fix up parent head and child
|
|
if (pParent->pChildHead == pNode)
|
|
pParent->pChildHead = pNew;
|
|
if (pParent->pChildTail == pNode)
|
|
pParent->pChildTail = pNew;
|
|
|
|
// Set pNode Parent
|
|
pNode->pParent = pNew;
|
|
|
|
// Fixup pNext and pPrev
|
|
pNext = pNode->pNext;
|
|
pPrev = pNode->pPrev;
|
|
if (pNext)
|
|
pNext->pPrev = pNew;
|
|
if (pPrev)
|
|
pPrev->pNext = pNew;
|
|
|
|
// Clear pNext and pPrev
|
|
pNode->pNext = NULL;
|
|
pNode->pPrev = NULL;
|
|
}
|
|
|
|
// Change this nodes content type
|
|
CHECKHR(hr = pNew->pContainer->SetProp(SYM_ATT_PRITYPE, STR_CNT_MULTIPART));
|
|
CHECKHR(hr = pNew->pContainer->SetProp(SYM_ATT_SUBTYPE, pszSubType));
|
|
|
|
pNode->pBody->CopyOptionsTo(pNew->pBody);
|
|
|
|
exit:
|
|
// Create Worked
|
|
_PostCreateTreeNode(hr, pNew);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrNodeFromHandle
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrNodeFromHandle(HBODY hBody, LPTREENODEINFO *ppBody)
|
|
{
|
|
// Invalid Arg
|
|
Assert(hBody && ppBody);
|
|
|
|
// Root ?
|
|
if ((HBODY)HBODY_ROOT == hBody)
|
|
{
|
|
// No Root
|
|
if (NULL == m_pRootNode)
|
|
return MIME_E_NO_DATA;
|
|
|
|
// Otherwise, use root
|
|
*ppBody = m_pRootNode;
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Validate handle
|
|
if (_FIsValidHandle(hBody) == FALSE)
|
|
return TrapError(MIME_E_INVALID_HANDLE);
|
|
|
|
// Get Node
|
|
*ppBody = _PNodeFromHBody(hBody);
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::IsBodyType
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::IsBodyType(HBODY hBody, IMSGBODYTYPE bodytype)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pBody->IsType(bodytype);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::IsContentType
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::IsContentType(HBODY hBody, LPCSTR pszPriType, LPCSTR pszSubType)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pContainer->IsContentType(pszPriType, pszSubType);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::QueryBodyProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::QueryBodyProp(HBODY hBody, LPCSTR pszName, LPCSTR pszCriteria, boolean fSubString, boolean fCaseSensitive)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pContainer->QueryProp(pszName, pszCriteria, fSubString, fCaseSensitive);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetBodyProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetBodyProp(HBODY hBody, LPCSTR pszName, DWORD dwFlags, LPPROPVARIANT pValue)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pContainer->GetProp(pszName, dwFlags, pValue);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SetBodyProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SetBodyProp(HBODY hBody, LPCSTR pszName, DWORD dwFlags, LPCPROPVARIANT pValue)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pContainer->SetProp(pszName, dwFlags, pValue);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DeleteBodyProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::DeleteBodyProp(HBODY hBody, LPCSTR pszName)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pNode;
|
|
|
|
// check params
|
|
if (NULL == hBody)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get body
|
|
CHECKHR(hr = _HrNodeFromHandle(hBody, &pNode));
|
|
|
|
// Call into body object
|
|
hr = pNode->pContainer->DeleteProp(pszName);
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_FIsUuencodeBegin
|
|
// --------------------------------------------------------------------------------
|
|
BOOL CMessageTree::_FIsUuencodeBegin(LPPROPSTRINGA pLine, LPSTR *ppszFileName)
|
|
{
|
|
// Locals
|
|
ULONG i;
|
|
|
|
// check params
|
|
Assert(ISVALIDSTRINGA(pLine));
|
|
|
|
// Length must be at least 11 to accomodate "begin 666 " and the first character of a filename.
|
|
if (pLine->cchVal < 11)
|
|
return FALSE;
|
|
|
|
// First 6 characters must be "begin ", or we're not a valid line.
|
|
if (StrCmpN(pLine->pszVal, "begin ", 6) != 0)
|
|
return FALSE;
|
|
|
|
// Check characters 6-8 for valid Unix filemode. They must all be digits between 0 and 7.
|
|
for (i=6; i<pLine->cchVal; i++)
|
|
{
|
|
if (pLine->pszVal[i] < '0' || pLine->pszVal[i] > '7')
|
|
break;
|
|
}
|
|
|
|
// Not a begin line
|
|
if (pLine->pszVal[i] != ' ')
|
|
return FALSE;
|
|
|
|
// Get File Name
|
|
if (ppszFileName)
|
|
{
|
|
*ppszFileName = PszDupA(pLine->pszVal + i + 1);
|
|
ULONG cbLine = lstrlen (*ppszFileName);
|
|
StripCRLF(*ppszFileName, &cbLine);
|
|
}
|
|
|
|
// Done
|
|
return TRUE;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_GetMimeBoundaryType
|
|
// --------------------------------------------------------------------------------
|
|
BOUNDARYTYPE CMessageTree::_GetMimeBoundaryType(LPPROPSTRINGA pLine, LPPROPSTRINGA pBoundary)
|
|
{
|
|
// Locals
|
|
BOUNDARYTYPE boundary=BOUNDARY_NONE;
|
|
CHAR ch;
|
|
ULONG cchLine=pLine->cchVal;
|
|
LPSTR psz1, psz2;
|
|
|
|
// Check Params
|
|
Assert(ISVALIDSTRINGA(pBoundary) && ISVALIDSTRINGA(pLine));
|
|
|
|
// Check First two chars of the line
|
|
if ('-' != pLine->pszVal[0] || '-' != pLine->pszVal[1])
|
|
goto exit;
|
|
|
|
// Removes trailing white spaces
|
|
while(pLine->cchVal > 0)
|
|
{
|
|
// Get the last character
|
|
ch = *(pLine->pszVal + (pLine->cchVal - 1));
|
|
|
|
// No LWSP or CRLF
|
|
if (' ' != ch && '\t' != ch && chCR != ch && chLF != ch)
|
|
break;
|
|
|
|
// Decrement Length
|
|
pLine->cchVal--;
|
|
}
|
|
|
|
// Decrement two for --
|
|
pLine->cchVal -= 2;
|
|
|
|
// Checks line length against boundary length
|
|
if (pLine->cchVal != pBoundary->cchVal && pLine->cchVal != pBoundary->cchVal + 2)
|
|
goto exit;
|
|
|
|
// Compare the line with the boundary
|
|
if (StrCmpN(pLine->pszVal + 2, pBoundary->pszVal, (size_t)pBoundary->cchVal) == 0)
|
|
{
|
|
// BOUNDARY_MIMEEND
|
|
if ((pLine->cchVal > pBoundary->cchVal) && (pLine->pszVal[pBoundary->cchVal+2] == '-') && (pLine->pszVal[pBoundary->cchVal+3] == '-'))
|
|
boundary = BOUNDARY_MIMEEND;
|
|
|
|
// BOUNDARY_MIMENEXT
|
|
else if (pLine->cchVal == pBoundary->cchVal)
|
|
boundary = BOUNDARY_MIMENEXT;
|
|
}
|
|
|
|
exit:
|
|
// Relace the Length
|
|
pLine->cchVal = cchLine;
|
|
|
|
// Done
|
|
return boundary;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::ResolveURL
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::ResolveURL(HBODY hRelated, LPCSTR pszBase, LPCSTR pszURL,
|
|
DWORD dwFlags, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pSearchRoot;
|
|
RESOLVEURLINFO rInfo;
|
|
HBODY hBody=NULL;
|
|
PROPSTRINGA rBaseUrl;
|
|
LPSTR pszFree=NULL;
|
|
LPSTR pszFree2=NULL;
|
|
BOOL fMultipartBase=FALSE;
|
|
|
|
// InvalidArg
|
|
if (NULL == pszURL)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// If hRelated is NULL, find the first multipart/related
|
|
if (NULL == hRelated)
|
|
{
|
|
// Find the Related
|
|
if (FAILED(MimeOleGetRelatedSection(this, FALSE, &hRelated, NULL)))
|
|
{
|
|
// Use Root
|
|
hRelated = m_pRootNode->hBody;
|
|
}
|
|
}
|
|
|
|
// Get Default Base
|
|
if (NULL == pszBase && FALSE == ISFLAGSET(dwFlags, URL_RESULVE_NO_BASE))
|
|
{
|
|
// Compute the content-base
|
|
if (SUCCEEDED(MimeOleComputeContentBase(this, hRelated, &pszFree, &fMultipartBase)))
|
|
pszBase = pszFree;
|
|
}
|
|
|
|
// Setup Resolve URL Info
|
|
ZeroMemory(&rInfo, sizeof(RESOLVEURLINFO));
|
|
|
|
// This is the base that we will use to absolutify URLs that are in the text/html body
|
|
rInfo.pszBase = pszBase;
|
|
|
|
// Set the url that we are looking for, could be combined with rInfo.pszBase
|
|
rInfo.pszURL = pszURL;
|
|
|
|
// Are we searching for a CID type URL.
|
|
if (StrCmpNI(pszURL, c_szCID, lstrlen(c_szCID)) == 0)
|
|
{
|
|
rInfo.fIsCID = TRUE;
|
|
rInfo.pszURL += 4;
|
|
}
|
|
else
|
|
rInfo.fIsCID = FALSE;
|
|
|
|
// Raid-62579: Athena: Need to support MHTML content-base inheritance
|
|
if (hRelated)
|
|
{
|
|
// Did pszBase come from the multipart/related section ?
|
|
if (fMultipartBase)
|
|
rInfo.pszInheritBase = pszBase;
|
|
|
|
// Otherwise, lookup the multipart/related base header
|
|
else
|
|
rInfo.pszInheritBase = pszFree2 = MimeOleContentBaseFromBody(this, hRelated);
|
|
}
|
|
|
|
// Get a Body from the Handle
|
|
CHECKHR(hr = _HrNodeFromHandle(rInfo.fIsCID ? HBODY_ROOT : hRelated, &pSearchRoot));
|
|
|
|
// Recurse the Tree
|
|
CHECKHR(hr = _HrRecurseResolveURL(pSearchRoot, &rInfo, &hBody));
|
|
|
|
// Not found
|
|
if (NULL == hBody)
|
|
{
|
|
hr = TrapError(MIME_E_NOT_FOUND);
|
|
goto exit;
|
|
}
|
|
|
|
// Return It ?
|
|
if (phBody)
|
|
*phBody = hBody;
|
|
|
|
// Mark as Resolved ?
|
|
if (ISFLAGSET(dwFlags, URL_RESOLVE_RENDERED))
|
|
{
|
|
// Defref the body
|
|
LPTREENODEINFO pNode = _PNodeFromHBody(hBody);
|
|
|
|
// Set Rendered
|
|
PROPVARIANT rVariant;
|
|
rVariant.vt = VT_UI4;
|
|
rVariant.ulVal = TRUE;
|
|
|
|
// Set the Property
|
|
SideAssert(SUCCEEDED(pNode->pContainer->SetProp(PIDTOSTR(PID_ATT_RENDERED), 0, &rVariant)));
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Cleanup
|
|
SafeMemFree(pszFree);
|
|
SafeMemFree(pszFree2);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrRecurseResolveURL
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrRecurseResolveURL(LPTREENODEINFO pNode, LPRESOLVEURLINFO pInfo, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode && pInfo && phBody);
|
|
|
|
// We must have not found the body yet ?
|
|
Assert(NULL == *phBody);
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrRecurseResolveURL(pChild, pInfo, phBody));
|
|
|
|
// Done
|
|
if (NULL != *phBody)
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get Character Set Information
|
|
else
|
|
{
|
|
// Ask the container to do the resolution
|
|
if (SUCCEEDED(pNode->pContainer->HrResolveURL(pInfo)))
|
|
{
|
|
// Cool we found the body, we resolved the URL
|
|
*phBody = pNode->hBody;
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetProp(LPCSTR pszName, DWORD dwFlags, LPPROPVARIANT pValue)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->GetProp(pszName, dwFlags, pValue);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::GetPropW(LPCWSTR pwszName, DWORD dwFlags, LPPROPVARIANT pValue)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SetProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SetProp(LPCSTR pszName, DWORD dwFlags, LPCPROPVARIANT pValue)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->SetProp(pszName, dwFlags, pValue);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::SetPropW(LPCWSTR pwszName, DWORD dwFlags, LPCPROPVARIANT pValue)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DeleteProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::DeleteProp(LPCSTR pszName)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->DeleteProp(pszName);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::DeletePropW(LPCWSTR pwszName)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::QueryProp
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::QueryProp(LPCSTR pszName, LPCSTR pszCriteria, boolean fSubString, boolean fCaseSensitive)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->QueryProp(pszName, pszCriteria, fSubString, fCaseSensitive);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::QueryPropW(LPCWSTR pwszName, LPCWSTR pwszCriteria, boolean fSubString, boolean fCaseSensitive)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAddressTable
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAddressTable(IMimeAddressTable **ppTable)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->BindToObject(IID_IMimeAddressTable, (LPVOID *)ppTable);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetSender
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetSender(LPADDRESSPROPS pAddress)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->GetSender(pAddress);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAddressTypes
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAddressTypes(DWORD dwAdrTypes, DWORD dwProps, LPADDRESSLIST pList)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->GetTypes(dwAdrTypes, dwProps, pList);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAddressFormat
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAddressFormat(DWORD dwAdrType, ADDRESSFORMAT format, LPSTR *ppszFormat)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->GetFormat(dwAdrType, format, ppszFormat);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAddressFormatW
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAddressFormatW(DWORD dwAdrType, ADDRESSFORMAT format, LPWSTR *ppszFormat)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->GetFormatW(dwAdrType, format, ppszFormat);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::EnumAddressTypes
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::EnumAddressTypes(DWORD dwAdrTypes, DWORD dwProps, IMimeEnumAddressTypes **ppEnum)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
Assert(m_pRootNode && m_pRootNode->pContainer);
|
|
HRESULT hr = m_pRootNode->pContainer->EnumTypes(dwAdrTypes, dwProps, ppEnum);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrGetTextTypeInfo
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrGetTextTypeInfo(DWORD dwTxtType, LPTEXTTYPEINFO *ppTextInfo)
|
|
{
|
|
// Invalid Arg
|
|
Assert(ppTextInfo);
|
|
|
|
// Init
|
|
*ppTextInfo = NULL;
|
|
|
|
// Locate the text type
|
|
for (ULONG i=0; i<ARRAYSIZE(g_rgTextInfo); i++)
|
|
{
|
|
// Desired Text Type
|
|
if (g_rgTextInfo[i].dwTxtType == dwTxtType)
|
|
{
|
|
// Found It
|
|
*ppTextInfo = (LPTEXTTYPEINFO)&g_rgTextInfo[i];
|
|
return S_OK;
|
|
}
|
|
}
|
|
|
|
// Un-identified text type
|
|
if (NULL == *ppTextInfo)
|
|
return TrapError(MIME_E_INVALID_TEXT_TYPE);
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_FindDisplayableTextBody
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_FindDisplayableTextBody(LPCSTR pszSubType,
|
|
LPTREENODEINFO pNode, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cBodies;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(pNode && phBody && pszSubType && NULL == *phBody);
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Bind the body table for this dude
|
|
hr = _FindDisplayableTextBody(pszSubType, pChild, phBody);
|
|
|
|
// Done ?
|
|
if (SUCCEEDED(hr))
|
|
{
|
|
Assert(*phBody);
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Otherwise...
|
|
else if (S_OK == pNode->pContainer->IsContentType(STR_CNT_TEXT, pszSubType))
|
|
{
|
|
// If not an attachment...
|
|
if (S_FALSE == IsBodyType(pNode->hBody, IBT_ATTACHMENT))
|
|
{
|
|
*phBody = pNode->hBody;
|
|
goto exit;
|
|
}
|
|
|
|
// Otherwise...Raid 43444: Inbox Direct messages showing as attachments
|
|
else
|
|
{
|
|
// Count Bodies
|
|
CHECKHR(hr = CountBodies(NULL, TRUE, &cBodies));
|
|
|
|
// Only one body...
|
|
if (cBodies == 1)
|
|
{
|
|
// Inline or Disposition is not set
|
|
if (m_pRootNode->pContainer->QueryProp(PIDTOSTR(PID_HDR_CNTDISP), STR_DIS_INLINE, FALSE, FALSE) == S_OK ||
|
|
m_pRootNode->pContainer->IsPropSet(PIDTOSTR(PID_HDR_CNTDISP)) == S_FALSE)
|
|
{
|
|
*phBody = pNode->hBody;
|
|
goto exit;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Not Found
|
|
hr = MIME_E_NOT_FOUND;
|
|
|
|
exit:
|
|
// Done
|
|
return(hr);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetTextBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetTextBody(DWORD dwTxtType, ENCODINGTYPE ietEncoding,
|
|
IStream **ppStream, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HRESULT hrFind;
|
|
LPTEXTTYPEINFO pTextInfo=NULL;
|
|
FINDBODY rFind;
|
|
IMimeBody *pBody=NULL;
|
|
PROPVARIANT rStart;
|
|
PROPVARIANT rVariant;
|
|
HBODY hAlternativeParent;
|
|
HBODY hFirst=NULL;
|
|
HBODY hChild;
|
|
HBODY hBody=NULL;
|
|
HBODY hRelated;
|
|
LPSTR pszStartCID=NULL;
|
|
BOOL fMarkRendered=TRUE;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
if (ppStream)
|
|
*ppStream = NULL;
|
|
|
|
// Init
|
|
MimeOleVariantInit(&rStart);
|
|
|
|
// Get the Text Info
|
|
CHECKHR(hr = _HrGetTextTypeInfo(dwTxtType, &pTextInfo));
|
|
|
|
// MimeHTML
|
|
if (SUCCEEDED(MimeOleGetRelatedSection(this, FALSE, &hRelated, NULL)))
|
|
{
|
|
// Get start= parameter
|
|
if (SUCCEEDED(GetBodyProp(hRelated, STR_PAR_START, 0, &rStart)))
|
|
{
|
|
// Raid 63823: Mail : Content-Location Href's inside the message do not work if there is a Start Parameter in headers
|
|
// The start parameter can only specify a CID.
|
|
|
|
// I need to prefix cid: onto the front of rStart
|
|
DWORD cchSize = (lstrlen(rStart.pszVal) + lstrlen(c_szCID) + 1);
|
|
CHECKALLOC(pszStartCID = PszAllocA(cchSize));
|
|
|
|
// Format the CID
|
|
wnsprintfA(pszStartCID, cchSize, "%s%s", c_szCID, rStart.pszVal);
|
|
|
|
// Resolve this URL
|
|
ResolveURL(hRelated, NULL, pszStartCID, URL_RESULVE_NO_BASE, &hBody);
|
|
}
|
|
}
|
|
|
|
// Still haven't found a text body ?
|
|
if (NULL == hBody)
|
|
{
|
|
// FindTextBody
|
|
hr = _FindDisplayableTextBody(pTextInfo->pszSubType, m_pRootNode, &hBody);
|
|
|
|
// If that failed and we were looking for html, try to get enriched text...
|
|
if (FAILED(hr) && ISFLAGSET(dwTxtType, TXT_HTML))
|
|
{
|
|
// Looking for text/html, lets try to find text/enriched
|
|
hr = _FindDisplayableTextBody(STR_SUB_ENRICHED, m_pRootNode, &hBody);
|
|
}
|
|
|
|
// Not Found
|
|
if (FAILED(hr))
|
|
{
|
|
hr = TrapError(MIME_E_NOT_FOUND);
|
|
goto exit;
|
|
}
|
|
|
|
// Reset hr
|
|
hr = S_OK;
|
|
}
|
|
|
|
// Get the stream...
|
|
CHECKHR(hr = BindToObject(hBody, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// If Empty...
|
|
if (pBody->IsType(IBT_EMPTY) == S_OK)
|
|
{
|
|
hr = MIME_E_NO_DATA;
|
|
goto exit;
|
|
}
|
|
|
|
// User Wants the Data
|
|
if (ppStream)
|
|
{
|
|
// If content-type is text/enriched, convert to html
|
|
if (pBody->IsContentType(STR_CNT_TEXT, STR_SUB_ENRICHED) == S_OK)
|
|
{
|
|
// Better be asking for html
|
|
Assert(ISFLAGSET(dwTxtType, TXT_HTML));
|
|
|
|
// Do the Conversion
|
|
CHECKHR(hr = MimeOleConvertEnrichedToHTMLEx(pBody, ietEncoding, ppStream));
|
|
}
|
|
|
|
// Otherwise, non-text enriched case
|
|
else
|
|
{
|
|
// Get Data
|
|
CHECKHR(hr = pBody->GetData(ietEncoding, ppStream));
|
|
}
|
|
}
|
|
|
|
// If we are in OE5 compat mode...
|
|
if (TRUE == ISFLAGSET(g_dwCompatMode, MIMEOLE_COMPAT_OE5))
|
|
{
|
|
// If there is no stream requested, then don't mark rendered
|
|
if (NULL == ppStream)
|
|
{
|
|
// Don't Mark Rendered
|
|
fMarkRendered = FALSE;
|
|
}
|
|
}
|
|
|
|
// Mark Rendered
|
|
if (fMarkRendered)
|
|
{
|
|
// Rendered
|
|
rVariant.vt = VT_UI4;
|
|
rVariant.ulVal = TRUE;
|
|
|
|
// Lets set the resourl flag
|
|
SideAssert(SUCCEEDED(pBody->SetProp(PIDTOSTR(PID_ATT_RENDERED), 0, &rVariant)));
|
|
|
|
// Raid-45116: new text attachment contains message body on Communicator inline image message
|
|
if (FAILED(GetBody(IBL_PARENT, hBody, &hAlternativeParent)))
|
|
hAlternativeParent = NULL;
|
|
|
|
// Try to find an alternative parent...
|
|
while(hAlternativeParent)
|
|
{
|
|
// If multipart/alternative, were done
|
|
if (IsContentType(hAlternativeParent, STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_OK)
|
|
break;
|
|
|
|
// Get Next Parent
|
|
if (FAILED(GetBody(IBL_PARENT, hAlternativeParent, &hAlternativeParent)))
|
|
hAlternativeParent = NULL;
|
|
}
|
|
|
|
// Get Parent
|
|
if (hAlternativeParent)
|
|
{
|
|
// Resolve all first level children
|
|
hrFind = GetBody(IBL_FIRST, hAlternativeParent, &hChild);
|
|
while(SUCCEEDED(hrFind) && hChild)
|
|
{
|
|
// Set Resolve Property
|
|
SideAssert(SUCCEEDED(SetBodyProp(hChild, PIDTOSTR(PID_ATT_RENDERED), 0, &rVariant)));
|
|
|
|
// Find Next
|
|
hrFind = GetBody(IBL_NEXT, hChild, &hChild);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the hBody
|
|
if (phBody)
|
|
*phBody = hBody;
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pBody);
|
|
SafeMemFree(pszStartCID);
|
|
MimeOleVariantFree(&rStart);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SetTextBody
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SetTextBody(DWORD dwTxtType, ENCODINGTYPE ietEncoding,
|
|
HBODY hAlternative, IStream *pStream, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK,
|
|
hrFind;
|
|
HBODY hRoot,
|
|
hBody,
|
|
hTextBody=NULL,
|
|
hSection,
|
|
hParent,
|
|
hPrevious,
|
|
hInsertAfter;
|
|
LPTEXTTYPEINFO pTextInfo=NULL;
|
|
BOOL fFound,
|
|
fFoundInsertLocation;
|
|
DWORD dwWeightBody;
|
|
ULONG i;
|
|
IMimeBody *pBody=NULL;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pStream)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
|
|
// Debug Dump
|
|
// DebugDumpTree("SetTextBody", TRUE);
|
|
|
|
// Get the Text Info
|
|
CHECKHR(hr = _HrGetTextTypeInfo(dwTxtType, &pTextInfo));
|
|
|
|
// Raid-45369: message from Eudora Pro comes in .txt attachment which is lost when forwarded.
|
|
// If hAlternative is NULL, then this means that the client wants to replace all text bodies
|
|
// with this new text body. If hAlternative is not NULL, then the client has already inserted
|
|
// a text body and created a alternative section, no more deleting.
|
|
if (NULL == hAlternative)
|
|
{
|
|
// Loop through the text type
|
|
for (i=0; i<ARRAYSIZE(g_rgTextInfo); i++)
|
|
{
|
|
// Get the Current Text Body Associated with this type
|
|
if (SUCCEEDED(GetTextBody(g_rgTextInfo[i].dwTxtType, IET_BINARY, NULL, &hBody)))
|
|
{
|
|
// If the parent of hBody is an alternative section, delete the alternative
|
|
if (SUCCEEDED(GetBody(IBL_PARENT, hBody, &hParent)) && IsContentType(hParent, STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_OK)
|
|
{
|
|
// Delete multipart/alternative
|
|
hBody = hParent;
|
|
}
|
|
|
|
// Not if hBody is equal to hAlternative
|
|
if (hBody != hAlternative)
|
|
{
|
|
// Locals
|
|
HRESULT hrFind;
|
|
HBODY hFind;
|
|
|
|
// Raid-54277: Mail : Inline replying losses inline images sent from Nav4 using Plain text & HTML format
|
|
hrFind = GetBody(IBL_FIRST, hBody, &hFind);
|
|
while(SUCCEEDED(hrFind) && hFind)
|
|
{
|
|
// If not a multipart/related, delete it
|
|
if (S_FALSE == IsContentType(hFind, STR_CNT_MULTIPART, STR_SUB_RELATED))
|
|
{
|
|
// Delete this body
|
|
CHECKHR(hr = DeleteBody(hFind, 0));
|
|
|
|
// Use the hPrevious
|
|
hrFind = GetBody(IBL_FIRST, hBody, &hFind);
|
|
}
|
|
|
|
// Get Next
|
|
else
|
|
{
|
|
// Find Next
|
|
hrFind = GetBody(IBL_NEXT, hFind, &hFind);
|
|
}
|
|
}
|
|
|
|
// Delete the multipart/alternative section, promoting any multipart/related section
|
|
CHECKHR(hr = DeleteBody(hBody, DELETE_PROMOTE_CHILDREN));
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Get Root
|
|
CHECKHR(hr = GetBody(IBL_ROOT, NULL, &hRoot));
|
|
|
|
// If only one body..
|
|
if (IsBodyType(hRoot, IBT_EMPTY) == S_OK)
|
|
{
|
|
// Just use the root
|
|
hTextBody = hRoot;
|
|
}
|
|
|
|
// Otherwise, if not inserting an alternative body, we must need a multipart/mixed or multipart/related section
|
|
else if (NULL == hAlternative)
|
|
{
|
|
// Better not be an alternative section
|
|
Assert(FAILED(MimeOleGetAlternativeSection(this, &hSection, NULL)));
|
|
|
|
// If there is a related section use it
|
|
if (FAILED(MimeOleGetRelatedSection(this, FALSE, &hSection, NULL)))
|
|
{
|
|
// Find or Create a multipart/mixed section
|
|
CHECKHR(hr = MimeOleGetMixedSection(this, TRUE, &hSection, NULL));
|
|
}
|
|
|
|
// Insert an element at the head of this section...
|
|
CHECKHR(hr = InsertBody(IBL_FIRST, hSection, &hTextBody));
|
|
}
|
|
|
|
// Otherwise, if inserting an alternative body
|
|
else if (hAlternative != NULL)
|
|
{
|
|
// Verify pBody is STR_CNT_TEXT
|
|
Assert(IsContentType(hAlternative, STR_CNT_TEXT, NULL) == S_OK);
|
|
|
|
// Get hAlternative's parent
|
|
if (FAILED(GetBody(IBL_PARENT, hAlternative, &hParent)))
|
|
hParent = NULL;
|
|
|
|
// If hAlternative is the root
|
|
if (hRoot == hAlternative || NULL == hParent || IsContentType(hParent, STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_FALSE)
|
|
{
|
|
// Convert this body to a multipart/alternative
|
|
CHECKHR(hr = ToMultipart(hAlternative, STR_SUB_ALTERNATIVE, &hSection));
|
|
}
|
|
|
|
// Otherwise, hSection is equal to hParent
|
|
else
|
|
hSection = hParent;
|
|
|
|
// We better have an alternative section now...
|
|
Assert(IsContentType(hSection, STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_OK);
|
|
|
|
// Init Search
|
|
hPrevious = NULL;
|
|
fFound = FALSE;
|
|
fFoundInsertLocation = FALSE;
|
|
dwWeightBody = 0;
|
|
hInsertAfter = NULL;
|
|
|
|
// Lets enum the children of rLayout.hAlternative and verify that hAlternative is still a child...and decide what alternative body to insert after
|
|
hrFind = GetBody(IBL_FIRST, hSection, &hBody);
|
|
while(SUCCEEDED(hrFind) && hBody)
|
|
{
|
|
// Default dwWeightBody
|
|
dwWeightBody = 0xffffffff;
|
|
|
|
// Get Weight of hBody
|
|
for (i=0; i<ARRAYSIZE(g_rgTextInfo); i++)
|
|
{
|
|
// Compare Content Type
|
|
if (IsContentType(hBody, STR_CNT_TEXT, g_rgTextInfo[i].pszSubType) == S_OK)
|
|
{
|
|
dwWeightBody = g_rgTextInfo[i].dwWeight;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Get Alternative Weight of the body we are inserting
|
|
if (pTextInfo->dwWeight <= dwWeightBody && FALSE == fFoundInsertLocation)
|
|
{
|
|
fFoundInsertLocation = TRUE;
|
|
hInsertAfter = hPrevious;
|
|
}
|
|
|
|
// Is this the alternative brother...
|
|
if (hAlternative == hBody)
|
|
fFound = TRUE;
|
|
|
|
// Set hPrev
|
|
hPrevious = hBody;
|
|
|
|
// Find Next
|
|
hrFind = GetBody(IBL_NEXT, hBody, &hBody);
|
|
}
|
|
|
|
// If we didn't find hAlternative, we're hosed
|
|
if (FALSE == fFound)
|
|
{
|
|
Assert(FALSE);
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// If no after was found... insert first..
|
|
if (NULL == hInsertAfter)
|
|
{
|
|
// BODY_LAST_CHILD
|
|
if (pTextInfo->dwWeight > dwWeightBody)
|
|
{
|
|
// Insert a new body...
|
|
CHECKHR(hr = InsertBody(IBL_LAST, hSection, &hTextBody));
|
|
}
|
|
|
|
// BODY_FIRST_CHILD
|
|
else
|
|
{
|
|
// Insert a new body...
|
|
CHECKHR(hr = InsertBody(IBL_FIRST, hSection, &hTextBody));
|
|
}
|
|
}
|
|
|
|
// Otherwise insert after hInsertAfter
|
|
else
|
|
{
|
|
// Insert a new body...
|
|
CHECKHR(hr = InsertBody(IBL_NEXT, hInsertAfter, &hTextBody));
|
|
}
|
|
}
|
|
|
|
// Open the object
|
|
Assert(hTextBody);
|
|
CHECKHR(hr = BindToObject(hTextBody, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// Set the root...
|
|
CHECKHR(hr = pBody->SetData(ietEncoding, STR_CNT_TEXT, pTextInfo->pszSubType, IID_IStream, (LPVOID)pStream));
|
|
|
|
// Release This
|
|
SafeRelease(pBody);
|
|
|
|
// Set multipart/related; type=...
|
|
if (SUCCEEDED(GetBody(IBL_PARENT, hTextBody, &hParent)))
|
|
{
|
|
// If parent is multipart/related, set type
|
|
if (IsContentType(hParent, STR_CNT_MULTIPART, STR_SUB_RELATED) == S_OK)
|
|
{
|
|
// Get Parent
|
|
CHECKHR(hr = BindToObject(hParent, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// type = text/plain
|
|
if (ISFLAGSET(dwTxtType, TXT_PLAIN))
|
|
{
|
|
// Setup Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)STR_MIME_TEXT_PLAIN;
|
|
|
|
// Set the Properyt
|
|
CHECKHR(hr = pBody->SetProp(STR_PAR_TYPE, 0, &rVariant));
|
|
}
|
|
|
|
// type = text/plain
|
|
else if (ISFLAGSET(dwTxtType, TXT_HTML))
|
|
{
|
|
// Setup Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)STR_MIME_TEXT_HTML;
|
|
|
|
// Set the Properyt
|
|
CHECKHR(hr = pBody->SetProp(STR_PAR_TYPE, 0, &rVariant));
|
|
}
|
|
else
|
|
AssertSz(FALSE, "UnKnown dwTxtType passed to IMimeMessage::SetTextBody");
|
|
}
|
|
|
|
// Otherwise, if hParent is multipart/alternative
|
|
else if (IsContentType(hParent, STR_CNT_MULTIPART, STR_SUB_ALTERNATIVE) == S_OK)
|
|
{
|
|
// Set multipart/related; type=...
|
|
if (SUCCEEDED(GetBody(IBL_PARENT, hParent, &hParent)))
|
|
{
|
|
// If parent is multipart/related, set type
|
|
if (IsContentType(hParent, STR_CNT_MULTIPART, STR_SUB_RELATED) == S_OK)
|
|
{
|
|
// Get Parent
|
|
CHECKHR(hr = BindToObject(hParent, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// Setup Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)STR_MIME_MPART_ALT;
|
|
|
|
// Set the Properyt
|
|
CHECKHR(hr = pBody->SetProp(STR_PAR_TYPE, 0, &rVariant));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Set body handle
|
|
if (phBody)
|
|
*phBody = hTextBody;
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pBody);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::AttachObject
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::AttachObject(REFIID riid, void *pvObject, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HBODY hBody,
|
|
hMixed;
|
|
IMimeBody *pBody=NULL;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pvObject || FALSE == FBODYSETDATAIID(riid))
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
|
|
// Get Mixed Section
|
|
CHECKHR(hr = MimeOleGetMixedSection(this, TRUE, &hMixed, NULL));
|
|
|
|
// Append a child to the mixed part...
|
|
CHECKHR(hr = InsertBody(IBL_LAST, hMixed, &hBody));
|
|
|
|
// Bind to the Body Object
|
|
CHECKHR(hr = BindToObject(hBody, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// Set Data Object
|
|
CHECKHR(hr = pBody->SetData(IET_INETCSET, NULL, NULL, riid, pvObject));
|
|
|
|
// Setup Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)STR_DIS_ATTACHMENT;
|
|
|
|
// Mark as Attachment
|
|
CHECKHR(hr = SetBodyProp(hBody, PIDTOSTR(PID_HDR_CNTDISP), 0, &rVariant));
|
|
|
|
// Return hBody
|
|
if (phBody)
|
|
*phBody = hBody;
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pBody);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::AttachFile
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::AttachFile(LPCSTR pszFilePath, IStream *pstmFile, LPHBODY phBody)
|
|
{
|
|
LPWSTR pwszFilePath;
|
|
HRESULT hr = S_OK;
|
|
|
|
IF_NULLEXIT(pwszFilePath = PszToUnicode(CP_ACP, pszFilePath));
|
|
|
|
IF_FAILEXIT(hr = AttachFileW(pwszFilePath, pstmFile, phBody));
|
|
|
|
exit:
|
|
MemFree(pwszFilePath);
|
|
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::AttachFileW(LPCWSTR pszFilePath, IStream *pstmFile, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
IStream *pstmTemp=NULL;
|
|
LPWSTR pszCntType=NULL,
|
|
pszSubType=NULL,
|
|
pszFName=NULL;
|
|
HBODY hBody;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pszFilePath)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (phBody)
|
|
*phBody = NULL;
|
|
|
|
// Did the user give me a file stream
|
|
if (NULL == pstmFile)
|
|
{
|
|
// Get File Stream
|
|
CHECKHR(hr = OpenFileStreamW((LPWSTR)pszFilePath, OPEN_EXISTING, GENERIC_READ, &pstmTemp));
|
|
|
|
// Set The File Stream
|
|
pstmFile = pstmTemp;
|
|
}
|
|
|
|
// Attach as object
|
|
CHECKHR(hr = AttachObject(IID_IStream, (LPVOID)pstmFile, &hBody));
|
|
|
|
// Get mime file info
|
|
hr = MimeOleGetFileInfoW((LPWSTR)pszFilePath, &pszCntType, &pszSubType, NULL, &pszFName, NULL);
|
|
|
|
// Failure
|
|
if (FAILED(hr) && NULL == pszFName)
|
|
{
|
|
Assert(FALSE);
|
|
hr = TrapError(hr);
|
|
goto exit;
|
|
}
|
|
|
|
// Success
|
|
hr = S_OK;
|
|
|
|
// Attachment FileName
|
|
if (pszFName)
|
|
{
|
|
rVariant.vt = VT_LPWSTR;
|
|
rVariant.pwszVal = pszFName;
|
|
CHECKHR(hr = SetBodyProp(hBody, PIDTOSTR(PID_ATT_FILENAME), 0, &rVariant));
|
|
}
|
|
|
|
// ContentType
|
|
if (pszCntType && pszSubType)
|
|
{
|
|
// PriType
|
|
rVariant.vt = VT_LPWSTR;
|
|
rVariant.pwszVal = pszCntType;
|
|
CHECKHR(hr = SetBodyProp(hBody, PIDTOSTR(PID_ATT_PRITYPE), 0, &rVariant));
|
|
|
|
// SubType
|
|
rVariant.vt = VT_LPWSTR;
|
|
rVariant.pwszVal = pszSubType;
|
|
CHECKHR(hr = SetBodyProp(hBody, PIDTOSTR(PID_ATT_SUBTYPE), 0, &rVariant));
|
|
}
|
|
|
|
// Otherwise, default content type
|
|
else
|
|
{
|
|
// Default to text/plain
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = (LPSTR)STR_MIME_TEXT_PLAIN;
|
|
CHECKHR(hr = SetBodyProp(hBody, PIDTOSTR(PID_HDR_CNTTYPE), 0, &rVariant));
|
|
}
|
|
|
|
// Return hBody
|
|
if (phBody)
|
|
*phBody = hBody;
|
|
|
|
exit:
|
|
// Cleanup
|
|
ReleaseObj(pstmTemp);
|
|
MemFree(pszCntType);
|
|
MemFree(pszSubType);
|
|
MemFree(pszFName);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAttachments
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAttachments(ULONG *pcAttach, LPHBODY *pprghAttach)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPHBODY prghBody=NULL;
|
|
ULONG cAlloc=0;
|
|
ULONG cCount=0;
|
|
ULONG i;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pcAttach)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (pprghAttach)
|
|
*pprghAttach = NULL;
|
|
*pcAttach = 0;
|
|
|
|
// Setup Variant
|
|
rVariant.vt = VT_UI4;
|
|
|
|
// Walk through the tree and look for unrendered bodies
|
|
for (i=0; i<m_rTree.cNodes; i++)
|
|
{
|
|
// Better have it
|
|
if (NULL == m_rTree.prgpNode[i])
|
|
continue;
|
|
|
|
// Not a multipart
|
|
if (_IsMultiPart(m_rTree.prgpNode[i]))
|
|
continue;
|
|
|
|
// Raid-44193: reply to multipart/digest message yields text attachment
|
|
if (m_rTree.prgpNode[i]->pBody->IsType(IBT_EMPTY) == S_OK)
|
|
continue;
|
|
|
|
// Raid-56665: We are showing tnef attachments again
|
|
if (TRUE == m_rOptions.fHideTnef && S_OK == m_rTree.prgpNode[i]->pBody->IsContentType(STR_CNT_APPLICATION, STR_SUB_MSTNEF))
|
|
continue;
|
|
|
|
// an attachment shows up in the attachment well if it has NOT been rendered yet, OR it has been renderd but was auto inlined
|
|
// eg: if (!r || a) (as a implies r)
|
|
|
|
if ( (!(m_rTree.prgpNode[i]->pContainer->GetProp(PIDTOSTR(PID_ATT_RENDERED), 0, &rVariant) == S_OK && TRUE == rVariant.ulVal)) ||
|
|
(m_rTree.prgpNode[i]->pContainer->GetProp(PIDTOSTR(PID_ATT_AUTOINLINED), 0, &rVariant)==S_OK && TRUE == rVariant.ulVal))
|
|
{
|
|
// Realloc
|
|
if (cCount + 1 > cAlloc)
|
|
{
|
|
// Realloc
|
|
CHECKHR(hr = HrRealloc((LPVOID *)&prghBody, sizeof(HBODY) * (cAlloc + 10)));
|
|
|
|
// Increment cAlloc
|
|
cAlloc += 10;
|
|
}
|
|
|
|
// Insert the hBody
|
|
prghBody[cCount] = m_rTree.prgpNode[i]->hBody;
|
|
|
|
// Increment Count
|
|
cCount++;
|
|
}
|
|
}
|
|
|
|
// Done
|
|
*pcAttach = cCount;
|
|
|
|
// Return hBody Array
|
|
if (pprghAttach)
|
|
{
|
|
*pprghAttach = prghBody;
|
|
prghBody = NULL;
|
|
}
|
|
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(prghBody);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
#if 0
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAttachments
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetAttachments(ULONG *pcAttach, LPHBODY *pprghAttach)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cBodies;
|
|
LPHBODY prghBody=NULL;
|
|
HBODY hRoot;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pcAttach)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
if (pprghAttach)
|
|
*pprghAttach = NULL;
|
|
*pcAttach = 0;
|
|
|
|
// Count the number of items in the tree
|
|
CHECKHR(hr = CountBodies(NULL, TRUE, &cBodies));
|
|
|
|
// No Data
|
|
if (0 == cBodies)
|
|
{
|
|
hr = MIME_E_NO_DATA;
|
|
goto exit;
|
|
}
|
|
|
|
// Get the root body
|
|
CHECKHR(hr = GetBody(IBL_ROOT, NULL, &hRoot));
|
|
|
|
// Allocate an array that can old the handle to all text items
|
|
CHECKALLOC(prghBody = (LPHBODY)g_pMalloc->Alloc(sizeof(HBODY) * cBodies));
|
|
|
|
// Zero Init
|
|
ZeroMemory(prghBody, sizeof(HBODY) * cBodies);
|
|
|
|
// Get Content
|
|
CHECKHR(hr = _HrEnumeratAttachments(hRoot, pcAttach, prghBody));
|
|
|
|
// Return this array
|
|
if (pprghAttach && *pcAttach > 0)
|
|
{
|
|
*pprghAttach = prghBody;
|
|
prghBody = NULL;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(prghBody);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrEnumeratAttachments
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrEnumeratAttachments(HBODY hBody, ULONG *pcBodies, LPHBODY prghBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK,
|
|
hrFind;
|
|
HBODY hChild;
|
|
ULONG i;
|
|
|
|
// multipart/alternative
|
|
if (IsContentType(hBody, STR_CNT_MULTIPART, NULL) == S_OK)
|
|
{
|
|
// Is Alternative
|
|
if (IsContentType(hBody, NULL, STR_SUB_ALTERNATIVE) == S_OK)
|
|
{
|
|
// Get First Child
|
|
hrFind = GetBody(IBL_FIRST, hBody, &hChild);
|
|
while(SUCCEEDED(hrFind) && NULL != hChild)
|
|
{
|
|
// If this text type is support by the client, then
|
|
for (i=0; i<ARRAYSIZE(g_rgTextInfo); i++)
|
|
{
|
|
// text/XXXX
|
|
if (IsContentType(hChild, STR_CNT_TEXT, g_rgTextInfo[i].pszSubType) == S_OK)
|
|
goto exit;
|
|
}
|
|
|
|
// Next Child
|
|
hrFind = GetBody(IBL_NEXT, hChild, &hChild);
|
|
}
|
|
}
|
|
|
|
// Get First Child
|
|
hrFind = GetBody(IBL_FIRST, hBody, &hChild);
|
|
while(SUCCEEDED(hrFind) && hChild)
|
|
{
|
|
// Bind the body table for this dude
|
|
CHECKHR(hr = _HrEnumeratAttachments(hChild, pcBodies, prghBody));
|
|
|
|
// Next Child
|
|
hrFind = GetBody(IBL_NEXT, hChild, &hChild);
|
|
}
|
|
}
|
|
|
|
// Otherwise, is it an attachment
|
|
else if (IsBodyType(hBody, IBT_ATTACHMENT) == S_OK)
|
|
{
|
|
// Insert as an attachment
|
|
prghBody[(*pcBodies)] = hBody;
|
|
(*pcBodies)++;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
#endif
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::AttachURL
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::AttachURL(LPCSTR pszBase, LPCSTR pszURL, DWORD dwFlags,
|
|
IStream *pstmURL, LPSTR *ppszCIDURL, LPHBODY phBody)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HBODY hRoot,
|
|
hBody=NULL,
|
|
hSection;
|
|
CHAR szCID[CCHMAX_CID];
|
|
LPSTR pszFree=NULL;
|
|
LPSTR pszBaseFree=NULL;
|
|
IMimeBody *pBody=NULL;
|
|
LPWSTR pwszUrl=NULL;
|
|
IMimeWebDocument *pWebDocument=NULL;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Get the Root Body
|
|
CHECKHR(hr = GetBody(IBL_ROOT, NULL, &hRoot));
|
|
|
|
// multipart/mixed
|
|
if (ISFLAGSET(dwFlags, URL_ATTACH_INTO_MIXED))
|
|
{
|
|
// Get Mixed Section
|
|
CHECKHR(hr = MimeOleGetMixedSection(this, TRUE, &hSection, NULL));
|
|
}
|
|
|
|
// multipart/related
|
|
else
|
|
{
|
|
// Get Mixed Section
|
|
CHECKHR(hr = MimeOleGetRelatedSection(this, TRUE, &hSection, NULL));
|
|
}
|
|
|
|
// Get Default Base
|
|
if (NULL == pszBase && SUCCEEDED(MimeOleComputeContentBase(this, hSection, &pszBaseFree, NULL)))
|
|
pszBase = pszBaseFree;
|
|
|
|
// Append a child to the mixed part...
|
|
CHECKHR(hr = InsertBody(IBL_LAST, hSection, &hBody));
|
|
|
|
// Bind to IMimeBody
|
|
CHECKHR(hr = BindToObject(hBody, IID_IMimeBody, (LPVOID *)&pBody));
|
|
|
|
// If I have a stream
|
|
if (pstmURL)
|
|
{
|
|
// Set the data
|
|
CHECKHR(hr = pBody->SetData(IET_INETCSET, NULL, NULL, IID_IStream, (LPVOID)pstmURL));
|
|
}
|
|
|
|
// Otherwise, Set the content type
|
|
else
|
|
{
|
|
// Create a WebDocument
|
|
CHECKHR(hr = MimeOleCreateWebDocument(pszBase, pszURL, &pWebDocument));
|
|
|
|
// Set Web Document on the body object
|
|
CHECKHR(hr = pBody->SetData(IET_BINARY, NULL, NULL, IID_IMimeWebDocument, (LPVOID)pWebDocument));
|
|
}
|
|
|
|
// URL_ATTACH_SET_CNTTYPE
|
|
if (ISFLAGSET(dwFlags, URL_ATTACH_SET_CNTTYPE))
|
|
{
|
|
// Locals
|
|
LPSTR pszCntType=(LPSTR)STR_MIME_APPL_STREAM;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Get the Content Type from the Url
|
|
if (SUCCEEDED(MimeOleContentTypeFromUrl(pszBase, pszURL, &pszFree)))
|
|
pszCntType = pszFree;
|
|
|
|
// Setup the Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
rVariant.pszVal = pszCntType;
|
|
|
|
// Set the Content Type
|
|
CHECKHR(hr = pBody->SetProp(PIDTOSTR(PID_HDR_CNTTYPE), 0, &rVariant));
|
|
}
|
|
|
|
// Set Content-Base
|
|
if (pszBase && pszBase != pszBaseFree)
|
|
{
|
|
// Set Base
|
|
CHECKHR(hr = MimeOleSetPropA(pBody, PIDTOSTR(PID_HDR_CNTBASE), 0, pszBase));
|
|
}
|
|
|
|
// User Wants a CID: URL Back
|
|
if (ISFLAGSET(dwFlags, URL_ATTACH_GENERATE_CID))
|
|
{
|
|
// Generate CID
|
|
CHECKHR(hr = MimeOleGenerateCID(szCID, CCHMAX_CID, FALSE));
|
|
|
|
// Set the Body Property
|
|
CHECKHR(hr = MimeOleSetPropA(pBody, PIDTOSTR(PID_HDR_CNTID), 0, szCID));
|
|
|
|
// User Wants CID Back...
|
|
if (ppszCIDURL)
|
|
{
|
|
DWORD cchSize = (lstrlen(szCID) + 5);
|
|
CHECKALLOC(MemAlloc((LPVOID *)ppszCIDURL, cchSize));
|
|
StrCpyN(*ppszCIDURL, "cid:", cchSize);
|
|
StrCatBuff(*ppszCIDURL, szCID, cchSize);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (pszURL)
|
|
// Setup Content-Location
|
|
CHECKHR(hr = MimeOleSetPropA(pBody, PIDTOSTR(PID_HDR_CNTLOC), 0, pszURL));
|
|
}
|
|
|
|
// Return the hBody
|
|
if (phBody)
|
|
*phBody = hBody;
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszFree);
|
|
SafeMemFree(pszBaseFree);
|
|
SafeMemFree(pwszUrl);
|
|
SafeRelease(pBody);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::AttachURLW(LPCWSTR pwszBase, LPCWSTR pwszURL, DWORD dwFlags,
|
|
IStream *pstmURL, LPWSTR *ppwszCIDURL, LPHBODY phBody)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
STDMETHODIMP CMessageTree::ResolveURLW(HBODY hRelated, LPCWSTR pwszBase, LPCWSTR pwszURL,
|
|
DWORD dwFlags, LPHBODY phBody)
|
|
{
|
|
return TraceResult(E_NOTIMPL);
|
|
}
|
|
|
|
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SplitMessage
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::SplitMessage(ULONG cbMaxPart, IMimeMessageParts **ppParts)
|
|
{
|
|
EnterCriticalSection(&m_cs);
|
|
HRESULT hr = MimeOleSplitMessage(this, cbMaxPart, ppParts);
|
|
LeaveCriticalSection(&m_cs);
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::EnumFormatEtc
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::EnumFormatEtc(DWORD dwDirection, IEnumFORMATETC **ppEnum)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cFormat=0;
|
|
DATAOBJINFO rgFormat[CFORMATS_IDATAOBJECT];
|
|
ULONG cBodies;
|
|
IEnumFORMATETC *pEnum=NULL;
|
|
DWORD dwFlags;
|
|
|
|
// Invalid Arg
|
|
if (NULL == ppEnum)
|
|
return TrapError(E_INVALIDARG);
|
|
if (DATADIR_SET == dwDirection)
|
|
return TrapError(E_NOTIMPL);
|
|
else if (DATADIR_GET != dwDirection)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Init
|
|
*ppEnum = NULL;
|
|
|
|
// No Data...
|
|
CHECKHR(hr = CountBodies(NULL, TRUE, &cBodies));
|
|
|
|
// If there are bodies...
|
|
if (cBodies)
|
|
{
|
|
// I can always give CF_INETMSG now...
|
|
SETDefFormatEtc(rgFormat[cFormat].fe, CF_INETMSG, TYMED_ISTREAM | TYMED_HGLOBAL);
|
|
cFormat++;
|
|
|
|
// Get Some Flags
|
|
dwFlags = DwGetFlags();
|
|
|
|
// Get the HTML body stream
|
|
if (ISFLAGSET(dwFlags, IMF_HTML))
|
|
{
|
|
SETDefFormatEtc(rgFormat[cFormat].fe, CF_HTML, TYMED_ISTREAM | TYMED_HGLOBAL);
|
|
cFormat++;
|
|
}
|
|
|
|
// Get the TEXT body stream
|
|
if (ISFLAGSET(dwFlags, IMF_PLAIN))
|
|
{
|
|
// Unicode Text
|
|
SETDefFormatEtc(rgFormat[cFormat].fe, CF_UNICODETEXT, TYMED_ISTREAM | TYMED_HGLOBAL);
|
|
cFormat++;
|
|
|
|
// Plain Text
|
|
SETDefFormatEtc(rgFormat[cFormat].fe, CF_TEXT, TYMED_ISTREAM | TYMED_HGLOBAL);
|
|
cFormat++;
|
|
}
|
|
}
|
|
|
|
// Create the enumerator
|
|
CHECKHR(hr = CreateEnumFormatEtc(GetInner(), cFormat, rgFormat, NULL, &pEnum));
|
|
|
|
// Set Return
|
|
*ppEnum = pEnum;
|
|
(*ppEnum)->AddRef();
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pEnum);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetCanonicalFormatEtc
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetCanonicalFormatEtc(FORMATETC *pFormatIn, FORMATETC *pFormatOut)
|
|
{
|
|
// E_INVALIDARG
|
|
if (NULL == pFormatOut)
|
|
return E_INVALIDARG;
|
|
|
|
// Target device independent
|
|
pFormatOut->ptd = NULL;
|
|
|
|
// Done
|
|
return DATA_S_SAMEFORMATETC;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetData
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetData(FORMATETC *pFormat, STGMEDIUM *pMedium)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pstmData=NULL;
|
|
BOOL fFreeGlobal=FALSE;
|
|
|
|
// E_INVALIDARG
|
|
if (NULL == pFormat || NULL == pMedium)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// TYMED_ISTREAM
|
|
if (ISFLAGSET(pFormat->tymed, TYMED_ISTREAM))
|
|
{
|
|
// Use a fast IStream
|
|
if (FAILED(MimeOleCreateVirtualStream(&pstmData)))
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Get data object source
|
|
if (FAILED(hr = _HrDataObjectGetSource(pFormat->cfFormat, pstmData)))
|
|
goto exit;
|
|
|
|
// Set pmedium
|
|
pMedium->tymed = TYMED_ISTREAM;
|
|
pMedium->pstm = pstmData;
|
|
pstmData->AddRef();
|
|
}
|
|
|
|
// TYMED_HGLOBAL
|
|
else if (ISFLAGSET(pFormat->tymed, TYMED_HGLOBAL))
|
|
{
|
|
fFreeGlobal = TRUE;
|
|
|
|
// don't have the stream release the global
|
|
if (FAILED(CreateStreamOnHGlobal(NULL, FALSE, &pstmData)))
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Get data object source
|
|
if (FAILED(hr = _HrDataObjectGetSource(pFormat->cfFormat, pstmData)))
|
|
goto exit;
|
|
|
|
// Create HGLOBAL from stream
|
|
if (FAILED(GetHGlobalFromStream(pstmData, &pMedium->hGlobal)))
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Set pmedium type
|
|
pMedium->tymed = TYMED_HGLOBAL;
|
|
// Release the strema
|
|
pstmData->Release();
|
|
pstmData = NULL;
|
|
fFreeGlobal = FALSE;
|
|
}
|
|
|
|
// Bad Medium Type
|
|
else
|
|
{
|
|
hr = TrapError(DV_E_TYMED);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
if (pstmData)
|
|
{
|
|
if (fFreeGlobal)
|
|
{
|
|
// we may fail had have to free the hglobal
|
|
HGLOBAL hGlobal;
|
|
|
|
// Free the underlying HGLOBAL
|
|
if (SUCCEEDED(GetHGlobalFromStream(pstmData, &hGlobal)))
|
|
GlobalFree(hGlobal);
|
|
}
|
|
|
|
// Release the Stream
|
|
pstmData->Release();
|
|
}
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetDataHere
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetDataHere(FORMATETC *pFormat, STGMEDIUM *pMedium)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pstmData=NULL;
|
|
ULONG cb;
|
|
LPVOID pv=NULL;
|
|
|
|
// E_INVALIDARG
|
|
if (NULL == pFormat || NULL == pMedium)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// TYMED_ISTREAM
|
|
if (ISFLAGSET(pFormat->tymed, TYMED_ISTREAM))
|
|
{
|
|
// No dest stream...
|
|
if (NULL == pMedium->pstm)
|
|
{
|
|
hr = TrapError(E_INVALIDARG);
|
|
goto exit;
|
|
}
|
|
|
|
// Set pmedium
|
|
pMedium->tymed = TYMED_ISTREAM;
|
|
|
|
// Get the data
|
|
CHECKHR(hr = _HrDataObjectGetSource(pFormat->cfFormat, pMedium->pstm));
|
|
}
|
|
|
|
// TYMED_HGLOBAL
|
|
else if (ISFLAGSET(pFormat->tymed, TYMED_HGLOBAL))
|
|
{
|
|
// No dest stream...
|
|
if (NULL == pMedium->hGlobal)
|
|
{
|
|
hr = TrapError(E_INVALIDARG);
|
|
goto exit;
|
|
}
|
|
|
|
// Set pmedium type
|
|
pMedium->tymed = TYMED_HGLOBAL;
|
|
|
|
// Create a place to store the data
|
|
if (FAILED(MimeOleCreateVirtualStream(&pstmData)))
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Get data object source
|
|
CHECKHR(hr = _HrDataObjectGetSource(pFormat->cfFormat, pstmData));
|
|
|
|
// Get Size
|
|
CHECKHR(hr = HrGetStreamSize(pstmData, &cb));
|
|
|
|
// Is it big enought ?
|
|
if (cb > GlobalSize(pMedium->hGlobal))
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Lock the hglobal
|
|
pv = GlobalLock(pMedium->hGlobal);
|
|
if (NULL == pv)
|
|
{
|
|
hr = TrapError(STG_E_MEDIUMFULL);
|
|
goto exit;
|
|
}
|
|
|
|
// Copy the Data
|
|
CHECKHR(hr = HrCopyStreamToByte(pstmData, (LPBYTE)pv, NULL));
|
|
|
|
// Unlock it
|
|
GlobalUnlock(pMedium->hGlobal);
|
|
}
|
|
|
|
// Bad Medium Type
|
|
else
|
|
{
|
|
hr = TrapError(DV_E_TYMED);
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmData);
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDataObjectWriteHeaderA
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDataObjectWriteHeaderA(LPSTREAM pStream, UINT idsHeader, LPSTR pszData)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
CHAR szRes[CCHMAX_STRINGRES];
|
|
|
|
// Invalid Arg
|
|
Assert(idsHeader && pStream && pszData);
|
|
|
|
// Load Localized Header Name
|
|
LoadString(g_hLocRes, idsHeader, szRes, ARRAYSIZE(szRes));
|
|
|
|
// Write Header Name
|
|
CHECKHR(hr = pStream->Write(szRes, lstrlen(szRes), NULL));
|
|
|
|
// Write space
|
|
CHECKHR(hr = pStream->Write(c_szColonSpace, lstrlen(c_szColonSpace), NULL));
|
|
|
|
// Write Data
|
|
CHECKHR(hr = pStream->Write(pszData, lstrlen(pszData), NULL));
|
|
|
|
// Final CRLF
|
|
CHECKHR(hr = pStream->Write(g_szCRLF, lstrlen(g_szCRLF), NULL));
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDataObjectGetHeaderA
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDataObjectGetHeaderA(LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Init
|
|
MimeOleVariantInit(&rVariant);
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_FROM), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderA(pStream, IDS_FROM, rVariant.pszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_TO), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderA(pStream, IDS_TO, rVariant.pszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_CC), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderA(pStream, IDS_CC, rVariant.pszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_SUBJECT), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderA(pStream, IDS_SUBJECT, rVariant.pszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_FILETIME;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_ATT_RECVTIME), 0, &rVariant)))
|
|
{
|
|
// Locals
|
|
CHAR szDate[CCHMAX_STRINGRES];
|
|
|
|
// Convert to user friendly date format
|
|
CchFileTimeToDateTimeSz(&rVariant.filetime, szDate, ARRAYSIZE(szDate), DTM_NOSECONDS | DTM_LONGDATE);
|
|
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderA(pStream, IDS_DATE, szDate));
|
|
}
|
|
|
|
// Final CRLF
|
|
CHECKHR(hr = pStream->Write(g_szCRLF, lstrlen(g_szCRLF), NULL));
|
|
|
|
exit:
|
|
// Cleanup
|
|
MimeOleVariantFree(&rVariant);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDataObjectWriteHeaderW
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDataObjectWriteHeaderW(LPSTREAM pStream, UINT idsHeader, LPWSTR pwszData)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
CHAR szRes[CCHMAX_STRINGRES];
|
|
LPWSTR pwszRes=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(idsHeader && pStream && pwszData);
|
|
|
|
// Load Localized Header Name
|
|
LoadString(g_hLocRes, idsHeader, szRes, ARRAYSIZE(szRes));
|
|
|
|
// Convert to Unicode
|
|
IF_NULLEXIT(pwszRes = PszToUnicode(CP_ACP, szRes));
|
|
|
|
// Write Header Name
|
|
CHECKHR(hr = pStream->Write(pwszRes, (lstrlenW(pwszRes) * sizeof(WCHAR)), NULL));
|
|
|
|
// Write space
|
|
CHECKHR(hr = pStream->Write(L": ", (lstrlenW(L": ") * sizeof(WCHAR)), NULL));
|
|
|
|
// Write Data
|
|
CHECKHR(hr = pStream->Write(pwszData, (lstrlenW(pwszData) * sizeof(WCHAR)), NULL));
|
|
|
|
// Final CRLF
|
|
CHECKHR(hr = pStream->Write(L"\r\n", (lstrlenW(L"\r\n") * sizeof(WCHAR)), NULL));
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pwszRes);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDataObjectGetHeaderW
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDataObjectGetHeaderW(LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPWSTR pwszDate=NULL;
|
|
PROPVARIANT rVariant;
|
|
|
|
// Init
|
|
MimeOleVariantInit(&rVariant);
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPWSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_FROM), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderW(pStream, IDS_FROM, rVariant.pwszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPWSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_TO), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderW(pStream, IDS_TO, rVariant.pwszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPWSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_CC), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderW(pStream, IDS_CC, rVariant.pwszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_LPWSTR;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_HDR_SUBJECT), 0, &rVariant)))
|
|
{
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderW(pStream, IDS_SUBJECT, rVariant.pwszVal));
|
|
|
|
// Free It
|
|
MimeOleVariantFree(&rVariant);
|
|
}
|
|
|
|
// Init Variant
|
|
rVariant.vt = VT_FILETIME;
|
|
|
|
// Get address table from header...
|
|
if (SUCCEEDED(GetBodyProp(HBODY_ROOT, PIDTOSTR(PID_ATT_RECVTIME), 0, &rVariant)))
|
|
{
|
|
// Locals
|
|
WCHAR wszDate[CCHMAX_STRINGRES];
|
|
|
|
// Convert to user friendly date format
|
|
AthFileTimeToDateTimeW(&rVariant.filetime, wszDate, ARRAYSIZE(wszDate), DTM_NOSECONDS | DTM_LONGDATE);
|
|
|
|
// Write it
|
|
CHECKHR(hr = _HrDataObjectWriteHeaderW(pStream, IDS_DATE, wszDate));
|
|
}
|
|
|
|
// Final CRLF
|
|
CHECKHR(hr = pStream->Write(L"\r\n", (lstrlenW(L"\r\n") * sizeof(WCHAR)), NULL));
|
|
|
|
exit:
|
|
// Cleanup
|
|
MimeOleVariantFree(&rVariant);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrDataObjectGetSource
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrDataObjectGetSource(CLIPFORMAT cfFormat, LPSTREAM pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPSTREAM pstmSrc=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream);
|
|
|
|
// text body
|
|
if (CF_TEXT == cfFormat || CF_UNICODETEXT == cfFormat)
|
|
{
|
|
// Get Plain Text Source
|
|
CHECKHR(hr = GetTextBody(TXT_PLAIN, (cfFormat == CF_UNICODETEXT) ? IET_UNICODE : IET_BINARY, &pstmSrc, NULL));
|
|
}
|
|
|
|
// HTML Body
|
|
else if (CF_HTML == cfFormat)
|
|
{
|
|
// Get HTML Text Source
|
|
CHECKHR(hr = GetTextBody(TXT_HTML, IET_INETCSET, &pstmSrc, NULL));
|
|
}
|
|
|
|
// Raw Message Stream
|
|
else if (CF_INETMSG == cfFormat)
|
|
{
|
|
// Get source
|
|
CHECKHR(hr = GetMessageSource(&pstmSrc, COMMIT_ONLYIFDIRTY));
|
|
}
|
|
|
|
// Format not handled
|
|
else
|
|
{
|
|
hr = DV_E_FORMATETC;
|
|
goto exit;
|
|
}
|
|
|
|
// No Data
|
|
if (NULL == pstmSrc)
|
|
{
|
|
hr = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// Rewind Source
|
|
CHECKHR(hr = HrRewindStream(pstmSrc));
|
|
|
|
// If TEXT, put in friendly header
|
|
if (CF_TEXT == cfFormat)
|
|
{
|
|
CHECKHR(hr = _HrDataObjectGetHeaderA(pStream));
|
|
}
|
|
|
|
// Otherwise, unicode
|
|
else if (CF_UNICODETEXT == cfFormat)
|
|
{
|
|
CHECKHR(hr = _HrDataObjectGetHeaderW(pStream));
|
|
}
|
|
|
|
// Copy Source to destination
|
|
CHECKHR(hr = HrCopyStream(pstmSrc, pStream, NULL));
|
|
|
|
// Write a NULL
|
|
if (CF_TEXT == cfFormat)
|
|
{
|
|
CHECKHR(hr = pStream->Write(c_szEmpty, 1, NULL));
|
|
}
|
|
|
|
// Otherwise, unicode
|
|
else if (CF_UNICODETEXT == cfFormat)
|
|
{
|
|
CHECKHR(hr = pStream->Write(L"", 2, NULL));
|
|
}
|
|
|
|
// Commit
|
|
CHECKHR(hr = pStream->Commit(STGC_DEFAULT));
|
|
|
|
// Rewind it
|
|
CHECKHR(hr = HrRewindStream(pStream));
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pstmSrc);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::QueryGetData
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::QueryGetData(FORMATETC *pFormat)
|
|
{
|
|
// Invalid Arg
|
|
if (NULL == pFormat)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Bad Medium
|
|
if (!(TYMED_ISTREAM & pFormat->tymed) && !(TYMED_HGLOBAL & pFormat->tymed))
|
|
return DV_E_TYMED;
|
|
|
|
// Bad format
|
|
if (CF_TEXT != pFormat->cfFormat && CF_HTML != pFormat->cfFormat &&
|
|
CF_UNICODETEXT != pFormat->cfFormat && CF_INETMSG != pFormat->cfFormat)
|
|
return DV_E_FORMATETC;
|
|
|
|
// Success
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::OnStartBinding
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::OnStartBinding(DWORD dwReserved, IBinding *pBinding)
|
|
{
|
|
// Locals
|
|
HBODY hBody;
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// I Should not have a current binding
|
|
Assert(NULL == m_pBinding);
|
|
|
|
// Remove Bind Finished Flag
|
|
FLAGCLEAR(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// Assume the Binding
|
|
if (pBinding)
|
|
{
|
|
// Assume It
|
|
m_pBinding = pBinding;
|
|
m_pBinding->AddRef();
|
|
}
|
|
|
|
// Get the Root Body
|
|
Assert(m_pRootNode);
|
|
|
|
// Current Bind Result
|
|
m_hrBind = S_OK;
|
|
|
|
// Bind to that object
|
|
m_pBindNode = m_pRootNode;
|
|
|
|
// Set Bound Start
|
|
m_pBindNode->boundary = BOUNDARY_ROOT;
|
|
|
|
// Set Node Bind State
|
|
m_pBindNode->bindstate = BINDSTATE_PARSING_HEADER;
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetPriority
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetPriority(LONG *plPriority)
|
|
{
|
|
// Normal Priority
|
|
*plPriority = THREAD_PRIORITY_NORMAL;
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::OnLowResource
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::OnLowResource(DWORD reserved)
|
|
{
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// If we have a binding operation, try to abort it
|
|
if (m_pBinding)
|
|
m_pBinding->Abort();
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::OnProgress
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::OnProgress(ULONG ulProgress, ULONG ulProgressMax, ULONG ulStatusCode, LPCWSTR pszStatusText)
|
|
{
|
|
// Debuging
|
|
//DebugTrace("CMessageTree::OnProgress - %d of %d Bytes\n", ulProgress, ulProgressMax);
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::OnStopBinding
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::OnStopBinding(HRESULT hrResult, LPCWSTR pszError)
|
|
{
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Release the Binding Object
|
|
SafeRelease(m_pBinding);
|
|
|
|
// Bind Finished
|
|
FLAGSET(m_dwState, TREESTATE_BINDDONE);
|
|
|
|
// Nuke the no cache flag....
|
|
FLAGCLEAR(m_dwState, TREESTATE_RESYNCHRONIZE);
|
|
|
|
// No m_pInternet Object ?
|
|
if (NULL == m_pInternet)
|
|
{
|
|
m_hrBind = TrapError(E_FAIL);
|
|
goto exit;
|
|
}
|
|
|
|
// It must be fully available
|
|
m_pInternet->SetFullyAvailable(TRUE);
|
|
|
|
// Make sure we have read all the way to the end of the stream
|
|
m_pInternet->HrReadToEnd();
|
|
|
|
// Keep Saving Total
|
|
m_cbMessage = m_pInternet->DwGetOffset();
|
|
|
|
#ifdef DEBUG
|
|
STATSTG rStat;
|
|
SideAssert(SUCCEEDED(m_pStmLock->Stat(&rStat, STATFLAG_NONAME)));
|
|
if (rStat.cbSize.QuadPart != m_cbMessage)
|
|
DebugTrace("CMessageTree Size Difference m_pStmLock::Stat = %d, m_cbMessage = %d\n", rStat.cbSize.QuadPart, m_cbMessage);
|
|
#endif
|
|
|
|
// Terminate current parsing state
|
|
if (m_pBindNode)
|
|
{
|
|
// Set Error
|
|
if (SUCCEEDED(m_hrBind))
|
|
m_hrBind = TrapError(E_FAIL);
|
|
|
|
// Mark remaining bodies as incomplete
|
|
while(m_pBindNode)
|
|
{
|
|
// Must not be complete
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// Must not have found the end
|
|
Assert(0 == m_pBindNode->cbBodyEnd);
|
|
|
|
// cbBodyEnd
|
|
m_pBindNode->cbBodyEnd = m_cbMessage;
|
|
|
|
// Pop the stack
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
}
|
|
}
|
|
|
|
// Check hrResult
|
|
if (FAILED(hrResult) && SUCCEEDED(m_hrBind))
|
|
m_hrBind = hrResult;
|
|
|
|
// DispatchBindRequest
|
|
_HrProcessPendingUrlRequests();
|
|
|
|
// Tell the webpage that we are done
|
|
if (m_pWebPage)
|
|
{
|
|
m_pWebPage->OnBindComplete(this);
|
|
m_pWebPage->Release();
|
|
m_pWebPage = NULL;
|
|
}
|
|
|
|
// Bind Node Better be Null
|
|
m_pBindNode = NULL;
|
|
|
|
// Release the Internet Stream Object
|
|
SafeRelease(m_pInternet);
|
|
|
|
// If we have a bind stream...
|
|
if (m_pStmBind)
|
|
{
|
|
#ifdef DEBUG
|
|
// m_pStmBind->DebugDumpDestStream("d:\\binddst.txt");
|
|
#endif
|
|
// Get hands off source
|
|
m_pStmBind->HandsOffSource();
|
|
|
|
// Release, m_pStmLock should still have this object
|
|
SideAssert(m_pStmBind->Release() > 0);
|
|
|
|
// Don't release it again
|
|
m_pStmBind = NULL;
|
|
}
|
|
|
|
// HandleCanInlineTextOption
|
|
_HandleCanInlineTextOption();
|
|
|
|
exit:
|
|
if (m_pBC)
|
|
{
|
|
// we only regiser our own bscb is m_pbc is set
|
|
RevokeBindStatusCallback(m_pBC, (IBindStatusCallback *)this);
|
|
SafeRelease(m_pBC);
|
|
}
|
|
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return m_hrBind;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetBindInfo
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::GetBindInfo(DWORD *grfBINDF, BINDINFO *pBindInfo)
|
|
{
|
|
// Setup the BindInfo
|
|
*grfBINDF = BINDF_ASYNCHRONOUS | BINDF_ASYNCSTORAGE | BINDF_PULLDATA;
|
|
|
|
// No CACHE?
|
|
if (ISFLAGSET(m_dwState, TREESTATE_RESYNCHRONIZE))
|
|
{
|
|
// Don't load from cache
|
|
FLAGSET(*grfBINDF, BINDF_RESYNCHRONIZE);
|
|
}
|
|
|
|
// Done
|
|
return S_OK;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrInitializeStorage
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrInitializeStorage(IStream *pStream)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD dwOffset;
|
|
|
|
// Invalid Arg
|
|
Assert(pStream && NULL == m_pInternet && NULL == m_pStmLock && NULL == m_pStmBind);
|
|
|
|
// TREESTATE_BINDUSEFILE
|
|
if (ISFLAGSET(m_dwState, TREESTATE_BINDUSEFILE))
|
|
{
|
|
// Create a Binding Stream
|
|
CHECKALLOC(m_pStmBind = new CBindStream(pStream));
|
|
|
|
// Set pStmSource
|
|
pStream = (IStream *)m_pStmBind;
|
|
}
|
|
|
|
// $$BUGBUG$$ Urlmon fails on getting the current position of a stream
|
|
if (FAILED(HrGetStreamPos(pStream, &dwOffset)))
|
|
dwOffset = 0;
|
|
|
|
// Create a ILockBytes
|
|
CHECKALLOC(m_pStmLock = new CStreamLockBytes(pStream));
|
|
|
|
// Create a Text Stream
|
|
CHECKALLOC(m_pInternet = new CInternetStream);
|
|
|
|
// Initialize the TextStream
|
|
m_pInternet->InitNew(dwOffset, m_pStmLock);
|
|
|
|
exit:
|
|
// Failure
|
|
if (FAILED(hr))
|
|
{
|
|
SafeRelease(m_pStmLock);
|
|
SafeRelease(m_pInternet);
|
|
}
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::OnDataAvailable
|
|
// --------------------------------------------------------------------------------
|
|
STDMETHODIMP CMessageTree::OnDataAvailable(DWORD grfBSCF, DWORD dwSize, FORMATETC *pFormat, STGMEDIUM *pMedium)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// No Storage Medium
|
|
if (NULL == pMedium || TYMED_ISTREAM != pMedium->tymed || NULL == pMedium->pstm)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Trace
|
|
// DebugTrace("CMessageTree::OnDataAvailable - Nodes=%d, m_pBindNode=%0x, dwSize = %d\n", m_rTree.cNodes, m_pBindNode, dwSize);
|
|
|
|
// Do I have an internal lock bytes yet ?
|
|
if (NULL == m_pStmLock)
|
|
{
|
|
// InitializeStorage
|
|
CHECKHR(hr = _HrInitializeStorage(pMedium->pstm));
|
|
|
|
// Assume not fully available
|
|
if (BINDSTATUS_ENDDOWNLOADDATA == grfBSCF)
|
|
m_pInternet->SetFullyAvailable(TRUE);
|
|
else
|
|
m_pInternet->SetFullyAvailable(FALSE);
|
|
}
|
|
|
|
// Done downloading the data
|
|
else if (BINDSTATUS_ENDDOWNLOADDATA == grfBSCF)
|
|
m_pInternet->SetFullyAvailable(TRUE);
|
|
|
|
// If we are in a failed read state
|
|
if (SUCCEEDED(m_hrBind))
|
|
{
|
|
// State Pumper
|
|
while(m_pBindNode)
|
|
{
|
|
// Execute current - could return E_PENDING
|
|
hr = ((this->*m_rgBindStates[m_pBindNode->bindstate])());
|
|
|
|
// Failure
|
|
if (FAILED(hr))
|
|
{
|
|
// E_PENDING
|
|
if (E_PENDING == hr)
|
|
goto exit;
|
|
|
|
// Otherwise, set m_hrBind
|
|
m_hrBind = hr;
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If m_hrBind has failed, read until endof stream
|
|
if (FAILED(m_hrBind))
|
|
{
|
|
// Read to the end of the internet stream
|
|
CHECKHR(hr = m_pInternet->HrReadToEnd());
|
|
}
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindParsingHeader
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindParsingHeader(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
MIMEVARIANT rVariant;
|
|
|
|
// Invalid Arg
|
|
BINDASSERTARGS(BINDSTATE_PARSING_HEADER, FALSE);
|
|
|
|
// Load the Current Body with the header
|
|
CHECKHR(hr = m_pBindNode->pContainer->Load(m_pInternet));
|
|
|
|
// End of the Header
|
|
m_pBindNode->cbBodyStart = m_pInternet->DwGetOffset();
|
|
|
|
// Multipart ?
|
|
if (_IsMultiPart(m_pBindNode))
|
|
{
|
|
// Setup the variant
|
|
rVariant.type = MVT_STRINGA;
|
|
|
|
// Get the boundary String
|
|
hr = m_pBindNode->pContainer->GetProp(SYM_PAR_BOUNDARY, 0, &rVariant);
|
|
|
|
// Raid-63150: Athena version 1 MSN issue: unable to download messages from SCSPromo
|
|
if (FAILED(hr))
|
|
{
|
|
// Incomplete Body
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// Convert to a text part only if we read more than two bytes from body start
|
|
m_pBindNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN);
|
|
|
|
// Boundary Mismatch
|
|
hr = TrapError(MIME_E_BOUNDARY_MISMATCH);
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Set PropStringA
|
|
m_pBindNode->rBoundary.pszVal = rVariant.rStringA.pszVal;
|
|
m_pBindNode->rBoundary.cchVal = rVariant.rStringA.cchVal;
|
|
|
|
// Free this boundary later
|
|
FLAGCLEAR(m_pBindNode->dwState, NODESTATE_BOUNDNOFREE);
|
|
|
|
// Modify Bind Parser State
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_MIMEFIRST;
|
|
}
|
|
|
|
// Otherwise
|
|
else
|
|
{
|
|
// Message In a Message
|
|
if (m_pBindNode->pContainer->IsContentType(STR_CNT_MESSAGE, NULL) == S_OK)
|
|
{
|
|
// We are parsing a message attachment
|
|
FLAGSET(m_pBindNode->dwState, NODESTATE_MESSAGE);
|
|
}
|
|
|
|
// Otherwise, if parent and parent is a multipart/digest
|
|
else if (m_pBindNode->pParent && m_pBindNode->pParent->pContainer->IsContentType(STR_CNT_MULTIPART, STR_SUB_DIGEST) == S_OK &&
|
|
m_pBindNode->pContainer->IsPropSet(PIDTOSTR(PID_HDR_CNTTYPE)) == S_FALSE)
|
|
{
|
|
// Change the Content Type
|
|
m_pBindNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_MSG_RFC822);
|
|
|
|
// This is a message
|
|
FLAGSET(m_pBindNode->dwState, NODESTATE_MESSAGE);
|
|
}
|
|
|
|
// If parsing a body inside of a parent multipart section
|
|
if (m_pBindNode->pParent && !ISFLAGSET(m_pBindNode->pParent->dwType, NODETYPE_FAKEMULTIPART))
|
|
{
|
|
// Find Next Mime Part
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_MIMENEXT;
|
|
}
|
|
|
|
// Otherwise, Reading Body and Looking for a uuencode begin boundary
|
|
else
|
|
{
|
|
// Parse the RFC1154 header
|
|
_DecodeRfc1154();
|
|
|
|
if (m_pBT1154)
|
|
{
|
|
HBODY hBody;
|
|
|
|
// This is an RFC1154 message. We convert the root node
|
|
// to a multi-part, and create a new node for the first
|
|
// of the body parts.
|
|
Assert(m_pBindNode == m_pRootNode);
|
|
m_pBindNode->bindstate = BINDSTATE_PARSING_RFC1154;
|
|
m_pBindNode->cbBodyEnd = m_pBindNode->cbBodyStart;
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_FAKEMULTIPART);
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_RFC1154_ROOT);
|
|
CHECKHR(hr = m_pBindNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_MPART_MIXED));
|
|
CHECKHR(hr = InsertBody(IBL_LAST,m_pBindNode->hBody,&hBody));
|
|
m_pBindNode = _PNodeFromHBody(hBody);
|
|
m_pBindNode->bindstate = BINDSTATE_PARSING_RFC1154;
|
|
}
|
|
else
|
|
{
|
|
// Search for nested uuencoded block of data
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_UUBEGIN;
|
|
}
|
|
}
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrOnFoundNodeEnd
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrOnFoundNodeEnd(DWORD cbBoundaryStart, HRESULT hrBind /* =S_OK */)
|
|
{
|
|
// Locals
|
|
HRESULT hr =S_OK;
|
|
|
|
// Compute the real body end
|
|
if (cbBoundaryStart < 2 || cbBoundaryStart == m_pBindNode->cbBodyStart)
|
|
m_pBindNode->cbBodyEnd = m_pBindNode->cbBodyStart;
|
|
else
|
|
m_pBindNode->cbBodyEnd = cbBoundaryStart - 2;
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(m_pBindNode, hrBind));
|
|
|
|
// POP the stack
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrOnFoundMultipartEnd
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrOnFoundMultipartEnd(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Set m_pBindNode which is a multipart, end
|
|
m_pBindNode->cbBodyEnd = m_pInternet->DwGetOffset();
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(m_pBindNode, S_OK));
|
|
|
|
// Finished with the multipart, pop it off the stack
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
|
|
// If I still have a bind node, it should now be looking for a mime first boundary
|
|
if (m_pBindNode)
|
|
{
|
|
// New Bind State
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_MIMEFIRST;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindFindingMimeFirst
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindFindingMimeFirst(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD cbBoundaryStart;
|
|
PROPSTRINGA rLine;
|
|
BOUNDARYTYPE boundary=BOUNDARY_NONE;
|
|
|
|
// Invalid Arg
|
|
BINDASSERTARGS(BINDSTATE_FINDING_MIMEFIRST, TRUE);
|
|
|
|
// Sit and Spin
|
|
while(BOUNDARY_NONE == boundary)
|
|
{
|
|
// Mark Boundary Start
|
|
cbBoundaryStart = m_pInternet->DwGetOffset();
|
|
|
|
// Read a line
|
|
CHECKHR(hr = m_pInternet->HrReadLine(&rLine));
|
|
|
|
// Zero bytes read, were done, but this should not happen, we should find a boundary first
|
|
if (0 == rLine.cchVal)
|
|
break;
|
|
|
|
// Is MimeBoundary
|
|
boundary = _GetMimeBoundaryType(&rLine, &m_pBindNode->rBoundary);
|
|
}
|
|
|
|
// BOUNDARY_MIMENEXT
|
|
if (BOUNDARY_MIMENEXT == boundary)
|
|
{
|
|
// MultipartMimeNext
|
|
CHECKHR(hr = _HrMultipartMimeNext(cbBoundaryStart));
|
|
}
|
|
|
|
// RAID-38241: Mail: some attached files not getting parsed from Communicator to OE
|
|
// RAID-31255: multipart/mixed with single child which is multipart/alternative
|
|
else if (BOUNDARY_MIMEEND == boundary)
|
|
{
|
|
// Finished with a multipart
|
|
if (_IsMultiPart(m_pBindNode))
|
|
{
|
|
// Done
|
|
CHECKHR(hr = _HrOnFoundMultipartEnd());
|
|
}
|
|
|
|
// Found end of current node
|
|
else
|
|
{
|
|
// Done
|
|
CHECKHR(hr = _HrOnFoundNodeEnd(cbBoundaryStart));
|
|
}
|
|
}
|
|
|
|
else
|
|
{
|
|
// Incomplete Body
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// Get Offset
|
|
DWORD dwOffset = m_pInternet->DwGetOffset();
|
|
|
|
// Convert to a text part only if we read more than two bytes from body start
|
|
if (dwOffset > m_pBindNode->cbBodyStart && dwOffset - m_pBindNode->cbBodyStart > 2)
|
|
m_pBindNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN);
|
|
|
|
// Boundary Mismatch
|
|
hr = TrapError(MIME_E_BOUNDARY_MISMATCH);
|
|
|
|
// This node is finished binding
|
|
_HrOnFoundNodeEnd(dwOffset, hr);
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrMultipartMimeNext
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrMultipartMimeNext(DWORD cbBoundaryStart)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HBODY hBody;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Get the Root Body
|
|
CHECKHR(hr = InsertBody(IBL_LAST, m_pBindNode->hBody, &hBody));
|
|
|
|
// Bind to that object
|
|
pChild = _PNodeFromHBody(hBody);
|
|
|
|
// Align the stack correctly
|
|
pChild->pBindParent = m_pBindNode;
|
|
|
|
// Setup Offset Information
|
|
pChild->boundary = BOUNDARY_MIMENEXT;
|
|
pChild->cbBoundaryStart = cbBoundaryStart;
|
|
pChild->cbHeaderStart = m_pInternet->DwGetOffset();
|
|
|
|
// Assume the Boundary
|
|
pChild->rBoundary.pszVal = m_pBindNode->rBoundary.pszVal;
|
|
pChild->rBoundary.cchVal = m_pBindNode->rBoundary.cchVal;
|
|
|
|
// Don't Free this string...
|
|
FLAGSET(pChild->dwState, NODESTATE_BOUNDNOFREE);
|
|
|
|
// New State for parent
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_MIMENEXT;
|
|
|
|
// Set New Current Node
|
|
m_pBindNode = pChild;
|
|
|
|
// Change State
|
|
m_pBindNode->bindstate = BINDSTATE_PARSING_HEADER;
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindFindingMimeNext
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindFindingMimeNext(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
DWORD cbBoundaryStart;
|
|
PROPSTRINGA rLine;
|
|
BOUNDARYTYPE boundary=BOUNDARY_NONE;
|
|
|
|
// Invalid Arg
|
|
BINDASSERTARGS(BINDSTATE_FINDING_MIMENEXT, TRUE);
|
|
|
|
// Sit and Spin
|
|
while(BOUNDARY_NONE == boundary)
|
|
{
|
|
// Mark Boundary Start
|
|
cbBoundaryStart = m_pInternet->DwGetOffset();
|
|
|
|
// Read a line
|
|
CHECKHR(hr = m_pInternet->HrReadLine(&rLine));
|
|
|
|
// Zero bytes read, were done, but this should not happen, we should find a boundary first
|
|
if (0 == rLine.cchVal)
|
|
break;
|
|
|
|
// Next or Ending Mime Boundary
|
|
boundary = _GetMimeBoundaryType(&rLine, &m_pBindNode->rBoundary);
|
|
}
|
|
|
|
// Not found
|
|
if (BOUNDARY_NONE == boundary)
|
|
{
|
|
// Incomplete Body
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// Boundary Mismatch
|
|
hr = TrapError(MIME_E_BOUNDARY_MISMATCH);
|
|
|
|
// This node is finished binding
|
|
_HrOnFoundNodeEnd(m_pInternet->DwGetOffset(), hr);
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Compute Ending Offset
|
|
CHECKHR(hr = _HrOnFoundNodeEnd(cbBoundaryStart));
|
|
|
|
// If BOUNDARY_MIMEEND
|
|
if (BOUNDARY_MIMEEND == boundary)
|
|
{
|
|
// OnFoundMultipartEnd
|
|
CHECKHR(hr = _HrOnFoundMultipartEnd());
|
|
}
|
|
|
|
// BOUNDARY_MIMENEXT
|
|
else
|
|
{
|
|
// MultipartMimeNext
|
|
CHECKHR(hr = _HrMultipartMimeNext(cbBoundaryStart));
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// _FIsUuencodeEnd
|
|
// --------------------------------------------------------------------------------
|
|
BOOL _FIsUuencodeEnd(LPCSTR pszVal)
|
|
{
|
|
|
|
// UU Encode End
|
|
if (StrCmpN(pszVal, "end", 3) == 0)
|
|
{
|
|
|
|
// Skip the first three chars
|
|
pszVal += 3;
|
|
|
|
// Make sure there is only space after the word end
|
|
while (*pszVal)
|
|
{
|
|
// LWSP or CRLF
|
|
if (' ' != *pszVal && '\t' != *pszVal && chCR != *pszVal && chLF != *pszVal)
|
|
{
|
|
// Oops, this isn't the end
|
|
return (FALSE);
|
|
|
|
// Done
|
|
break;
|
|
}
|
|
|
|
// Next Char
|
|
pszVal++;
|
|
}
|
|
return (TRUE);
|
|
}
|
|
return (FALSE);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindRfc1154
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindRfc1154(void)
|
|
{
|
|
static CHAR szBINHEXSTART[] = "(This file must be converted with BinHex";
|
|
HRESULT hr=S_OK;
|
|
ULONG cbThisLine;
|
|
PROPSTRINGA rLine;
|
|
BT1154BODY *pCurrBody;
|
|
ULONG cbLastLine=0;
|
|
|
|
BINDASSERTARGS(BINDSTATE_PARSING_RFC1154, FALSE);
|
|
Assert(m_pBT1154 != NULL);
|
|
Assert(m_pBT1154->cBodies > m_pBT1154->cCurrentBody);
|
|
|
|
pCurrBody = &m_pBT1154->aBody[m_pBT1154->cCurrentBody];
|
|
Assert((BT1154ENC_MINIMUM <= pCurrBody->encEncoding) &&
|
|
(BT1154ENC_MAXIMUM >= pCurrBody->encEncoding));
|
|
|
|
// Sit and Spin
|
|
while (1)
|
|
{
|
|
// Get the current offset, and read a line
|
|
cbThisLine = m_pInternet->DwGetOffset();
|
|
CHECKHR(hr = m_pInternet->HrReadLine(&rLine));
|
|
|
|
if (0 == m_pBT1154->cCurrentLine)
|
|
{
|
|
// This is the first line in the body.
|
|
m_pBindNode->cbBoundaryStart = cbThisLine;
|
|
m_pBindNode->cbHeaderStart = cbThisLine;
|
|
switch (pCurrBody->encEncoding)
|
|
{
|
|
case BT1154ENC_TEXT:
|
|
// For a TEXT body, the "body start" and the "boundary start"
|
|
// are the same thing.
|
|
m_pBindNode->cbBodyStart = cbThisLine;
|
|
m_pBindNode->boundary = BOUNDARY_NONE;
|
|
_HrComputeDefaultContent(m_pBindNode,NULL);
|
|
break;
|
|
|
|
case BT1154ENC_UUENCODE:
|
|
// This is UUENCODE - we won't know the "content type" until we
|
|
// see the filename.
|
|
m_pBindNode->boundary = BOUNDARY_UUBEGIN;
|
|
break;
|
|
|
|
case BT1154ENC_BINHEX:
|
|
// For a BINHEX body, we set the "body start" and "boundary start"
|
|
// to the same thing - the "body start" will be set forward later
|
|
// if we see the BINHEX start line. We set the "content disposition"
|
|
// to "attachment", the "content type" to be "application/mac-binhex40",
|
|
// and HrBindNodeComplete will end up setting the "content transfer
|
|
// encoding" to "mac-binhex40".
|
|
m_pBindNode->cbBodyStart = cbThisLine;
|
|
m_pBindNode->boundary = BOUNDARY_NONE;
|
|
FLAGSET(m_pBindNode->dwType,NODETYPE_RFC1154_BINHEX);
|
|
CHECKHR(hr = m_pBindNode->pContainer->SetProp(SYM_HDR_CNTDISP, STR_DIS_ATTACHMENT));
|
|
CHECKHR(hr = m_pBindNode->pContainer->SetProp(PIDTOSTR(PID_ATT_PRITYPE), STR_CNT_APPLICATION));
|
|
CHECKHR(hr = m_pBindNode->pContainer->SetProp(PIDTOSTR(PID_ATT_SUBTYPE), STR_SUB_BINHEX));
|
|
break;
|
|
|
|
default:
|
|
AssertSz(FALSE,"Unknown encoding type.");
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (0 == rLine.cchVal)
|
|
{
|
|
// Zero bytes read, we're done
|
|
if ((pCurrBody->cLines != 0xffffffff) &&
|
|
(m_pBT1154->cCurrentLine+1 <= pCurrBody->cLines))
|
|
{
|
|
// We weren't in the special "read as many lines as
|
|
// we can" state, and we haven't consumed all of
|
|
// the lines yet for this body part. So, we need to
|
|
// go into the "There were parsing errors" state.
|
|
m_pBT1154->hrLoadResult = MIME_E_NO_DATA;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (m_pBT1154->cCurrentLine == pCurrBody->cLines)
|
|
{
|
|
// We have just read the line past the end
|
|
// of the body. Let's remember this spot...
|
|
cbLastLine = cbThisLine;
|
|
}
|
|
|
|
m_pBT1154->cCurrentLine++;
|
|
|
|
if (m_pBT1154->cCurrentLine > pCurrBody->cLines)
|
|
{
|
|
// We are reading lines past the end of a body part.
|
|
|
|
if ((rLine.cchVal != 2) || (rLine.pszVal[0] != '\r') || (rLine.pszVal[1] != '\n'))
|
|
{
|
|
// All of the lines past the end of a body part (i.e. lines that are
|
|
// either between body parts, or at the end of the message) should be
|
|
// blank - and this one isn't. Since it isn't, we go into the "There
|
|
// were parsing errors" state.
|
|
m_pBT1154->hrLoadResult = MIME_E_NO_MULTIPART_BOUNDARY;
|
|
}
|
|
|
|
if (m_pBT1154->cCurrentBody+1 < m_pBT1154->cBodies)
|
|
{
|
|
// We are *between* body parts, which means we just
|
|
// consumed the single (blank) line which is between
|
|
// them. So we break out so we can add this body part
|
|
// and move on to the next one.
|
|
break;
|
|
}
|
|
|
|
// If we get to this point, it means that we are consuming the
|
|
// (blank) lines which are beyond the end of the last body
|
|
// part. We continue consuming all of those lines until they
|
|
// are gone. If any of them were non-blank, then we will have
|
|
// set the m_pBT1154->hrLoadResult member to MIME_E_NO_MULTIPART_BOUNDARY
|
|
// (above).
|
|
Assert(m_pBT1154->cCurrentBody+1 == m_pBT1154->cBodies);
|
|
}
|
|
else if (BT1154ENC_UUENCODE == pCurrBody->encEncoding)
|
|
{
|
|
// This is an else-if clause because we never look for the UUENCODE
|
|
// begin and end keywords past the end of the body part.
|
|
LPSTR pszFileName = NULL;
|
|
|
|
// We are dealing with UUENCODE.
|
|
if ((0 == m_pBindNode->cbBodyStart) && _FIsUuencodeBegin(&rLine, &pszFileName))
|
|
{
|
|
// We are looking for the start of UUENCODE - and this is it! We set
|
|
// the boundary start to be at the begin marker, and the body start to be
|
|
// *after* the begin marker.
|
|
m_pBindNode->cbBoundaryStart = cbThisLine;
|
|
m_pBindNode->cbHeaderStart = cbThisLine;
|
|
m_pBindNode->cbBodyStart = m_pInternet->DwGetOffset();
|
|
_HrComputeDefaultContent(m_pBindNode, pszFileName);
|
|
SafeMemFree(pszFileName);
|
|
}
|
|
else if ((0 != m_pBindNode->cbBodyStart) &&
|
|
(0 == m_pBindNode->cbBodyEnd) &&
|
|
_FIsUuencodeEnd(rLine.pszVal))
|
|
{
|
|
// We are looking for the end of UUENCODE - and this is it! We set
|
|
// the body end to be *before* the end marker.
|
|
m_pBindNode->cbBodyEnd = cbThisLine;
|
|
|
|
// We *don't* break out - we keep reading until we've consumed all
|
|
// of the lines for this body.
|
|
}
|
|
}
|
|
else if (BT1154ENC_BINHEX == pCurrBody->encEncoding)
|
|
{
|
|
// This is an else-if clause because we never look for the BINHEX
|
|
// start line past the end of the body part.
|
|
if (m_pBindNode->cbBodyStart == m_pBindNode->cbBoundaryStart)
|
|
{
|
|
// We haven't found the BINHEX start line yet.
|
|
if (StrCmpNI(szBINHEXSTART,rLine.pszVal,sizeof(szBINHEXSTART)-1) == 0)
|
|
{
|
|
// And this is it! So set the body start to this line.
|
|
m_pBindNode->cbBodyStart = cbThisLine;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// We only get to this point when we are at the end of a body - either
|
|
// by having consumed the correct number of lines (plus the blank line
|
|
// between bodies), or by having run off the end of the message.
|
|
Assert((0 == rLine.cchVal) || (m_pBT1154->cCurrentLine == pCurrBody->cLines+1));
|
|
|
|
// The only way we should have set the body end is if we are UUENCODE.
|
|
Assert((BT1154ENC_UUENCODE == pCurrBody->encEncoding) || (0 == m_pBindNode->cbBodyEnd));
|
|
|
|
if (0 == m_pBindNode->cbBodyEnd)
|
|
{
|
|
// We are either a TEXT or BINHEX body, or we are a UUENCODE and we
|
|
// didn't find the end.
|
|
|
|
if (BT1154ENC_UUENCODE == pCurrBody->encEncoding)
|
|
{
|
|
// We are doing UUENCODE, and we haven't seen the end keyword (and
|
|
// maybe not even the begin keyword). So we go into the "There
|
|
// were parsing errors" state.
|
|
if (0 == m_pBindNode->cbBodyStart)
|
|
{
|
|
// We haven't seen the begin keyword - so set the
|
|
// body start to be the same as the boundary start.
|
|
m_pBindNode->cbBodyStart = m_pBindNode->cbBoundaryStart;
|
|
}
|
|
m_pBT1154->hrLoadResult = MIME_E_BOUNDARY_MISMATCH;
|
|
}
|
|
|
|
// We need to set the body end...
|
|
if (0 != cbLastLine)
|
|
{
|
|
// We found the "last line" above, so we set the
|
|
// body end to that line.
|
|
m_pBindNode->cbBodyEnd = cbLastLine;
|
|
}
|
|
else
|
|
{
|
|
// Since we didn't find the "last line" above, we set the
|
|
// body end to this line.
|
|
m_pBindNode->cbBodyEnd = cbThisLine;
|
|
}
|
|
}
|
|
|
|
// We're done with this body part, so bind it.
|
|
_HrBindNodeComplete(m_pBindNode, m_pBT1154->hrLoadResult);
|
|
|
|
if (0 == rLine.cchVal)
|
|
{
|
|
// We have consumed the entire message - so clean everything up.
|
|
|
|
// ****************************************************
|
|
// NOTE - We set hr to the return value for the binding
|
|
// operation. Don't change hr between this point and
|
|
// where we return.
|
|
// ****************************************************
|
|
|
|
m_pRootNode->cbBodyEnd = m_pInternet->DwGetOffset();
|
|
_HrBindNodeComplete(m_pRootNode,S_OK);
|
|
|
|
hr = m_pBT1154->hrLoadResult;
|
|
|
|
SafeMemFree(m_pBT1154);
|
|
m_pBindNode = NULL;
|
|
}
|
|
else
|
|
{
|
|
HBODY hBody;
|
|
|
|
// When we are processing the last body part, we consume all
|
|
// of the lines after the last body part. So, if we haven't
|
|
// consumed the entire message, that means that we must have
|
|
// some bodies left to process...
|
|
Assert(m_pBT1154->cBodies > m_pBT1154->cCurrentBody+1);
|
|
|
|
m_pBT1154->cCurrentBody++;
|
|
m_pBT1154->cCurrentLine = 0;
|
|
Assert(m_pBindNode != m_pRootNode);
|
|
m_pBindNode = NULL; // set this to NULL in case we get an error from InsertBody
|
|
CHECKHR(hr = InsertBody(IBL_LAST, m_pRootNode->hBody, &hBody));
|
|
m_pBindNode = _PNodeFromHBody(hBody);
|
|
m_pBindNode->bindstate = BINDSTATE_PARSING_RFC1154;
|
|
}
|
|
|
|
// *********************************************************
|
|
// NOTE - Don't change hr below this point. See NOTE above.
|
|
// *********************************************************
|
|
|
|
exit:
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindFindingUuencodeBegin
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindFindingUuencodeBegin(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
ULONG cbBoundaryStart;
|
|
PROPSTRINGA rLine;
|
|
BOUNDARYTYPE boundary=BOUNDARY_NONE;
|
|
LPTREENODEINFO pChild;
|
|
LPSTR pszFileName=NULL;
|
|
HBODY hBody;
|
|
BOOL fAddTextBody=FALSE;
|
|
ULONG cbTextBodyStart=0;
|
|
|
|
// Invalid Arg
|
|
BINDASSERTARGS(BINDSTATE_FINDING_UUBEGIN, FALSE);
|
|
|
|
// Sit and Spin
|
|
while(1)
|
|
{
|
|
// Mark Boundary Start
|
|
cbBoundaryStart = m_pInternet->DwGetOffset();
|
|
|
|
// Read a line
|
|
CHECKHR(hr = m_pInternet->HrReadLine(&rLine));
|
|
|
|
// Zero bytes read, were done, but this should not happen, we should find a boundary first
|
|
if (0 == rLine.cchVal)
|
|
break;
|
|
|
|
// If not parsing a message
|
|
if (!ISFLAGSET(m_pBindNode->dwState, NODESTATE_MESSAGE))
|
|
{
|
|
// Is uuencode begine line
|
|
if (_FIsUuencodeBegin(&rLine, &pszFileName) == TRUE)
|
|
{
|
|
boundary = BOUNDARY_UUBEGIN;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// No Boundary
|
|
if (BOUNDARY_NONE == boundary)
|
|
{
|
|
// Stuff after the last UUENCODED body must be appended as a text body
|
|
if (m_pBindNode->pChildTail)
|
|
{
|
|
// De-ref Last Child
|
|
pChild = m_pBindNode->pChildTail;
|
|
|
|
// Artificial text body start
|
|
cbTextBodyStart = pChild->cbBodyEnd;
|
|
|
|
// AddTextBody ? lstrlen(end\r\n) = 5
|
|
if (BOUNDARY_UUBEGIN == pChild->boundary && !ISFLAGSET(pChild->dwType, NODETYPE_INCOMPLETE))
|
|
cbTextBodyStart += 5;
|
|
|
|
// Space between last body end and boundary start is greater than sizeof(crlf)
|
|
if (cbBoundaryStart > cbTextBodyStart && cbBoundaryStart - cbTextBodyStart > 2)
|
|
{
|
|
// Create Root Body Node
|
|
CHECKHR(hr = InsertBody(IBL_LAST, m_pBindNode->hBody, &hBody));
|
|
|
|
// Bind to that object
|
|
pChild = _PNodeFromHBody(hBody);
|
|
|
|
// Fixup the STack
|
|
pChild->pBindParent = m_pBindNode;
|
|
|
|
// This body should assume the new text offsets
|
|
CHECKHR(hr = pChild->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN));
|
|
|
|
// Set Encoding
|
|
CHECKHR(hr = pChild->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_7BIT));
|
|
|
|
// Set Offsets
|
|
pChild->boundary = BOUNDARY_NONE;
|
|
pChild->cbBoundaryStart = cbTextBodyStart;
|
|
pChild->cbHeaderStart = cbTextBodyStart;
|
|
pChild->cbBodyStart = cbTextBodyStart;
|
|
pChild->cbBodyEnd = cbBoundaryStart;
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(pChild, S_OK));
|
|
}
|
|
}
|
|
|
|
// Body Offset Information
|
|
m_pBindNode->cbBodyEnd = m_pInternet->DwGetOffset();
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(m_pBindNode, S_OK));
|
|
|
|
// Pop the parsing Stack
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
}
|
|
|
|
// Otherwise, if we hit a uuencode boundary
|
|
else
|
|
{
|
|
// If not a fake multipart yet...
|
|
if (!ISFLAGSET(m_pBindNode->dwType, NODETYPE_FAKEMULTIPART))
|
|
{
|
|
// Its a faked multipart
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_FAKEMULTIPART);
|
|
|
|
// Free current content type
|
|
CHECKHR(hr = m_pBindNode->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_MPART_MIXED));
|
|
|
|
// Modify this dudes bound start
|
|
Assert(m_pBindNode->boundary == BOUNDARY_ROOT);
|
|
|
|
// Set the parse state
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_UUBEGIN;
|
|
}
|
|
|
|
// ------------------------------------------------------------------------------------
|
|
// \/ \/ \/ Raid 41599 - lost/munged attachments on forward/uuencode \/ \/ \/
|
|
|
|
// If root node and body size is greater than sizeof(crlf)
|
|
if (NULL == m_pBindNode->pChildTail && cbBoundaryStart - m_pBindNode->cbBodyStart > 2)
|
|
{
|
|
// Validate bind node
|
|
Assert(m_pRootNode == m_pBindNode && m_pBindNode->cChildren == 0);
|
|
|
|
// Set artificial text body start
|
|
cbTextBodyStart = m_pBindNode->cbBodyStart;
|
|
|
|
// Yes, add artificial text body
|
|
fAddTextBody = TRUE;
|
|
}
|
|
|
|
// Otherwise, if last child parsed had an ending boundary of UUEND, and body size is greater than sizeof(crlf)
|
|
else if (m_pBindNode->pChildTail)
|
|
{
|
|
// De-ref Last Child
|
|
pChild = m_pBindNode->pChildTail;
|
|
|
|
// Artificial text body start
|
|
cbTextBodyStart = pChild->cbBodyEnd;
|
|
|
|
// AddTextBody ? lstrlen(end\r\n) = 5
|
|
if (BOUNDARY_UUBEGIN == pChild->boundary && !ISFLAGSET(pChild->dwType, NODETYPE_INCOMPLETE))
|
|
cbTextBodyStart += 5;
|
|
|
|
// Otherwise, what was the ending boundary
|
|
else
|
|
AssertSz(FALSE, "I should have only seen and uuencoded ending boundary.");
|
|
|
|
// Space between last body end and boundary start is greater than sizeof(crlf)
|
|
if (cbBoundaryStart > cbTextBodyStart && cbBoundaryStart - cbTextBodyStart > 2)
|
|
fAddTextBody = TRUE;
|
|
}
|
|
|
|
// /\ /\ /\ Raid 41599 - lost/munged attachments on forward/uuencode /\ /\ /\
|
|
// ------------------------------------------------------------------------------------
|
|
|
|
// Create Root Body Node
|
|
CHECKHR(hr = InsertBody(IBL_LAST, m_pBindNode->hBody, &hBody));
|
|
|
|
// Bind to that object
|
|
pChild = _PNodeFromHBody(hBody);
|
|
|
|
// Fixup the STack
|
|
pChild->pBindParent = m_pBindNode;
|
|
|
|
// Enough text to create a text/plain body ?
|
|
if (fAddTextBody)
|
|
{
|
|
// This body should assume the new text offsets
|
|
CHECKHR(hr = pChild->pContainer->SetProp(SYM_HDR_CNTTYPE, STR_MIME_TEXT_PLAIN));
|
|
|
|
// Set Encoding
|
|
CHECKHR(hr = pChild->pContainer->SetProp(SYM_HDR_CNTXFER, STR_ENC_7BIT));
|
|
|
|
// Set Offsets
|
|
pChild->boundary = BOUNDARY_NONE;
|
|
pChild->cbBoundaryStart = cbTextBodyStart;
|
|
pChild->cbHeaderStart = cbTextBodyStart;
|
|
pChild->cbBodyStart = cbTextBodyStart;
|
|
pChild->cbBodyEnd = cbBoundaryStart;
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(pChild, S_OK));
|
|
|
|
// Create Root Body Node
|
|
CHECKHR(hr = InsertBody(IBL_LAST, m_pBindNode->hBody, &hBody));
|
|
|
|
// Bind to that object
|
|
pChild = _PNodeFromHBody(hBody);
|
|
|
|
// Fixup the STack
|
|
pChild->pBindParent = m_pBindNode;
|
|
}
|
|
|
|
// Set Offsets
|
|
pChild->boundary = BOUNDARY_UUBEGIN;
|
|
pChild->cbBoundaryStart = cbBoundaryStart;
|
|
pChild->cbHeaderStart = cbBoundaryStart;
|
|
pChild->cbBodyStart = m_pInternet->DwGetOffset();
|
|
|
|
// Update m_pBindNode
|
|
Assert(m_pBindNode->bindstate == BINDSTATE_FINDING_UUBEGIN);
|
|
m_pBindNode = pChild;
|
|
|
|
// Default Node Content Type
|
|
_HrComputeDefaultContent(m_pBindNode, pszFileName);
|
|
|
|
// New Node BindState
|
|
m_pBindNode->bindstate = BINDSTATE_FINDING_UUEND;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeMemFree(pszFileName);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindFindingUuencodeEnd
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindFindingUuencodeEnd(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
PROPSTRINGA rLine;
|
|
DWORD cbBoundaryStart;
|
|
BOUNDARYTYPE boundary=BOUNDARY_NONE;
|
|
|
|
// Invalid Arg
|
|
BINDASSERTARGS(BINDSTATE_FINDING_UUEND, FALSE);
|
|
|
|
// Sit and Spin
|
|
while(BOUNDARY_NONE == boundary)
|
|
{
|
|
// Mark Boundary Start
|
|
cbBoundaryStart = m_pInternet->DwGetOffset();
|
|
|
|
// Read a line
|
|
CHECKHR(hr = m_pInternet->HrReadLine(&rLine));
|
|
|
|
// Zero bytes read, were done, but this should not happen, we should find a boundary first
|
|
if (0 == rLine.cchVal)
|
|
break;
|
|
|
|
// UU Encode End
|
|
if (_FIsUuencodeEnd(rLine.pszVal))
|
|
{
|
|
boundary = BOUNDARY_UUEND;
|
|
}
|
|
}
|
|
|
|
// Incomplete
|
|
if (BOUNDARY_UUEND != boundary)
|
|
{
|
|
// Incomplete Body
|
|
FLAGSET(m_pBindNode->dwType, NODETYPE_INCOMPLETE);
|
|
|
|
// Adjust body start to boundary start
|
|
m_pBindNode->cbBodyStart = m_pBindNode->cbBoundaryStart;
|
|
|
|
// Body End
|
|
m_pBindNode->cbBodyEnd = m_pInternet->DwGetOffset();
|
|
|
|
// This node is finished binding
|
|
CHECKHR(hr = _HrBindNodeComplete(m_pBindNode, S_OK));
|
|
|
|
// Pop the tree
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Get the offset
|
|
m_pBindNode->cbBodyEnd = cbBoundaryStart;
|
|
|
|
// POP the stack
|
|
m_pBindNode = m_pBindNode->pBindParent;
|
|
|
|
// Should now be looking for next uubegin
|
|
Assert(m_pBindNode ? m_pBindNode->bindstate == BINDSTATE_FINDING_UUBEGIN : TRUE);
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrBindNodeComplete
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrBindNodeComplete(LPTREENODEINFO pNode, HRESULT hrResult)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPURLREQUEST pRequest;
|
|
LPURLREQUEST pNext;
|
|
|
|
// The bind for this node is complete
|
|
pNode->bindstate = BINDSTATE_COMPLETE;
|
|
|
|
// Save the bind result
|
|
pNode->hrBind = hrResult;
|
|
|
|
// If pNode has not been bound yet, lets do it
|
|
if (!ISFLAGSET(pNode->dwState, NODESTATE_BOUNDTOTREE))
|
|
{
|
|
// Bind it to the tree
|
|
hr = pNode->pBody->HrBindToTree(m_pStmLock, pNode);
|
|
|
|
// If HrBindToTree failed
|
|
if (SUCCEEDED(pNode->hrBind) && FAILED(hr))
|
|
pNode->hrBind = hr;
|
|
|
|
// Process the bind Request Table
|
|
CHECKHR(hr = _HrProcessPendingUrlRequests());
|
|
|
|
// If there is a WebPage being built, lets add this body
|
|
if (m_pWebPage)
|
|
{
|
|
// Add the Body
|
|
m_pWebPage->OnBodyBoundToTree(this, pNode);
|
|
}
|
|
}
|
|
|
|
// Init the Loop
|
|
pRequest = pNode->pResolved;
|
|
|
|
// Loop
|
|
while(pRequest)
|
|
{
|
|
// Set Next
|
|
pNext = pRequest->m_pNext;
|
|
|
|
// Unlink this pending request
|
|
_RelinkUrlRequest(pRequest, &pNode->pResolved, &m_pComplete);
|
|
|
|
// OnComplete
|
|
pRequest->OnBindingComplete(pNode->hrBind);
|
|
|
|
// Set pRequest
|
|
pRequest = pNext;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::HrRegisterRequest
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::HrActiveUrlRequest(LPURLREQUEST pRequest)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
|
|
// Invalid Arg
|
|
if (NULL == pRequest)
|
|
return TrapError(E_INVALIDARG);
|
|
|
|
// Thread Safety
|
|
EnterCriticalSection(&m_cs);
|
|
|
|
// Check State
|
|
Assert(m_rRootUrl.pszVal);
|
|
|
|
// AddRef the Request
|
|
pRequest->GetInner()->AddRef();
|
|
|
|
// Put the Request into the pending list
|
|
_LinkUrlRequest(pRequest, &m_pPending);
|
|
|
|
// Process Pending Url Requests
|
|
CHECKHR(hr = _HrProcessPendingUrlRequests());
|
|
|
|
exit:
|
|
// Thread Safety
|
|
LeaveCriticalSection(&m_cs);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrProcessPendingUrlRequests
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrProcessPendingUrlRequests(void)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPURLREQUEST pRequest=m_pPending;
|
|
LPURLREQUEST pNext;
|
|
HBODY hBody;
|
|
BOOL fResolved;
|
|
|
|
// Loop the request
|
|
while(pRequest)
|
|
{
|
|
// Set Next
|
|
pNext = pRequest->m_pNext;
|
|
|
|
// Try to resolve the request
|
|
CHECKHR(hr = _HrResolveUrlRequest(pRequest, &fResolved));
|
|
|
|
// Resolved
|
|
if (FALSE == fResolved && ISFLAGSET(m_dwState, TREESTATE_BINDDONE))
|
|
{
|
|
// Unlink this pending request
|
|
_RelinkUrlRequest(pRequest, &m_pPending, &m_pComplete);
|
|
|
|
// Not found, use default protocol
|
|
pRequest->OnBindingComplete(E_FAIL);
|
|
}
|
|
|
|
// Next
|
|
pRequest = pNext;
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrResolveUrlRequest
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrResolveUrlRequest(LPURLREQUEST pRequest, BOOL *pfResolved)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
HBODY hBody=NULL;
|
|
LPTREENODEINFO pNode;
|
|
LPWSTR pwszCntType=NULL;
|
|
IStream *pStream=NULL;
|
|
|
|
// Invalid Arg
|
|
Assert(pfResolved);
|
|
|
|
// Initialize
|
|
*pfResolved = FALSE;
|
|
|
|
// Is this the root request ?
|
|
if (NULL == pRequest->m_pszBodyUrl)
|
|
{
|
|
// Do I have a user supplied root data stream...I assume its html
|
|
if (m_pRootStm)
|
|
{
|
|
// Unlink this pending request
|
|
_RelinkUrlRequest(pRequest, &m_pPending, &m_pComplete);
|
|
|
|
// Use client driven root html stream
|
|
pRequest->OnFullyAvailable(STR_TEXTHTML, m_pRootStm, this, NULL);
|
|
|
|
// Resolved
|
|
*pfResolved = TRUE;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Otherwise, try to resolve the text/html body
|
|
else
|
|
{
|
|
// We should not have a webpage object yet...
|
|
Assert(NULL == m_pWebPage);
|
|
|
|
// Create a CMessageWebPage Object
|
|
CHECKALLOC(m_pWebPage = new CMessageWebPage(pRequest));
|
|
|
|
// Unlink this pending request
|
|
_RelinkUrlRequest(pRequest, &m_pPending, &m_pComplete);
|
|
|
|
// Feed the current amount of data read into the binder
|
|
pRequest->OnStartBinding(STR_TEXTHTML, (IStream *)m_pWebPage, this, HBODY_ROOT);
|
|
|
|
// Initialize
|
|
CHECKHR(hr = m_pWebPage->Initialize(m_pCallback, this, &m_rWebPageOpt));
|
|
|
|
// I need to feed all bound nodes to the web page for generation...
|
|
CHECKHR(hr = _HrSychronizeWebPage(m_pRootNode));
|
|
|
|
// If the entire tree bind is complete, then tell the webpage we are complete
|
|
if (ISFLAGSET(m_dwState, TREESTATE_BINDDONE))
|
|
{
|
|
// Tell the webpage that we are done
|
|
m_pWebPage->OnBindComplete(this);
|
|
m_pWebPage->Release();
|
|
m_pWebPage = NULL;
|
|
}
|
|
|
|
// Resolved
|
|
*pfResolved = TRUE;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
}
|
|
|
|
// Otherwise, look for the Url
|
|
else if (FAILED(ResolveURL(NULL, NULL, pRequest->m_pszBodyUrl, URL_RESOLVE_RENDERED, &hBody)))
|
|
goto exit;
|
|
|
|
// We better have a body handle by now
|
|
Assert(_FIsValidHandle(hBody) && pRequest);
|
|
|
|
// Dereference the body
|
|
pNode = _PNodeFromHBody(hBody);
|
|
|
|
// Get the Content Type
|
|
MimeOleGetPropW(pNode->pBody, PIDTOSTR(PID_HDR_CNTTYPE), 0, &pwszCntType);
|
|
|
|
// Get the BodyStream
|
|
if (FAILED(pNode->pBody->GetData(IET_INETCSET, &pStream)))
|
|
goto exit;
|
|
|
|
// Complete
|
|
if (BINDSTATE_COMPLETE == pNode->bindstate)
|
|
{
|
|
// Unlink this pending request
|
|
_RelinkUrlRequest(pRequest, &m_pPending, &m_pComplete);
|
|
|
|
// OnComplete
|
|
pRequest->OnFullyAvailable(pwszCntType, pStream, this, pNode->hBody);
|
|
|
|
// Resolved
|
|
*pfResolved = TRUE;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
// Otherwise, start binding
|
|
else if (ISFLAGSET(pNode->dwState, NODESTATE_BOUNDTOTREE))
|
|
{
|
|
// Should have pNode->pLockBytes
|
|
Assert(pNode->pLockBytes);
|
|
|
|
// Relink Request into the Node
|
|
_RelinkUrlRequest(pRequest, &m_pPending, &pNode->pResolved);
|
|
|
|
// Feed the current amount of data read into the binder
|
|
pRequest->OnStartBinding(pwszCntType, pStream, this, pNode->hBody);
|
|
|
|
// Resolved
|
|
*pfResolved = TRUE;
|
|
|
|
// Done
|
|
goto exit;
|
|
}
|
|
|
|
exit:
|
|
// Cleanup
|
|
SafeRelease(pStream);
|
|
SafeMemFree(pwszCntType);
|
|
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_HrSychronizeWebPage
|
|
// --------------------------------------------------------------------------------
|
|
HRESULT CMessageTree::_HrSychronizeWebPage(LPTREENODEINFO pNode)
|
|
{
|
|
// Locals
|
|
HRESULT hr=S_OK;
|
|
LPTREENODEINFO pChild;
|
|
|
|
// Invalid Arg
|
|
Assert(m_pWebPage && pNode);
|
|
|
|
// Clear the "OnWebPage" Flag, we are re-generating the webpage
|
|
FLAGCLEAR(pNode->dwState, WEBPAGE_NODESTATE_BITS);
|
|
|
|
// If this is a multipart item, lets search it's children
|
|
if (_IsMultiPart(pNode))
|
|
{
|
|
// Loop Children
|
|
for (pChild=pNode->pChildHead; pChild!=NULL; pChild=pChild->pNext)
|
|
{
|
|
// Check body
|
|
Assert(pChild->pParent == pNode);
|
|
|
|
// Get the flags for this child node
|
|
CHECKHR(hr = _HrSychronizeWebPage(pChild));
|
|
}
|
|
|
|
// Bind the multipart to the webpage
|
|
m_pWebPage->OnBodyBoundToTree(this, pNode);
|
|
}
|
|
|
|
// Otherwise, if the node is bound and gagged...
|
|
else if (BINDSTATE_COMPLETE == pNode->bindstate)
|
|
{
|
|
// Append to the WebPage
|
|
m_pWebPage->OnBodyBoundToTree(this, pNode);
|
|
}
|
|
|
|
exit:
|
|
// Done
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_UnlinkUrlRequest
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_RelinkUrlRequest(LPURLREQUEST pRequest, LPURLREQUEST *ppSource,
|
|
LPURLREQUEST *ppDest)
|
|
{
|
|
// Unlink this pending request
|
|
_UnlinkUrlRequest(pRequest, ppSource);
|
|
|
|
// Link the bind request into pNode
|
|
_LinkUrlRequest(pRequest, ppDest);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_UnlinkUrlRequest
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_UnlinkUrlRequest(LPURLREQUEST pRequest, LPURLREQUEST *ppHead)
|
|
{
|
|
// Invalid Arg
|
|
Assert(pRequest && ppHead);
|
|
|
|
// Debug make sure pRequest is part of *ppHead chain
|
|
#ifdef DEBUG
|
|
for(LPURLREQUEST pCurr=*ppHead; pCurr!=NULL; pCurr=pCurr->m_pNext)
|
|
if (pCurr == pRequest)
|
|
break;
|
|
AssertSz(pCurr, "pRequest is not part of *ppHead linked list");
|
|
#endif
|
|
|
|
// Fixup Previous and Next
|
|
LPURLREQUEST pNext = pRequest->m_pNext;
|
|
LPURLREQUEST pPrev = pRequest->m_pPrev;
|
|
|
|
// Fixup Links
|
|
if (pNext)
|
|
pNext->m_pPrev = pPrev;
|
|
if (pPrev)
|
|
pPrev->m_pNext = pNext;
|
|
|
|
// Fixup ppHead
|
|
if (pRequest == *ppHead)
|
|
{
|
|
Assert(pPrev == NULL);
|
|
*ppHead = pNext;
|
|
}
|
|
|
|
// Set Next and Prev
|
|
pRequest->m_pNext = NULL;
|
|
pRequest->m_pPrev = NULL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_LinkUrlRequest
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_LinkUrlRequest(LPURLREQUEST pRequest, LPURLREQUEST *ppHead)
|
|
{
|
|
// Invalid Arg
|
|
Assert(pRequest && ppHead);
|
|
|
|
// Is the head set
|
|
if (NULL != *ppHead)
|
|
{
|
|
// Set Next
|
|
pRequest->m_pNext = *ppHead;
|
|
|
|
// Set Previous
|
|
(*ppHead)->m_pPrev = pRequest;
|
|
}
|
|
|
|
// Set the head
|
|
(*ppHead) = pRequest;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_ReleaseUrlRequestList
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_ReleaseUrlRequestList(LPURLREQUEST *ppHead)
|
|
{
|
|
// Locals
|
|
LPURLREQUEST pCurr;
|
|
LPURLREQUEST pNext;
|
|
|
|
// Invalid Arg
|
|
Assert(ppHead);
|
|
|
|
// Init
|
|
pCurr = *ppHead;
|
|
|
|
// Loop the Elements
|
|
while(pCurr)
|
|
{
|
|
// Set Next
|
|
pNext = pCurr->m_pNext;
|
|
|
|
// Free pCurr
|
|
pCurr->GetInner()->Release();
|
|
|
|
// Next
|
|
pCurr = pNext;
|
|
}
|
|
|
|
// Done
|
|
*ppHead = NULL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// IsRfc1154Token
|
|
//
|
|
// --------------------------------------------------------------------------------
|
|
inline BOOL IsRfc1154Token(LPSTR pszDesired, LPSTR pszEndPtr, ULONG cchLen)
|
|
{
|
|
|
|
if (StrCmpNI(pszDesired,pszEndPtr,cchLen) != 0)
|
|
{
|
|
return (FALSE);
|
|
}
|
|
if ((pszEndPtr[cchLen] != '\0') &&
|
|
(pszEndPtr[cchLen] != ' ') &&
|
|
(pszEndPtr[cchLen] != '\t') &&
|
|
(pszEndPtr[cchLen] != ','))
|
|
{
|
|
return (FALSE);
|
|
}
|
|
return (TRUE);
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::_DecodeRfc1154
|
|
// --------------------------------------------------------------------------------
|
|
void CMessageTree::_DecodeRfc1154() {
|
|
BOOL bRes = FALSE;
|
|
HRESULT hr;
|
|
LPSTR pszEncoding = NULL;
|
|
LPSTR pszEndPtr;
|
|
ULONG cAlloc = 0;
|
|
|
|
if (!m_rOptions.fDecodeRfc1154)
|
|
{
|
|
goto exit;
|
|
}
|
|
hr = m_pBindNode->pContainer->GetProp(SYM_HDR_ENCODING, &pszEncoding);
|
|
if (!SUCCEEDED(hr))
|
|
{
|
|
goto exit;
|
|
}
|
|
pszEndPtr = pszEncoding;
|
|
// Each time we enter this loop, pszEndPtr points to the start of the
|
|
// next subfield. Each subfield is something like "103 TEXT". The
|
|
// number is always decimal, and the number is optional in the last
|
|
// subfield.
|
|
while (1)
|
|
{
|
|
LPSTR pszTmp;
|
|
ULONG cLines;
|
|
BOOL bNumberFound;
|
|
BT1154ENCODING encEncoding;
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
//
|
|
// or (if there isn't a number)
|
|
//
|
|
// " TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
bNumberFound = FALSE;
|
|
|
|
// Skip past any leading whitespace.
|
|
while ((*pszEndPtr==' ')||(*pszEndPtr=='\t'))
|
|
{
|
|
pszEndPtr++;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
//
|
|
// or (if there isn't a number)
|
|
//
|
|
// " TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
pszTmp = pszEndPtr;
|
|
|
|
// We use strtoul to convert a decimal number.
|
|
// pszEndPtr will be left pointing at the
|
|
// character which terminated the number.
|
|
cLines = strtoul(pszTmp, &pszEndPtr, 10);
|
|
|
|
if (0xffffffff == cLines)
|
|
{
|
|
// We don't allow this - we use cLines == 0xffffffff to signal
|
|
// the case where the body part didn't include a line count and
|
|
// thus should consume all remaining lines. So we'll (silently)
|
|
// convert this to 0xfffffffe...
|
|
cLines = 0xfffffffe;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
//
|
|
// or (if there isn't a number)
|
|
//
|
|
// " TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
if (cLines && !((*pszEndPtr==' ')||(*pszEndPtr=='\t')))
|
|
{
|
|
// Malformed - if the subfield specifies a number, then
|
|
// the number *must* be followed by whitespace.
|
|
goto exit;
|
|
}
|
|
// Now we skip past any whitespace...
|
|
while ((*pszEndPtr==' ') || (*pszEndPtr=='\t'))
|
|
{
|
|
bNumberFound = TRUE;
|
|
pszEndPtr++;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
// We should now be pointing at the body type.
|
|
if (IsRfc1154Token("text",pszEndPtr,4))
|
|
{
|
|
encEncoding = BT1154ENC_TEXT;
|
|
pszEndPtr += 4;
|
|
}
|
|
else if (IsRfc1154Token("uuencode",pszEndPtr,8))
|
|
{
|
|
encEncoding = BT1154ENC_UUENCODE;
|
|
pszEndPtr += 8;
|
|
}
|
|
else if (IsRfc1154Token("x-binhex",pszEndPtr,8))
|
|
{
|
|
encEncoding = BT1154ENC_BINHEX;
|
|
pszEndPtr += 8;
|
|
}
|
|
else
|
|
{
|
|
// Malformed - we don't really support anything except
|
|
// TEXT, UUENCODE, and X-BINHEX. But, instead of
|
|
// falling back to "fake multipart" handling, we'll just
|
|
// pretend that this body part is TEXT...
|
|
encEncoding = BT1154ENC_TEXT;
|
|
// We need to consume the body part from the Encoding: string - that means
|
|
// that we advance until we see a NULL, a space, a tab, or a comma.
|
|
while ((*pszEndPtr != '\0') &&
|
|
(*pszEndPtr != ' ') &&
|
|
(*pszEndPtr != '\t') &&
|
|
(*pszEndPtr != ','))
|
|
{
|
|
pszEndPtr++;
|
|
}
|
|
// TBD - We could add the body type as a property on the
|
|
// body part. To do that, we would need to save it in the
|
|
// m_pBT1154 structure. We'd also have to figure out which
|
|
// property to set it as.
|
|
}
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
// Now we skip past any whitespace...
|
|
while ((*pszEndPtr==' ') || (*pszEndPtr=='\t'))
|
|
{
|
|
pszEndPtr++;
|
|
}
|
|
|
|
// ------------------------------------
|
|
// " 103 TEXT , ..."
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
if ((*pszEndPtr!='\0') && (*pszEndPtr!=','))
|
|
{
|
|
// Malformed - a subfield is terminated either by a comma,
|
|
// or by a NULL.
|
|
goto exit;
|
|
}
|
|
if (*pszEndPtr != '\0' && !bNumberFound)
|
|
{
|
|
// Malformed - only the *last* subfield can get away with
|
|
// not specifying a line count.
|
|
goto exit;
|
|
}
|
|
if (*pszEndPtr == '\0' && !bNumberFound)
|
|
{
|
|
// This is the last subfield, and there wasn't
|
|
// a line count specified. This means that the
|
|
// last body part should consume all of the remaining
|
|
// lines - so we'll set the line count really high...
|
|
cLines = 0xffffffff;
|
|
}
|
|
if (!m_pBT1154 || (m_pBT1154->cBodies == cAlloc))
|
|
{
|
|
ULONG cbCurrSize = offsetof(BOOKTREE1154, aBody) + (sizeof(BT1154BODY) * cAlloc);
|
|
ULONG cbAllocSize = cbCurrSize + sizeof(BT1154BODY) * 4;
|
|
LPBOOKTREE1154 pTmp;
|
|
|
|
CHECKALLOC(pTmp = (LPBOOKTREE1154)g_pMalloc->Alloc(cbAllocSize));
|
|
if (!m_pBT1154)
|
|
{
|
|
ZeroMemory(pTmp, cbAllocSize);
|
|
}
|
|
else
|
|
{
|
|
CopyMemory(pTmp, m_pBT1154, cbCurrSize);
|
|
ZeroMemory(((LPBYTE) pTmp) + cbCurrSize, cbAllocSize - cbCurrSize);
|
|
}
|
|
SafeMemFree(m_pBT1154);
|
|
m_pBT1154 = pTmp;
|
|
cAlloc += 4;
|
|
}
|
|
Assert(0 == m_pBT1154->aBody[m_pBT1154->cBodies].encEncoding);
|
|
Assert(0 == m_pBT1154->aBody[m_pBT1154->cBodies].cLines);
|
|
m_pBT1154->aBody[m_pBT1154->cBodies].encEncoding = encEncoding;
|
|
m_pBT1154->aBody[m_pBT1154->cBodies].cLines = cLines;
|
|
m_pBT1154->cBodies++;
|
|
if (*pszEndPtr == '\0')
|
|
{
|
|
// The end of the line...
|
|
break;
|
|
}
|
|
// Skip past the comma.
|
|
Assert(*pszEndPtr==',');
|
|
Assert(bNumberFound);
|
|
pszEndPtr++;
|
|
|
|
// ------------------------------------
|
|
// " ... , 975 UUENCODE"
|
|
// ^
|
|
// |-- pszEndPtr
|
|
// ------------------------------------
|
|
|
|
}
|
|
Assert(m_pBT1154);
|
|
Assert(m_pBT1154->cBodies);
|
|
Assert(!m_pBT1154->cCurrentBody);
|
|
Assert(!m_pBT1154->cCurrentLine);
|
|
Assert(S_OK == m_pBT1154->hrLoadResult);
|
|
|
|
bRes = TRUE;
|
|
|
|
exit:
|
|
SafeMemFree(pszEncoding);
|
|
if (!bRes)
|
|
{
|
|
SafeMemFree(m_pBT1154);
|
|
}
|
|
}
|
|
|
|
#endif // !WIN16
|
|
|
|
#ifdef SMIME_V3
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::Encode
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::Encode(HWND hwnd, DWORD dwFlags)
|
|
{
|
|
HRESULT hr;
|
|
CSMime * pSMime = NULL;
|
|
|
|
// Create the object
|
|
CHECKALLOC(pSMime = new CSMime);
|
|
|
|
// Initialize the object
|
|
CHECKHR(hr = pSMime->InitNew());
|
|
|
|
// Set the state flag to tell us about re-use of boundaries
|
|
FLAGSET(m_dwState, TREESTATE_REUSESIGNBOUND);
|
|
|
|
// Encode the message
|
|
CHECKHR(hr = pSMime->EncodeMessage2(this, m_rOptions.ulSecIgnoreMask |
|
|
dwFlags, hwnd));
|
|
|
|
exit:
|
|
ReleaseObj(pSMime);
|
|
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::Decode
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::Decode(HWND hwnd, DWORD dwFlags, IMimeSecurityCallback * pCallback)
|
|
{
|
|
HRESULT hr;
|
|
CSMime * pSMime = NULL;
|
|
|
|
// Create the object
|
|
CHECKALLOC(pSMime = new CSMime);
|
|
|
|
// Initialize the object
|
|
CHECKHR(hr = pSMime->InitNew());
|
|
|
|
// Encode the message
|
|
CHECKHR(hr = pSMime->DecodeMessage2(this, m_rOptions.ulSecIgnoreMask |
|
|
dwFlags, hwnd, pCallback));
|
|
|
|
exit:
|
|
ReleaseObj(pSMime);
|
|
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetRecipientCount
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::GetRecipientCount(DWORD dwFlags, DWORD * pdwRecipCount)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->GetRecipientCount(dwFlags, pdwRecipCount));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::AddRecipient
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::AddRecipient(DWORD dwFlags, DWORD cRecipData,
|
|
PCMS_RECIPIENT_INFO precipData)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->AddRecipient(dwFlags, cRecipData, precipData));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
|
|
// ------------------------------------------------------------------------------
|
|
// CMessageTree::GetRecipient
|
|
// ------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::GetRecipient(DWORD dwFlags, DWORD iRecipient, DWORD cRecipients, PCMS_RECIPIENT_INFO pRecipData)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->GetRecipient(dwFlags, iRecipient, cRecipients, pRecipData));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::DeleteRecipient
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::DeleteRecipient(DWORD dwFlags, DWORD iRecipient, DWORD cRecipients)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->DeleteRecipient(dwFlags, iRecipient, cRecipients));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetAttribute
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::GetAttribute(DWORD dwFlags, DWORD iSigner, DWORD iAttribSet,
|
|
DWORD iInstance, LPCSTR pszObjId,
|
|
CRYPT_ATTRIBUTE ** ppattr)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->GetAttribute(dwFlags, iSigner, iAttribSet, iInstance,
|
|
pszObjId, ppattr));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::SetAttribute
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::SetAttribute(DWORD dwFlags, DWORD iSigner, DWORD iAttribSet,
|
|
const CRYPT_ATTRIBUTE * ppattr)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->SetAttribute(dwFlags, iSigner, iAttribSet, ppattr));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageBody::DeleteAttribute
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::DeleteAttribute(DWORD dwFlags, DWORD iSigner,
|
|
DWORD iAttributeSet, DWORD iInstance,
|
|
LPCSTR pszObjId)
|
|
{
|
|
HRESULT hr;
|
|
IMimeSecurity2 * pms2 = NULL;
|
|
|
|
CHECKHR(hr = BindToObject(HBODY_ROOT, IID_IMimeSecurity2, (LPVOID *) &pms2));
|
|
|
|
CHECKHR(hr = pms2->DeleteAttribute(dwFlags, iSigner, iAttributeSet,
|
|
iInstance, pszObjId));
|
|
|
|
exit:
|
|
if (pms2 != NULL) pms2->Release();
|
|
return hr;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CreateReceipt
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::CreateReceipt(DWORD dwFlags, DWORD cbFromNames,
|
|
const BYTE *pbFromNames, DWORD cSignerCertificates,
|
|
PCCERT_CONTEXT *rgSignerCertificates,
|
|
IMimeMessage ** ppMimeMessageReceipt)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::GetReceiptSendersList
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::GetReceiptSendersList(DWORD dwFlags, DWORD *pcSendersList,
|
|
CERT_NAME_BLOB * *rgSendersList)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::VerifyReceipt
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::VerifyReceipt(DWORD dwFlags,
|
|
IMimeMessage * pMimeMessageReceipt)
|
|
{
|
|
return E_FAIL;
|
|
}
|
|
|
|
// --------------------------------------------------------------------------------
|
|
// CMessageTree::CapabilitiesSupported
|
|
// --------------------------------------------------------------------------------
|
|
|
|
HRESULT CMessageTree::CapabilitiesSupported(DWORD * pdwFlags)
|
|
{
|
|
// Assume no capabilities
|
|
*pdwFlags = 0;
|
|
|
|
// If we have msasn1.dll on the system, then we can support labels
|
|
if (FIsMsasn1Loaded()) *pdwFlags |= SMIME_SUPPORT_LABELS;
|
|
|
|
// If we have a correct crypt32, then we can support receipts & key agreement
|
|
DemandLoadCrypt32();
|
|
if (g_FSupportV3 && FIsMsasn1Loaded())
|
|
*pdwFlags |= SMIME_SUPPORT_RECEIPTS;
|
|
|
|
if (g_FSupportV3)
|
|
*pdwFlags |= SMIME_SUPPORT_KEY_AGREE;
|
|
|
|
// If we have a correct advapi32, then we can support maillist keys
|
|
DemandLoadAdvApi32();
|
|
if (VAR_CryptContextAddRef != MY_CryptContextAddRef)
|
|
*pdwFlags |= SMIME_SUPPORT_MAILLIST;
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
#endif // SMIME_V3
|