/* * Compound Storage (32 bit version) * Storage implementation * * This file contains the compound file implementation * of the storage interface. * * Copyright 1999 Francis Beaudet * Copyright 1999 Sylvain St-Germain * Copyright 1999 Thuy Nguyen * Copyright 2005 Mike McCormack * Copyright 2005 Juan Lang * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA * * There's a decent overview of property set storage here: * http://msdn.microsoft.com/archive/en-us/dnarolegen/html/msdn_propset.asp * It's a little bit out of date, and more definitive references are given * below, but it gives the best "big picture" that I've found. * * TODO: * - I don't honor the maximum property set size. * - Certain bogus files could result in reading past the end of a buffer. * - Mac-generated files won't be read correctly, even if they're little * endian, because I disregard whether the generator was a Mac. This means * strings will probably be munged (as I don't understand Mac scripts.) * - Not all PROPVARIANT types are supported. * - User defined properties are not supported, see comment in * PropertyStorage_ReadFromStream * - IPropertyStorage::Enum is unimplemented */ #include #include #include #include #include #define COBJMACROS #define NONAMELESSUNION #define NONAMELESSSTRUCT #include "windef.h" #include "winbase.h" #include "winnls.h" #include "winuser.h" #include "wine/unicode.h" #include "wine/debug.h" #include "dictionary.h" #include "storage32.h" WINE_DEFAULT_DEBUG_CHANNEL(storage); #define _IPropertySetStorage_Offset ((int)(&(((StorageImpl*)0)->base.pssVtbl))) #define _ICOM_THIS_From_IPropertySetStorage(class, name) \ class* This = (class*)(((char*)name)-_IPropertySetStorage_Offset) /* These are documented in MSDN, e.g. * http://msdn.microsoft.com/library/en-us/stg/stg/property_set_header.asp * http://msdn.microsoft.com/library/library/en-us/stg/stg/section.asp * but they don't seem to be in any header file. */ #define PROPSETHDR_BYTEORDER_MAGIC 0xfffe #define PROPSETHDR_OSVER_KIND_WIN16 0 #define PROPSETHDR_OSVER_KIND_MAC 1 #define PROPSETHDR_OSVER_KIND_WIN32 2 #define CP_UNICODE 1200 #define MAX_VERSION_0_PROP_NAME_LENGTH 256 /* The format version (and what it implies) is described here: * http://msdn.microsoft.com/library/en-us/stg/stg/format_version.asp */ typedef struct tagPROPERTYSETHEADER { WORD wByteOrder; /* always 0xfffe */ WORD wFormat; /* can be zero or one */ DWORD dwOSVer; /* OS version of originating system */ CLSID clsid; /* application CLSID */ DWORD reserved; /* always 1 */ } PROPERTYSETHEADER; typedef struct tagFORMATIDOFFSET { FMTID fmtid; DWORD dwOffset; /* from beginning of stream */ } FORMATIDOFFSET; typedef struct tagPROPERTYSECTIONHEADER { DWORD cbSection; DWORD cProperties; } PROPERTYSECTIONHEADER; typedef struct tagPROPERTYIDOFFSET { DWORD propid; DWORD dwOffset; /* from beginning of section */ } PROPERTYIDOFFSET; struct tagPropertyStorage_impl; /* Initializes the property storage from the stream (and undoes any uncommitted * changes in the process.) Returns an error if there is an error reading or * if the stream format doesn't match what's expected. */ static HRESULT PropertyStorage_ReadFromStream(struct tagPropertyStorage_impl *); static HRESULT PropertyStorage_WriteToStream(struct tagPropertyStorage_impl *); /* Creates the dictionaries used by the property storage. If successful, all * the dictionaries have been created. If failed, none has been. (This makes * it a bit easier to deal with destroying them.) */ static HRESULT PropertyStorage_CreateDictionaries( struct tagPropertyStorage_impl *); static void PropertyStorage_DestroyDictionaries( struct tagPropertyStorage_impl *); /* Copies from propvar to prop. If propvar's type is VT_LPSTR, copies the * string using PropertyStorage_StringCopy. */ static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, const PROPVARIANT *propvar, LCID targetCP, LCID srcCP); /* Copies the string src, which is encoded using code page srcCP, and returns * it in *dst, in the code page specified by targetCP. The returned string is * allocated using CoTaskMemAlloc. * If srcCP is CP_UNICODE, src is in fact an LPCWSTR. Similarly, if targetCP * is CP_UNICODE, the returned string is in fact an LPWSTR. * Returns S_OK on success, something else on failure. */ static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, LCID targetCP); static const IPropertyStorageVtbl IPropertyStorage_Vtbl; /*********************************************************************** * Implementation of IPropertyStorage */ typedef struct tagPropertyStorage_impl { const IPropertyStorageVtbl *vtbl; DWORD ref; CRITICAL_SECTION cs; IStream *stm; BOOL dirty; FMTID fmtid; CLSID clsid; WORD format; DWORD originatorOS; DWORD grfFlags; DWORD grfMode; UINT codePage; LCID locale; PROPID highestProp; struct dictionary *name_to_propid; struct dictionary *propid_to_name; struct dictionary *propid_to_prop; } PropertyStorage_impl; /************************************************************************ * IPropertyStorage_fnQueryInterface (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnQueryInterface( IPropertyStorage *iface, REFIID riid, void** ppvObject) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; if ( (This==0) || (ppvObject==0) ) return E_INVALIDARG; *ppvObject = 0; if (IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IPropertyStorage, riid)) { IPropertyStorage_AddRef(iface); *ppvObject = (IPropertyStorage*)iface; return S_OK; } return E_NOINTERFACE; } /************************************************************************ * IPropertyStorage_fnAddRef (IPropertyStorage) */ static ULONG WINAPI IPropertyStorage_fnAddRef( IPropertyStorage *iface) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; return InterlockedIncrement(&This->ref); } /************************************************************************ * IPropertyStorage_fnRelease (IPropertyStorage) */ static ULONG WINAPI IPropertyStorage_fnRelease( IPropertyStorage *iface) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG ref; ref = InterlockedDecrement(&This->ref); if (ref == 0) { TRACE("Destroying %p\n", This); if (This->dirty) IPropertyStorage_Commit(iface, STGC_DEFAULT); IStream_Release(This->stm); DeleteCriticalSection(&This->cs); PropertyStorage_DestroyDictionaries(This); HeapFree(GetProcessHeap(), 0, This); } return ref; } static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, DWORD propid) { PROPVARIANT *ret = NULL; assert(This); dictionary_find(This->propid_to_prop, (void *)propid, (void **)&ret); TRACE("returning %p\n", ret); return ret; } /* Returns NULL if name is NULL. */ static PROPVARIANT *PropertyStorage_FindPropertyByName( PropertyStorage_impl *This, LPCWSTR name) { PROPVARIANT *ret = NULL; PROPID propid; assert(This); if (!name) return NULL; if (This->codePage == CP_UNICODE) { if (dictionary_find(This->name_to_propid, name, (void **)&propid)) ret = PropertyStorage_FindProperty(This, (PROPID)propid); } else { LPSTR ansiName; HRESULT hr = PropertyStorage_StringCopy((LPCSTR)name, CP_UNICODE, &ansiName, This->codePage); if (SUCCEEDED(hr)) { if (dictionary_find(This->name_to_propid, ansiName, (void **)&propid)) ret = PropertyStorage_FindProperty(This, (PROPID)propid); CoTaskMemFree(ansiName); } } TRACE("returning %p\n", ret); return ret; } static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, DWORD propid) { LPWSTR ret = NULL; assert(This); dictionary_find(This->propid_to_name, (void *)propid, (void **)&ret); TRACE("returning %p\n", ret); return ret; } /************************************************************************ * IPropertyStorage_fnReadMultiple (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnReadMultiple( IPropertyStorage* iface, ULONG cpspec, const PROPSPEC rgpspec[], PROPVARIANT rgpropvar[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; HRESULT hr = S_FALSE; ULONG i; TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); if (!This) return E_INVALIDARG; if (cpspec && (!rgpspec || !rgpropvar)) return E_INVALIDARG; EnterCriticalSection(&This->cs); for (i = 0; i < cpspec; i++) { PropVariantInit(&rgpropvar[i]); if (rgpspec[i].ulKind == PRSPEC_LPWSTR) { PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, rgpspec[i].u.lpwstr); if (prop) PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, GetACP(), This->codePage); } else { switch (rgpspec[i].u.propid) { case PID_CODEPAGE: rgpropvar[i].vt = VT_I2; rgpropvar[i].u.iVal = This->codePage; break; case PID_LOCALE: rgpropvar[i].vt = VT_I4; rgpropvar[i].u.lVal = This->locale; break; default: { PROPVARIANT *prop = PropertyStorage_FindProperty(This, rgpspec[i].u.propid); if (prop) PropertyStorage_PropVariantCopy(&rgpropvar[i], prop, GetACP(), This->codePage); } } } } LeaveCriticalSection(&This->cs); return hr; } static HRESULT PropertyStorage_StringCopy(LPCSTR src, LCID srcCP, LPSTR *dst, LCID dstCP) { HRESULT hr = S_OK; int len; TRACE("%s, %p, %ld, %ld\n", srcCP == CP_UNICODE ? debugstr_w((LPCWSTR)src) : debugstr_a(src), dst, dstCP, srcCP); assert(src); assert(dst); *dst = NULL; if (dstCP == srcCP) { size_t len; if (dstCP == CP_UNICODE) len = (strlenW((LPCWSTR)src) + 1) * sizeof(WCHAR); else len = strlen(src) + 1; *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); if (!*dst) hr = STG_E_INSUFFICIENTMEMORY; else memcpy(*dst, src, len); } else { if (dstCP == CP_UNICODE) { len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); *dst = CoTaskMemAlloc(len * sizeof(WCHAR)); if (!*dst) hr = STG_E_INSUFFICIENTMEMORY; else MultiByteToWideChar(srcCP, 0, src, -1, (LPWSTR)*dst, len); } else { LPWSTR wideStr; if (srcCP == CP_UNICODE) wideStr = (LPWSTR)src; else { len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); wideStr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); if (wideStr) MultiByteToWideChar(srcCP, 0, src, -1, wideStr, len); else hr = STG_E_INSUFFICIENTMEMORY; } if (SUCCEEDED(hr)) { len = WideCharToMultiByte(dstCP, 0, wideStr, -1, NULL, 0, NULL, NULL); *dst = CoTaskMemAlloc(len); if (!*dst) hr = STG_E_INSUFFICIENTMEMORY; else { BOOL defCharUsed = FALSE; if (WideCharToMultiByte(dstCP, 0, wideStr, -1, *dst, len, NULL, &defCharUsed) == 0 || defCharUsed) { CoTaskMemFree(*dst); *dst = NULL; hr = HRESULT_FROM_WIN32(ERROR_NO_UNICODE_TRANSLATION); } } } if (wideStr != (LPWSTR)src) HeapFree(GetProcessHeap(), 0, wideStr); } } TRACE("returning 0x%08lx (%s)\n", hr, dstCP == CP_UNICODE ? debugstr_w((LPCWSTR)*dst) : debugstr_a(*dst)); return hr; } static HRESULT PropertyStorage_PropVariantCopy(PROPVARIANT *prop, const PROPVARIANT *propvar, LCID targetCP, LCID srcCP) { HRESULT hr = S_OK; assert(prop); assert(propvar); if (propvar->vt == VT_LPSTR) { hr = PropertyStorage_StringCopy(propvar->u.pszVal, srcCP, &prop->u.pszVal, targetCP); if (SUCCEEDED(hr)) prop->vt = VT_LPSTR; } else PropVariantCopy(prop, propvar); return hr; } /* Stores the property with id propid and value propvar into this property * storage. lcid is ignored if propvar's type is not VT_LPSTR. If propvar's * type is VT_LPSTR, converts the string using lcid as the source code page * and This->codePage as the target code page before storing. * As a side effect, may change This->format to 1 if the type of propvar is * a version 1-only property. */ static HRESULT PropertyStorage_StorePropWithId(PropertyStorage_impl *This, PROPID propid, const PROPVARIANT *propvar, LCID lcid) { HRESULT hr = S_OK; PROPVARIANT *prop = PropertyStorage_FindProperty(This, propid); assert(This); assert(propvar); if (propvar->vt & VT_BYREF || propvar->vt & VT_ARRAY) This->format = 1; switch (propvar->vt) { case VT_DECIMAL: case VT_I1: case VT_INT: case VT_UINT: case VT_VECTOR|VT_I1: This->format = 1; } TRACE("Setting 0x%08lx to type %d\n", propid, propvar->vt); if (prop) { PropVariantClear(prop); hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, lcid); } else { prop = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(PROPVARIANT)); if (prop) { hr = PropertyStorage_PropVariantCopy(prop, propvar, This->codePage, lcid); if (SUCCEEDED(hr)) { dictionary_insert(This->propid_to_prop, (void *)propid, prop); if (propid > This->highestProp) This->highestProp = propid; } else HeapFree(GetProcessHeap(), 0, prop); } else hr = STG_E_INSUFFICIENTMEMORY; } return hr; } /* Adds the name srcName to the name dictionaries, mapped to property ID id. * srcName is encoded in code page cp, and is converted to This->codePage. * If cp is CP_UNICODE, srcName is actually a unicode string. * As a side effect, may change This->format to 1 if srcName is too long for * a version 0 property storage. * Doesn't validate id. */ static HRESULT PropertyStorage_StoreNameWithId(PropertyStorage_impl *This, LPCSTR srcName, LCID cp, PROPID id) { LPSTR name; HRESULT hr; assert(srcName); hr = PropertyStorage_StringCopy((LPCSTR)srcName, cp, &name, This->codePage); if (SUCCEEDED(hr)) { if (This->codePage == CP_UNICODE) { if (lstrlenW((LPWSTR)name) >= MAX_VERSION_0_PROP_NAME_LENGTH) This->format = 1; } else { if (strlen(name) >= MAX_VERSION_0_PROP_NAME_LENGTH) This->format = 1; } TRACE("Adding prop name %s, propid %ld\n", This->codePage == CP_UNICODE ? debugstr_w((LPCWSTR)name) : debugstr_a(name), id); dictionary_insert(This->name_to_propid, name, (void *)id); dictionary_insert(This->propid_to_name, (void *)id, name); } return hr; } /************************************************************************ * IPropertyStorage_fnWriteMultiple (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnWriteMultiple( IPropertyStorage* iface, ULONG cpspec, const PROPSPEC rgpspec[], const PROPVARIANT rgpropvar[], PROPID propidNameFirst) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; HRESULT hr = S_OK; ULONG i; TRACE("(%p, %ld, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); if (!This) return E_INVALIDARG; if (cpspec && (!rgpspec || !rgpropvar)) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; EnterCriticalSection(&This->cs); This->dirty = TRUE; This->originatorOS = (DWORD)MAKELONG(LOWORD(GetVersion()), PROPSETHDR_OSVER_KIND_WIN32) ; for (i = 0; i < cpspec; i++) { if (rgpspec[i].ulKind == PRSPEC_LPWSTR) { PROPVARIANT *prop = PropertyStorage_FindPropertyByName(This, rgpspec[i].u.lpwstr); if (prop) PropVariantCopy(prop, &rgpropvar[i]); else { /* Note that I don't do the special cases here that I do below, * because naming the special PIDs isn't supported. */ if (propidNameFirst < PID_FIRST_USABLE || propidNameFirst >= PID_MIN_READONLY) hr = STG_E_INVALIDPARAMETER; else { PROPID nextId = max(propidNameFirst, This->highestProp + 1); hr = PropertyStorage_StoreNameWithId(This, (LPCSTR)rgpspec[i].u.lpwstr, CP_UNICODE, nextId); if (SUCCEEDED(hr)) hr = PropertyStorage_StorePropWithId(This, nextId, &rgpropvar[i], GetACP()); } } } else { switch (rgpspec[i].u.propid) { case PID_DICTIONARY: /* Can't set the dictionary */ hr = STG_E_INVALIDPARAMETER; break; case PID_CODEPAGE: /* Can only set the code page if nothing else has been set */ if (dictionary_num_entries(This->propid_to_prop) == 0 && rgpropvar[i].vt == VT_I2) { This->codePage = rgpropvar[i].u.iVal; if (This->codePage == CP_UNICODE) This->grfFlags &= ~PROPSETFLAG_ANSI; else This->grfFlags |= PROPSETFLAG_ANSI; } else hr = STG_E_INVALIDPARAMETER; break; case PID_LOCALE: /* Can only set the locale if nothing else has been set */ if (dictionary_num_entries(This->propid_to_prop) == 0 && rgpropvar[i].vt == VT_I4) This->locale = rgpropvar[i].u.lVal; else hr = STG_E_INVALIDPARAMETER; break; case PID_ILLEGAL: /* silently ignore like MSDN says */ break; default: if (rgpspec[i].u.propid >= PID_MIN_READONLY) hr = STG_E_INVALIDPARAMETER; else hr = PropertyStorage_StorePropWithId(This, rgpspec[i].u.propid, &rgpropvar[i], GetACP()); } } } if (This->grfFlags & PROPSETFLAG_UNBUFFERED) IPropertyStorage_Commit(iface, STGC_DEFAULT); LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnDeleteMultiple (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnDeleteMultiple( IPropertyStorage* iface, ULONG cpspec, const PROPSPEC rgpspec[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG i; HRESULT hr; TRACE("(%p, %ld, %p)\n", iface, cpspec, rgpspec); if (!This) return E_INVALIDARG; if (cpspec && !rgpspec) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; hr = S_OK; EnterCriticalSection(&This->cs); This->dirty = TRUE; for (i = 0; i < cpspec; i++) { if (rgpspec[i].ulKind == PRSPEC_LPWSTR) { PROPID propid; if (dictionary_find(This->name_to_propid, (void *)rgpspec[i].u.lpwstr, (void **)&propid)) dictionary_remove(This->propid_to_prop, (void *)propid); } else { if (rgpspec[i].u.propid >= PID_FIRST_USABLE && rgpspec[i].u.propid < PID_MIN_READONLY) dictionary_remove(This->propid_to_prop, (void *)rgpspec[i].u.propid); else hr = STG_E_INVALIDPARAMETER; } } if (This->grfFlags & PROPSETFLAG_UNBUFFERED) IPropertyStorage_Commit(iface, STGC_DEFAULT); LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnReadPropertyNames (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnReadPropertyNames( IPropertyStorage* iface, ULONG cpropid, const PROPID rgpropid[], LPOLESTR rglpwstrName[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG i; HRESULT hr = S_FALSE; TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); if (!This) return E_INVALIDARG; if (cpropid && (!rgpropid || !rglpwstrName)) return E_INVALIDARG; EnterCriticalSection(&This->cs); for (i = 0; i < cpropid && SUCCEEDED(hr); i++) { LPWSTR name = PropertyStorage_FindPropertyNameById(This, rgpropid[i]); if (name) { size_t len = lstrlenW(name); hr = S_OK; rglpwstrName[i] = CoTaskMemAlloc((len + 1) * sizeof(WCHAR)); if (rglpwstrName) memcpy(rglpwstrName, name, (len + 1) * sizeof(WCHAR)); else hr = STG_E_INSUFFICIENTMEMORY; } else rglpwstrName[i] = NULL; } LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnWritePropertyNames (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnWritePropertyNames( IPropertyStorage* iface, ULONG cpropid, const PROPID rgpropid[], const LPOLESTR rglpwstrName[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG i; HRESULT hr; TRACE("(%p, %ld, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); if (!This) return E_INVALIDARG; if (cpropid && (!rgpropid || !rglpwstrName)) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; hr = S_OK; EnterCriticalSection(&This->cs); This->dirty = TRUE; for (i = 0; SUCCEEDED(hr) && i < cpropid; i++) { if (rgpropid[i] != PID_ILLEGAL) hr = PropertyStorage_StoreNameWithId(This, (LPCSTR)rglpwstrName[i], CP_UNICODE, rgpropid[i]); } if (This->grfFlags & PROPSETFLAG_UNBUFFERED) IPropertyStorage_Commit(iface, STGC_DEFAULT); LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnDeletePropertyNames (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnDeletePropertyNames( IPropertyStorage* iface, ULONG cpropid, const PROPID rgpropid[]) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; ULONG i; HRESULT hr; TRACE("(%p, %ld, %p)\n", iface, cpropid, rgpropid); if (!This) return E_INVALIDARG; if (cpropid && !rgpropid) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; hr = S_OK; EnterCriticalSection(&This->cs); This->dirty = TRUE; for (i = 0; i < cpropid; i++) { LPWSTR name = NULL; if (dictionary_find(This->propid_to_name, (void *)rgpropid[i], (void **)&name)) { dictionary_remove(This->propid_to_name, (void *)rgpropid[i]); dictionary_remove(This->name_to_propid, name); } } if (This->grfFlags & PROPSETFLAG_UNBUFFERED) IPropertyStorage_Commit(iface, STGC_DEFAULT); LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnCommit (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnCommit( IPropertyStorage* iface, DWORD grfCommitFlags) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; HRESULT hr; TRACE("(%p, 0x%08lx)\n", iface, grfCommitFlags); if (!This) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; EnterCriticalSection(&This->cs); if (This->dirty) hr = PropertyStorage_WriteToStream(This); else hr = S_OK; LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnRevert (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnRevert( IPropertyStorage* iface) { HRESULT hr; PropertyStorage_impl *This = (PropertyStorage_impl *)iface; TRACE("%p\n", iface); if (!This) return E_INVALIDARG; EnterCriticalSection(&This->cs); if (This->dirty) { PropertyStorage_DestroyDictionaries(This); hr = PropertyStorage_CreateDictionaries(This); if (SUCCEEDED(hr)) hr = PropertyStorage_ReadFromStream(This); } else hr = S_OK; LeaveCriticalSection(&This->cs); return hr; } /************************************************************************ * IPropertyStorage_fnEnum (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnEnum( IPropertyStorage* iface, IEnumSTATPROPSTG** ppenum) { FIXME("\n"); return E_NOTIMPL; } /************************************************************************ * IPropertyStorage_fnSetTimes (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnSetTimes( IPropertyStorage* iface, const FILETIME* pctime, const FILETIME* patime, const FILETIME* pmtime) { FIXME("\n"); return E_NOTIMPL; } /************************************************************************ * IPropertyStorage_fnSetClass (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnSetClass( IPropertyStorage* iface, REFCLSID clsid) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; TRACE("%p, %s\n", iface, debugstr_guid(clsid)); if (!This || !clsid) return E_INVALIDARG; if (!(This->grfMode & STGM_READWRITE)) return STG_E_ACCESSDENIED; memcpy(&This->clsid, clsid, sizeof(This->clsid)); This->dirty = TRUE; if (This->grfFlags & PROPSETFLAG_UNBUFFERED) IPropertyStorage_Commit(iface, STGC_DEFAULT); return S_OK; } /************************************************************************ * IPropertyStorage_fnStat (IPropertyStorage) */ static HRESULT WINAPI IPropertyStorage_fnStat( IPropertyStorage* iface, STATPROPSETSTG* statpsstg) { PropertyStorage_impl *This = (PropertyStorage_impl *)iface; STATSTG stat; HRESULT hr; TRACE("%p, %p\n", iface, statpsstg); if (!This || !statpsstg) return E_INVALIDARG; hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); if (SUCCEEDED(hr)) { memcpy(&statpsstg->fmtid, &This->fmtid, sizeof(statpsstg->fmtid)); memcpy(&statpsstg->clsid, &This->clsid, sizeof(statpsstg->clsid)); statpsstg->grfFlags = This->grfFlags; memcpy(&statpsstg->mtime, &stat.mtime, sizeof(statpsstg->mtime)); memcpy(&statpsstg->ctime, &stat.ctime, sizeof(statpsstg->ctime)); memcpy(&statpsstg->atime, &stat.atime, sizeof(statpsstg->atime)); statpsstg->dwOSVersion = This->originatorOS; } return hr; } static int PropertyStorage_PropNameCompare(const void *a, const void *b, void *extra) { PropertyStorage_impl *This = (PropertyStorage_impl *)extra; if (This->codePage == CP_UNICODE) { TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) return lstrcmpW((LPCWSTR)a, (LPCWSTR)b); else return lstrcmpiW((LPCWSTR)a, (LPCWSTR)b); } else { TRACE("(%s, %s)\n", debugstr_a(a), debugstr_a(b)); if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) return lstrcmpA((LPCSTR)a, (LPCSTR)b); else return lstrcmpiA((LPCSTR)a, (LPCSTR)b); } } static void PropertyStorage_PropNameDestroy(void *k, void *d, void *extra) { CoTaskMemFree(k); } static int PropertyStorage_PropCompare(const void *a, const void *b, void *extra) { TRACE("(%ld, %ld)\n", (PROPID)a, (PROPID)b); return (PROPID)a - (PROPID)b; } static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) { PropVariantClear((PROPVARIANT *)d); HeapFree(GetProcessHeap(), 0, d); } #ifdef WORDS_BIGENDIAN /* Swaps each character in str to or from little endian; assumes the conversion * is symmetric, that is, that le16toh is equivalent to htole16. */ static void PropertyStorage_ByteSwapString(LPWSTR str, size_t len) { DWORD i; /* Swap characters to host order. * FIXME: alignment? */ for (i = 0; i < len; i++) str[i] = le16toh(str[i]); } #else #define PropertyStorage_ByteSwapString(s, l) #endif /* Reads the dictionary from the memory buffer beginning at ptr. Interprets * the entries according to the values of This->codePage and This->locale. * FIXME: there isn't any checking whether the read property extends past the * end of the buffer. */ static HRESULT PropertyStorage_ReadDictionary(PropertyStorage_impl *This, BYTE *ptr) { DWORD numEntries, i; HRESULT hr = S_OK; assert(This); assert(This->name_to_propid); assert(This->propid_to_name); StorageUtl_ReadDWord(ptr, 0, &numEntries); TRACE("Reading %ld entries:\n", numEntries); ptr += sizeof(DWORD); for (i = 0; SUCCEEDED(hr) && i < numEntries; i++) { PROPID propid; DWORD cbEntry; StorageUtl_ReadDWord(ptr, 0, &propid); ptr += sizeof(PROPID); StorageUtl_ReadDWord(ptr, 0, &cbEntry); ptr += sizeof(DWORD); TRACE("Reading entry with ID 0x%08lx, %ld bytes\n", propid, cbEntry); /* Make sure the source string is NULL-terminated */ if (This->codePage != CP_UNICODE) ptr[cbEntry - 1] = '\0'; else *((LPWSTR)ptr + cbEntry / sizeof(WCHAR)) = '\0'; hr = PropertyStorage_StoreNameWithId(This, ptr, This->codePage, propid); if (This->codePage == CP_UNICODE) { /* Unicode entries are padded to DWORD boundaries */ if (cbEntry % sizeof(DWORD)) ptr += sizeof(DWORD) - (cbEntry % sizeof(DWORD)); } ptr += sizeof(DWORD) + cbEntry; } return hr; } /* FIXME: there isn't any checking whether the read property extends past the * end of the buffer. */ static HRESULT PropertyStorage_ReadProperty(PropertyStorage_impl *This, PROPVARIANT *prop, const BYTE *data) { HRESULT hr = S_OK; assert(prop); assert(data); StorageUtl_ReadDWord(data, 0, (DWORD *)&prop->vt); data += sizeof(DWORD); switch (prop->vt) { case VT_EMPTY: case VT_NULL: break; case VT_I1: prop->u.cVal = *(const char *)data; TRACE("Read char 0x%x ('%c')\n", prop->u.cVal, prop->u.cVal); break; case VT_UI1: prop->u.bVal = *(const UCHAR *)data; TRACE("Read byte 0x%x\n", prop->u.bVal); break; case VT_I2: StorageUtl_ReadWord(data, 0, &prop->u.iVal); TRACE("Read short %d\n", prop->u.iVal); break; case VT_UI2: StorageUtl_ReadWord(data, 0, &prop->u.uiVal); TRACE("Read ushort %d\n", prop->u.uiVal); break; case VT_INT: case VT_I4: StorageUtl_ReadDWord(data, 0, &prop->u.lVal); TRACE("Read long %ld\n", prop->u.lVal); break; case VT_UINT: case VT_UI4: StorageUtl_ReadDWord(data, 0, &prop->u.ulVal); TRACE("Read ulong %ld\n", prop->u.ulVal); break; case VT_LPSTR: { DWORD count; StorageUtl_ReadDWord(data, 0, &count); if (This->codePage == CP_UNICODE && count / 2) { WARN("Unicode string has odd number of bytes\n"); hr = STG_E_INVALIDHEADER; } else { prop->u.pszVal = CoTaskMemAlloc(count); if (prop->u.pszVal) { memcpy(prop->u.pszVal, data + sizeof(DWORD), count); /* This is stored in the code page specified in This->codePage. * Don't convert it, the caller will just store it as-is. */ if (This->codePage == CP_UNICODE) { /* Make sure it's NULL-terminated */ prop->u.pszVal[count / sizeof(WCHAR) - 1] = '\0'; TRACE("Read string value %s\n", debugstr_w(prop->u.pwszVal)); } else { /* Make sure it's NULL-terminated */ prop->u.pszVal[count - 1] = '\0'; TRACE("Read string value %s\n", debugstr_a(prop->u.pszVal)); } } else hr = STG_E_INSUFFICIENTMEMORY; } break; } case VT_LPWSTR: { DWORD count; StorageUtl_ReadDWord(data, 0, &count); prop->u.pwszVal = CoTaskMemAlloc(count * sizeof(WCHAR)); if (prop->u.pwszVal) { memcpy(prop->u.pwszVal, data + sizeof(DWORD), count * sizeof(WCHAR)); /* make sure string is NULL-terminated */ prop->u.pwszVal[count - 1] = '\0'; PropertyStorage_ByteSwapString(prop->u.pwszVal, count); TRACE("Read string value %s\n", debugstr_w(prop->u.pwszVal)); } else hr = STG_E_INSUFFICIENTMEMORY; break; } case VT_FILETIME: StorageUtl_ReadULargeInteger(data, 0, (ULARGE_INTEGER *)&prop->u.filetime); break; default: FIXME("unsupported type %d\n", prop->vt); hr = STG_E_INVALIDPARAMETER; } return hr; } static HRESULT PropertyStorage_ReadHeaderFromStream(IStream *stm, PROPERTYSETHEADER *hdr) { BYTE buf[sizeof(PROPERTYSETHEADER)]; ULONG count = 0; HRESULT hr; assert(stm); assert(hdr); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { if (count != sizeof(buf)) { WARN("read %ld, expected %d\n", count, sizeof(buf)); hr = STG_E_INVALIDHEADER; } else { StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wByteOrder), &hdr->wByteOrder); StorageUtl_ReadWord(buf, offsetof(PROPERTYSETHEADER, wFormat), &hdr->wFormat); StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, dwOSVer), &hdr->dwOSVer); StorageUtl_ReadGUID(buf, offsetof(PROPERTYSETHEADER, clsid), &hdr->clsid); StorageUtl_ReadDWord(buf, offsetof(PROPERTYSETHEADER, reserved), &hdr->reserved); } } TRACE("returning 0x%08lx\n", hr); return hr; } static HRESULT PropertyStorage_ReadFmtIdOffsetFromStream(IStream *stm, FORMATIDOFFSET *fmt) { BYTE buf[sizeof(FORMATIDOFFSET)]; ULONG count = 0; HRESULT hr; assert(stm); assert(fmt); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { if (count != sizeof(buf)) { WARN("read %ld, expected %d\n", count, sizeof(buf)); hr = STG_E_INVALIDHEADER; } else { StorageUtl_ReadGUID(buf, offsetof(FORMATIDOFFSET, fmtid), &fmt->fmtid); StorageUtl_ReadDWord(buf, offsetof(FORMATIDOFFSET, dwOffset), &fmt->dwOffset); } } TRACE("returning 0x%08lx\n", hr); return hr; } static HRESULT PropertyStorage_ReadSectionHeaderFromStream(IStream *stm, PROPERTYSECTIONHEADER *hdr) { BYTE buf[sizeof(PROPERTYSECTIONHEADER)]; ULONG count = 0; HRESULT hr; assert(stm); assert(hdr); hr = IStream_Read(stm, buf, sizeof(buf), &count); if (SUCCEEDED(hr)) { if (count != sizeof(buf)) { WARN("read %ld, expected %d\n", count, sizeof(buf)); hr = STG_E_INVALIDHEADER; } else { StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, cbSection), &hdr->cbSection); StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, cProperties), &hdr->cProperties); } } TRACE("returning 0x%08lx\n", hr); return hr; } static HRESULT PropertyStorage_ReadFromStream(PropertyStorage_impl *This) { PROPERTYSETHEADER hdr; FORMATIDOFFSET fmtOffset; PROPERTYSECTIONHEADER sectionHdr; LARGE_INTEGER seek; ULONG i; STATSTG stat; HRESULT hr; BYTE *buf = NULL; ULONG count = 0; DWORD dictOffset = 0; assert(This); This->dirty = FALSE; This->highestProp = 0; hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); if (FAILED(hr)) goto end; if (stat.cbSize.u.HighPart) { WARN("stream too big\n"); /* maximum size varies, but it can't be this big */ hr = STG_E_INVALIDHEADER; goto end; } if (stat.cbSize.u.LowPart == 0) { /* empty stream is okay */ hr = S_OK; goto end; } else if (stat.cbSize.u.LowPart < sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET)) { WARN("stream too small\n"); hr = STG_E_INVALIDHEADER; goto end; } seek.QuadPart = 0; hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; hr = PropertyStorage_ReadHeaderFromStream(This->stm, &hdr); /* I've only seen reserved == 1, but the article says I shouldn't disallow * higher values. */ if (hdr.wByteOrder != PROPSETHDR_BYTEORDER_MAGIC || hdr.reserved < 1) { WARN("bad magic in prop set header\n"); hr = STG_E_INVALIDHEADER; goto end; } if (hdr.wFormat != 0 && hdr.wFormat != 1) { WARN("bad format version %d\n", hdr.wFormat); hr = STG_E_INVALIDHEADER; goto end; } This->format = hdr.wFormat; memcpy(&This->clsid, &hdr.clsid, sizeof(This->clsid)); This->originatorOS = hdr.dwOSVer; if (PROPSETHDR_OSVER_KIND(hdr.dwOSVer) == PROPSETHDR_OSVER_KIND_MAC) WARN("File comes from a Mac, strings will probably be screwed up\n"); hr = PropertyStorage_ReadFmtIdOffsetFromStream(This->stm, &fmtOffset); if (FAILED(hr)) goto end; if (fmtOffset.dwOffset > stat.cbSize.u.LowPart) { WARN("invalid offset %ld (stream length is %ld)\n", fmtOffset.dwOffset, stat.cbSize.u.LowPart); hr = STG_E_INVALIDHEADER; goto end; } /* wackiness alert: if the format ID is FMTID_DocSummaryInformation, there * follow not one, but two sections. The first is the standard properties * for the document summary information, and the second is user-defined * properties. This is the only case in which multiple sections are * allowed. * Reading the second stream isn't implemented yet. */ hr = PropertyStorage_ReadSectionHeaderFromStream(This->stm, §ionHdr); if (FAILED(hr)) goto end; /* The section size includes the section header, so check it */ if (sectionHdr.cbSection < sizeof(PROPERTYSECTIONHEADER)) { WARN("section header too small, got %ld, expected at least %d\n", sectionHdr.cbSection, sizeof(PROPERTYSECTIONHEADER)); hr = STG_E_INVALIDHEADER; goto end; } buf = HeapAlloc(GetProcessHeap(), 0, sectionHdr.cbSection - sizeof(PROPERTYSECTIONHEADER)); if (!buf) { hr = STG_E_INSUFFICIENTMEMORY; goto end; } hr = IStream_Read(This->stm, buf, sectionHdr.cbSection - sizeof(PROPERTYSECTIONHEADER), &count); if (FAILED(hr)) goto end; TRACE("Reading %ld properties:\n", sectionHdr.cProperties); for (i = 0; SUCCEEDED(hr) && i < sectionHdr.cProperties; i++) { PROPERTYIDOFFSET *idOffset = (PROPERTYIDOFFSET *)(buf + i * sizeof(PROPERTYIDOFFSET)); if (idOffset->dwOffset < sizeof(PROPERTYSECTIONHEADER) || idOffset->dwOffset >= sectionHdr.cbSection - sizeof(DWORD)) hr = STG_E_INVALIDPOINTER; else { if (idOffset->propid >= PID_FIRST_USABLE && idOffset->propid < PID_MIN_READONLY && idOffset->propid > This->highestProp) This->highestProp = idOffset->propid; if (idOffset->propid == PID_DICTIONARY) { /* Don't read the dictionary yet, its entries depend on the * code page. Just store the offset so we know to read it * later. */ dictOffset = idOffset->dwOffset; TRACE("Dictionary offset is %ld\n", dictOffset); } else { PROPVARIANT prop; if (SUCCEEDED(PropertyStorage_ReadProperty(This, &prop, buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER)))) { TRACE("Read property with ID 0x%08lx, type %d\n", idOffset->propid, prop.vt); switch(idOffset->propid) { case PID_CODEPAGE: if (prop.vt == VT_I2) This->codePage = (UINT)prop.u.iVal; break; case PID_LOCALE: if (prop.vt == VT_I4) This->locale = (LCID)prop.u.lVal; break; case PID_BEHAVIOR: if (prop.vt == VT_I4 && prop.u.lVal) This->grfFlags |= PROPSETFLAG_CASE_SENSITIVE; /* The format should already be 1, but just in case */ This->format = 1; break; default: hr = PropertyStorage_StorePropWithId(This, idOffset->propid, &prop, This->codePage); } } } } } if (!This->codePage) { /* default to Unicode unless told not to, as specified here: * http://msdn.microsoft.com/library/en-us/stg/stg/names_in_istorage.asp */ if (This->grfFlags & PROPSETFLAG_ANSI) This->codePage = GetACP(); else This->codePage = CP_UNICODE; } if (!This->locale) This->locale = LOCALE_SYSTEM_DEFAULT; TRACE("Code page is %d, locale is %ld\n", This->codePage, This->locale); if (dictOffset) hr = PropertyStorage_ReadDictionary(This, buf + dictOffset - sizeof(PROPERTYSECTIONHEADER)); end: HeapFree(GetProcessHeap(), 0, buf); if (FAILED(hr)) { dictionary_destroy(This->name_to_propid); This->name_to_propid = NULL; dictionary_destroy(This->propid_to_name); This->propid_to_name = NULL; dictionary_destroy(This->propid_to_prop); This->propid_to_prop = NULL; } return hr; } static void PropertyStorage_MakeHeader(PropertyStorage_impl *This, PROPERTYSETHEADER *hdr) { assert(This); assert(hdr); StorageUtl_WriteWord((BYTE *)&hdr->wByteOrder, 0, PROPSETHDR_BYTEORDER_MAGIC); StorageUtl_WriteWord((BYTE *)&hdr->wFormat, 0, This->format); StorageUtl_WriteDWord((BYTE *)&hdr->dwOSVer, 0, This->originatorOS); StorageUtl_WriteGUID((BYTE *)&hdr->clsid, 0, &This->clsid); StorageUtl_WriteDWord((BYTE *)&hdr->reserved, 0, 1); } static void PropertyStorage_MakeFmtIdOffset(PropertyStorage_impl *This, FORMATIDOFFSET *fmtOffset) { assert(This); assert(fmtOffset); StorageUtl_WriteGUID((BYTE *)fmtOffset, 0, &This->fmtid); StorageUtl_WriteDWord((BYTE *)fmtOffset, offsetof(FORMATIDOFFSET, dwOffset), sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET)); } static void PropertyStorage_MakeSectionHdr(DWORD cbSection, DWORD numProps, PROPERTYSECTIONHEADER *hdr) { assert(hdr); StorageUtl_WriteDWord((BYTE *)hdr, 0, cbSection); StorageUtl_WriteDWord((BYTE *)hdr, offsetof(PROPERTYSECTIONHEADER, cProperties), numProps); } static void PropertyStorage_MakePropertyIdOffset(DWORD propid, DWORD dwOffset, PROPERTYIDOFFSET *propIdOffset) { assert(propIdOffset); StorageUtl_WriteDWord((BYTE *)propIdOffset, 0, propid); StorageUtl_WriteDWord((BYTE *)propIdOffset, offsetof(PROPERTYIDOFFSET, dwOffset), dwOffset); } struct DictionaryClosure { HRESULT hr; DWORD bytesWritten; }; static BOOL PropertyStorage_DictionaryWriter(const void *key, const void *value, void *extra, void *closure) { PropertyStorage_impl *This = (PropertyStorage_impl *)extra; struct DictionaryClosure *c = (struct DictionaryClosure *)closure; DWORD propid; ULONG count; assert(key); assert(This); assert(closure); StorageUtl_WriteDWord((LPBYTE)&propid, 0, (DWORD)value); c->hr = IStream_Write(This->stm, &propid, sizeof(propid), &count); if (FAILED(c->hr)) goto end; c->bytesWritten += sizeof(DWORD); if (This->codePage == CP_UNICODE) { DWORD keyLen, pad = 0; StorageUtl_WriteDWord((LPBYTE)&keyLen, 0, (lstrlenW((LPWSTR)key) + 1) * sizeof(WCHAR)); c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); if (FAILED(c->hr)) goto end; c->bytesWritten += sizeof(DWORD); /* Rather than allocate a copy, I'll swap the string to little-endian * in-place, write it, then swap it back. */ PropertyStorage_ByteSwapString(key, keyLen); c->hr = IStream_Write(This->stm, key, keyLen, &count); PropertyStorage_ByteSwapString(key, keyLen); if (FAILED(c->hr)) goto end; c->bytesWritten += keyLen; if (keyLen % sizeof(DWORD)) { c->hr = IStream_Write(This->stm, &pad, sizeof(DWORD) - keyLen % sizeof(DWORD), &count); if (FAILED(c->hr)) goto end; c->bytesWritten += sizeof(DWORD) - keyLen % sizeof(DWORD); } } else { DWORD keyLen; StorageUtl_WriteDWord((LPBYTE)&keyLen, 0, strlen((LPCSTR)key) + 1); c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); if (FAILED(c->hr)) goto end; c->bytesWritten += sizeof(DWORD); c->hr = IStream_Write(This->stm, key, keyLen, &count); if (FAILED(c->hr)) goto end; c->bytesWritten += keyLen; } end: return SUCCEEDED(c->hr); } #define SECTIONHEADER_OFFSET sizeof(PROPERTYSETHEADER) + sizeof(FORMATIDOFFSET) /* Writes the dictionary to the stream. Assumes without checking that the * dictionary isn't empty. */ static HRESULT PropertyStorage_WriteDictionaryToStream( PropertyStorage_impl *This, DWORD *sectionOffset) { HRESULT hr; LARGE_INTEGER seek; PROPERTYIDOFFSET propIdOffset; ULONG count; DWORD dwTemp; struct DictionaryClosure closure; assert(This); assert(sectionOffset); /* The dictionary's always the first property written, so seek to its * spot. */ seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER); hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; PropertyStorage_MakePropertyIdOffset(PID_DICTIONARY, *sectionOffset, &propIdOffset); hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); if (FAILED(hr)) goto end; seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, dictionary_num_entries(This->name_to_propid)); hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); if (FAILED(hr)) goto end; *sectionOffset += sizeof(dwTemp); closure.hr = S_OK; closure.bytesWritten = 0; dictionary_enumerate(This->name_to_propid, PropertyStorage_DictionaryWriter, &closure); hr = closure.hr; if (FAILED(hr)) goto end; *sectionOffset += closure.bytesWritten; if (closure.bytesWritten % sizeof(DWORD)) { TRACE("adding %ld bytes of padding\n", sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD)); *sectionOffset += sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD); } end: return hr; } static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, DWORD propNum, DWORD propid, PROPVARIANT *var, DWORD *sectionOffset) { HRESULT hr; LARGE_INTEGER seek; PROPERTYIDOFFSET propIdOffset; ULONG count; DWORD dwType, bytesWritten; assert(This); assert(var); assert(sectionOffset); TRACE("%p, %ld, 0x%08lx, (%d), (%ld)\n", This, propNum, propid, var->vt, *sectionOffset); seek.QuadPart = SECTIONHEADER_OFFSET + sizeof(PROPERTYSECTIONHEADER) + propNum * sizeof(PROPERTYIDOFFSET); hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; PropertyStorage_MakePropertyIdOffset(propid, *sectionOffset, &propIdOffset); hr = IStream_Write(This->stm, &propIdOffset, sizeof(propIdOffset), &count); if (FAILED(hr)) goto end; seek.QuadPart = SECTIONHEADER_OFFSET + *sectionOffset; hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; StorageUtl_WriteDWord((LPBYTE)&dwType, 0, var->vt); hr = IStream_Write(This->stm, &dwType, sizeof(dwType), &count); if (FAILED(hr)) goto end; *sectionOffset += sizeof(dwType); switch (var->vt) { case VT_EMPTY: case VT_NULL: bytesWritten = 0; break; case VT_I1: case VT_UI1: hr = IStream_Write(This->stm, &var->u.cVal, sizeof(var->u.cVal), &count); bytesWritten = count; break; case VT_I2: case VT_UI2: { WORD wTemp; StorageUtl_WriteWord((LPBYTE)&wTemp, 0, var->u.iVal); hr = IStream_Write(This->stm, &wTemp, sizeof(wTemp), &count); bytesWritten = count; break; } case VT_I4: case VT_UI4: { DWORD dwTemp; StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, var->u.lVal); hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); bytesWritten = count; break; } case VT_LPSTR: { DWORD len, dwTemp; if (This->codePage == CP_UNICODE) len = (lstrlenW(var->u.pwszVal) + 1) * sizeof(WCHAR); else len = lstrlenA(var->u.pszVal) + 1; StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); if (FAILED(hr)) goto end; hr = IStream_Write(This->stm, var->u.pszVal, len, &count); bytesWritten = count + sizeof(DWORD); break; } case VT_LPWSTR: { DWORD len = lstrlenW(var->u.pwszVal) + 1, dwTemp; StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, len); hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); if (FAILED(hr)) goto end; hr = IStream_Write(This->stm, var->u.pwszVal, len * sizeof(WCHAR), &count); bytesWritten = count + sizeof(DWORD); break; } case VT_FILETIME: { FILETIME temp; StorageUtl_WriteULargeInteger((BYTE *)&temp, 0, (ULARGE_INTEGER *)&var->u.filetime); hr = IStream_Write(This->stm, &temp, sizeof(FILETIME), &count); bytesWritten = count; break; } default: FIXME("unsupported type: %d\n", var->vt); return STG_E_INVALIDPARAMETER; } if (SUCCEEDED(hr)) { *sectionOffset += bytesWritten; if (bytesWritten % sizeof(DWORD)) { TRACE("adding %ld bytes of padding\n", sizeof(DWORD) - bytesWritten % sizeof(DWORD)); *sectionOffset += sizeof(DWORD) - bytesWritten % sizeof(DWORD); } } end: return hr; } struct PropertyClosure { HRESULT hr; DWORD propNum; DWORD *sectionOffset; }; static BOOL PropertyStorage_PropertiesWriter(const void *key, const void *value, void *extra, void *closure) { PropertyStorage_impl *This = (PropertyStorage_impl *)extra; struct PropertyClosure *c = (struct PropertyClosure *)closure; assert(key); assert(value); assert(extra); assert(closure); c->hr = PropertyStorage_WritePropertyToStream(This, c->propNum++, (DWORD)key, (PROPVARIANT *)value, c->sectionOffset); return SUCCEEDED(c->hr); } static HRESULT PropertyStorage_WritePropertiesToStream( PropertyStorage_impl *This, DWORD startingPropNum, DWORD *sectionOffset) { struct PropertyClosure closure; assert(This); assert(sectionOffset); closure.hr = S_OK; closure.propNum = startingPropNum; closure.sectionOffset = sectionOffset; dictionary_enumerate(This->propid_to_prop, PropertyStorage_PropertiesWriter, &closure); return closure.hr; } static HRESULT PropertyStorage_WriteHeadersToStream(PropertyStorage_impl *This) { HRESULT hr; ULONG count = 0; LARGE_INTEGER seek = { {0} }; PROPERTYSETHEADER hdr; FORMATIDOFFSET fmtOffset; assert(This); hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; PropertyStorage_MakeHeader(This, &hdr); hr = IStream_Write(This->stm, &hdr, sizeof(hdr), &count); if (FAILED(hr)) goto end; if (count != sizeof(hdr)) { hr = STG_E_WRITEFAULT; goto end; } PropertyStorage_MakeFmtIdOffset(This, &fmtOffset); hr = IStream_Write(This->stm, &fmtOffset, sizeof(fmtOffset), &count); if (FAILED(hr)) goto end; if (count != sizeof(fmtOffset)) { hr = STG_E_WRITEFAULT; goto end; } hr = S_OK; end: return hr; } static HRESULT PropertyStorage_WriteToStream(PropertyStorage_impl *This) { PROPERTYSECTIONHEADER sectionHdr; HRESULT hr; ULONG count; LARGE_INTEGER seek; DWORD numProps, prop, sectionOffset, dwTemp; PROPVARIANT var; assert(This); PropertyStorage_WriteHeadersToStream(This); /* Count properties. Always at least one property, the code page */ numProps = 1; if (dictionary_num_entries(This->name_to_propid)) numProps++; if (This->locale != LOCALE_SYSTEM_DEFAULT) numProps++; if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) numProps++; numProps += dictionary_num_entries(This->propid_to_prop); /* Write section header with 0 bytes right now, I'll adjust it after * writing properties. */ PropertyStorage_MakeSectionHdr(0, numProps, §ionHdr); seek.QuadPart = SECTIONHEADER_OFFSET; hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; hr = IStream_Write(This->stm, §ionHdr, sizeof(sectionHdr), &count); if (FAILED(hr)) goto end; prop = 0; sectionOffset = sizeof(PROPERTYSECTIONHEADER) + numProps * sizeof(PROPERTYIDOFFSET); if (dictionary_num_entries(This->name_to_propid)) { prop++; hr = PropertyStorage_WriteDictionaryToStream(This, §ionOffset); if (FAILED(hr)) goto end; } PropVariantInit(&var); var.vt = VT_I2; var.u.iVal = This->codePage; hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_CODEPAGE, &var, §ionOffset); if (FAILED(hr)) goto end; if (This->locale != LOCALE_SYSTEM_DEFAULT) { var.vt = VT_I4; var.u.lVal = This->locale; hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_LOCALE, &var, §ionOffset); if (FAILED(hr)) goto end; } if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) { var.vt = VT_I4; var.u.lVal = 1; hr = PropertyStorage_WritePropertyToStream(This, prop++, PID_BEHAVIOR, &var, §ionOffset); if (FAILED(hr)) goto end; } hr = PropertyStorage_WritePropertiesToStream(This, prop, §ionOffset); if (FAILED(hr)) goto end; /* Now write the byte count of the section */ seek.QuadPart = SECTIONHEADER_OFFSET; hr = IStream_Seek(This->stm, seek, STREAM_SEEK_SET, NULL); if (FAILED(hr)) goto end; StorageUtl_WriteDWord((LPBYTE)&dwTemp, 0, sectionOffset); hr = IStream_Write(This->stm, &dwTemp, sizeof(dwTemp), &count); end: return hr; } /*********************************************************************** * PropertyStorage_Construct */ static void PropertyStorage_DestroyDictionaries(PropertyStorage_impl *This) { assert(This); dictionary_destroy(This->name_to_propid); This->name_to_propid = NULL; dictionary_destroy(This->propid_to_name); This->propid_to_name = NULL; dictionary_destroy(This->propid_to_prop); This->propid_to_prop = NULL; } static HRESULT PropertyStorage_CreateDictionaries(PropertyStorage_impl *This) { HRESULT hr = S_OK; assert(This); This->name_to_propid = dictionary_create( PropertyStorage_PropNameCompare, PropertyStorage_PropNameDestroy, This); if (!This->name_to_propid) { hr = STG_E_INSUFFICIENTMEMORY; goto end; } This->propid_to_name = dictionary_create(PropertyStorage_PropCompare, NULL, This); if (!This->propid_to_name) { hr = STG_E_INSUFFICIENTMEMORY; goto end; } This->propid_to_prop = dictionary_create(PropertyStorage_PropCompare, PropertyStorage_PropertyDestroy, This); if (!This->propid_to_prop) { hr = STG_E_INSUFFICIENTMEMORY; goto end; } end: if (FAILED(hr)) PropertyStorage_DestroyDictionaries(This); return hr; } static HRESULT PropertyStorage_BaseConstruct(IStream *stm, REFFMTID rfmtid, DWORD grfMode, PropertyStorage_impl **pps) { HRESULT hr = S_OK; assert(pps); assert(rfmtid); *pps = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof **pps); if (!*pps) return E_OUTOFMEMORY; (*pps)->vtbl = &IPropertyStorage_Vtbl; (*pps)->ref = 1; InitializeCriticalSection(&(*pps)->cs); (*pps)->stm = stm; memcpy(&(*pps)->fmtid, rfmtid, sizeof((*pps)->fmtid)); (*pps)->grfMode = grfMode; hr = PropertyStorage_CreateDictionaries(*pps); if (FAILED(hr)) { IStream_Release(stm); DeleteCriticalSection(&(*pps)->cs); HeapFree(GetProcessHeap(), 0, *pps); *pps = NULL; } return hr; } static HRESULT PropertyStorage_ConstructFromStream(IStream *stm, REFFMTID rfmtid, DWORD grfMode, IPropertyStorage** pps) { PropertyStorage_impl *ps; HRESULT hr; assert(pps); hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); if (SUCCEEDED(hr)) { hr = PropertyStorage_ReadFromStream(ps); if (SUCCEEDED(hr)) { *pps = (IPropertyStorage *)ps; TRACE("PropertyStorage %p constructed\n", ps); hr = S_OK; } else { PropertyStorage_DestroyDictionaries(ps); HeapFree(GetProcessHeap(), 0, ps); } } return hr; } static HRESULT PropertyStorage_ConstructEmpty(IStream *stm, REFFMTID rfmtid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** pps) { PropertyStorage_impl *ps; HRESULT hr; assert(pps); hr = PropertyStorage_BaseConstruct(stm, rfmtid, grfMode, &ps); if (SUCCEEDED(hr)) { ps->format = 0; ps->grfFlags = grfFlags; if (ps->grfFlags & PROPSETFLAG_CASE_SENSITIVE) ps->format = 1; /* default to Unicode unless told not to, as specified here: * http://msdn.microsoft.com/library/en-us/stg/stg/names_in_istorage.asp */ if (ps->grfFlags & PROPSETFLAG_ANSI) ps->codePage = GetACP(); else ps->codePage = CP_UNICODE; ps->locale = LOCALE_SYSTEM_DEFAULT; TRACE("Code page is %d, locale is %ld\n", ps->codePage, ps->locale); *pps = (IPropertyStorage *)ps; TRACE("PropertyStorage %p constructed\n", ps); hr = S_OK; } return hr; } /*********************************************************************** * Implementation of IPropertySetStorage */ /************************************************************************ * IPropertySetStorage_fnQueryInterface (IUnknown) * * This method forwards to the common QueryInterface implementation */ static HRESULT WINAPI IPropertySetStorage_fnQueryInterface( IPropertySetStorage *ppstg, REFIID riid, void** ppvObject) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); return IStorage_QueryInterface( (IStorage*)This, riid, ppvObject ); } /************************************************************************ * IPropertySetStorage_fnAddRef (IUnknown) * * This method forwards to the common AddRef implementation */ static ULONG WINAPI IPropertySetStorage_fnAddRef( IPropertySetStorage *ppstg) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); return IStorage_AddRef( (IStorage*)This ); } /************************************************************************ * IPropertySetStorage_fnRelease (IUnknown) * * This method forwards to the common Release implementation */ static ULONG WINAPI IPropertySetStorage_fnRelease( IPropertySetStorage *ppstg) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); return IStorage_Release( (IStorage*)This ); } /************************************************************************ * IPropertySetStorage_fnCreate (IPropertySetStorage) */ static HRESULT WINAPI IPropertySetStorage_fnCreate( IPropertySetStorage *ppstg, REFFMTID rfmtid, const CLSID* pclsid, DWORD grfFlags, DWORD grfMode, IPropertyStorage** ppprstg) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); WCHAR name[CCH_MAX_PROPSTG_NAME]; IStream *stm = NULL; HRESULT r; TRACE("%p %s %08lx %08lx %p\n", This, debugstr_guid(rfmtid), grfFlags, grfMode, ppprstg); /* be picky */ if (grfMode != (STGM_CREATE|STGM_READWRITE|STGM_SHARE_EXCLUSIVE)) { r = STG_E_INVALIDFLAG; goto end; } if (!rfmtid) { r = E_INVALIDARG; goto end; } /* FIXME: if (grfFlags & PROPSETFLAG_NONSIMPLE), we need to create a * storage, not a stream. For now, disallow it. */ if (grfFlags & PROPSETFLAG_NONSIMPLE) { FIXME("PROPSETFLAG_NONSIMPLE not supported\n"); r = STG_E_INVALIDFLAG; goto end; } r = FmtIdToPropStgName(rfmtid, name); if (FAILED(r)) goto end; r = IStorage_CreateStream( (IStorage*)This, name, grfMode, 0, 0, &stm ); if (FAILED(r)) goto end; r = PropertyStorage_ConstructEmpty(stm, rfmtid, grfFlags, grfMode, ppprstg); end: TRACE("returning 0x%08lx\n", r); return r; } /************************************************************************ * IPropertySetStorage_fnOpen (IPropertySetStorage) */ static HRESULT WINAPI IPropertySetStorage_fnOpen( IPropertySetStorage *ppstg, REFFMTID rfmtid, DWORD grfMode, IPropertyStorage** ppprstg) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); IStream *stm = NULL; WCHAR name[CCH_MAX_PROPSTG_NAME]; HRESULT r; TRACE("%p %s %08lx %p\n", This, debugstr_guid(rfmtid), grfMode, ppprstg); /* be picky */ if (grfMode != (STGM_READWRITE|STGM_SHARE_EXCLUSIVE) && grfMode != (STGM_READ|STGM_SHARE_EXCLUSIVE)) { r = STG_E_INVALIDFLAG; goto end; } if (!rfmtid) { r = E_INVALIDARG; goto end; } r = FmtIdToPropStgName(rfmtid, name); if (FAILED(r)) goto end; r = IStorage_OpenStream((IStorage*) This, name, 0, grfMode, 0, &stm ); if (FAILED(r)) goto end; r = PropertyStorage_ConstructFromStream(stm, rfmtid, grfMode, ppprstg); end: TRACE("returning 0x%08lx\n", r); return r; } /************************************************************************ * IPropertySetStorage_fnDelete (IPropertySetStorage) */ static HRESULT WINAPI IPropertySetStorage_fnDelete( IPropertySetStorage *ppstg, REFFMTID rfmtid) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); IStorage *stg = NULL; WCHAR name[CCH_MAX_PROPSTG_NAME]; HRESULT r; TRACE("%p %s\n", This, debugstr_guid(rfmtid)); if (!rfmtid) return E_INVALIDARG; r = FmtIdToPropStgName(rfmtid, name); if (FAILED(r)) return r; stg = (IStorage*) This; return IStorage_DestroyElement(stg, name); } /************************************************************************ * IPropertySetStorage_fnEnum (IPropertySetStorage) */ static HRESULT WINAPI IPropertySetStorage_fnEnum( IPropertySetStorage *ppstg, IEnumSTATPROPSETSTG** ppenum) { _ICOM_THIS_From_IPropertySetStorage(StorageImpl, ppstg); FIXME("%p\n", This); return E_NOTIMPL; } /*********************************************************************** * vtables */ const IPropertySetStorageVtbl IPropertySetStorage_Vtbl = { IPropertySetStorage_fnQueryInterface, IPropertySetStorage_fnAddRef, IPropertySetStorage_fnRelease, IPropertySetStorage_fnCreate, IPropertySetStorage_fnOpen, IPropertySetStorage_fnDelete, IPropertySetStorage_fnEnum }; static const IPropertyStorageVtbl IPropertyStorage_Vtbl = { IPropertyStorage_fnQueryInterface, IPropertyStorage_fnAddRef, IPropertyStorage_fnRelease, IPropertyStorage_fnReadMultiple, IPropertyStorage_fnWriteMultiple, IPropertyStorage_fnDeleteMultiple, IPropertyStorage_fnReadPropertyNames, IPropertyStorage_fnWritePropertyNames, IPropertyStorage_fnDeletePropertyNames, IPropertyStorage_fnCommit, IPropertyStorage_fnRevert, IPropertyStorage_fnEnum, IPropertyStorage_fnSetTimes, IPropertyStorage_fnSetClass, IPropertyStorage_fnStat, }; /*********************************************************************** * Format ID <-> name conversion */ static const WCHAR szSummaryInfo[] = { 5,'S','u','m','m','a','r','y', 'I','n','f','o','r','m','a','t','i','o','n',0 }; static const WCHAR szDocSummaryInfo[] = { 5,'D','o','c','u','m','e','n','t', 'S','u','m','m','a','r','y','I','n','f','o','r','m','a','t','i','o','n',0 }; #define BITS_PER_BYTE 8 #define CHARMASK 0x1f #define BITS_IN_CHARMASK 5 #define NUM_ALPHA_CHARS 26 /*********************************************************************** * FmtIdToPropStgName [ole32.@] * Returns the storage name of the format ID rfmtid. * PARAMS * rfmtid [I] Format ID for which to return a storage name * str [O] Storage name associated with rfmtid. * * RETURNS * E_INVALIDARG if rfmtid or str i NULL, S_OK otherwise. * * NOTES * str must be at least CCH_MAX_PROPSTG_NAME characters in length. * Based on the algorithm described here: * http://msdn.microsoft.com/library/en-us/stg/stg/names_in_istorage.asp */ HRESULT WINAPI FmtIdToPropStgName(const FMTID *rfmtid, LPOLESTR str) { static const char fmtMap[] = "abcdefghijklmnopqrstuvwxyz012345"; TRACE("%s, %p\n", debugstr_guid(rfmtid), str); if (!rfmtid) return E_INVALIDARG; if (!str) return E_INVALIDARG; if (IsEqualGUID(&FMTID_SummaryInformation, rfmtid)) lstrcpyW(str, szSummaryInfo); else if (IsEqualGUID(&FMTID_DocSummaryInformation, rfmtid)) lstrcpyW(str, szDocSummaryInfo); else if (IsEqualGUID(&FMTID_UserDefinedProperties, rfmtid)) lstrcpyW(str, szDocSummaryInfo); else { BYTE *fmtptr; WCHAR *pstr = str; ULONG bitsRemaining = BITS_PER_BYTE; *pstr++ = 5; for (fmtptr = (BYTE *)rfmtid; fmtptr < (BYTE *)rfmtid + sizeof(FMTID); ) { ULONG i = *fmtptr >> (BITS_PER_BYTE - bitsRemaining); if (bitsRemaining >= BITS_IN_CHARMASK) { *pstr = (WCHAR)(fmtMap[i & CHARMASK]); if (bitsRemaining == BITS_PER_BYTE && *pstr >= 'a' && *pstr <= 'z') *pstr += 'A' - 'a'; pstr++; bitsRemaining -= BITS_IN_CHARMASK; if (bitsRemaining == 0) { fmtptr++; bitsRemaining = BITS_PER_BYTE; } } else { if (++fmtptr < (BYTE *)rfmtid + sizeof(FMTID)) i |= *fmtptr << bitsRemaining; *pstr++ = (WCHAR)(fmtMap[i & CHARMASK]); bitsRemaining += BITS_PER_BYTE - BITS_IN_CHARMASK; } } *pstr = 0; } TRACE("returning %s\n", debugstr_w(str)); return S_OK; } /*********************************************************************** * PropStgNameToFmtId [ole32.@] * Returns the format ID corresponding to the given name. * PARAMS * str [I] Storage name to convert to a format ID. * rfmtid [O] Format ID corresponding to str. * * RETURNS * E_INVALIDARG if rfmtid or str is NULL or if str can't be converted to * a format ID, S_OK otherwise. * * NOTES * Based on the algorithm described here: * http://msdn.microsoft.com/library/en-us/stg/stg/names_in_istorage.asp */ HRESULT WINAPI PropStgNameToFmtId(const LPOLESTR str, FMTID *rfmtid) { HRESULT hr = STG_E_INVALIDNAME; TRACE("%s, %p\n", debugstr_w(str), rfmtid); if (!rfmtid) return E_INVALIDARG; if (!str) return STG_E_INVALIDNAME; if (!lstrcmpiW(str, szDocSummaryInfo)) { memcpy(rfmtid, &FMTID_DocSummaryInformation, sizeof(*rfmtid)); hr = S_OK; } else if (!lstrcmpiW(str, szSummaryInfo)) { memcpy(rfmtid, &FMTID_SummaryInformation, sizeof(*rfmtid)); hr = S_OK; } else { ULONG bits; BYTE *fmtptr = (BYTE *)rfmtid - 1; const WCHAR *pstr = str; memset(rfmtid, 0, sizeof(*rfmtid)); for (bits = 0; bits < sizeof(FMTID) * BITS_PER_BYTE; bits += BITS_IN_CHARMASK) { ULONG bitsUsed = bits % BITS_PER_BYTE, bitsStored; WCHAR wc; if (bitsUsed == 0) fmtptr++; wc = *++pstr - 'A'; if (wc > NUM_ALPHA_CHARS) { wc += 'A' - 'a'; if (wc > NUM_ALPHA_CHARS) { wc += 'a' - '0' + NUM_ALPHA_CHARS; if (wc > CHARMASK) { WARN("invalid character (%d)\n", *pstr); goto end; } } } *fmtptr |= wc << bitsUsed; bitsStored = min(BITS_PER_BYTE - bitsUsed, BITS_IN_CHARMASK); if (bitsStored < BITS_IN_CHARMASK) { wc >>= BITS_PER_BYTE - bitsUsed; if (bits + bitsStored == sizeof(FMTID) * BITS_PER_BYTE) { if (wc != 0) { WARN("extra bits\n"); goto end; } break; } fmtptr++; *fmtptr |= (BYTE)wc; } } hr = S_OK; } end: return hr; }