Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

735 lines
20 KiB

/*
* m h t m l . c p p
*
* Purpose:
* MHTML packing utilities
*
* History
* August '96: brettm - created
*
* Copyright (C) Microsoft Corp. 1995, 1996.
*/
#include <pch.hxx>
#include "dllmain.h"
#include "resource.h"
#include "strconst.h"
#include "htmlstr.h"
#include "mimeutil.h"
#include "triutil.h"
#include "util.h"
#include "oleutil.h"
#include "demand.h"
#include "mhtml.h"
#include "tags.h"
ASSERTDATA
/*
* m a c r o s
*/
/*
* c o n s t a n t s
*/
static const TCHAR c_szRegExtension[] = "SOFTWARE\\Microsoft\\MimeEdit\\MHTML Extension";
/*
* t y p e d e f s
*/
/*
* g l o b a l s
*/
/*
* f u n c t i o n p r o t y p e s
*/
/*
* f u n c t i o n s
*/
class CPackager
{
public:
CPackager();
virtual ~CPackager();
ULONG AddRef();
ULONG Release();
HRESULT PackageData(IHTMLDocument2 *pDoc, IMimeMessage *pMsgSrc, IMimeMessage *pMsgDest, DWORD dwFlags, IHashTable *pHashRestricted);
private:
ULONG m_cRef;
IMimeMessage *m_pMsgSrc,
*m_pMsgDest;
IHashTable *m_pHash,
*m_pHashRestricted;
HRESULT _PackageCollectionData(IMimeEditTagCollection *pCollect);
HRESULT _PackageUrlData(IMimeEditTag *pTag);
HRESULT _RemapUrls(IMimeEditTagCollection *pCollect, BOOL fSave);
HRESULT _CanonicaliseContentId(LPWSTR pszUrlW, BSTR *pbstr);
HRESULT _ShouldUseContentId(LPSTR pszUrl);
HRESULT _BuildCollectionTable(DWORD dwFlags, IHTMLDocument2 *pDoc, IMimeEditTagCollection ***prgpCollect, ULONG *pcCount);
};
CPackager::CPackager()
{
m_cRef = 1;
m_pMsgSrc = NULL;
m_pMsgDest = NULL;
m_pHash = NULL;
m_pHashRestricted = NULL;
}
CPackager::~CPackager()
{
ReleaseObj(m_pMsgSrc);
ReleaseObj(m_pMsgDest);
ReleaseObj(m_pHash);
ReleaseObj(m_pHashRestricted);
}
ULONG CPackager::AddRef()
{
return ++m_cRef;
}
ULONG CPackager::Release()
{
if (--m_cRef==0)
{
delete this;
return 0;
}
return m_cRef;
}
HRESULT CPackager::PackageData(IHTMLDocument2 *pDoc, IMimeMessage *pMsgSrc, IMimeMessage *pMsgDest, DWORD dwFlags, IHashTable *pHashRestricted)
{
LPSTREAM pstm;
HRESULT hr,
hrWarnings=S_OK;
HBODY hBodyHtml=0;
IMimeEditTagCollection **rgpCollect=NULL;
ULONG uCollect,
cCollect=0,
cItems=0,
cCount;
// BUGBUG: propagate hrWarnings back up
TraceCall("CBody::Save");
if (pDoc==NULL || pMsgDest==NULL)
return E_INVALIDARG;
ReplaceInterface(m_pMsgSrc, pMsgSrc);
ReplaceInterface(m_pMsgDest, pMsgDest);
ReplaceInterface(m_pHashRestricted, pHashRestricted);
hr = _BuildCollectionTable(dwFlags, pDoc, &rgpCollect, &cCollect);
if (FAILED(hr))
hrWarnings = hr;
// count the number of items we need to package and
// prepare a hash-table for duplicate entries
for (uCollect = 0; uCollect < cCollect; uCollect++)
{
Assert (rgpCollect[uCollect]);
if ((rgpCollect[uCollect])->Count(&cCount)==S_OK)
cItems+=cCount;
}
if (cItems)
{
// create the hashtable
hr = MimeOleCreateHashTable(cItems, TRUE, &m_pHash);
if (FAILED(hr))
goto error;
}
// package the data required for each collection
for (uCollect = 0; uCollect < cCollect; uCollect++)
{
// package the data
hr = _PackageCollectionData(rgpCollect[uCollect]);
if (FAILED(hr))
goto error;
if (hr != S_OK) // retain any 'warnings'
hrWarnings = hr;
// map all the URLs to CID:// urls if necessary
hr = _RemapUrls(rgpCollect[uCollect], TRUE);
if (FAILED(hr))
goto error;
}
// get an HTML stream
if(dwFlags & MECD_HTML)
{
hr = GetBodyStream(pDoc, TRUE, &pstm);
if (!FAILED(hr))
{
hr = pMsgDest->SetTextBody(TXT_HTML, IET_INETCSET, NULL, pstm, &hBodyHtml);
pstm->Release();
}
if (FAILED(hr))
goto error;
}
// get a plain-text stream
if(dwFlags & MECD_PLAINTEXT)
{
hr = GetBodyStream(pDoc, FALSE, &pstm);
if (!FAILED(hr))
{
// if we set a html body part, then be sure to pass in hBodyHtml so Opie knows what the alternate is
// alternative to.
hr = pMsgDest->SetTextBody(TXT_PLAIN, IET_UNICODE, hBodyHtml, pstm, NULL);
pstm->Release();
}
if (FAILED(hr))
goto error;
}
for (uCollect = 0; uCollect < cCollect; uCollect++)
{
// remap all of the URL's back to their original location
hr = _RemapUrls(rgpCollect[uCollect], FALSE);
if (FAILED(hr))
goto error;
}
error:
// release the collection objects
if (rgpCollect)
{
for (uCollect = 0; uCollect < cCollect; uCollect++)
ReleaseObj(rgpCollect[uCollect]);
MemFree(rgpCollect);
}
return hr==S_OK ? hrWarnings : hr;
}
HRESULT CPackager::_BuildCollectionTable(DWORD dwFlags, IHTMLDocument2 *pDoc, IMimeEditTagCollection ***prgpCollect, ULONG *pcCount)
{
IMimeEditTagCollection **rgpCollect=NULL;
HKEY hkey;
ULONG cPlugin=0,
cCount = 0,
cAlloc = 0,
i,
cb;
TCHAR szGUID[MAX_PATH];
IID iid;
HRESULT hr=E_FAIL;
LONG lResult;
LPWSTR pszGuidW;
*prgpCollect = NULL;
*pcCount = NULL;
// reserve space for 2 image collections (bgimage and img)
if (dwFlags & MECD_ENCODEIMAGES)
cAlloc+=2;
// reserver space for bgsounds
if (dwFlags & MECD_ENCODESOUNDS)
cAlloc++;
// reserver space for active-movies
if (dwFlags & MECD_ENCODEVIDEO)
cAlloc++;
// reserve space for plugin types
if ((dwFlags & MECD_ENCODEPLUGINS) &&
RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegExtension, 0, KEY_READ, &hkey) == ERROR_SUCCESS)
{
if (RegQueryInfoKey(hkey, NULL, NULL, 0, &cPlugin, NULL, NULL, NULL, NULL, NULL, NULL, NULL) == ERROR_SUCCESS &&
cPlugin > 0)
cAlloc += cPlugin;
RegCloseKey(hkey);
}
// allocate the table of collection pointers
if (!MemAlloc((LPVOID *)&rgpCollect, sizeof(IMimeEditTagCollection *) * cAlloc))
{
hr = TraceResult(E_OUTOFMEMORY);
goto error;
}
// zero-init the table
ZeroMemory((LPVOID)rgpCollect, sizeof(IMimeEditTagCollection *) * cAlloc);
if (dwFlags & MECD_ENCODEIMAGES)
{
// image collection
if (FAILED(CreateOEImageCollection(pDoc, &rgpCollect[cCount])))
hr = MIMEEDIT_W_BADURLSNOTATTACHED; // bubble back a warning, but don't fail
else
cCount++;
}
if (dwFlags & MECD_ENCODEIMAGES)
{
// background images
if (FAILED(CreateBGImageCollection(pDoc, &rgpCollect[cCount])))
hr = MIMEEDIT_W_BADURLSNOTATTACHED; // bubble back a warning, but don't fail
else
cCount++;
}
if (dwFlags & MECD_ENCODESOUNDS)
{
// background sounds
if (FAILED(CreateBGSoundCollection(pDoc, &rgpCollect[cCount])))
hr = MIMEEDIT_W_BADURLSNOTATTACHED; // bubble back a warning, but don't fail
else
cCount++;
}
if (dwFlags & MECD_ENCODEVIDEO)
{
// active-movie controls (for MSPHONE)
if (FAILED(CreateActiveMovieCollection(pDoc, &rgpCollect[cCount])))
hr = MIMEEDIT_W_BADURLSNOTATTACHED; // bubble back a warning, but don't fail
else
cCount++;
}
if ((dwFlags & MECD_ENCODEPLUGINS) &&
cPlugin &&
RegOpenKeyEx(HKEY_LOCAL_MACHINE, c_szRegExtension, 0, KEY_READ, &hkey) == ERROR_SUCCESS)
{
// Start Enumerating the keys
for (i = 0; i < cPlugin; i++)
{
// Enumerate Friendly Names
cb = sizeof(szGUID);
lResult = RegEnumKeyEx(hkey, i, szGUID, &cb, 0, NULL, NULL, NULL);
// No more items
if (lResult == ERROR_NO_MORE_ITEMS)
break;
// Error, lets move onto the next account
if (lResult != ERROR_SUCCESS)
{
Assert(FALSE);
continue;
}
pszGuidW = PszToUnicode(CP_ACP, szGUID);
if (pszGuidW)
{
// convert the string to a guid
if (IIDFromString(pszGuidW, &iid) == S_OK)
{
// cocreate the plugin
if (CoCreateInstance(iid, NULL, CLSCTX_INPROC_SERVER, IID_IMimeEditTagCollection, (LPVOID *)&rgpCollect[cCount])==S_OK)
{
// try and init the document
if (!FAILED((rgpCollect[cCount])->Init(pDoc)))
{
cCount++;
}
else
{
SafeRelease(rgpCollect[cCount]);
}
}
else
hr = MIMEEDIT_W_BADURLSNOTATTACHED; // bubble back a warning, but don't fail
}
MemFree(pszGuidW);
}
}
RegCloseKey(hkey);
}
*prgpCollect = rgpCollect;
*pcCount = cCount;
rgpCollect = NULL;
hr = S_OK;
error:
if (rgpCollect)
{
for (i = 0; i < cAlloc; i++)
SafeRelease(rgpCollect[i]);
MemFree(rgpCollect);
}
return hr;
}
HRESULT CPackager::_PackageCollectionData(IMimeEditTagCollection *pCollect)
{
ULONG cFetched;
IMimeEditTag *pTag;
BOOL fBadLinks = FALSE;
Assert (pCollect);
pCollect->Reset();
while (pCollect->Next(1, &pTag, &cFetched)==S_OK && cFetched==1)
{
Assert (pTag);
if (pTag->CanPackage() != S_OK ||
_PackageUrlData(pTag) != S_OK)
{
// we failed to package this body part. Be sure to return a warning when we're done
// but let's keep trucking for now...
fBadLinks = TRUE;
}
pTag->Release();
}
return fBadLinks ? MIMEEDIT_W_BADURLSNOTATTACHED : S_OK;;
}
HRESULT CPackager::_PackageUrlData(IMimeEditTag *pTag)
{
HRESULT hr=S_OK;
LPSTREAM pstm;
HBODY hBody=0,
hBodyOld;
LPSTR lpszCID=0,
lpszCIDUrl;
LPSTR pszUrlA=0,
pszBody;
BSTR bstrSrc=NULL,
bstrCID=NULL;
LPWSTR pszMimeTypeW=NULL;
if (pTag == NULL)
return E_INVALIDARG;
pTag->GetSrc(&bstrSrc);
pszUrlA = PszToANSI(CP_ACP, bstrSrc);
if (!pszUrlA)
return TraceResult(E_OUTOFMEMORY);
// if the URL is a restricted URL then we simply exit without packing any data
if (m_pHashRestricted &&
m_pHashRestricted->Find(pszUrlA, FALSE, (LPVOID *)&hBody)==S_OK)
{
MemFree(pszUrlA);
return S_OK;
}
// hack: if it's an MHTML: url then we have to fixup to get the cid:
if (StrCmpNIA(pszUrlA, "mhtml:", 6)==0)
{
if (!FAILED(MimeOleParseMhtmlUrl(pszUrlA, NULL, &pszBody)))
{
// pszBody pszUrlA is guarnteed to be smaller
lstrcpy(pszUrlA, pszBody);
SafeMimeOleFree(pszBody);
}
}
if (m_pHash &&
m_pHash->Find(pszUrlA, FALSE, (LPVOID *)&hBody)==S_OK)
{
// we've already seen this url one before in this document, and have it's HBODY already
// so there's no need to do any work
// try and get the content-id incase the caller is interested
// BUGBUG? possible more than CID need to be ported here...
MimeOleGetBodyPropA(m_pMsgDest, hBody, PIDTOSTR(PID_HDR_CNTID), NOFLAGS, &lpszCID);
goto found;
}
// see if szUrl is in the related section of the source message
if (m_pMsgSrc &&
m_pMsgSrc->ResolveURL(NULL, NULL, pszUrlA, 0, &hBody)==S_OK)
{
hBodyOld = hBody;
// this URL is already in the related section, and we haven't seen it already.
// then let's bind to the data and attach it
if (m_pMsgSrc->BindToObject(hBody, IID_IStream, (LPVOID *)&pstm)==S_OK)
{
// if it's a FILE:// url we use CID: else we use Content-Location
hr = m_pMsgDest->AttachURL(NULL, pszUrlA, (_ShouldUseContentId(pszUrlA)==S_OK ? URL_ATTACH_GENERATE_CID : 0 )|URL_ATTACH_SET_CNTTYPE, pstm, &lpszCID, &hBody);
pstm->Release();
}
// be sure to copy the old content-type and filename over
HrCopyHeader(m_pMsgDest, hBody, m_pMsgSrc, hBodyOld, PIDTOSTR(PID_HDR_CNTTYPE));
HrCopyHeader(m_pMsgDest, hBody, m_pMsgSrc, hBodyOld, PIDTOSTR(PID_HDR_CNTLOC));
HrCopyHeader(m_pMsgDest, hBody, m_pMsgSrc, hBodyOld, PIDTOSTR(STR_PAR_FILENAME));
}
else
{
// if not, then let's try and bind to it ourselves. We don't go thro' MimeOle for this, as we want to
// fail if the URL is bad, so we don't add the part to the Tree.
hr = HrBindToUrl(pszUrlA, &pstm);
if (!FAILED(hr))
{
hr = SniffStreamForMimeType(pstm, &pszMimeTypeW);
if (!FAILED(hr))
{
if (pTag->IsValidMimeType(pszMimeTypeW)==S_OK)
{
LPWSTR pszFileNameW;
// if it's a FILE:// url we use CID: else we use Content-Location
hr = m_pMsgDest->AttachURL(NULL, pszUrlA, (_ShouldUseContentId(pszUrlA)==S_OK ? URL_ATTACH_GENERATE_CID : 0 )|URL_ATTACH_SET_CNTTYPE, pstm, &lpszCID, &hBody);
if (!FAILED(hr))
{
// if attaching a new attachment, try and sniff the file-name
pszFileNameW = PathFindFileNameW(bstrSrc);
if (pszFileNameW)
MimeOleSetBodyPropW(m_pMsgDest, hBody, PIDTOSTR(STR_PAR_FILENAME), NOFLAGS, pszFileNameW);
}
}
else
hr = E_FAIL;
CoTaskMemFree(pszMimeTypeW);
}
pstm->Release();
}
}
// add to the hash table
if (m_pHash &&
!FAILED(hr) && hBody)
hr = m_pHash->Insert(pszUrlA, (void*)hBody, NOFLAGS);
found:
// if we found the content-ID we need to return an allocated BSTR with it in.
if (lpszCID)
{
LPWSTR pszCIDW;
pszCIDW = PszToUnicode(CP_ACP, lpszCID);
if (pszCIDW)
{
if (_CanonicaliseContentId(pszCIDW, &bstrCID)==S_OK)
{
pTag->SetDest(bstrCID);
SysFreeString(bstrCID);
}
MemFree(pszCIDW);
}
SafeMimeOleFree(lpszCID);
}
SafeMemFree(pszUrlA);
return hr;
}
HRESULT CPackager::_ShouldUseContentId(LPSTR pszUrl)
{
// we use Content-Location for urls that begin with "http:", "https:" and "ftp:" for all
// others we will use Content-Id
if (StrCmpNIA(pszUrl, "ftp:", 4)==0 ||
StrCmpNIA(pszUrl, "http:", 5)==0 ||
StrCmpNIA(pszUrl, "https:", 6)==0)
return S_FALSE;
return S_OK;
}
HRESULT CPackager::_RemapUrls(IMimeEditTagCollection *pCollect, BOOL fSave)
{
ULONG cFetched;
IMimeEditTag *pTag;
Assert (pCollect);
pCollect->Reset();
while (pCollect->Next(1, &pTag, &cFetched)==S_OK && cFetched==1)
{
Assert (pTag);
if (fSave)
pTag->OnPreSave();
else
pTag->OnPostSave();
pTag->Release();
}
return S_OK;
}
HRESULT CPackager::_CanonicaliseContentId(LPWSTR pszUrlW, BSTR *pbstr)
{
HRESULT hr;
*pbstr = NULL;
if (StrCmpNIW(pszUrlW, L"cid:", 4)!=0)
{
*pbstr = SysAllocStringLen(NULL, lstrlenW(pszUrlW) + 4);
if (*pbstr)
{
StrCpyW(*pbstr, L"cid:");
StrCatW(*pbstr, pszUrlW);
}
}
else
*pbstr = SysAllocString(pszUrlW);
return *pbstr ? S_OK : E_OUTOFMEMORY;
}
HRESULT SaveAsMHTML(IHTMLDocument2 *pDoc, DWORD dwFlags, IMimeMessage *pMsgSrc, IMimeMessage *pMsgDest, IHashTable *pHashRestricted)
{
CPackager *pPacker=0;
HRESULT hr;
TraceCall("CBody::Save");
pPacker = new CPackager();
if (!pPacker)
{
hr = TraceResult(E_OUTOFMEMORY);
goto error;
}
hr = pPacker->PackageData(pDoc, pMsgSrc, pMsgDest, dwFlags, pHashRestricted);
if (FAILED(hr))
goto error;
error:
ReleaseObj(pPacker);
return hr;
}
HRESULT HashExternalReferences(IHTMLDocument2 *pDoc, IMimeMessage *pMsg, IHashTable **ppHash)
{
HRESULT hr;
IMimeEditTagCollection *rgpCollect[4];
IMimeEditTagCollection *pCollect;
ULONG uCollect,
cCollect=0,
cItems=0,
cCount,
cFetched;
IHashTable *pHash;
IMimeEditTag *pTag;
BSTR bstrSrc;
LPSTR pszUrlA;
// keep trucking if we fail, to catch as many as we can
*ppHash = NULL;
// image collection
if (CreateOEImageCollection(pDoc, &rgpCollect[cCollect])==S_OK)
cCollect++;
// background images
if (CreateBGImageCollection(pDoc, &rgpCollect[cCollect])==S_OK)
cCollect++;
// background sounds
if (CreateBGSoundCollection(pDoc, &rgpCollect[cCollect])==S_OK)
cCollect++;
// active-movie controls (for MSPHONE)
if (CreateActiveMovieCollection(pDoc, &rgpCollect[cCollect])==S_OK)
cCollect++;
// count the number of items we need to package and
// prepare a hash-table for duplicate entries
for (uCollect = 0; uCollect < cCollect; uCollect++)
{
Assert (rgpCollect[uCollect]);
if ((rgpCollect[uCollect])->Count(&cCount)==S_OK)
cItems+=cCount;
}
// create the hashtable
hr = MimeOleCreateHashTable(cItems, TRUE, &pHash);
if (FAILED(hr))
goto error;
// looks for external references in each
for (uCollect = 0; uCollect < cCollect; uCollect++)
{
pCollect = rgpCollect[uCollect];
if (pCollect)
{
pCollect->Reset();
while (pCollect->Next(1, &pTag, &cFetched)==S_OK && cFetched==1)
{
Assert (pTag);
if (pTag->GetSrc(&bstrSrc)==S_OK)
{
pszUrlA = PszToANSI(CP_ACP, bstrSrc);
if (pszUrlA)
{
if (HrFindUrlInMsg(pMsg, pszUrlA, FINDURL_SEARCH_RELATED_ONLY, NULL)!=S_OK)
{
// this URL was not in the message and it external
// let's track it as a restricted URL in our hash
pHash->Insert(pszUrlA, NULL, NOFLAGS);
}
MemFree(pszUrlA);
}
SysFreeString(bstrSrc);
}
pTag->Release();
}
pCollect->Release();
}
}
// return our new hash
*ppHash = pHash;
pHash = NULL;
error:
ReleaseObj(pHash);
return hr;
}