[PATCH v3 0/4] MR10708: mfplat: Strip leading slashes from the URL in source_resolver_CreateObjectFromURL().
-- v3: mfplat: Normalise URLs before passing to BeginCreateObject(). mfplat/tests: Test leading forward slashes and colons in filename URLs. mfplat/tests: Create the new file in the temp path in test_file_stream(). mf/scheme_handler: Call PathCreateFromUrlW() for URL to path conversion. https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mf/Makefile.in | 2 +- dlls/mf/scheme_handler.c | 11 +++++++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/dlls/mf/Makefile.in b/dlls/mf/Makefile.in index d9f0123767d..9e85615f3b0 100644 --- a/dlls/mf/Makefile.in +++ b/dlls/mf/Makefile.in @@ -1,6 +1,6 @@ MODULE = mf.dll IMPORTLIB = mf -IMPORTS = advapi32 mfplat ole32 uuid mfuuid strmiids +IMPORTS = advapi32 mfplat ole32 uuid mfuuid strmiids kernelbase DELAYIMPORTS = evr urlmon user32 EXTRADLLFLAGS = -Wb,--prefer-native diff --git a/dlls/mf/scheme_handler.c b/dlls/mf/scheme_handler.c index 7e92748aeb9..d049060ee96 100644 --- a/dlls/mf/scheme_handler.c +++ b/dlls/mf/scheme_handler.c @@ -23,6 +23,8 @@ #include "windef.h" #include "winbase.h" +#include "shlwapi.h" + #include "evr.h" #include "mfidl.h" #include "mf_private.h" @@ -454,8 +456,13 @@ static const IMFAsyncCallbackVtbl scheme_handler_callback_vtbl = static HRESULT file_stream_create(const WCHAR *url, DWORD flags, IMFByteStream **out) { - if (!wcsnicmp(url, L"file://", 7)) - url += 7; + WCHAR path[MAX_PATH]; + DWORD len; + + len = ARRAY_SIZE(path); + if (SUCCEEDED(PathCreateFromUrlW(url, path, &len, 0))) + url = path; + return MFCreateFile(flags & MF_RESOLUTION_WRITE ? MF_ACCESSMODE_READWRITE : MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, url, out); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
From: Conor McCarthy <cmccarthy@codeweavers.com> Fixes access denied in Windows 11. --- dlls/mfplat/tests/mfplat.c | 21 +++++++++++---------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index e28fece1418..6a15b646b77 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -2976,7 +2976,6 @@ static void test_MFCreateMFByteStreamOnStream(void) static void test_file_stream(void) { - static const WCHAR newfilename[] = L"new.mp4"; IMFByteStream *bytestream, *bytestream2; QWORD bytestream_length, position; IMFAttributes *attributes = NULL; @@ -3074,8 +3073,11 @@ static void test_file_stream(void) IMFByteStream_Release(bytestream); + GetTempPathW(ARRAY_SIZE(pathW), pathW); + lstrcatW(pathW, L"new.mp4"); + hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, - MF_FILEFLAGS_NONE, newfilename, &bytestream); + MF_FILEFLAGS_NONE, pathW, &bytestream); ok(hr == HRESULT_FROM_WIN32(ERROR_FILE_NOT_FOUND), "Unexpected hr %#lx.\n", hr); hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_EXIST, @@ -3083,31 +3085,32 @@ static void test_file_stream(void) ok(hr == HRESULT_FROM_WIN32(ERROR_FILE_EXISTS), "Unexpected hr %#lx.\n", hr); hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_EXIST, - MF_FILEFLAGS_NONE, newfilename, &bytestream); + MF_FILEFLAGS_NONE, pathW, &bytestream); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); - hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, newfilename, &bytestream2); + hr = MFCreateFile(MF_ACCESSMODE_READ, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, pathW, &bytestream2); ok(hr == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), "Unexpected hr %#lx.\n", hr); - hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, newfilename, &bytestream2); + hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_NONE, pathW, &bytestream2); ok(hr == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), "Unexpected hr %#lx.\n", hr); hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_ALLOW_WRITE_SHARING, - newfilename, &bytestream2); + pathW, &bytestream2); ok(hr == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), "Unexpected hr %#lx.\n", hr); IMFByteStream_Release(bytestream); hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_NOT_EXIST, - MF_FILEFLAGS_ALLOW_WRITE_SHARING, newfilename, &bytestream); + MF_FILEFLAGS_ALLOW_WRITE_SHARING, pathW, &bytestream); ok(hr == S_OK, "Unexpected hr %#lx.\n", hr); /* Opening the file again fails even though MF_FILEFLAGS_ALLOW_WRITE_SHARING is set. */ hr = MFCreateFile(MF_ACCESSMODE_WRITE, MF_OPENMODE_FAIL_IF_NOT_EXIST, MF_FILEFLAGS_ALLOW_WRITE_SHARING, - newfilename, &bytestream2); + pathW, &bytestream2); ok(hr == HRESULT_FROM_WIN32(ERROR_SHARING_VIOLATION), "Unexpected hr %#lx.\n", hr); IMFByteStream_Release(bytestream); + DeleteFileW(pathW); /* Explicit file: scheme */ lstrcpyW(pathW, fileschemeW); @@ -3122,8 +3125,6 @@ static void test_file_stream(void) hr = MFShutdown(); ok(hr == S_OK, "Failed to shut down, hr %#lx.\n", hr); - - DeleteFileW(newfilename); } static void test_system_memory_buffer(void) -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mfplat/tests/mfplat.c | 42 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 6a15b646b77..7bbb8757046 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -1276,6 +1276,30 @@ static void test_source_resolver(void) ULONG refcount; BOOL ret; + static const struct + { + const WCHAR *chars; + UINT win_error; + BOOL todo; + } + leading_char_tests[] = + { + {L"/", ERROR_SUCCESS, TRUE}, + {L"//", ERROR_SUCCESS, TRUE}, + {L"///", ERROR_SUCCESS, TRUE}, + {L"/////", ERROR_SUCCESS, TRUE}, + {L":", ERROR_INVALID_NAME, TRUE}, + {L"::", ERROR_PATH_NOT_FOUND}, + {L":::::", ERROR_PATH_NOT_FOUND}, + {L"/file://", ERROR_INVALID_NAME, TRUE}, + {L"//file://", ERROR_BAD_NETPATH, TRUE}, + {L"///file://", ERROR_INVALID_NAME, TRUE}, + {L"/////file://", ERROR_BAD_NETPATH, TRUE}, + {L":file://", ERROR_INVALID_NAME, TRUE}, + {L"::file://", ERROR_PATH_NOT_FOUND}, + {L":::::file://", ERROR_PATH_NOT_FOUND}, + }; + if (!pMFCreateSourceResolver) { win_skip("MFCreateSourceResolver() not found\n"); @@ -1340,6 +1364,24 @@ static void test_source_resolver(void) if (SUCCEEDED(hr)) WaitForSingleObject(callback->event, INFINITE); + /* With leading forward slashes or colons. */ + for (i = 0; i < ARRAY_SIZE(leading_char_tests); ++i) + { + winetest_push_context("test %d", i); + + lstrcpyW(pathW, leading_char_tests[i].chars); + lstrcatW(pathW, filename); + + hr = IMFSourceResolver_CreateObjectFromURL(resolver, pathW, MF_RESOLUTION_BYTESTREAM, NULL, &obj_type, + (IUnknown **)&stream); + todo_wine_if(leading_char_tests[i].todo) + ok(hr == HRESULT_FROM_WIN32(leading_char_tests[i].win_error), "Unexpected hr %#lx.\n", hr); + if (SUCCEEDED(hr)) + IMFByteStream_Release(stream); + + winetest_pop_context(); + } + /* With explicit scheme. */ lstrcpyW(pathW, fileschemeW); lstrcatW(pathW, filename); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
From: Conor McCarthy <cmccarthy@codeweavers.com> --- dlls/mfplat/main.c | 109 +++++++++++++++++++++++++++++-------- dlls/mfplat/tests/mfplat.c | 8 +-- 2 files changed, 90 insertions(+), 27 deletions(-) diff --git a/dlls/mfplat/main.c b/dlls/mfplat/main.c index da9dce416ba..6763ee86887 100644 --- a/dlls/mfplat/main.c +++ b/dlls/mfplat/main.c @@ -6421,13 +6421,20 @@ static HRESULT resolver_create_scheme_handler(const WCHAR *scheme, DWORD flags, return hr; } -static HRESULT resolver_get_scheme_handler(const WCHAR *url, DWORD flags, IMFSchemeHandler **handler) +/* Also in kernelbase */ +static BOOL is_escaped_drive_spec(const WCHAR *str) +{ + return isalpha(str[0]) && (str[1] == ':' || str[1] == '|'); +} + +static BOOL resolver_find_scheme(const WCHAR *url, unsigned int *len) { - static const WCHAR fileschemeW[] = L"file:"; const WCHAR *ptr = url; - unsigned int len; - WCHAR *scheme; - HRESULT hr; + + *len = 0; + + if (is_escaped_drive_spec(url)) + return FALSE; /* RFC 3986: scheme = ALPHA *( ALPHA / DIGIT / "+" / "-" / "." ) */ while (*ptr) @@ -6449,14 +6456,22 @@ static HRESULT resolver_get_scheme_handler(const WCHAR *url, DWORD flags, IMFSch ptr++; } - /* Schemes must end with a ':', if not found try "file:" */ + /* Schemes must end with a ':' */ if (ptr == url || *ptr != ':') - { - url = fileschemeW; - len = ARRAY_SIZE(fileschemeW) - 1; - } - else - len = ptr - url + 1; + return FALSE; + + *len = ptr - url + 1; + return TRUE; +} + +static HRESULT resolver_get_scheme_handler(const WCHAR *url, DWORD flags, IMFSchemeHandler **handler) +{ + unsigned int len; + WCHAR *scheme; + HRESULT hr; + + if (!resolver_find_scheme(url, &len)) + return MF_E_UNSUPPORTED_SCHEME; scheme = malloc((len + 1) * sizeof(WCHAR)); if (!scheme) @@ -6466,8 +6481,6 @@ static HRESULT resolver_get_scheme_handler(const WCHAR *url, DWORD flags, IMFSch scheme[len] = 0; hr = resolver_create_scheme_handler(scheme, flags, handler); - if (FAILED(hr) && url != fileschemeW) - hr = resolver_create_scheme_handler(fileschemeW, flags, handler); free(scheme); @@ -6571,10 +6584,49 @@ static ULONG WINAPI source_resolver_Release(IMFSourceResolver *iface) return refcount; } +static WCHAR *resolver_normalise_url(const WCHAR *url) +{ + static const WCHAR filescheme[] = L"file:", filescheme_dblslash[] = L"file://"; + unsigned int len = 0, slash_count; + WCHAR *normalised_url; + const WCHAR *scheme; + + if (resolver_find_scheme(url, &len)) + return wcsdup(url); + + /* If not found try "file:" + * Native allows leading slashes in file paths. If "file:" is missing, native prepends + * it before calling BeginCreateObject(), and includes two or three slashes if any were + * present. The rules don't seem to follow much of a pattern, and may have been designed + * to get the desired result from PathCreateFromUrlW(). */ + while (url[len] == L'/') + ++len; + if (!len) + slash_count = 0; + else if (is_escaped_drive_spec(&url[len])) + slash_count = 1 - (len == 5 || len == 6); + else if (len < 5) + slash_count = len & 1; + else + slash_count = len > 6; + url += len - slash_count; + + scheme = len ? filescheme_dblslash : filescheme; + len = wcslen(url); + if (!(normalised_url = malloc((wcslen(scheme) + len + 1) * sizeof(WCHAR)))) + return NULL; + + wcscpy(normalised_url, scheme); + wcscat(normalised_url, url); + + return normalised_url; +} + static HRESULT WINAPI source_resolver_CreateObjectFromURL(IMFSourceResolver *iface, const WCHAR *url, DWORD flags, IPropertyStore *props, MF_OBJECT_TYPE *obj_type, IUnknown **object) { struct source_resolver *resolver = impl_from_IMFSourceResolver(iface); + WCHAR *normalised_url = NULL; IMFSchemeHandler *handler; IRtwqAsyncResult *result; RTWQASYNCRESULT *data; @@ -6585,23 +6637,26 @@ static HRESULT WINAPI source_resolver_CreateObjectFromURL(IMFSourceResolver *ifa if (!url || !obj_type || !object) return E_POINTER; - if (FAILED(hr = resolver_get_scheme_handler(url, flags, &handler))) - return hr; + if (!(normalised_url = resolver_normalise_url(url))) + return E_OUTOFMEMORY; + + if (FAILED(hr = resolver_get_scheme_handler(normalised_url, flags, &handler))) + goto done; hr = RtwqCreateAsyncResult((IUnknown *)handler, NULL, NULL, &result); IMFSchemeHandler_Release(handler); if (FAILED(hr)) - return hr; + goto done; data = (RTWQASYNCRESULT *)result; data->hEvent = CreateEventW(NULL, FALSE, FALSE, NULL); - hr = IMFSchemeHandler_BeginCreateObject(handler, url, flags, props, NULL, (IMFAsyncCallback *)&resolver->url_callback, + hr = IMFSchemeHandler_BeginCreateObject(handler, normalised_url, flags, props, NULL, (IMFAsyncCallback *)&resolver->url_callback, (IUnknown *)result); if (FAILED(hr)) { IRtwqAsyncResult_Release(result); - return hr; + goto done; } WaitForSingleObject(data->hEvent, INFINITE); @@ -6609,6 +6664,8 @@ static HRESULT WINAPI source_resolver_CreateObjectFromURL(IMFSourceResolver *ifa hr = resolver_end_create_object(resolver, OBJECT_FROM_URL, result, obj_type, object); IRtwqAsyncResult_Release(result); +done: + free(normalised_url); return hr; } @@ -6660,12 +6717,16 @@ static HRESULT WINAPI source_resolver_BeginCreateObjectFromURL(IMFSourceResolver IMFSchemeHandler *handler; IUnknown *inner_cookie = NULL; IRtwqAsyncResult *result; + WCHAR *normalised_url; HRESULT hr; TRACE("%p, %s, %#lx, %p, %p, %p, %p.\n", iface, debugstr_w(url), flags, props, cancel_cookie, callback, state); - if (FAILED(hr = resolver_get_scheme_handler(url, flags, &handler))) - return hr; + if (!(normalised_url = resolver_normalise_url(url))) + return E_OUTOFMEMORY; + + if (FAILED(hr = resolver_get_scheme_handler(normalised_url, flags, &handler))) + goto done; if (cancel_cookie) *cancel_cookie = NULL; @@ -6673,9 +6734,9 @@ static HRESULT WINAPI source_resolver_BeginCreateObjectFromURL(IMFSourceResolver hr = RtwqCreateAsyncResult((IUnknown *)handler, (IRtwqAsyncCallback *)callback, state, &result); IMFSchemeHandler_Release(handler); if (FAILED(hr)) - return hr; + goto done; - hr = IMFSchemeHandler_BeginCreateObject(handler, url, flags, props, cancel_cookie ? &inner_cookie : NULL, + hr = IMFSchemeHandler_BeginCreateObject(handler, normalised_url, flags, props, cancel_cookie ? &inner_cookie : NULL, (IMFAsyncCallback *)&resolver->url_callback, (IUnknown *)result); if (SUCCEEDED(hr) && inner_cookie) @@ -6686,6 +6747,8 @@ static HRESULT WINAPI source_resolver_BeginCreateObjectFromURL(IMFSourceResolver IRtwqAsyncResult_Release(result); +done: + free(normalised_url); return hr; } diff --git a/dlls/mfplat/tests/mfplat.c b/dlls/mfplat/tests/mfplat.c index 7bbb8757046..ab5a997dbf5 100644 --- a/dlls/mfplat/tests/mfplat.c +++ b/dlls/mfplat/tests/mfplat.c @@ -1284,10 +1284,10 @@ static void test_source_resolver(void) } leading_char_tests[] = { - {L"/", ERROR_SUCCESS, TRUE}, - {L"//", ERROR_SUCCESS, TRUE}, - {L"///", ERROR_SUCCESS, TRUE}, - {L"/////", ERROR_SUCCESS, TRUE}, + {L"/", ERROR_SUCCESS}, + {L"//", ERROR_SUCCESS}, + {L"///", ERROR_SUCCESS}, + {L"/////", ERROR_SUCCESS}, {L":", ERROR_INVALID_NAME, TRUE}, {L"::", ERROR_PATH_NOT_FOUND}, {L":::::", ERROR_PATH_NOT_FOUND}, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
On Wed May 6 05:25:37 2026 +0000, Nikolay Sivov wrote:
I think we need a generic way to parse url, that will work for any scheme. For example what happens for "/file://..." or "file://////c:/...". If disk paths are turned to file:// urls, we should do that too, probably before looking for a scheme handler. Async case probably needs the same fix? I made these changes.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10708#note_138967
Nikolay Sivov (@nsivov) commented about dlls/mfplat/main.c:
+static WCHAR *resolver_normalise_url(const WCHAR *url) +{ + static const WCHAR filescheme[] = L"file:", filescheme_dblslash[] = L"file://"; + unsigned int len = 0, slash_count; + WCHAR *normalised_url; + const WCHAR *scheme; + + if (resolver_find_scheme(url, &len)) + return wcsdup(url); + + /* If not found try "file:" + * Native allows leading slashes in file paths. If "file:" is missing, native prepends + * it before calling BeginCreateObject(), and includes two or three slashes if any were + * present. The rules don't seem to follow much of a pattern, and may have been designed + * to get the desired result from PathCreateFromUrlW(). */ + while (url[len] == L'/') Do you mean the desired result from PathCreateFromUrlW if it's used in the handler?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10708#note_139993
On Fri May 15 10:53:04 2026 +0000, Nikolay Sivov wrote:
Do you mean the desired result from PathCreateFromUrlW if it's used in the handler? Yes. I'm not certain native uses it, but it seems very likely it would be used.
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10708#note_140017
This merge request was approved by Nikolay Sivov. -- https://gitlab.winehq.org/wine/wine/-/merge_requests/10708
participants (3)
-
Conor McCarthy -
Conor McCarthy (@cmccarthy) -
Nikolay Sivov (@nsivov)