--- 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";