WindowsXP-SP1/shell/shlwapi/stream.cpp

648 lines
18 KiB
C++

//---------------------------------------------------------------------------
//
// Copyright (c) Microsoft Corporation 1991-1993
//
// File: stream.c
//
// This file contains some of the stream support code that is used by
// the shell. It also contains the shells implementation of a memory
// stream that is used by the cabinet to allow views to be serialized.
//
// History:
// 08-20-93 KurtE Added header block and memory stream.
//
//---------------------------------------------------------------------------
#include "priv.h"
#include <new.h>
#include "nullstm.h"
// This code was stolen from shell32. This is the BETTER_STRONGER_FASTER
// version (smaller and half the allocs), added after Win95 shipped.
#include "stream.h"
EXTERN_C HKEY SHRegDuplicateHKey(HKEY hkey);
// The Win95/NT4/IE4 code did not enforce the grfMode. Turn this on to enforce:
//#define ENFORCE_GRFMODE // Note: I haven't tested compat issues with this turned on yet... [mikesh]
STDMETHODIMP CMemStream::QueryInterface(REFIID riid, void **ppvObj)
{
if (IsEqualIID(riid, IID_IStream) || IsEqualIID(riid, IID_IUnknown))
{
*ppvObj=this;
this->cRef++;
return S_OK;
}
*ppvObj = NULL;
return E_NOINTERFACE;
}
STDMETHODIMP_(ULONG) CMemStream::AddRef()
{
this->cRef++;
return this->cRef;
}
BOOL CMemStream::WriteToReg()
{
if (this->cbData)
{
return ERROR_SUCCESS == RegSetValueEx(this->hkey,
this->szValue[0] ? this->szValue : NULL, 0, REG_BINARY,
this->cbData ? this->pBuf : (LPBYTE)"", this->cbData);
}
else
{
DWORD dwRet = SHDeleteValue(this->hkey, NULL, this->szValue);
// If the Stream is being stored in the default key, then
// we should clean up the key. Otherwise, the caller
// passed us the key, and they need it. It would be rude for us
// to delete it. Fixes a Start Menu bug (NT#361333) where we would delete the
// programs key where start menu stores it's stuff on a load, so we
// never persist anything. - lamadio (6.25.99)
if (this->szValue[0] == TEXT('\0'))
{
SHDeleteEmptyKey(this->hkey, NULL);
}
return ERROR_SUCCESS == dwRet;
}
}
STDMETHODIMP_(ULONG) CMemStream::Release()
{
this->cRef--;
if (this->cRef > 0)
return this->cRef;
// If this is backed up by the registry serialize the data
if (this->hkey)
{
// Backed by the registry.
// Write and cleanup.
WriteToReg();
RegCloseKey(this->hkey);
}
// Free the data buffer that is allocated to the stream
if (this->pBuf)
LocalFree(this->pBuf);
LocalFree((HLOCAL)this);
return 0;
}
STDMETHODIMP CMemStream::Read(void *pv, ULONG cb, ULONG *pcbRead)
{
#ifdef ENFORCE_GRFMODE
if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_WRITE)
{
if (pcbRead != NULL)
*pcbRead = 0;
return STG_E_ACCESSDENIED;
}
#endif
ASSERT(pv);
// I guess a null read is ok.
if (!cb)
{
if (pcbRead != NULL)
*pcbRead = 0;
return S_OK;
}
if (this->iSeek >= this->cbData)
{
if (pcbRead != NULL)
*pcbRead = 0; // nothing read
}
else
{
if ((this->iSeek + cb) > this->cbData)
cb = this->cbData - this->iSeek;
// Now Copy the memory
ASSERT(this->pBuf);
CopyMemory(pv, this->pBuf + this->iSeek, cb);
this->iSeek += (UINT)cb;
if (pcbRead != NULL)
*pcbRead = cb;
}
return S_OK;
}
LPBYTE CMemStream::GrowBuffer(ULONG cbNew)
{
if (this->pBuf == NULL)
{
this->pBuf = (LPBYTE)LocalAlloc(LPTR, cbNew);
}
else
{
LPBYTE pTemp = (LPBYTE)LocalReAlloc(this->pBuf, cbNew, LMEM_MOVEABLE | LMEM_ZEROINIT);
if (pTemp)
{
this->pBuf = pTemp;
}
else
{
TraceMsg(TF_ERROR, "Stream buffer realloc failed");
return NULL;
}
}
if (this->pBuf)
this->cbAlloc = cbNew;
return this->pBuf;
}
#define SIZEINCR 0x1000
STDMETHODIMP CMemStream::Write(void const *pv, ULONG cb, ULONG *pcbWritten)
{
#ifdef ENFORCE_GRFMODE
if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_READ)
{
if (pcbWritten != NULL)
*pcbWritten = 0;
return STG_E_ACCESSDENIED;
}
#endif
// I guess a null write is ok.
if (!cb)
{
if (pcbWritten != NULL)
*pcbWritten = 0;
return S_OK;
}
// See if the data will fit into our current buffer
if ((this->iSeek + cb) > this->cbAlloc)
{
// enlarge the buffer
// Give it a little slop to avoid a lot of reallocs.
if (GrowBuffer(this->iSeek + (UINT)cb + SIZEINCR) == NULL)
return STG_E_INSUFFICIENTMEMORY;
}
ASSERT(this->pBuf);
// See if we need to fill the area between the data size and
// the seek position
if (this->iSeek > this->cbData)
{
ZeroMemory(this->pBuf + this->cbData, this->iSeek - this->cbData);
}
CopyMemory(this->pBuf + this->iSeek, pv, cb);
this->iSeek += (UINT)cb;
if (this->iSeek > this->cbData)
this->cbData = this->iSeek;
if (pcbWritten != NULL)
*pcbWritten = cb;
return S_OK;
}
STDMETHODIMP CMemStream::Seek(LARGE_INTEGER dlibMove,
DWORD dwOrigin, ULARGE_INTEGER *plibNewPosition)
{
LONG lNewSeek;
// Note: curently not testing for error conditions for number wrap...
switch (dwOrigin)
{
case STREAM_SEEK_SET:
lNewSeek = (LONG)dlibMove.LowPart;
break;
case STREAM_SEEK_CUR:
lNewSeek = (LONG)this->iSeek + (LONG)dlibMove.LowPart;
break;
case STREAM_SEEK_END:
lNewSeek = (LONG)this->cbData + (LONG)dlibMove.LowPart;
break;
default:
return STG_E_INVALIDPARAMETER;
}
if (lNewSeek < 0)
return STG_E_INVALIDFUNCTION;
this->iSeek = (UINT)lNewSeek;
if (plibNewPosition != NULL)
{
plibNewPosition->LowPart = (DWORD)lNewSeek;
plibNewPosition->HighPart = 0;
}
return S_OK;
}
STDMETHODIMP CMemStream::SetSize(ULARGE_INTEGER libNewSize)
{
#ifdef ENFORCE_GRFMODE
if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_READ)
{
return STG_E_ACCESSDENIED;
}
#endif
UINT cbNew = (UINT)libNewSize.LowPart;
// See if the data will fit into our current buffer
if (cbNew > this->cbData)
{
// See if we have to Enlarge the buffer.
if (cbNew > this->cbAlloc)
{
// enlarge the buffer - Does not check wrap...
// Give it a little slop to avoid a lot of reallocs.
if (GrowBuffer(cbNew) == NULL)
return STG_E_INSUFFICIENTMEMORY;
}
// Now fill some memory
ZeroMemory(this->pBuf + this->cbData, cbNew - this->cbData);
}
// Save away the new size.
this->cbData = cbNew;
return S_OK;
}
STDMETHODIMP CMemStream::CopyTo(IStream *pstmTo,
ULARGE_INTEGER cb, ULARGE_INTEGER *pcbRead, ULARGE_INTEGER *pcbWritten)
{
#ifdef ENFORCE_GRFMODE
if ((this->grfMode & (STGM_READ|STGM_WRITE|STGM_READWRITE)) == STGM_WRITE)
{
if (pcbRead != NULL)
ZeroMemory(pcbRead, sizeof(pcbRead));
if (pcbWritten != NULL)
ZeroMemory(pcbWritten, sizeof(pcbWritten));
return STG_E_ACCESSDENIED;
}
#endif
HRESULT hres = S_OK;
UINT cbRead = this->cbData - this->iSeek;
ULONG cbWritten = 0;
if (cb.HighPart == 0 && cb.LowPart < cbRead)
{
cbRead = cb.LowPart;
}
if (cbRead > 0)
{
hres = pstmTo->Write(this->pBuf + this->iSeek, cbRead, &cbWritten);
this->iSeek += cbRead;
}
if (pcbRead)
{
pcbRead->LowPart = cbRead;
pcbRead->HighPart = 0;
}
if (pcbWritten)
{
pcbWritten->LowPart = cbWritten;
pcbWritten->HighPart = 0;
}
return hres;
}
STDMETHODIMP CMemStream::Commit(DWORD grfCommitFlags)
{
return E_NOTIMPL;
}
STDMETHODIMP CMemStream::Revert()
{
return E_NOTIMPL;
}
STDMETHODIMP CMemStream::LockRegion(ULARGE_INTEGER libOffset,
ULARGE_INTEGER cb, DWORD dwLockType)
{
return E_NOTIMPL;
}
STDMETHODIMP CMemStream::UnlockRegion(ULARGE_INTEGER libOffset,
ULARGE_INTEGER cb, DWORD dwLockType)
{
return E_NOTIMPL;
}
// Trident calls this to determine the size of the structure.
// No reason to not support this one.
STDMETHODIMP CMemStream::Stat(STATSTG *pstatstg, DWORD grfStatFlag)
{
ZeroMemory(pstatstg, sizeof(*pstatstg));
// we have no name
pstatstg->type = STGTY_STREAM;
pstatstg->cbSize.LowPart = this->cbData;
// blow off modify, create, access times (we don't track anyway)
pstatstg->grfMode = this->grfMode;
// we're not transacting, so we have no lock modes
// we're the null clsid already
// we're not based on storage, so we have no state or storage bits
return S_OK;
}
STDMETHODIMP CMemStream::Clone(IStream **ppstm)
{
*ppstm = NULL;
return E_NOTIMPL;
}
CMemStream *
CreateMemStreamEx(
LPBYTE pInit,
UINT cbInit,
LPCTSTR pszValue) OPTIONAL
{
UINT l_cbAlloc = sizeof(CMemStream) + (pszValue ? lstrlen(pszValue) * sizeof(TCHAR) : 0);
CMemStream *localthis = (CMemStream *)LocalAlloc(LPTR, l_cbAlloc);
if (localthis)
{
new (localthis) CMemStream;
localthis->cRef = 1;
// See if there is some initial data we should map in here.
if ((pInit != NULL) && (cbInit > 0))
{
if (localthis->GrowBuffer(cbInit) == NULL)
{
// Could not allocate buffer!
LocalFree((HLOCAL)localthis);
return NULL;
}
localthis->cbData = cbInit;
CopyMemory(localthis->pBuf, pInit, cbInit);
}
if (pszValue)
lstrcpy(localthis->szValue, pszValue);
// We have no other value to set this to
localthis->grfMode = STGM_READWRITE;
return localthis;
}
return NULL;
}
STDAPI_(IStream *)
SHCreateMemStream(
LPBYTE pInit,
UINT cbInit)
{
CMemStream *localthis = CreateMemStreamEx(pInit, cbInit, NULL);
if (localthis)
return localthis;
return NULL;
}
//----------------------------------------------------------------------------
// Open a stream to the reg file given an open key.
// NB pszValue can be NULL.
//
// Win9x exported OpenRegStream which *always* returned a stream, even for read,
// even when there was no data there. IE4 shell32 delegated to shlwapi's SHOpenRegStream
// which needs to support this sub-optimal behavior. See NT5 bug 190878 (shell32 fault).
//
STDAPI_(IStream *)
SHOpenRegStreamW(
HKEY hkey,
LPCWSTR pszSubkey,
LPCWSTR pszValue, OPTIONAL
DWORD grfMode)
{
IStream * pstm = SHOpenRegStream2W(hkey, pszSubkey, pszValue, grfMode);
#ifndef UNIX
if (!pstm)
pstm = SHConstNullStream();
#endif
return pstm;
}
STDAPI_(IStream *)
SHOpenRegStreamA(
HKEY hkey,
LPCSTR pszSubkey,
LPCSTR pszValue, OPTIONAL
DWORD grfMode)
{
IStream * pstm = SHOpenRegStream2A(hkey, pszSubkey, pszValue, grfMode);
#ifndef UNIX
if (!pstm)
pstm = SHConstNullStream();
#endif
return pstm;
}
// We should add STGM_CREATE support to the shlwapi streams. When saving out
// streams, we currently create the stream with STGM_WRITE (but not STGM_CREATE)
// so shlwapi goes to all the wasted trouble of reading the old stream data into
// memory, only to throw it away when we write over it.
//
// STGM_CREATE means "I don't care about the old values because I'm going to
// overwrite them anyway." (It really should be named STGM_TRUNCATEONOPEN.)
//
STDAPI_(IStream *)
SHOpenRegStream2(
HKEY hkey,
LPCTSTR pszSubkey,
LPCTSTR pszValue, OPTIONAL
DWORD grfMode)
{
CMemStream *localthis; // In bed with class...
RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2: Caller passed invalid hkey");
RIPMSG(!pszSubkey || IS_VALID_STRING_PTR(pszSubkey, -1), "SHOpenRegStream2: Caller passed invalid pszSubkey");
RIPMSG(!pszValue || IS_VALID_STRING_PTR(pszValue, -1), "SHOpenRegStream2: Caller passed invalid pszValue");
// Null keys are illegal.
if (!hkey)
{
return NULL;
}
localthis = CreateMemStreamEx(NULL, 0, pszValue);
if (!localthis)
return NULL; // Failed to allocate space
localthis->grfMode = grfMode;
// Get the hkey we're going to deal with
//
// Did the caller pass us a subkey, and does it contain a string?
if (pszSubkey && *pszSubkey)
{
// Yes; The try to bind to that key.
// If this stream is one the user mentioned as wanting to write to
// we need to save away the regkey and value.
if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_READ)
{
// Store away the key.
if (RegCreateKey(hkey, pszSubkey, &localthis->hkey) != ERROR_SUCCESS)
{
TraceMsg(TF_ERROR, "SHOpenRegStream: Unable to create key.");
localthis->hkey = NULL; // be paranoid
}
}
else if (RegOpenKey(hkey, pszSubkey, &localthis->hkey) != ERROR_SUCCESS)
{
localthis->hkey = NULL; // be paranoid
}
}
else
{
localthis->hkey = SHRegDuplicateHKey(hkey);
}
// we don't have an hkey, bail
if (NULL == localthis->hkey)
{
localthis->Release();
return NULL;
}
// Now see if we need to initialize the stream.
if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) != STGM_WRITE)
{
DWORD dwType;
DWORD cbData;
if ((RegQueryValueEx(localthis->hkey, pszValue, NULL, &dwType, NULL, &cbData) == ERROR_SUCCESS) && cbData)
{
if (localthis->GrowBuffer(cbData) != NULL)
{
ASSERT(localthis->cbAlloc >= cbData);
// Get the data.
RegQueryValueEx(localthis->hkey, pszValue, NULL, &dwType, localthis->pBuf, &cbData);
ASSERT(localthis->cbAlloc >= cbData);
localthis->cbData = cbData;
}
else
{
TraceMsg(TF_ERROR, "OpenRegStream: Unable to initialize stream to registry.");
localthis->Release();
return NULL;
}
}
}
if (IsOS(OS_WHISTLERORGREATER))
{
// If the stream was opened read-only, then close the key so
// CMemStream::Release won't try to write the "updates" back out to the
// registry.
if ((grfMode & (STGM_READ | STGM_WRITE | STGM_READWRITE)) == STGM_READ)
{
RegCloseKey(localthis->hkey);
localthis->hkey = NULL;
}
}
return localthis;
}
#ifdef UNICODE
STDAPI_(IStream *)
SHOpenRegStream2A(
HKEY hkey,
LPCSTR pszSubkey,
LPCSTR pszValue, OPTIONAL
DWORD grfMode)
{
IStream * pstm = NULL;
RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2A: Caller passed invalid hkey");
RIPMSG(!pszSubkey || IS_VALID_STRING_PTRA(pszSubkey, -1), "SHOpenRegStream2A: Caller passed invalid pszSubkey");
RIPMSG(!pszValue || IS_VALID_STRING_PTRA(pszValue, -1), "SHOpenRegStream2A: Caller passed invalid pszValue");
WCHAR wszSubkey[MAX_PATH];
if (pszSubkey)
{
if (!MultiByteToWideChar(CP_ACP, 0, pszSubkey, -1, wszSubkey, SIZECHARS(wszSubkey)))
return NULL;
pszSubkey = (LPCSTR)wszSubkey;
}
WCHAR wszValue[MAX_PATH];
if (pszValue)
{
if (!MultiByteToWideChar(CP_ACP, 0, pszValue, -1, wszValue, SIZECHARS(wszValue)))
return NULL;
pszValue = (LPCSTR)wszValue;
}
pstm = SHOpenRegStream2W(hkey, (LPCWSTR)pszSubkey, (LPCWSTR)pszValue, grfMode);
return pstm;
}
#else
STDAPI_(IStream *)
SHOpenRegStream2W(
HKEY hkey,
LPCWSTR pszSubkey,
LPCWSTR pszValue, OPTIONAL
DWORD grfMode)
{
IStream * pstm = NULL;
RIPMSG(IS_VALID_HANDLE(hkey, KEY), "SHOpenRegStream2W: Caller passed invalid hkey");
RIPMSG(!pszSubkey || IS_VALID_STRING_PTRW(pszSubkey, -1), "SHOpenRegStream2W: Caller passed invalid pszSubkey");
RIPMSG(!pszValue || IS_VALID_STRING_PTRW(pszValue, -1), "SHOpenRegStream2W: Caller passed invalid pszValue");
CHAR szSubkey[MAX_PATH];
if (pszSubkey)
{
if (!WideCharToMultiByte(CP_ACP, 0, pszSubkey, -1, szSubkey, SIZECHARS(szSubkey), NULL, NULL))
return NULL;
pszSubkey = (LPCWSTR)szSubkey;
}
CHAR szValue[MAX_PATH];
if (pszValue)
{
if (!WideCharToMultiByte(CP_ACP, 0, pszValue, -1, szValue, SIZECHARS(szValue), NULL, NULL))
return NULL;
pszValue = (LPCWSTR)szValue;
}
pstm = SHOpenRegStream2A(hkey, (LPCSTR)pszSubkey, (LPCSTR)pszValue, grfMode);
return pstm;
}
#endif // UNICODE