[PATCH 6/6] shell32: Properly implement IPropertyStore for shell links
Jonas Kümmerlin
rgcjonas at gmail.com
Tue Jul 14 10:07:46 CDT 2015
---
dlls/shell32/shelllink.c | 218 ++++++++++++++++++++++++++++----------
dlls/shell32/tests/shell32_test.h | 9 ++
dlls/shell32/tests/shelllink.c | 123 +++++++++++++++++++++
3 files changed, 292 insertions(+), 58 deletions(-)
diff --git a/dlls/shell32/shelllink.c b/dlls/shell32/shelllink.c
index bd3507e..71960a9 100644
--- a/dlls/shell32/shelllink.c
+++ b/dlls/shell32/shelllink.c
@@ -155,6 +155,8 @@ typedef struct
IUnknown *site;
LPOLESTR filepath; /* file path returned by IPersistFile::GetCurFile */
+
+ IPropertyStore *propStorage; /* In-memory property backing store */
} IShellLinkImpl;
static inline IShellLinkImpl *impl_from_IShellLinkA(IShellLinkA *iface)
@@ -577,6 +579,9 @@ static HRESULT Stream_ReadChunk( IStream* stm, LPVOID *data )
if( FAILED( r ) || count != sizeof(size) )
return E_FAIL;
+ if( size == 0 )
+ return S_FALSE;
+
chunk = HeapAlloc( GetProcessHeap(), 0, size );
if( !chunk )
return E_OUTOFMEMORY;
@@ -643,7 +648,7 @@ static HRESULT Stream_LoadLocation( IStream *stm,
DWORD n;
r = Stream_ReadChunk( stm, (LPVOID*) &p );
- if( FAILED(r) )
+ if( FAILED(r) || r == S_FALSE )
return r;
loc = (LOCATION_INFO*) p;
@@ -689,45 +694,30 @@ static HRESULT Stream_LoadLocation( IStream *stm,
* In the original Win32 implementation the buffers are not initialized
* to zero, so data trailing the string is random garbage.
*/
-static HRESULT Stream_LoadAdvertiseInfo( IStream* stm, LPWSTR *str )
+static HRESULT Stream_LoadAdvertiseInfo( EXP_DARWIN_LINK *chunk, LPWSTR *str )
{
- DWORD size;
- ULONG count;
- HRESULT r;
- EXP_DARWIN_LINK buffer;
-
- TRACE("%p\n",stm);
-
- r = IStream_Read( stm, &buffer.dbh.cbSize, sizeof (DWORD), &count );
- if( FAILED( r ) )
- return r;
+ TRACE("%p %p\n",chunk,str);
- /* make sure that we read the size of the structure even on error */
- size = sizeof buffer - sizeof (DWORD);
- if( buffer.dbh.cbSize != sizeof buffer )
+ if( chunk->dbh.cbSize != sizeof *chunk )
{
ERR("Ooops. This structure is not as expected...\n");
return E_FAIL;
}
- r = IStream_Read( stm, &buffer.dbh.dwSignature, size, &count );
- if( FAILED( r ) )
- return r;
+ /* ensure null termination */
+ chunk->szwDarwinID[MAX_PATH-1] = 0;
- if( count != size )
- return E_FAIL;
+ TRACE("magic %08x string = %s\n", chunk->dbh.dwSignature, debugstr_w(chunk->szwDarwinID));
- TRACE("magic %08x string = %s\n", buffer.dbh.dwSignature, debugstr_w(buffer.szwDarwinID));
-
- if( (buffer.dbh.dwSignature&0xffff0000) != 0xa0000000 )
+ if( (chunk->dbh.dwSignature&0xffff0000) != 0xa0000000 )
{
- ERR("Unknown magic number %08x in advertised shortcut\n", buffer.dbh.dwSignature);
+ ERR("Unknown magic number %08x in advertised shortcut\n", chunk->dbh.dwSignature);
return E_FAIL;
}
*str = HeapAlloc( GetProcessHeap(), 0,
- (lstrlenW(buffer.szwDarwinID)+1) * sizeof(WCHAR) );
- lstrcpyW( *str, buffer.szwDarwinID );
+ (lstrlenW(chunk->szwDarwinID)+1) * sizeof(WCHAR) );
+ lstrcpyW( *str, chunk->szwDarwinID );
return S_OK;
}
@@ -743,7 +733,7 @@ static HRESULT WINAPI IPersistStream_fnLoad(
ULONG dwBytesRead;
BOOL unicode;
HRESULT r;
- DWORD zero;
+ DATABLOCK_HEADER *extra = NULL;
IShellLinkImpl *This = impl_from_IPersistStream(iface);
@@ -860,33 +850,88 @@ static HRESULT WINAPI IPersistStream_fnLoad(
if( FAILED( r ) )
goto end;
- if( hdr.dwFlags & SLDF_HAS_LOGO3ID )
+ /* Extra data chunks can appear in any order, and there may be
+ * any number of them. We can make use of some of them, and will
+ * ignore the others
+ */
+ for (;;)
{
- r = Stream_LoadAdvertiseInfo( stm, &This->sProduct );
- TRACE("Product -> %s\n",debugstr_w(This->sProduct));
- }
- if( FAILED( r ) )
- goto end;
+ if ( extra )
+ HeapFree( GetProcessHeap(), 0, extra );
- if( hdr.dwFlags & SLDF_HAS_DARWINID )
- {
- r = Stream_LoadAdvertiseInfo( stm, &This->sComponent );
- TRACE("Component -> %s\n",debugstr_w(This->sComponent));
- }
- if( FAILED( r ) )
- goto end;
+ r = Stream_ReadChunk(stm, (void**)&extra);
+ if ( r == S_FALSE ) /* size zero chunk -> end of extra data */
+ break;
- r = IStream_Read(stm, &zero, sizeof zero, &dwBytesRead);
- if( FAILED( r ) || zero || dwBytesRead != sizeof zero )
- {
- /* Some lnk files have extra data blocks starting with a
- * DATABLOCK_HEADER. For instance EXP_SPECIAL_FOLDER and an unknown
- * one with a 0xa0000003 signature. However these don't seem to matter
- * too much.
- */
- WARN("Last word was not zero\n");
+ if ( FAILED( r ) )
+ break;
+
+ if ( extra->cbSize <= sizeof(*extra) )
+ {
+ WARN("Extra chunk has invalid size\n");
+ continue;
+ }
+
+ if ( extra->dwSignature == 0xA0000006 )
+ {
+ /* Darwin data block */
+ if ( !( hdr.dwFlags & SLDF_HAS_DARWINID ) )
+ {
+ WARN("Found unexpected DarwinDataBlock, parsing anyway\n");
+ }
+
+ if ( This->sComponent )
+ {
+ WARN("Found doubled DarwinDataBlock, ignoring\n");
+ }
+ else
+ {
+ r = Stream_LoadAdvertiseInfo( (EXP_DARWIN_LINK*)extra, &This->sComponent );
+ TRACE("Component -> %s\n",debugstr_w(This->sComponent));
+ }
+
+ if ( FAILED( r ) )
+ break;
+ }
+ else if ( extra->dwSignature == 0xA0000009 )
+ {
+ /* Property store data block */
+ IPersistSerializedPropStorage *serialized = NULL;
+
+ r = IPropertyStore_QueryInterface(This->propStorage,
+ &IID_IPersistSerializedPropStorage,
+ (void**)&serialized);
+ if ( FAILED( r ) )
+ {
+ WARN("QueryInterface(IPersistSerializedPropStorage): %08x\n", r);
+ break;
+ }
+
+ r = IPersistSerializedPropStorage_SetPropertyStorage(
+ serialized,
+ (PCUSERIALIZEDPROPSTORAGE)((EXP_PROPERTYSTORAGE*)extra)->abPropertyStorage,
+ extra->cbSize - 8);
+
+ IPersistSerializedPropStorage_Release(serialized);
+
+ if ( FAILED( r ) )
+ {
+ WARN("IPersistSerializedPropStorage::SetPropertyStorage: %08x\n", r);
+ break;
+ }
+ }
+ else
+ {
+ WARN("Extra data chunk with unknown signature 0x%08x, ignoring\n", extra->dwSignature);
+ }
}
+ if ( extra )
+ HeapFree( GetProcessHeap(), 0, extra);
+
+ if ( FAILED( r ) )
+ goto end;
+
TRACE("OK\n");
pdump (This->pPidl);
@@ -1011,6 +1056,44 @@ static HRESULT Stream_WriteAdvertiseInfo( IStream* stm, LPCWSTR string, DWORD ma
return IStream_Write( stm, buffer, buffer->dbh.cbSize, &count );
}
+static HRESULT Stream_WritePropertyStore( IStream *stm, IPropertyStore *store )
+{
+ DWORD size;
+ DWORD count;
+ SERIALIZEDPROPSTORAGE *storage = NULL;
+ DATABLOCK_HEADER header;
+ HRESULT r = S_OK;
+ IPersistSerializedPropStorage *serialized = NULL;
+
+ TRACE("%p %p\n", stm, store);
+
+ r = IPropertyStore_QueryInterface( store, &IID_IPersistSerializedPropStorage, (void**)&serialized );
+ if ( FAILED( r ) )
+ return r;
+
+ r = IPersistSerializedPropStorage_GetPropertyStorage(serialized, &storage, &size);
+ if ( FAILED( r ) )
+ goto end;
+
+ header.cbSize = size + sizeof(header);
+ header.dwSignature = 0xA0000009;
+
+ r = IStream_Write( stm, &header, sizeof(header), &count );
+ if ( FAILED( r ) || count != sizeof(header) )
+ goto end;
+
+ r = IStream_Write( stm, storage, size, &count );
+
+end:
+ if ( storage )
+ CoTaskMemFree( storage );
+
+ if ( serialized )
+ IPersistSerializedPropStorage_Release( serialized );
+
+ return r;
+}
+
/************************************************************************
* IPersistStream_Save (IPersistStream)
*
@@ -1104,6 +1187,8 @@ static HRESULT WINAPI IPersistStream_fnSave(
if( This->sComponent )
r = Stream_WriteAdvertiseInfo( stm, This->sComponent, EXP_DARWIN_ID_SIG );
+ r = Stream_WritePropertyStore( stm, This->propStorage );
+
/* the last field is a single zero dword */
zero = 0;
r = IStream_Write( stm, &zero, sizeof zero, &count );
@@ -1641,6 +1726,8 @@ static ULONG WINAPI IShellLinkW_fnRelease(IShellLinkW * iface)
if (This->pPidl)
ILFree(This->pPidl);
+ IPropertyStore_Release(This->propStorage);
+
LocalFree(This);
return 0;
@@ -2581,36 +2668,44 @@ static ULONG WINAPI propertystore_Release(IPropertyStore *iface)
static HRESULT WINAPI propertystore_GetCount(IPropertyStore *iface, DWORD *props)
{
IShellLinkImpl *This = impl_from_IPropertyStore(iface);
- FIXME("(%p)->(%p): stub\n", This, props);
- return E_NOTIMPL;
+
+ return IPropertyStore_GetCount(This->propStorage, props);
}
static HRESULT WINAPI propertystore_GetAt(IPropertyStore *iface, DWORD propid, PROPERTYKEY *key)
{
IShellLinkImpl *This = impl_from_IPropertyStore(iface);
- FIXME("(%p)->(%d %p): stub\n", This, propid, key);
- return E_NOTIMPL;
+
+ return IPropertyStore_GetAt(This->propStorage, propid, key);
}
static HRESULT WINAPI propertystore_GetValue(IPropertyStore *iface, REFPROPERTYKEY key, PROPVARIANT *value)
{
IShellLinkImpl *This = impl_from_IPropertyStore(iface);
- FIXME("(%p)->(%p %p): stub\n", This, key, value);
- return E_NOTIMPL;
+
+ return IPropertyStore_GetValue(This->propStorage, key, value);
}
static HRESULT WINAPI propertystore_SetValue(IPropertyStore *iface, REFPROPERTYKEY key, REFPROPVARIANT value)
{
IShellLinkImpl *This = impl_from_IPropertyStore(iface);
- FIXME("(%p)->(%p %p): stub\n", This, key, value);
- return E_NOTIMPL;
+ HRESULT r;
+
+ r = IPropertyStore_SetValue(This->propStorage, key, value);
+ if (SUCCEEDED(r))
+ This->bDirty = TRUE;
+
+ return r;
}
static HRESULT WINAPI propertystore_Commit(IPropertyStore *iface)
{
IShellLinkImpl *This = impl_from_IPropertyStore(iface);
+
+ /* The in-memory property store doesn't need it, we don't care, so just fake it */
+ /* or, preferably, research the correct semantics for the commit call */
FIXME("(%p): stub\n", This);
- return E_NOTIMPL;
+ return S_OK;
}
static const IPropertyStoreVtbl propertystorevtbl = {
@@ -2627,6 +2722,7 @@ static const IPropertyStoreVtbl propertystorevtbl = {
HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj)
{
IShellLinkImpl * sl;
+ IPropertyStore * propStorage;
HRESULT r;
TRACE("outer=%p riid=%s\n", outer, debugstr_guid(riid));
@@ -2636,6 +2732,11 @@ HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj)
if (outer)
return CLASS_E_NOAGGREGATION;
+ r = CoCreateInstance(&CLSID_InMemoryPropertyStore, NULL, CLSCTX_INPROC_SERVER,
+ &IID_IPropertyStore, (void**)&propStorage);
+ if (FAILED(r))
+ return E_FAIL;
+
sl = LocalAlloc(LMEM_ZEROINIT,sizeof(IShellLinkImpl));
if (!sl)
return E_OUTOFMEMORY;
@@ -2655,6 +2756,7 @@ HRESULT WINAPI IShellLink_Constructor(IUnknown *outer, REFIID riid, void **obj)
sl->iIdOpen = -1;
sl->site = NULL;
sl->filepath = NULL;
+ sl->propStorage = propStorage;
TRACE("(%p)\n", sl);
diff --git a/dlls/shell32/tests/shell32_test.h b/dlls/shell32/tests/shell32_test.h
index 42a74fb..d3ade95 100644
--- a/dlls/shell32/tests/shell32_test.h
+++ b/dlls/shell32/tests/shell32_test.h
@@ -18,10 +18,17 @@
* Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
*/
+#include "propsys.h"
/* Helper function for creating .lnk files */
typedef struct
{
+ PROPERTYKEY key;
+ PROPVARIANT value;
+} lnk_prop_t;
+
+typedef struct
+{
const char* description;
const char* workdir;
const char* path;
@@ -31,6 +38,8 @@ typedef struct
const char* icon;
int icon_id;
WORD hotkey;
+ ULONG c_props;
+ lnk_prop_t *props;
} lnk_desc_t;
#define create_lnk(a,b,c) create_lnk_(__LINE__, (a), (b), (c))
diff --git a/dlls/shell32/tests/shelllink.c b/dlls/shell32/tests/shelllink.c
index b258052..21513e0 100644
--- a/dlls/shell32/tests/shelllink.c
+++ b/dlls/shell32/tests/shelllink.c
@@ -35,6 +35,11 @@
# define SLDF_HAS_LOGO3ID 0x00000800 /* not available in the Vista SDK */
#endif
+#include "initguid.h"
+#include "propkey.h"
+
+DEFINE_PROPERTYKEY(PKEY_WineTest, 0x7b317433, 0xdfa3, 0x4c44, 0xad, 0x3e, 0x2f, 0x80, 0x4b, 0x90, 0xdb, 0xf4, 2);
+
static void (WINAPI *pILFree)(LPITEMIDLIST);
static BOOL (WINAPI *pILIsEqual)(LPCITEMIDLIST, LPCITEMIDLIST);
static HRESULT (WINAPI *pSHILCreateFromPath)(LPCWSTR, LPITEMIDLIST *,DWORD*);
@@ -100,6 +105,7 @@ static void test_get_set(void)
HRESULT r;
IShellLinkA *sl;
IShellLinkW *slW = NULL;
+ IPropertyStore *ps = NULL;
char mypath[MAX_PATH];
char buffer[INFOTIPSIZE];
LPITEMIDLIST pidl, tmp_pidl;
@@ -342,6 +348,36 @@ static void test_get_set(void)
ok(r == S_OK, "GetHotkey failed (0x%08x)\n", r);
ok(w==0x5678, "GetHotkey returned %d'\n", w);
+ /* Test the IPropertyStore interface provided since 7 */
+ r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps);
+ if (FAILED(r))
+ {
+ win_skip("IPropertyStore not available for shell links\n");
+ }
+ else
+ {
+ PROPVARIANT var;
+ WCHAR id[] = { 'W','i','n','e','.','T','e','s','t','.','H','e','l','l','o',0 };
+
+ ok(ps != NULL, "WTF: Got NULL property store\n");
+
+ /* write a simple and common property */
+ var.vt = VT_LPWSTR;
+ U(var).pwszVal = id;
+ r = IPropertyStore_SetValue(ps, &PKEY_AppUserModel_ID, &var);
+ ok(r == S_OK, "IPropertyStore::SetValue failed (0x%08x)\n", r);
+
+ /* and immediately read it back */
+ r = IPropertyStore_GetValue(ps, &PKEY_AppUserModel_ID, &var);
+ ok(r == S_OK, "IPropertyStore::GetValue failed(0x%08x)\n", r);
+ ok(var.vt == VT_LPWSTR, "Wrong variant type %d has been returned\n", (int)var.vt);
+ ok(!lstrcmpW(U(var).pwszVal, id), "Wrong result %s returned\n", wine_dbgstr_w(U(var).pwszVal));
+
+ PropVariantClear(&var);
+
+ IPropertyStore_Release(ps);
+ }
+
IShellLinkA_Release(sl);
}
@@ -412,6 +448,34 @@ void create_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int save_fails)
lok(r == S_OK, "SetHotkey failed (0x%08x)\n", r);
}
+ if (desc->c_props)
+ {
+ IPropertyStore *ps = NULL;
+ r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps);
+ if (FAILED(r))
+ {
+ win_skip("IPropertyStore not availble for shell links\n");
+ }
+ else
+ {
+ ULONG i;
+ for (i = 0; i < desc->c_props; ++i)
+ {
+ r = IPropertyStore_SetValue(ps, &desc->props[i].key, &desc->props[i].value);
+ lok(r == S_OK, "IPropertyStore::SetValue failed (0x%08x)\n", r);
+ }
+
+ /* On windows, this is sometimes necessary so that all properties are
+ * saved in IPersistFile::Save. But sometimes, it works well without
+ * committing. It remains a mystery Wine will most likely not replicate.
+ */
+ r = IPropertyStore_Commit(ps);
+ lok(r == S_OK, "IPropertyStore::Commit failed(0x%08x)\n", r);
+
+ IPropertyStore_Release(ps);
+ }
+ }
+
r = IShellLinkA_QueryInterface(sl, &IID_IPersistFile, (void**)&pf);
lok(r == S_OK, "no IID_IPersistFile (0x%08x)\n", r);
if (r == S_OK)
@@ -594,6 +658,52 @@ static void check_lnk_(int line, const WCHAR* path, lnk_desc_t* desc, int todo)
"GetHotkey returned 0x%04x instead of 0x%04x\n",
i, desc->hotkey);
}
+ if (desc->c_props)
+ {
+ IPropertyStore *ps = NULL;
+ r = IShellLinkA_QueryInterface(sl, &IID_IPropertyStore, (void**)&ps);
+ if (FAILED(r))
+ {
+ win_skip("IPropertyStore not availble for shell links\n");
+ }
+ else
+ {
+ ULONG i;
+ PROPVARIANT v;
+ HMODULE hPropsys = NULL;
+ INT WINAPI (*pPropVariantCompareEx)(
+ REFPROPVARIANT propvar1, REFPROPVARIANT propvar2,
+ int uint, int flags) = NULL;
+
+ hPropsys = LoadLibraryA("propsys.dll");
+ if (hPropsys)
+ {
+ pPropVariantCompareEx = (void*)GetProcAddress(hPropsys, "PropVariantCompareEx");
+ }
+
+ for (i = 0; i < desc->c_props; ++i)
+ {
+ r = IPropertyStore_GetValue(ps, &desc->props[i].key, &v);
+ ok(r == S_OK, "IPropertyStore::GetValue failed (0x%08x)\n", r);
+
+ if (!pPropVariantCompareEx)
+ {
+ win_skip("PropVariantCompareEx not available, can't compare property\n");
+ }
+ else
+ {
+ lok(!pPropVariantCompareEx(&v, &desc->props[i].value, 0, 0),
+ "Property differs from expected one, found type %d, expected %d\n",
+ v.vt, desc->props[i].value.vt);
+ }
+
+ PropVariantClear(&v);
+ }
+
+ FreeLibrary(hPropsys);
+ IPropertyStore_Release(ps);
+ }
+ }
IShellLinkA_Release(sl);
}
@@ -603,6 +713,9 @@ static void test_load_save(void)
WCHAR lnkfile[MAX_PATH];
char lnkfileA[MAX_PATH];
static const char lnkfileA_name[] = "\\test.lnk";
+ static WCHAR aum_id[] = { 'W','i','n','e','.','T','e','s','t',0 };
+
+ lnk_prop_t props[2];
lnk_desc_t desc;
char mypath[MAX_PATH];
@@ -635,6 +748,16 @@ static void test_load_save(void)
desc.icon="";
check_lnk(lnkfile, &desc, 0x0);
+ /* Spice up future tests by introducing properties */
+ props[0].key = PKEY_AppUserModel_ID;
+ props[0].value.vt = VT_LPWSTR;
+ U(props[0].value).pwszVal = aum_id;
+ props[1].key = PKEY_WineTest;
+ props[1].value.vt = VT_UI4;
+ U(props[1].value).ulVal = 0xCAFEBABE;
+ desc.props = props;
+ desc.c_props = 2;
+
/* Point a .lnk file to nonexistent files */
desc.description="";
desc.workdir="c:\\Nonexitent\\work\\directory";
--
2.4.3
More information about the wine-devel
mailing list