// -------------------------------------------------------------------------------- // BookTree.cpp // Copyright (c)1993-1995 Microsoft Corporation, All Rights Reserved // Steven J. Bailey // -------------------------------------------------------------------------------- #include "pch.hxx" #include #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 %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; ipParent != 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 ( ) 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; ipBody->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; ipBody->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; iAlloc(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; idwType = 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; iiCacheNode = 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; ihBody == 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; ipContainer->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; idwState, 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; ipBody->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; ipPrev != 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.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; ipParent) { // 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; ipParent) { // 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; ipContainer->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; icchVal; 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; ipChildHead; 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; idwWeight <= 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; ipBody->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; iSetData(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