|  | /* | 
|  | * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA | 
|  | * | 
|  | * 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 | 
|  | */ | 
|  |  | 
|  | #include <assert.h> | 
|  | #include <stdarg.h> | 
|  | #include <stdio.h> | 
|  | #include <stdlib.h> | 
|  | #include <string.h> | 
|  |  | 
|  | #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" | 
|  | #include "enumx.h" | 
|  |  | 
|  | WINE_DEFAULT_DEBUG_CHANNEL(storage); | 
|  |  | 
|  | static inline StorageImpl *impl_from_IPropertySetStorage( IPropertySetStorage *iface ) | 
|  | { | 
|  | return (StorageImpl *)((char*)iface - FIELD_OFFSET(StorageImpl, base.pssVtbl)); | 
|  | } | 
|  |  | 
|  | /* These are documented in MSDN, | 
|  | * 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 | 
|  |  | 
|  | #define CFTAG_WINDOWS   (-1L) | 
|  | #define CFTAG_MACINTOSH (-2L) | 
|  | #define CFTAG_FMTID     (-3L) | 
|  | #define CFTAG_NODATA      0L | 
|  |  | 
|  | 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; | 
|  |  | 
|  | typedef struct tagPropertyStorage_impl PropertyStorage_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(PropertyStorage_impl *); | 
|  |  | 
|  | static HRESULT PropertyStorage_WriteToStream(PropertyStorage_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(PropertyStorage_impl *); | 
|  |  | 
|  | static void PropertyStorage_DestroyDictionaries(PropertyStorage_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; | 
|  | static const IEnumSTATPROPSETSTGVtbl IEnumSTATPROPSETSTG_Vtbl; | 
|  | static const IEnumSTATPROPSTGVtbl IEnumSTATPROPSTG_Vtbl; | 
|  | static HRESULT create_EnumSTATPROPSETSTG(StorageImpl *, IEnumSTATPROPSETSTG**); | 
|  | static HRESULT create_EnumSTATPROPSTG(PropertyStorage_impl *, IEnumSTATPROPSTG**); | 
|  |  | 
|  | /*********************************************************************** | 
|  | * Implementation of IPropertyStorage | 
|  | */ | 
|  | struct tagPropertyStorage_impl | 
|  | { | 
|  | const IPropertyStorageVtbl *vtbl; | 
|  | LONG 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; | 
|  | }; | 
|  |  | 
|  | /************************************************************************ | 
|  | * 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 = 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); | 
|  | This->cs.DebugInfo->Spare[0] = 0; | 
|  | DeleteCriticalSection(&This->cs); | 
|  | PropertyStorage_DestroyDictionaries(This); | 
|  | HeapFree(GetProcessHeap(), 0, This); | 
|  | } | 
|  | return ref; | 
|  | } | 
|  |  | 
|  | static PROPVARIANT *PropertyStorage_FindProperty(PropertyStorage_impl *This, | 
|  | DWORD propid) | 
|  | { | 
|  | PROPVARIANT *ret = NULL; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | if (!name) | 
|  | return NULL; | 
|  | if (This->codePage == CP_UNICODE) | 
|  | { | 
|  | if (dictionary_find(This->name_to_propid, name, (void **)&propid)) | 
|  | ret = PropertyStorage_FindProperty(This, 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); | 
|  | CoTaskMemFree(ansiName); | 
|  | } | 
|  | } | 
|  | TRACE("returning %p\n", ret); | 
|  | return ret; | 
|  | } | 
|  |  | 
|  | static LPWSTR PropertyStorage_FindPropertyNameById(PropertyStorage_impl *This, | 
|  | DWORD propid) | 
|  | { | 
|  | LPWSTR ret = NULL; | 
|  |  | 
|  | 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_OK; | 
|  | ULONG i; | 
|  |  | 
|  | TRACE("(%p, %d, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); | 
|  |  | 
|  | if (!cpspec) | 
|  | return S_FALSE; | 
|  | if (!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); | 
|  | else | 
|  | hr = S_FALSE; | 
|  | } | 
|  | } | 
|  | } | 
|  | } | 
|  | 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, %d, %d\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 | 
|  | { | 
|  | LPCWSTR wideStr = NULL; | 
|  | LPWSTR wideStr_tmp = NULL; | 
|  |  | 
|  | if (srcCP == CP_UNICODE) | 
|  | wideStr = (LPCWSTR)src; | 
|  | else | 
|  | { | 
|  | len = MultiByteToWideChar(srcCP, 0, src, -1, NULL, 0); | 
|  | wideStr_tmp = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | 
|  | if (wideStr_tmp) | 
|  | { | 
|  | MultiByteToWideChar(srcCP, 0, src, -1, wideStr_tmp, len); | 
|  | wideStr = wideStr_tmp; | 
|  | } | 
|  | 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); | 
|  | } | 
|  | } | 
|  | } | 
|  | HeapFree(GetProcessHeap(), 0, wideStr_tmp); | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%08x (%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(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%08x 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(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 %d\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, %d, %p, %p)\n", iface, cpspec, rgpspec, rgpropvar); | 
|  |  | 
|  | 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, %d, %p)\n", iface, cpspec, rgpspec); | 
|  |  | 
|  | 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, %d, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); | 
|  |  | 
|  | 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, %d, %p, %p)\n", iface, cpropid, rgpropid, rglpwstrName); | 
|  |  | 
|  | 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, %d, %p)\n", iface, cpropid, rgpropid); | 
|  |  | 
|  | 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%08x)\n", iface, grfCommitFlags); | 
|  |  | 
|  | 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); | 
|  |  | 
|  | 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) | 
|  | { | 
|  | PropertyStorage_impl *This = (PropertyStorage_impl *)iface; | 
|  | return create_EnumSTATPROPSTG(This, ppenum); | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * 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 (!clsid) | 
|  | return E_INVALIDARG; | 
|  | if (!(This->grfMode & STGM_READWRITE)) | 
|  | return STG_E_ACCESSDENIED; | 
|  | This->clsid = *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 (!statpsstg) | 
|  | return E_INVALIDARG; | 
|  |  | 
|  | hr = IStream_Stat(This->stm, &stat, STATFLAG_NONAME); | 
|  | if (SUCCEEDED(hr)) | 
|  | { | 
|  | statpsstg->fmtid = This->fmtid; | 
|  | statpsstg->clsid = This->clsid; | 
|  | statpsstg->grfFlags = This->grfFlags; | 
|  | statpsstg->mtime = stat.mtime; | 
|  | statpsstg->ctime = stat.ctime; | 
|  | statpsstg->atime = stat.atime; | 
|  | statpsstg->dwOSVersion = This->originatorOS; | 
|  | } | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static int PropertyStorage_PropNameCompare(const void *a, const void *b, | 
|  | void *extra) | 
|  | { | 
|  | PropertyStorage_impl *This = extra; | 
|  |  | 
|  | if (This->codePage == CP_UNICODE) | 
|  | { | 
|  | TRACE("(%s, %s)\n", debugstr_w(a), debugstr_w(b)); | 
|  | if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
|  | return lstrcmpW(a, b); | 
|  | else | 
|  | return lstrcmpiW(a, b); | 
|  | } | 
|  | else | 
|  | { | 
|  | TRACE("(%s, %s)\n", debugstr_a(a), debugstr_a(b)); | 
|  | if (This->grfFlags & PROPSETFLAG_CASE_SENSITIVE) | 
|  | return lstrcmpA(a, b); | 
|  | else | 
|  | return lstrcmpiA(a, 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("(%d, %d)\n", (PROPID)a, (PROPID)b); | 
|  | return (PROPID)a - (PROPID)b; | 
|  | } | 
|  |  | 
|  | static void PropertyStorage_PropertyDestroy(void *k, void *d, void *extra) | 
|  | { | 
|  | PropVariantClear(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 lendian16toh 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] = lendian16toh(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->name_to_propid); | 
|  | assert(This->propid_to_name); | 
|  |  | 
|  | StorageUtl_ReadDWord(ptr, 0, &numEntries); | 
|  | TRACE("Reading %d 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%08x, %d 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, (char*)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 = *data; | 
|  | TRACE("Read byte 0x%x\n", prop->u.bVal); | 
|  | break; | 
|  | case VT_I2: | 
|  | StorageUtl_ReadWord(data, 0, (WORD*)&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, (DWORD*)&prop->u.lVal); | 
|  | TRACE("Read long %d\n", prop->u.lVal); | 
|  | break; | 
|  | case VT_UINT: | 
|  | case VT_UI4: | 
|  | StorageUtl_ReadDWord(data, 0, &prop->u.ulVal); | 
|  | TRACE("Read ulong %d\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; | 
|  | case VT_CF: | 
|  | { | 
|  | DWORD len = 0, tag = 0; | 
|  |  | 
|  | StorageUtl_ReadDWord(data, 0, &len); | 
|  | StorageUtl_ReadDWord(data, 4, &tag); | 
|  | if (len > 8) | 
|  | { | 
|  | len -= 8; | 
|  | prop->u.pclipdata = CoTaskMemAlloc(sizeof (CLIPDATA)); | 
|  | prop->u.pclipdata->cbSize = len; | 
|  | prop->u.pclipdata->ulClipFmt = tag; | 
|  | prop->u.pclipdata->pClipData = CoTaskMemAlloc(len); | 
|  | memcpy(prop->u.pclipdata->pClipData, data+8, len); | 
|  | } | 
|  | else | 
|  | hr = STG_E_INVALIDPARAMETER; | 
|  | } | 
|  | 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 only %d\n", count); | 
|  | 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%08x\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 only %d\n", count); | 
|  | hr = STG_E_INVALIDHEADER; | 
|  | } | 
|  | else | 
|  | { | 
|  | StorageUtl_ReadGUID(buf, offsetof(FORMATIDOFFSET, fmtid), | 
|  | &fmt->fmtid); | 
|  | StorageUtl_ReadDWord(buf, offsetof(FORMATIDOFFSET, dwOffset), | 
|  | &fmt->dwOffset); | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%08x\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 only %d\n", count); | 
|  | hr = STG_E_INVALIDHEADER; | 
|  | } | 
|  | else | 
|  | { | 
|  | StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, | 
|  | cbSection), &hdr->cbSection); | 
|  | StorageUtl_ReadDWord(buf, offsetof(PROPERTYSECTIONHEADER, | 
|  | cProperties), &hdr->cProperties); | 
|  | } | 
|  | } | 
|  | TRACE("returning 0x%08x\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; | 
|  |  | 
|  | 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; | 
|  | This->clsid = hdr.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 %d (stream length is %d)\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 %d\n", sectionHdr.cbSection); | 
|  | 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 %d 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 %d\n", dictOffset); | 
|  | } | 
|  | else | 
|  | { | 
|  | PROPVARIANT prop; | 
|  |  | 
|  | PropVariantInit(&prop); | 
|  | if (SUCCEEDED(PropertyStorage_ReadProperty(This, &prop, | 
|  | buf + idOffset->dwOffset - sizeof(PROPERTYSECTIONHEADER)))) | 
|  | { | 
|  | TRACE("Read property with ID 0x%08x, 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 on msdn */ | 
|  | 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 %d\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(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(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); | 
|  | } | 
|  |  | 
|  | static inline HRESULT PropertStorage_WriteWStringToStream(IStream *stm, | 
|  | LPCWSTR str, DWORD len, DWORD *written) | 
|  | { | 
|  | #ifdef WORDS_BIGENDIAN | 
|  | WCHAR *leStr = HeapAlloc(GetProcessHeap(), 0, len * sizeof(WCHAR)); | 
|  | HRESULT hr; | 
|  |  | 
|  | if (!leStr) | 
|  | return E_OUTOFMEMORY; | 
|  | memcpy(leStr, str, len * sizeof(WCHAR)); | 
|  | PropertyStorage_ByteSwapString(leStr, len); | 
|  | hr = IStream_Write(stm, leStr, len, written); | 
|  | HeapFree(GetProcessHeap(), 0, leStr); | 
|  | return hr; | 
|  | #else | 
|  | return IStream_Write(stm, str, len, written); | 
|  | #endif | 
|  | } | 
|  |  | 
|  | struct DictionaryClosure | 
|  | { | 
|  | HRESULT hr; | 
|  | DWORD bytesWritten; | 
|  | }; | 
|  |  | 
|  | static BOOL PropertyStorage_DictionaryWriter(const void *key, | 
|  | const void *value, void *extra, void *closure) | 
|  | { | 
|  | PropertyStorage_impl *This = extra; | 
|  | struct DictionaryClosure *c = closure; | 
|  | DWORD propid; | 
|  | ULONG count; | 
|  |  | 
|  | assert(key); | 
|  | 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((LPCWSTR)key) + 1) * sizeof(WCHAR)); | 
|  | c->hr = IStream_Write(This->stm, &keyLen, sizeof(keyLen), &count); | 
|  | if (FAILED(c->hr)) | 
|  | goto end; | 
|  | c->bytesWritten += sizeof(DWORD); | 
|  | c->hr = PropertStorage_WriteWStringToStream(This->stm, key, keyLen, | 
|  | &count); | 
|  | if (FAILED(c->hr)) | 
|  | goto end; | 
|  | c->bytesWritten += keyLen * sizeof(WCHAR); | 
|  | 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(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)) | 
|  | { | 
|  | DWORD padding = sizeof(DWORD) - closure.bytesWritten % sizeof(DWORD); | 
|  | TRACE("adding %d bytes of padding\n", padding); | 
|  | *sectionOffset += padding; | 
|  | } | 
|  |  | 
|  | end: | 
|  | return hr; | 
|  | } | 
|  |  | 
|  | static HRESULT PropertyStorage_WritePropertyToStream(PropertyStorage_impl *This, | 
|  | DWORD propNum, DWORD propid, const PROPVARIANT *var, DWORD *sectionOffset) | 
|  | { | 
|  | HRESULT hr; | 
|  | LARGE_INTEGER seek; | 
|  | PROPERTYIDOFFSET propIdOffset; | 
|  | ULONG count; | 
|  | DWORD dwType, bytesWritten; | 
|  |  | 
|  | assert(var); | 
|  | assert(sectionOffset); | 
|  |  | 
|  | TRACE("%p, %d, 0x%08x, (%d), (%d)\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, | 
|  | (const ULARGE_INTEGER *)&var->u.filetime); | 
|  | hr = IStream_Write(This->stm, &temp, sizeof(FILETIME), &count); | 
|  | bytesWritten = count; | 
|  | break; | 
|  | } | 
|  | case VT_CF: | 
|  | { | 
|  | DWORD cf_hdr[2], len; | 
|  |  | 
|  | len = var->u.pclipdata->cbSize; | 
|  | StorageUtl_WriteDWord((LPBYTE)&cf_hdr[0], 0, len + 8); | 
|  | StorageUtl_WriteDWord((LPBYTE)&cf_hdr[1], 0, var->u.pclipdata->ulClipFmt); | 
|  | hr = IStream_Write(This->stm, cf_hdr, sizeof(cf_hdr), &count); | 
|  | if (FAILED(hr)) | 
|  | goto end; | 
|  | hr = IStream_Write(This->stm, &var->u.pclipdata->pClipData, len, &count); | 
|  | if (FAILED(hr)) | 
|  | goto end; | 
|  | bytesWritten = count + sizeof cf_hdr; | 
|  | break; | 
|  | } | 
|  | default: | 
|  | FIXME("unsupported type: %d\n", var->vt); | 
|  | return STG_E_INVALIDPARAMETER; | 
|  | } | 
|  |  | 
|  | if (SUCCEEDED(hr)) | 
|  | { | 
|  | *sectionOffset += bytesWritten; | 
|  | if (bytesWritten % sizeof(DWORD)) | 
|  | { | 
|  | DWORD padding = sizeof(DWORD) - bytesWritten % sizeof(DWORD); | 
|  | TRACE("adding %d bytes of padding\n", padding); | 
|  | *sectionOffset += padding; | 
|  | } | 
|  | } | 
|  |  | 
|  | 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 = extra; | 
|  | struct PropertyClosure *c = closure; | 
|  |  | 
|  | assert(key); | 
|  | assert(value); | 
|  | assert(extra); | 
|  | assert(closure); | 
|  | c->hr = PropertyStorage_WritePropertyToStream(This, c->propNum++, | 
|  | (DWORD)key, value, c->sectionOffset); | 
|  | return SUCCEEDED(c->hr); | 
|  | } | 
|  |  | 
|  | static HRESULT PropertyStorage_WritePropertiesToStream( | 
|  | PropertyStorage_impl *This, DWORD startingPropNum, DWORD *sectionOffset) | 
|  | { | 
|  | struct PropertyClosure closure; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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; | 
|  |  | 
|  | 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) | 
|  | { | 
|  | 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; | 
|  |  | 
|  | 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)->cs.DebugInfo->Spare[0] = (DWORD_PTR)(__FILE__ ": PropertyStorage_impl.cs"); | 
|  | (*pps)->stm = stm; | 
|  | (*pps)->fmtid = *rfmtid; | 
|  | (*pps)->grfMode = grfMode; | 
|  |  | 
|  | hr = PropertyStorage_CreateDictionaries(*pps); | 
|  | if (FAILED(hr)) | 
|  | { | 
|  | IStream_Release(stm); | 
|  | (*pps)->cs.DebugInfo->Spare[0] = 0; | 
|  | 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 on msdn */ | 
|  | 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 %d\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) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(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) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
|  | return IStorage_AddRef( (IStorage*)This ); | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * IPropertySetStorage_fnRelease (IUnknown) | 
|  | * | 
|  | *  This method forwards to the common Release implementation | 
|  | */ | 
|  | static ULONG WINAPI IPropertySetStorage_fnRelease( | 
|  | IPropertySetStorage *ppstg) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(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) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
|  | WCHAR name[CCH_MAX_PROPSTG_NAME]; | 
|  | IStream *stm = NULL; | 
|  | HRESULT r; | 
|  |  | 
|  | TRACE("%p %s %08x %08x %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%08x\n", r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * IPropertySetStorage_fnOpen (IPropertySetStorage) | 
|  | */ | 
|  | static HRESULT WINAPI IPropertySetStorage_fnOpen( | 
|  | IPropertySetStorage *ppstg, | 
|  | REFFMTID rfmtid, | 
|  | DWORD grfMode, | 
|  | IPropertyStorage** ppprstg) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
|  | IStream *stm = NULL; | 
|  | WCHAR name[CCH_MAX_PROPSTG_NAME]; | 
|  | HRESULT r; | 
|  |  | 
|  | TRACE("%p %s %08x %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%08x\n", r); | 
|  | return r; | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * IPropertySetStorage_fnDelete (IPropertySetStorage) | 
|  | */ | 
|  | static HRESULT WINAPI IPropertySetStorage_fnDelete( | 
|  | IPropertySetStorage *ppstg, | 
|  | REFFMTID rfmtid) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(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) | 
|  | { | 
|  | StorageImpl *This = impl_from_IPropertySetStorage(ppstg); | 
|  | return create_EnumSTATPROPSETSTG(This, ppenum); | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * Implement IEnumSTATPROPSETSTG using enumx | 
|  | */ | 
|  | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnQueryInterface( | 
|  | IEnumSTATPROPSETSTG *iface, | 
|  | REFIID riid, | 
|  | void** ppvObject) | 
|  | { | 
|  | return enumx_QueryInterface((enumx_impl*)iface, riid, ppvObject); | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI IEnumSTATPROPSETSTG_fnAddRef( | 
|  | IEnumSTATPROPSETSTG *iface) | 
|  | { | 
|  | return enumx_AddRef((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI IEnumSTATPROPSETSTG_fnRelease( | 
|  | IEnumSTATPROPSETSTG *iface) | 
|  | { | 
|  | return enumx_Release((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnNext( | 
|  | IEnumSTATPROPSETSTG *iface, | 
|  | ULONG celt, | 
|  | STATPROPSETSTG *rgelt, | 
|  | ULONG *pceltFetched) | 
|  | { | 
|  | return enumx_Next((enumx_impl*)iface, celt, rgelt, pceltFetched); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnSkip( | 
|  | IEnumSTATPROPSETSTG *iface, | 
|  | ULONG celt) | 
|  | { | 
|  | return enumx_Skip((enumx_impl*)iface, celt); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnReset( | 
|  | IEnumSTATPROPSETSTG *iface) | 
|  | { | 
|  | return enumx_Reset((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSETSTG_fnClone( | 
|  | IEnumSTATPROPSETSTG *iface, | 
|  | IEnumSTATPROPSETSTG **ppenum) | 
|  | { | 
|  | return enumx_Clone((enumx_impl*)iface, (enumx_impl**)ppenum); | 
|  | } | 
|  |  | 
|  | static HRESULT create_EnumSTATPROPSETSTG( | 
|  | StorageImpl *This, | 
|  | IEnumSTATPROPSETSTG** ppenum) | 
|  | { | 
|  | IStorage *stg = (IStorage*) &This->base.lpVtbl; | 
|  | IEnumSTATSTG *penum = NULL; | 
|  | STATSTG stat; | 
|  | ULONG count; | 
|  | HRESULT r; | 
|  | STATPROPSETSTG statpss; | 
|  | enumx_impl *enumx; | 
|  |  | 
|  | TRACE("%p %p\n", This, ppenum); | 
|  |  | 
|  | enumx = enumx_allocate(&IID_IEnumSTATPROPSETSTG, | 
|  | &IEnumSTATPROPSETSTG_Vtbl, | 
|  | sizeof (STATPROPSETSTG)); | 
|  |  | 
|  | /* add all the property set elements into a list */ | 
|  | r = IStorage_EnumElements(stg, 0, NULL, 0, &penum); | 
|  | if (FAILED(r)) | 
|  | return E_OUTOFMEMORY; | 
|  |  | 
|  | while (1) | 
|  | { | 
|  | count = 0; | 
|  | r = IEnumSTATSTG_Next(penum, 1, &stat, &count); | 
|  | if (FAILED(r)) | 
|  | break; | 
|  | if (!count) | 
|  | break; | 
|  | if (!stat.pwcsName) | 
|  | continue; | 
|  | if (stat.pwcsName[0] == 5 && stat.type == STGTY_STREAM) | 
|  | { | 
|  | PropStgNameToFmtId(stat.pwcsName, &statpss.fmtid); | 
|  | TRACE("adding %s (%s)\n", debugstr_w(stat.pwcsName), | 
|  | debugstr_guid(&statpss.fmtid)); | 
|  | statpss.mtime = stat.mtime; | 
|  | statpss.atime = stat.atime; | 
|  | statpss.ctime = stat.ctime; | 
|  | statpss.grfFlags = stat.grfMode; | 
|  | statpss.clsid = stat.clsid; | 
|  | enumx_add_element(enumx, &statpss); | 
|  | } | 
|  | CoTaskMemFree(stat.pwcsName); | 
|  | } | 
|  | IEnumSTATSTG_Release(penum); | 
|  |  | 
|  | *ppenum = (IEnumSTATPROPSETSTG*) enumx; | 
|  |  | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | /************************************************************************ | 
|  | * Implement IEnumSTATPROPSTG using enumx | 
|  | */ | 
|  | static HRESULT WINAPI IEnumSTATPROPSTG_fnQueryInterface( | 
|  | IEnumSTATPROPSTG *iface, | 
|  | REFIID riid, | 
|  | void** ppvObject) | 
|  | { | 
|  | return enumx_QueryInterface((enumx_impl*)iface, riid, ppvObject); | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI IEnumSTATPROPSTG_fnAddRef( | 
|  | IEnumSTATPROPSTG *iface) | 
|  | { | 
|  | return enumx_AddRef((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static ULONG WINAPI IEnumSTATPROPSTG_fnRelease( | 
|  | IEnumSTATPROPSTG *iface) | 
|  | { | 
|  | return enumx_Release((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSTG_fnNext( | 
|  | IEnumSTATPROPSTG *iface, | 
|  | ULONG celt, | 
|  | STATPROPSTG *rgelt, | 
|  | ULONG *pceltFetched) | 
|  | { | 
|  | return enumx_Next((enumx_impl*)iface, celt, rgelt, pceltFetched); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSTG_fnSkip( | 
|  | IEnumSTATPROPSTG *iface, | 
|  | ULONG celt) | 
|  | { | 
|  | return enumx_Skip((enumx_impl*)iface, celt); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSTG_fnReset( | 
|  | IEnumSTATPROPSTG *iface) | 
|  | { | 
|  | return enumx_Reset((enumx_impl*)iface); | 
|  | } | 
|  |  | 
|  | static HRESULT WINAPI IEnumSTATPROPSTG_fnClone( | 
|  | IEnumSTATPROPSTG *iface, | 
|  | IEnumSTATPROPSTG **ppenum) | 
|  | { | 
|  | return enumx_Clone((enumx_impl*)iface, (enumx_impl**)ppenum); | 
|  | } | 
|  |  | 
|  | static BOOL prop_enum_stat(const void *k, const void *v, void *extra, void *arg) | 
|  | { | 
|  | enumx_impl *enumx = arg; | 
|  | PROPID propid = (PROPID) k; | 
|  | const PROPVARIANT *prop = v; | 
|  | STATPROPSTG stat; | 
|  |  | 
|  | stat.lpwstrName = NULL; | 
|  | stat.propid = propid; | 
|  | stat.vt = prop->vt; | 
|  |  | 
|  | enumx_add_element(enumx, &stat); | 
|  |  | 
|  | return TRUE; | 
|  | } | 
|  |  | 
|  | static HRESULT create_EnumSTATPROPSTG( | 
|  | PropertyStorage_impl *This, | 
|  | IEnumSTATPROPSTG** ppenum) | 
|  | { | 
|  | enumx_impl *enumx; | 
|  |  | 
|  | TRACE("%p %p\n", This, ppenum); | 
|  |  | 
|  | enumx = enumx_allocate(&IID_IEnumSTATPROPSTG, | 
|  | &IEnumSTATPROPSTG_Vtbl, | 
|  | sizeof (STATPROPSTG)); | 
|  |  | 
|  | dictionary_enumerate(This->propid_to_prop, prop_enum_stat, enumx); | 
|  |  | 
|  | *ppenum = (IEnumSTATPROPSTG*) enumx; | 
|  |  | 
|  | return S_OK; | 
|  | } | 
|  |  | 
|  | /*********************************************************************** | 
|  | * 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, | 
|  | }; | 
|  |  | 
|  | static const IEnumSTATPROPSETSTGVtbl IEnumSTATPROPSETSTG_Vtbl = | 
|  | { | 
|  | IEnumSTATPROPSETSTG_fnQueryInterface, | 
|  | IEnumSTATPROPSETSTG_fnAddRef, | 
|  | IEnumSTATPROPSETSTG_fnRelease, | 
|  | IEnumSTATPROPSETSTG_fnNext, | 
|  | IEnumSTATPROPSETSTG_fnSkip, | 
|  | IEnumSTATPROPSETSTG_fnReset, | 
|  | IEnumSTATPROPSETSTG_fnClone, | 
|  | }; | 
|  |  | 
|  | static const IEnumSTATPROPSTGVtbl IEnumSTATPROPSTG_Vtbl = | 
|  | { | 
|  | IEnumSTATPROPSTG_fnQueryInterface, | 
|  | IEnumSTATPROPSTG_fnAddRef, | 
|  | IEnumSTATPROPSTG_fnRelease, | 
|  | IEnumSTATPROPSTG_fnNext, | 
|  | IEnumSTATPROPSTG_fnSkip, | 
|  | IEnumSTATPROPSTG_fnReset, | 
|  | IEnumSTATPROPSTG_fnClone, | 
|  | }; | 
|  |  | 
|  | /*********************************************************************** | 
|  | * 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. | 
|  | */ | 
|  | 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 | 
|  | { | 
|  | const BYTE *fmtptr; | 
|  | WCHAR *pstr = str; | 
|  | ULONG bitsRemaining = BITS_PER_BYTE; | 
|  |  | 
|  | *pstr++ = 5; | 
|  | for (fmtptr = (const BYTE *)rfmtid; fmtptr < (const 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 < (const 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. | 
|  | */ | 
|  | 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)) | 
|  | { | 
|  | *rfmtid = FMTID_DocSummaryInformation; | 
|  | hr = S_OK; | 
|  | } | 
|  | else if (!lstrcmpiW(str, szSummaryInfo)) | 
|  | { | 
|  | *rfmtid = FMTID_SummaryInformation; | 
|  | 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; | 
|  | } |