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.
304 lines
8.2 KiB
304 lines
8.2 KiB
#include "priv.h"
|
|
|
|
#define MAX_STREAMS 5
|
|
#define CP_UNICODE 1200
|
|
|
|
class CStreamWrap : public IStream
|
|
{
|
|
|
|
public:
|
|
// *** IUnknown methods ***
|
|
STDMETHOD(QueryInterface) (THIS_ REFIID riid, void **ppv);
|
|
STDMETHOD_(ULONG,AddRef) (THIS);
|
|
STDMETHOD_(ULONG,Release) (THIS);
|
|
|
|
// *** IStream methods ***
|
|
STDMETHOD(Read) (THIS_ void *pv, ULONG cb, ULONG *pcbRead);
|
|
STDMETHOD(Write) (THIS_ VOID const *pv, ULONG cb, ULONG *pcbWritten);
|
|
STDMETHOD(Seek) (THIS_ LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition);
|
|
STDMETHOD(SetSize) (THIS_ ULARGE_INTEGER libNewSize);
|
|
STDMETHOD(CopyTo) (THIS_ IStream *pstm, ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten);
|
|
STDMETHOD(Commit) (THIS_ DWORD grfCommitFlags);
|
|
STDMETHOD(Revert) (THIS);
|
|
STDMETHOD(LockRegion) (THIS_ ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
|
|
STDMETHOD(UnlockRegion) (THIS_ ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType);
|
|
STDMETHOD(Stat) (THIS_ STATSTG *pstatstg, DWORD grfStatFlag);
|
|
STDMETHOD(Clone)(THIS_ IStream **ppstm);
|
|
|
|
HRESULT Init(IStream *aStreams[], UINT cStreams, UINT uiCodePage);
|
|
CStreamWrap();
|
|
|
|
private:
|
|
~CStreamWrap();
|
|
|
|
LONG _cRef;
|
|
IStream *_aStreams[MAX_STREAMS];
|
|
BOOL _fFirstReadForStream[MAX_STREAMS];
|
|
UINT _cStreams;
|
|
UINT _iCurStream;
|
|
UINT _uiCodePage;
|
|
UINT _uiBOM; // Byte order marker
|
|
};
|
|
|
|
CStreamWrap::CStreamWrap() : _cRef(1)
|
|
{
|
|
}
|
|
|
|
CStreamWrap::~CStreamWrap()
|
|
{
|
|
while (_cStreams--)
|
|
{
|
|
if (_aStreams[_cStreams])
|
|
{
|
|
_aStreams[_cStreams]->Release();
|
|
_aStreams[_cStreams] = NULL;
|
|
}
|
|
}
|
|
}
|
|
|
|
HRESULT CStreamWrap::Init(IStream *aStreams[], UINT cStreams, UINT uiCodePage)
|
|
{
|
|
if (cStreams > ARRAYSIZE(_aStreams))
|
|
return E_FAIL;
|
|
|
|
for (_cStreams = 0; _cStreams < cStreams; _cStreams++)
|
|
{
|
|
_aStreams[_cStreams] = aStreams[_cStreams];
|
|
_fFirstReadForStream[_cStreams] = TRUE;
|
|
_aStreams[_cStreams]->AddRef();
|
|
}
|
|
|
|
_uiCodePage = uiCodePage;
|
|
_uiBOM = 0xfeff; // FEATURE - set default to byte order of machine
|
|
|
|
return S_OK;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::QueryInterface(REFIID riid, void **ppv)
|
|
{
|
|
if (IsEqualIID(riid, IID_IStream) || IsEqualIID(riid, IID_IUnknown))
|
|
{
|
|
*ppv = SAFECAST(this, IStream *);
|
|
}
|
|
else
|
|
{
|
|
*ppv = NULL;
|
|
return E_NOINTERFACE;
|
|
}
|
|
this->AddRef();
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CStreamWrap::AddRef()
|
|
{
|
|
return InterlockedIncrement(&this->_cRef);
|
|
}
|
|
|
|
STDMETHODIMP_(ULONG) CStreamWrap::Release()
|
|
{
|
|
ASSERT( 0 != this->_cRef );
|
|
ULONG cRef = InterlockedDecrement(&this->_cRef);
|
|
if ( 0 == cRef )
|
|
{
|
|
delete this;
|
|
}
|
|
return cRef;
|
|
}
|
|
|
|
// Byte order marker macros
|
|
#define IS_BOM_LITTLE_ENDIAN(pv) ((*(WORD*)pv) == 0xfffe)
|
|
#define IS_BOM_BIG_ENDIAN(pv) ((*(WORD*)pv) == 0xfeff)
|
|
|
|
STDMETHODIMP CStreamWrap::Read(void *pv, ULONG cb, ULONG *pcbRead)
|
|
{
|
|
ULONG cbReadTotal = 0;
|
|
ULONG cbLeftToRead = cb;
|
|
HRESULT hres = NOERROR;
|
|
|
|
while (cbLeftToRead && (_iCurStream < _cStreams))
|
|
{
|
|
ULONG cbReadThisStream;
|
|
hres = _aStreams[_iCurStream]->Read(pv, cbLeftToRead, &cbReadThisStream);
|
|
|
|
// REVIEW: what if one stream's implementation returns a failure code
|
|
// when reading at the end of the stream? We bail prematurely.
|
|
if (SUCCEEDED(hres))
|
|
{
|
|
cbLeftToRead -= cbReadThisStream;
|
|
|
|
if(_uiCodePage == CP_UNICODE)
|
|
{
|
|
if((_fFirstReadForStream[_iCurStream]) &&
|
|
(cbReadThisStream >= 2) &&
|
|
((IS_BOM_LITTLE_ENDIAN(pv)) || (IS_BOM_BIG_ENDIAN(pv)))
|
|
)
|
|
{
|
|
if(_iCurStream == 0)
|
|
{
|
|
_uiBOM = (*(WORD*)pv); // Save first streams byte order marker as default
|
|
}
|
|
else
|
|
{
|
|
// REVIEW: should handle swapping bytes to default for IE6
|
|
if(_uiBOM != (*(WORD*)pv)) // BOM not default
|
|
return(E_FAIL);
|
|
|
|
// Skip past unicode document lead bytes
|
|
cbReadThisStream -= 2;
|
|
MoveMemory((BYTE*)pv, (BYTE*)pv+2, cbReadThisStream);
|
|
}
|
|
}
|
|
|
|
_fFirstReadForStream[_iCurStream] = FALSE;
|
|
}
|
|
cbReadTotal += cbReadThisStream;
|
|
pv = (char *)pv + cbReadThisStream;
|
|
|
|
if (cbLeftToRead)
|
|
{
|
|
_iCurStream++;
|
|
hres = S_OK;
|
|
}
|
|
}
|
|
else
|
|
break;
|
|
}
|
|
|
|
if (pcbRead)
|
|
*pcbRead = cbReadTotal;
|
|
|
|
if (SUCCEEDED(hres) && cbLeftToRead)
|
|
hres = S_FALSE; // still success! but not completely
|
|
|
|
return hres;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::Write(const void *pv, ULONG cb, ULONG *pcbWritten)
|
|
{
|
|
if (pcbWritten)
|
|
*pcbWritten = 0;
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// FEATURE: could at least support seaking to 0, as that's a common thing to do.
|
|
// REVIEW: not too hard to implement thoroughly - cache Stat calls on each
|
|
// substream (help implement ::Stat in this file too, which IMO is needed.)
|
|
STDMETHODIMP CStreamWrap::Seek(LARGE_INTEGER dlibMove, DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::SetSize(ULARGE_INTEGER libNewSize)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
//
|
|
// REVIEW: this could use the internal buffer in the stream to avoid
|
|
// extra buffer copies.
|
|
//
|
|
STDMETHODIMP CStreamWrap::CopyTo(IStream *pstmTo, ULARGE_INTEGER cb,
|
|
ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)
|
|
{
|
|
BYTE buf[512];
|
|
ULONG cbRead;
|
|
HRESULT hres = NOERROR;
|
|
|
|
if (pcbRead)
|
|
{
|
|
pcbRead->LowPart = 0;
|
|
pcbRead->HighPart = 0;
|
|
}
|
|
if (pcbWritten)
|
|
{
|
|
pcbWritten->LowPart = 0;
|
|
pcbWritten->HighPart = 0;
|
|
}
|
|
|
|
ASSERT(cb.HighPart == 0);
|
|
|
|
while (cb.LowPart)
|
|
{
|
|
hres = this->Read(buf, min(cb.LowPart, SIZEOF(buf)), &cbRead);
|
|
|
|
if (FAILED(hres) || (cbRead == 0))
|
|
break;
|
|
|
|
if (pcbRead)
|
|
pcbRead->LowPart += cbRead;
|
|
|
|
cb.LowPart -= cbRead;
|
|
|
|
hres = pstmTo->Write(buf, cbRead, &cbRead);
|
|
|
|
if (pcbWritten)
|
|
pcbWritten->LowPart += cbRead;
|
|
|
|
if (FAILED(hres) || (cbRead == 0))
|
|
break;
|
|
}
|
|
|
|
return hres;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::Commit(DWORD grfCommitFlags)
|
|
{
|
|
return NOERROR;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::Revert()
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::LockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
STDMETHODIMP CStreamWrap::UnlockRegion(ULARGE_INTEGER libOffset, ULARGE_INTEGER cb, DWORD dwLockType)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// FEATURE: you gotta support Stat, or Trident will barf on this stream.
|
|
// Trivial to implement too, just call Stat on each sub-stream.
|
|
STDMETHODIMP CStreamWrap::Stat(STATSTG *pstatstg, DWORD grfStatFlag)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
|
|
// REVIEW: so simple to implement, it's probably worth doing
|
|
STDMETHODIMP CStreamWrap::Clone(IStream **ppstm)
|
|
{
|
|
return E_NOTIMPL;
|
|
}
|
|
|
|
// in:
|
|
// ppstm array of stream pointers
|
|
// cStreams number of streams in the array
|
|
//
|
|
|
|
SHDOCAPI SHCreateStreamWrapperCP(IStream *aStreams[], UINT cStreams, DWORD grfMode, UINT uiCodePage, IStream **ppstm)
|
|
{
|
|
HRESULT hres;
|
|
|
|
*ppstm = NULL;
|
|
|
|
if (grfMode != STGM_READ)
|
|
return E_INVALIDARG;
|
|
|
|
CStreamWrap *pwrap = new CStreamWrap();
|
|
if (pwrap)
|
|
{
|
|
hres = pwrap->Init(aStreams, cStreams, uiCodePage);
|
|
if (SUCCEEDED(hres))
|
|
pwrap->QueryInterface(IID_IStream, (void **)ppstm);
|
|
pwrap->Release();
|
|
}
|
|
else
|
|
hres = E_OUTOFMEMORY;
|
|
|
|
return hres;
|
|
}
|