[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