-- v2: urlmon: Add PersistentZoneIdentifier COM object
From: Mike Kozelkov augenzi@etersoft.ru
--- dlls/urlmon/Makefile.in | 3 +- dlls/urlmon/urlmon_main.c | 3 + dlls/urlmon/urlmon_main.h | 1 + dlls/urlmon/urlmon_urlmon.idl | 7 + dlls/urlmon/zone_id.c | 505 ++++++++++++++++++++++++++++++++++ include/urlmon.idl | 18 ++ 6 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 dlls/urlmon/zone_id.c
diff --git a/dlls/urlmon/Makefile.in b/dlls/urlmon/Makefile.in index ba6f1db87ad..1a2f73d4c36 100644 --- a/dlls/urlmon/Makefile.in +++ b/dlls/urlmon/Makefile.in @@ -26,7 +26,8 @@ SOURCES = \ urlmon.rc \ urlmon_main.c \ urlmon_urlmon.idl \ - usrmarshal.c + usrmarshal.c \ + zone_id.c
dlldata_EXTRADEFS = -DENTRY_PREFIX=URLMON_ -DPROXY_DELEGATION -DWINE_REGISTER_DLL \ -DPROXY_CLSID_IS="{0x79EAC9F1,0xBAF9,0x11CE,{0x8C,0x82,0x00,0xAA,0x00,0x4B,0xA9,0x0B}}" diff --git a/dlls/urlmon/urlmon_main.c b/dlls/urlmon/urlmon_main.c index c5a8fdcffa2..806a893d7d1 100644 --- a/dlls/urlmon/urlmon_main.c +++ b/dlls/urlmon/urlmon_main.c @@ -351,6 +351,8 @@ static const IClassFactoryVtbl ClassFactoryVtbl = CF_LockServer };
+static ClassFactory PersistentZoneIdentifierCF = + { { &ClassFactoryVtbl }, PersistentZoneIdentifier_Construct}; static ClassFactory FileProtocolCF = { { &ClassFactoryVtbl }, FileProtocol_Construct}; static ClassFactory FtpProtocolCF = @@ -383,6 +385,7 @@ struct object_creation_info
static const struct object_creation_info object_creation[] = { + { &CLSID_PersistentZoneIdentifier, &PersistentZoneIdentifierCF.IClassFactory_iface, NULL }, { &CLSID_FileProtocol, &FileProtocolCF.IClassFactory_iface, L"file" }, { &CLSID_FtpProtocol, &FtpProtocolCF.IClassFactory_iface, L"ftp" }, { &CLSID_GopherProtocol, &GopherProtocolCF.IClassFactory_iface, L"gopher" }, diff --git a/dlls/urlmon/urlmon_main.h b/dlls/urlmon/urlmon_main.h index 81b0d629f53..ceb3e09b761 100644 --- a/dlls/urlmon/urlmon_main.h +++ b/dlls/urlmon/urlmon_main.h @@ -34,6 +34,7 @@ #include "wine/list.h"
extern HINSTANCE hProxyDll; +extern HRESULT PersistentZoneIdentifier_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); extern HRESULT SecManagerImpl_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); extern HRESULT ZoneMgrImpl_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); extern HRESULT StdURLMoniker_Construct(IUnknown *pUnkOuter, LPVOID *ppobj); diff --git a/dlls/urlmon/urlmon_urlmon.idl b/dlls/urlmon/urlmon_urlmon.idl index c25bd8f34ae..d557708c6ca 100644 --- a/dlls/urlmon/urlmon_urlmon.idl +++ b/dlls/urlmon/urlmon_urlmon.idl @@ -111,3 +111,10 @@ coclass DeCompMimeFilter { interface IInternetProtocol; interface IInternetProto uuid(df2fce13-25ec-45bb-9d4c-cecd47c2430c) ] coclass CUri { interface IUri; } + +[ + helpstring("Persistent Zone Identifier"), + threading(both), + uuid(0968e258-16c7-4dba-aa86-462dd61e31a3) +] +coclass PersistentZoneIdentifier { interface IPersistFile; interface IZoneIdentifier; } diff --git a/dlls/urlmon/zone_id.c b/dlls/urlmon/zone_id.c new file mode 100644 index 00000000000..ee16d1d971c --- /dev/null +++ b/dlls/urlmon/zone_id.c @@ -0,0 +1,505 @@ +#include "urlmon_main.h" +#include "winreg.h" +#include "shlwapi.h" + +#include "wine/debug.h" + +#define PZI_CURRENT_FILE_VERSION 0x0001 + +WINE_DEFAULT_DEBUG_CHANNEL(urlmon); + +typedef struct +{ + USHORT product_version; + USHORT file_version; + URLZONE zone; +} FIXDLEN_DATA; + +typedef struct { + IUnknown IUnknown_inner; + IPersistFile IPersistFile_iface; + IZoneIdentifier IZoneIdentifier_iface; + + BOOL is_dirty; + LPWSTR file_name; + + URLZONE zone; + + IUnknown *outer; + + LONG ref; +} PersistentZoneIdentifier; + +static inline PersistentZoneIdentifier *impl_from_IUnknown(IUnknown *iface) +{ + return CONTAINING_RECORD(iface, PersistentZoneIdentifier, IUnknown_inner); +} + +static inline PersistentZoneIdentifier *impl_from_IPersistFile(IPersistFile *iface) +{ + return CONTAINING_RECORD(iface, PersistentZoneIdentifier, IPersistFile_iface); +} + +static inline PersistentZoneIdentifier *impl_from_IZoneIdentifier(IZoneIdentifier *iface) +{ + return CONTAINING_RECORD(iface, PersistentZoneIdentifier, IZoneIdentifier_iface); +} + +static HRESULT WINAPI PZIUnk_QueryInterface(IUnknown *iface, REFIID riid, void **ppv) +{ + PersistentZoneIdentifier *This = impl_from_IUnknown(iface); + + *ppv = NULL; + + if (IsEqualGUID(&IID_IUnknown, riid)) + { + TRACE("(%p)->(IID_IUnknown %p)\n", This, ppv); + *ppv = &This->IUnknown_inner; + } else if (IsEqualGUID(&IID_IPersist, riid)) + { + TRACE("(%p)->(IID_IPersist %p)\n", This, ppv); + *ppv = &This->IPersistFile_iface; + } else if (IsEqualGUID(&IID_IPersistFile, riid)) + { + TRACE("(%p)->(IID_IPersistFile %p)\n", This, ppv); + *ppv = &This->IPersistFile_iface; + } else if (IsEqualGUID(&IID_IZoneIdentifier, riid)) + { + TRACE("(%p)->(IID_IZoneIdentifier %p)\n", This, ppv); + *ppv = &This->IZoneIdentifier_iface; + } + + if (*ppv) + { + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; + } + + WARN("not supported interface %s\n", debugstr_guid(riid)); + return E_NOINTERFACE; +} + +static ULONG WINAPI PZIUnk_AddRef(IUnknown *iface) +{ + PersistentZoneIdentifier *This = impl_from_IUnknown(iface); + LONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + return ref; +} + +static ULONG WINAPI PZIUnk_Release(IUnknown *iface) +{ + PersistentZoneIdentifier *This = impl_from_IUnknown(iface); + LONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + if (!ref) + { + URLMON_UnlockModule(); + } + + return ref; +} + +static const IUnknownVtbl PZIUnkVtbl = { + PZIUnk_QueryInterface, + PZIUnk_AddRef, + PZIUnk_Release +}; + +static HRESULT WINAPI PZIPersistFile_QueryInterface(IPersistFile *iface, REFIID riid, void **ppv) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p, %s %p)\n", This, debugstr_guid(riid), ppv); + + return IUnknown_QueryInterface(This->outer, riid, ppv); +} + +static ULONG WINAPI PZIPersistFile_AddRef(IPersistFile *iface) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p)\n", This); + + return IUnknown_AddRef(This->outer); +} + +static ULONG WINAPI PZIPersistFile_Release(IPersistFile *iface) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p)\n", This); + + return IUnknown_Release(This->outer); +} + +static HRESULT WINAPI PZIPersistFile_GetClassID(IPersistFile *iface, CLSID *clsid) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p, %p)\n", This, clsid); + + *clsid = CLSID_PersistentZoneIdentifier; + + return S_OK; +} + +static HRESULT WINAPI PZIPersistFile_GetCurFile(IPersistFile *iface, LPOLESTR *file_name) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p, %p)\n", This, file_name); + + *file_name = CoTaskMemAlloc((lstrlenW(This->file_name) + 1) * sizeof(WCHAR)); + if (!*file_name) + { + return E_OUTOFMEMORY; + } + + lstrcpyW(*file_name, This->file_name); + + return S_OK; +} + +static HRESULT WINAPI PZIPersistFile_IsDirty(IPersistFile *iface) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + + TRACE("(%p)\n", This); + + return This->is_dirty ? S_OK : S_FALSE; +} + +static HRESULT load_zone_id_data(PersistentZoneIdentifier *This, BYTE *data, DWORD size) +{ + const FIXDLEN_DATA *fixed; + + if (size < sizeof(*fixed)) + { + TRACE("no space for FIXDLEN_DATA\n"); + return E_OUTOFMEMORY; + } + + fixed = (const FIXDLEN_DATA*)data; + + TRACE("product_version %04x\n", fixed->product_version); + TRACE("file_version %04x\n", fixed->file_version); + + TRACE("zone %08x\n", fixed->zone); + This->zone = fixed->zone; + + return S_OK; +} + +static HRESULT WINAPI PZIPersistFile_Load(IPersistFile *iface, LPCOLESTR file_name, DWORD mode) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + HANDLE mapping; + HANDLE hfile; + DWORD sharing; + DWORD access; + DWORD size; + DWORD try; + void* data; + HRESULT hres; + + TRACE("(%p, %s, 0x%08lx)\n", iface, debugstr_w(file_name), mode); + + switch (mode & 0x000f) + { + default: + case STGM_READ: + access = GENERIC_READ; + break; + case STGM_WRITE: + case STGM_READWRITE: + access = GENERIC_READ | GENERIC_WRITE; + break; + } + + switch (mode & 0x00f0) + { + default: + case STGM_SHARE_DENY_NONE: + sharing = FILE_SHARE_READ | FILE_SHARE_WRITE; + break; + case STGM_SHARE_DENY_READ: + sharing = FILE_SHARE_WRITE; + break; + case STGM_SHARE_DENY_WRITE: + sharing = FILE_SHARE_READ; + break; + case STGM_SHARE_EXCLUSIVE: + sharing = 0; + break; + } + + try = 1; + for (;;) + { + hfile = CreateFileW(file_name, access, sharing, NULL, OPEN_EXISTING, 0, NULL); + if (hfile != INVALID_HANDLE_VALUE) { break; } + + if (GetLastError() != ERROR_SHARING_VIOLATION || try++ >= 3) + { + TRACE("Failed to open %s, error %lu\n", debugstr_w(file_name), GetLastError()); + return HRESULT_FROM_WIN32(GetLastError()); + } + Sleep(100); + } + + size = GetFileSize(hfile, NULL); + + mapping = CreateFileMappingW(hfile, NULL, PAGE_READONLY, 0, 0, 0); + if (!mapping) + { + TRACE("Failed to create file mapping %s, error %lu\n", debugstr_w(file_name), GetLastError()); + CloseHandle(hfile); + return HRESULT_FROM_WIN32(GetLastError()); + } + + data = MapViewOfFile(mapping, FILE_MAP_READ, 0, 0, 0); + if (data) + { + hres = load_zone_id_data(This, data, size); + if (hres == S_OK) This->is_dirty = FALSE; + UnmapViewOfFile(data); + } + else + { + hres = HRESULT_FROM_WIN32(GetLastError()); + } + + CloseHandle(mapping); + CloseHandle(hfile); + + return hres; +} + +static HRESULT WINAPI PZIPersistFile_Save(IPersistFile *iface, LPCOLESTR file_name, BOOL remember) +{ + PersistentZoneIdentifier *This = impl_from_IPersistFile(iface); + FIXDLEN_DATA fixed; + DWORD disposition; + DWORD size; + DWORD try; + DWORD ver; + HANDLE hfile; + HRESULT hres; + + TRACE("(%p, %s, %d)\n", iface, debugstr_w(file_name), remember); + + disposition = file_name ? CREATE_NEW : OPEN_ALWAYS; + + if (!file_name) + { + file_name = This->file_name; + remember = FALSE; + } + + try = 1; + for (;;) + { + hfile = CreateFileW(file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, disposition, 0, NULL); + if (hfile != INVALID_HANDLE_VALUE) { break; } + + if (try++ >= 3) + { + hres = HRESULT_FROM_WIN32(GetLastError()); + goto cleanup; + } + Sleep(100); + } + + ver = GetVersion(); + fixed.product_version = MAKEWORD(ver >> 8, ver); + fixed.file_version = PZI_CURRENT_FILE_VERSION; + fixed.zone = This->zone; + + if (!WriteFile(hfile, &fixed, sizeof(fixed), &size, NULL)) + { + hres = HRESULT_FROM_WIN32(GetLastError()); + goto cleanup; + } + + hres = S_OK; + This->is_dirty = FALSE; + +cleanup: + if (hfile != INVALID_HANDLE_VALUE) + { + CloseHandle(hfile); + if (hres != S_OK) + { + DeleteFileW(file_name); + } + else if (remember) + { + if (This->file_name) { CoTaskMemFree(This->file_name); } + This->file_name = wcsdup(file_name); + } + } + + return hres; +} + +static HRESULT WINAPI PZIPersistFile_SaveCompleted( + IPersistFile* iface, + LPCOLESTR pszFileName) +{ + FIXME("(%p, %p) not implemented\n", iface, pszFileName); + + return E_NOTIMPL; +} + +static const IPersistFileVtbl PZIPersistFileVtbl = { + PZIPersistFile_QueryInterface, + PZIPersistFile_AddRef, + PZIPersistFile_Release, + PZIPersistFile_GetClassID, + PZIPersistFile_IsDirty, + PZIPersistFile_Load, + PZIPersistFile_Save, + PZIPersistFile_SaveCompleted, + PZIPersistFile_GetCurFile +}; + +static HRESULT WINAPI PZIZoneId_QueryInterface(IZoneIdentifier *iface, REFIID riid, void **ppv) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p, %s %p)\n", This, debugstr_guid(riid), ppv); + + return IUnknown_QueryInterface(This->outer, riid, ppv); +} + +static ULONG WINAPI PZIZoneId_AddRef(IZoneIdentifier *iface) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p)\n", This); + + return IUnknown_AddRef(This->outer); +} + +static ULONG WINAPI PZIZoneId_Release(IZoneIdentifier *iface) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p)\n", This); + + return IUnknown_Release(This->outer); +} + +static BOOL is_trusted_zone(URLZONE zone) +{ + switch (zone) + { + case URLZONE_INVALID: + case URLZONE_LOCAL_MACHINE: + case URLZONE_INTRANET: + case URLZONE_TRUSTED: + return TRUE; + default: + return FALSE; + } +} + +static BOOL is_known_zone(URLZONE zone) +{ + switch (zone) + { + case URLZONE_INVALID: + case URLZONE_LOCAL_MACHINE: + case URLZONE_INTRANET: + case URLZONE_TRUSTED: + case URLZONE_INTERNET: + case URLZONE_UNTRUSTED: + return TRUE; + default: + return FALSE; + } +} + +static HRESULT WINAPI PZIZoneId_GetId(IZoneIdentifier* iface, DWORD* pdwZone) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p, %p)\n", This, pdwZone); + + *pdwZone = This->zone; + + return is_trusted_zone(*pdwZone) ? S_OK : E_ACCESSDENIED; +} + +static HRESULT WINAPI PZIZoneId_Remove(IZoneIdentifier* iface) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p)\n", This); + + This->zone = URLZONE_LOCAL_MACHINE; + + return S_OK; +} + +static HRESULT WINAPI PZIZoneId_SetId(IZoneIdentifier* iface, DWORD dwZone) +{ + PersistentZoneIdentifier *This = impl_from_IZoneIdentifier(iface); + + TRACE("(%p, 0x%08lx)\n", This, dwZone); + + This->zone = dwZone; + This->is_dirty = TRUE; + + if (is_trusted_zone(This->zone)) + { + return S_OK; + } else if (!is_known_zone(This->zone)) + { + FIXME("Unknown zone identifier: 0x%08x\n", This->zone); + } + + return E_ACCESSDENIED; +} + +static const IZoneIdentifierVtbl PZIZoneIdVtbl = { + PZIZoneId_QueryInterface, + PZIZoneId_AddRef, + PZIZoneId_Release, + PZIZoneId_GetId, + PZIZoneId_SetId, + PZIZoneId_Remove +}; + +HRESULT PersistentZoneIdentifier_Construct(IUnknown *outer, LPVOID *ppobj) +{ + + PersistentZoneIdentifier *ret; + + TRACE("(%p %p)\n", outer, ppobj); + + URLMON_LockModule(); + + ret = malloc(sizeof(PersistentZoneIdentifier)); + + ret->IUnknown_inner.lpVtbl = &PZIUnkVtbl; + ret->IPersistFile_iface.lpVtbl = &PZIPersistFileVtbl; + ret->IZoneIdentifier_iface.lpVtbl = &PZIZoneIdVtbl; + + ret->file_name = NULL; + ret->is_dirty = FALSE; + + ret->zone = URLZONE_INVALID; + + ret->ref = 1; + ret->outer = outer ? outer : &ret->IUnknown_inner; + + *ppobj = &ret->IUnknown_inner; + + return S_OK; +} diff --git a/include/urlmon.idl b/include/urlmon.idl index 0aab6588658..04b6462d03a 100644 --- a/include/urlmon.idl +++ b/include/urlmon.idl @@ -1299,6 +1299,23 @@ interface IInternetHostSecurityManager : IUnknown [in] DWORD dwReserved); }
+/***************************************************************************** + * IZoneIdentifier interface + */ +[ + object, + uuid(cd45f185-1b21-48e2-967b-ead743a8914e), + pointer_default(unique) +] +interface IZoneIdentifier : IUnknown +{ + HRESULT GetId( + [out] DWORD *pdwZone); + HRESULT SetId( + [in] DWORD dwZone); + HRESULT Remove(); +}; + cpp_quote("#define URLACTION_MIN 0x00001000") cpp_quote("#define URLACTION_DOWNLOAD_MIN 0x00001000") cpp_quote("#define URLACTION_DOWNLOAD_SIGNED_ACTIVEX 0x00001001") @@ -2148,6 +2165,7 @@ cpp_quote("#define UAS_EXACTLEGACY 0x1000")
cpp_quote("EXTERN_C const GUID GUID_CUSTOM_CONFIRMOBJECTSAFETY;")
+cpp_quote("DEFINE_GUID(CLSID_PersistentZoneIdentifier, 0x0968e258, 0x16c7, 0x4dba, 0xaa, 0x86, 0x46, 0x2d, 0xd6, 0x1e, 0x31, 0xa3);") cpp_quote("DEFINE_GUID(CLSID_InternetSecurityManager, 0x7b8a2d94, 0x0ac9, 0x11d1, 0x89, 0x6c, 0x00, 0xc0, 0x4f, 0xB6, 0xbf, 0xc4);") cpp_quote("DEFINE_GUID(CLSID_InternetZoneManager, 0x7B8A2D95, 0x0AC9, 0x11D1, 0x89, 0x6C, 0x00, 0xC0, 0x4F, 0xB6, 0xBF, 0xC4);") cpp_quote("DEFINE_GUID(IID_IAsyncMoniker, 0x79EAC9D3, 0xBAF9, 0x11CE, 0x8C, 0x82, 0x00, 0xAA, 0x00, 0x4B, 0xA9, 0x0B);")
Do you have an application that needs this?
On Thu Jun 19 20:22:06 2025 +0000, Nikolay Sivov wrote:
Do you have an application that needs this?
MAX messenger calls IZoneIdentifier::SetId and IPersistFile::Save
Jacek Caban (@jacek) commented about dlls/urlmon/zone_id.c:
remember = FALSE;
- }
- try = 1;
- for (;;)
- {
hfile = CreateFileW(file_name, GENERIC_READ | GENERIC_WRITE, 0, NULL, disposition, 0, NULL);
if (hfile != INVALID_HANDLE_VALUE) { break; }
if (try++ >= 3)
{
hres = HRESULT_FROM_WIN32(GetLastError());
goto cleanup;
}
Sleep(100);
- }
Why is it needed? Could we just return failure on the first attempt?
Please add a test case.
On Fri Jun 20 13:05:34 2025 +0000, Jacek Caban wrote:
Why is it needed? Could we just return failure on the first attempt?
Another thread may keep open file with given name for reading/writing, so we have to wait some time for file availability or fail if the number of attempts is exceeded
On Mon Jun 23 09:28:30 2025 +0000, Jacek Caban wrote:
Please add a test case.
Should I add stubs, test cases and implementation in three merge requests?
On Mon Jun 23 09:26:55 2025 +0000, Mike Kozelkov wrote:
Another thread may keep open file with given name for reading/writing, so we have to wait some time for file availability or fail if the number of attempts is exceeded
The usual approach is to fail immediately. If the caller intends to wait in such cases, it can do so by retrying on its own. Sleeping inside a function that's otherwise non-blocking doesn't seem appropriate.
On Mon Jun 23 09:28:30 2025 +0000, Mike Kozelkov wrote:
Should I add stubs, test cases and implementation in three merge requests?
Splitting would be nice. There's no need for a separate MRs, you can split it into separate commits within the same one.
On Mon Jun 23 11:11:52 2025 +0000, Jacek Caban wrote:
The usual approach is to fail immediately. If the caller intends to wait in such cases, it can do so by retrying on its own. Sleeping inside a function that's otherwise non-blocking doesn't seem appropriate.
Also there is no need to map a file, only to read 4-8 bytes.
On Mon Jun 23 11:12:58 2025 +0000, Jacek Caban wrote:
Splitting would be nice. There's no need for a separate MRs, you can split it into separate commits within the same one.
While writing tests I found out that my implementation of Save and Load methods is incorrect. These methods shouldn't write and read zone information from the main file data stream. OS Windows uses NTFS alternative data stream Zone.Identifier to save and load zone information. Right now I have to look for a solution for a while. Actually, the main idea of the implementation was to make MAX messenger work (without PersistZoneIdentifier it crashes). MAX messenger can work with PersistZoneIdentifier stubs. So, is it ok to add only stubs in another merge request to make it merge faster and add here only tests and implementation?
On Thu Jun 26 12:45:58 2025 +0000, Mike Kozelkov wrote:
While writing tests I found out that my implementation of Save and Load methods is incorrect. These methods shouldn't write and read zone information from the main file data stream. OS Windows uses NTFS alternative data stream Zone.Identifier to save and load zone information. Right now I have to look for a solution for a while. Actually, the main idea of the implementation was to make MAX messenger work (without PersistZoneIdentifier it crashes). MAX messenger can work with PersistZoneIdentifier stubs. So, is it ok to add only stubs in another merge request to make it merge faster and add here only tests and implementation?
Yes, a stub is fine (and you may use this MR for that).