This is necessary for mshtml's navigator userAgent to work properly, since some apps expect it.
The second argument seems to be output pointer that gets allocated (by CoTaskMemAlloc, or something compatible with it), but the first argument seems to be a pointer to an unknown struct, which makes it almost impossible to guess reliably what it does by just inspecting its behavior...
Thankfully, it seems only the first DWORD field is necessary for this. Changing it returns the user agent for that given mode, if the rest is zeros.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
Unfortunately I was not able to find a public structure that starts with such a field in urlmon.
Sending these to get some feedback if there's a better way...
dlls/urlmon/session.c | 40 +++++++++++++++++++++++++++++ dlls/urlmon/tests/misc.c | 53 +++++++++++++++++++++++++++++++++++++++ dlls/urlmon/urlmon.spec | 2 +- dlls/urlmon/urlmon_main.c | 10 -------- 4 files changed, 94 insertions(+), 11 deletions(-)
diff --git a/dlls/urlmon/session.c b/dlls/urlmon/session.c index 052444d..671d74d 100644 --- a/dlls/urlmon/session.c +++ b/dlls/urlmon/session.c @@ -504,6 +504,7 @@ static BOOL get_url_encoding(HKEY root, DWORD *encoding) }
static LPWSTR user_agent; +static BOOL user_agent_set;
static size_t obtain_user_agent(unsigned int version, WCHAR *ret, size_t size) { @@ -710,6 +711,7 @@ HRESULT WINAPI UrlMkSetSessionOption(DWORD dwOption, LPVOID pBuffer, DWORD dwBuf
heap_free(user_agent); user_agent = new_user_agent; + user_agent_set = TRUE; update_user_agent(user_agent);
LeaveCriticalSection(&session_cs); @@ -748,6 +750,44 @@ HRESULT WINAPI ObtainUserAgentString(DWORD option, char *ret, DWORD *ret_size) return hres; }
+/*********************************************************************** + * MapBrowserEmulationModeToUserAgent (URLMON.445) + * Undocumented, added in IE8 + */ +HRESULT WINAPI MapBrowserEmulationModeToUserAgent(const void *arg, WCHAR **ret) +{ + DWORD size, version; + const WCHAR *ua; + WCHAR buf[1024]; + + FIXME("%p %p\n", arg, ret); + + if(user_agent_set) { + /* Native ignores first arg if custom user agent has been set, doesn't crash even if NULL */ + size = (wcslen(user_agent) + 1) * sizeof(WCHAR); + ua = user_agent; + }else { + *ret = NULL; + + /* First arg seems to be a pointer to a structure of unknown size, and crashes + if it's too small (or filled with arbitrary values from the stack). For our + purposes, we only check first field which seems to be the requested version. */ + version = *(DWORD*)arg; + if(version == 5) + version = 7; + if(version < 7 || version > 11) + return E_FAIL; + + size = obtain_user_agent(version, buf, ARRAY_SIZE(buf)) * sizeof(WCHAR); + ua = buf; + } + + if(!(*ret = CoTaskMemAlloc(size))) + return E_OUTOFMEMORY; + memcpy(*ret, ua, size); + return S_OK; +} + void free_session(void) { name_space *ns_iter, *ns_last; diff --git a/dlls/urlmon/tests/misc.c b/dlls/urlmon/tests/misc.c index 0575a43..72b3442 100644 --- a/dlls/urlmon/tests/misc.c +++ b/dlls/urlmon/tests/misc.c @@ -83,6 +83,7 @@ static HRESULT (WINAPI *pCompareSecurityIds)(BYTE*,DWORD,BYTE*,DWORD,DWORD); static HRESULT (WINAPI *pCoInternetIsFeatureEnabled)(INTERNETFEATURELIST,DWORD); static HRESULT (WINAPI *pCoInternetSetFeatureEnabled)(INTERNETFEATURELIST,DWORD,BOOL); static HRESULT (WINAPI *pIEInstallScope)(DWORD*); +static HRESULT (WINAPI *pMapBrowserEmulationModeToUserAgent)(const void*,WCHAR**);
static WCHAR *a2co(const char *str) { @@ -2721,6 +2722,55 @@ static void test_bsc_marshaling(void) TerminateThread(thread, 0); }
+static void test_MapBrowserEmulationModeToUserAgent(BOOL custom_ua) +{ + /* Undocumented structure of unknown size, crashes if it's too small (with random values from stack) */ + struct + { + DWORD version; + char unknown[252]; + } arg; + static char test_str[] = "test"; + const char *custom_ua_msg = ""; + HRESULT hres; + unsigned i; + WCHAR *ua; + + if(!pMapBrowserEmulationModeToUserAgent) { + win_skip("MapBrowserEmulationModeToUserAgent not available\n"); + return; + } + memset(&arg, 0, sizeof(arg)); + + if(custom_ua) { + hres = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, test_str, sizeof(test_str), 0); + ok(hres == S_OK, "UrlMkSetSessionOption failed: %08lx\n", hres); + custom_ua_msg = " (with custom ua)"; + } + + for(i = 0; i < 12; i++) { + arg.version = i; + ua = (WCHAR*)0xdeadbeef; + hres = pMapBrowserEmulationModeToUserAgent(&arg, &ua); + ok(hres == (i == 5 || i >= 7 || custom_ua ? S_OK : E_FAIL), + "[%d] MapBrowserEmulationModeToUserAgent%s returned %08lx\n", i, custom_ua_msg, hres); + if(hres != S_OK) + ok(ua == NULL, "[%d] ua%s = %p\n", i, custom_ua_msg, ua); + else { + char buf[1024]; + DWORD size = sizeof(buf); + WCHAR *ua2; + + hres = pObtainUserAgentString(custom_ua ? 0 : i, buf, &size); + ok(hres == S_OK, "[%d] ObtainUserAgentString%s failed: %08lx\n", i, custom_ua_msg, hres); + ua2 = a2co(buf); + ok(!lstrcmpW(ua, ua2), "[%d] ua%s = %s, expected %s\n", i, custom_ua_msg, wine_dbgstr_w(ua), wine_dbgstr_w(ua2)); + CoTaskMemFree(ua2); + CoTaskMemFree(ua); + } + } +} + START_TEST(misc) { HMODULE hurlmon; @@ -2745,6 +2795,7 @@ START_TEST(misc) pCoInternetIsFeatureEnabled = (void*) GetProcAddress(hurlmon, "CoInternetIsFeatureEnabled"); pCoInternetSetFeatureEnabled = (void*) GetProcAddress(hurlmon, "CoInternetSetFeatureEnabled"); pIEInstallScope = (void*) GetProcAddress(hurlmon, "IEInstallScope"); + pMapBrowserEmulationModeToUserAgent = (void*) GetProcAddress(hurlmon, (const char*)445);
if (!pCoInternetCompareUrl || !pCoInternetGetSecurityUrl || !pCoInternetGetSession || !pCoInternetParseUrl || !pCompareSecurityIds) { @@ -2757,6 +2808,7 @@ START_TEST(misc) if(argc <= 2 || strcmp(argv[2], "internet_features")) { register_protocols();
+ test_MapBrowserEmulationModeToUserAgent(FALSE); test_CreateFormatEnum(); test_RegisterFormatEnumerator(); test_CoInternetParseUrl(); @@ -2773,6 +2825,7 @@ START_TEST(misc) test_MkParseDisplayNameEx(); test_IsValidURL(); test_bsc_marshaling(); + test_MapBrowserEmulationModeToUserAgent(TRUE); }
test_internet_features(); diff --git a/dlls/urlmon/urlmon.spec b/dlls/urlmon/urlmon.spec index ea3cd38..2fda69e 100644 --- a/dlls/urlmon/urlmon.spec +++ b/dlls/urlmon/urlmon.spec @@ -110,6 +110,6 @@ 410 stdcall @(long long) LogSqmBits 423 stdcall @(long long long long) LogSqmUXCommandOffsetInternal 444 stdcall @(long long long) MapUriToBrowserEmulationState -445 stdcall @(long long) MapBrowserEmulationModeToUserAgent +445 stdcall @(ptr ptr) MapBrowserEmulationModeToUserAgent 446 stdcall @(long) CoInternetGetBrowserProfile 455 stdcall @() FlushUrlmonZonesCache diff --git a/dlls/urlmon/urlmon_main.c b/dlls/urlmon/urlmon_main.c index b540313..56c2257 100644 --- a/dlls/urlmon/urlmon_main.c +++ b/dlls/urlmon/urlmon_main.c @@ -791,16 +791,6 @@ int WINAPI MapUriToBrowserEmulationState(DWORD unk1, DWORD unk2, DWORD unk3) return 0; }
-/*********************************************************************** - * MapBrowserEmulationModeToUserAgent (URLMON.445) - * Undocumented, added in IE8 - */ -int WINAPI MapBrowserEmulationModeToUserAgent(DWORD unk1, DWORD unk2) -{ - FIXME("stub: %ld %ld\n", unk1, unk2); - return 0; -} - /*********************************************************************** * CoInternetGetBrowserProfile (URLMON.446) * Undocumented, added in IE8
This uses the undocumented MapBrowserEmulationModeToUserAgent and only the first field of the unknown struct, but it is enough for this purpose. It is important for some apps (e.g. FFXIV Launcher) which expects it to work like this.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
I've exhaustively tried other options (and flags) for ObtainUserAgentString, but none of them gave any special result on native. This is not fixable on urlmon side for ObtainUserAgentString API, see next patch (which would break the mshtml tests by itself, that's why it is after the fix).
dlls/mshtml/omnavigator.c | 42 +++++++++++++++------------------- dlls/mshtml/tests/dom.c | 6 +++++ dlls/mshtml/tests/rsrc.rc | 3 +++ dlls/mshtml/tests/script.c | 32 ++++++++++++++++++++++++++ dlls/mshtml/tests/useragent.js | 29 +++++++++++++++++++++++ dlls/urlmon/urlmon.spec | 2 +- 6 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 dlls/mshtml/tests/useragent.js
diff --git a/dlls/mshtml/omnavigator.c b/dlls/mshtml/omnavigator.c index 0a2a6bb..844fa63 100644 --- a/dlls/mshtml/omnavigator.c +++ b/dlls/mshtml/omnavigator.c @@ -1182,57 +1182,51 @@ static unsigned int get_ua_version(OmNavigator *navigator) return 0; }
+/* undocumented, added in IE8 */ +extern HRESULT WINAPI MapBrowserEmulationModeToUserAgent(const void*,WCHAR**); + static HRESULT WINAPI OmNavigator_get_appVersion(IOmNavigator *iface, BSTR *p) { OmNavigator *This = impl_from_IOmNavigator(iface); - - char user_agent[512]; - DWORD size; + DWORD len, version = get_ua_version(This); + WCHAR *user_agent; HRESULT hres; const unsigned skip_prefix = 8; /* strlen("Mozilla/") */
TRACE("(%p)->(%p)\n", This, p);
- size = sizeof(user_agent); - hres = ObtainUserAgentString(get_ua_version(This), user_agent, &size); + hres = MapBrowserEmulationModeToUserAgent(&version, &user_agent); if(FAILED(hres)) return hres; + len = wcslen(user_agent);
- if(size <= skip_prefix) { + if(len <= skip_prefix) { + CoTaskMemFree(user_agent); *p = NULL; return S_OK; }
- size = MultiByteToWideChar(CP_ACP, 0, user_agent + skip_prefix, -1, NULL, 0); - *p = SysAllocStringLen(NULL, size-1); - if(!*p) - return E_OUTOFMEMORY; - - MultiByteToWideChar(CP_ACP, 0, user_agent + skip_prefix, -1, *p, size); - return S_OK; + *p = SysAllocStringLen(user_agent + skip_prefix, len - skip_prefix); + CoTaskMemFree(user_agent); + return *p ? S_OK : E_OUTOFMEMORY; }
static HRESULT WINAPI OmNavigator_get_userAgent(IOmNavigator *iface, BSTR *p) { OmNavigator *This = impl_from_IOmNavigator(iface); - char user_agent[512]; - DWORD size; + DWORD version = get_ua_version(This); + WCHAR *user_agent; HRESULT hres;
TRACE("(%p)->(%p)\n", This, p);
- size = sizeof(user_agent); - hres = ObtainUserAgentString(get_ua_version(This), user_agent, &size); + hres = MapBrowserEmulationModeToUserAgent(&version, &user_agent); if(FAILED(hres)) return hres;
- size = MultiByteToWideChar(CP_ACP, 0, user_agent, -1, NULL, 0); - *p = SysAllocStringLen(NULL, size-1); - if(!*p) - return E_OUTOFMEMORY; - - MultiByteToWideChar(CP_ACP, 0, user_agent, -1, *p, size); - return S_OK; + *p = SysAllocString(user_agent); + CoTaskMemFree(user_agent); + return *p ? S_OK : E_OUTOFMEMORY; }
static HRESULT WINAPI OmNavigator_javaEnabled(IOmNavigator *iface, VARIANT_BOOL *enabled) diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index b66073f..2de34b5 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -6479,6 +6479,12 @@ static void test_navigator(IHTMLDocument2 *doc) ok(!lstrcmpW(bstr, buf+8), "appVersion returned %s, expected "%s"\n", wine_dbgstr_w(bstr), wine_dbgstr_w(buf+8)); SysFreeString(bstr);
+ bstr = NULL; + hres = IOmNavigator_get_userAgent(navigator, &bstr); + ok(hres == S_OK, "get_userAgent failed: %08lx\n", hres); + ok(!lstrcmpW(bstr, buf), "userAgent returned %s, expected "%s"\n", wine_dbgstr_w(bstr), wine_dbgstr_w(buf)); + SysFreeString(bstr); + hres = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, buf, lstrlenW(buf), 0); ok(hres == S_OK, "UrlMkSetSessionOption failed: %08lx\n", hres);
diff --git a/dlls/mshtml/tests/rsrc.rc b/dlls/mshtml/tests/rsrc.rc index 61e6c94..2b9a9ba 100644 --- a/dlls/mshtml/tests/rsrc.rc +++ b/dlls/mshtml/tests/rsrc.rc @@ -64,6 +64,9 @@ events.js HTML "events.js" /* @makedep: documentmode.js */ documentmode.js HTML "documentmode.js"
+/* @makedep: useragent.js */ +useragent.js HTML "useragent.js" + /* @makedep: blank.html */ blank.html HTML "blank.html"
diff --git a/dlls/mshtml/tests/script.c b/dlls/mshtml/tests/script.c index de42e89..05cdad1 100644 --- a/dlls/mshtml/tests/script.c +++ b/dlls/mshtml/tests/script.c @@ -151,6 +151,7 @@ DEFINE_EXPECT(GetTypeInfo); #define DISPID_EXTERNAL_BROKEN 0x300004 #define DISPID_EXTERNAL_WIN_SKIP 0x300005 #define DISPID_EXTERNAL_WRITESTREAM 0x300006 +#define DISPID_EXTERNAL_SET_UA 0x300007
static const GUID CLSID_TestScript = {0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x07,0x46}}; @@ -589,6 +590,10 @@ static HRESULT WINAPI externalDisp_GetDispID(IDispatchEx *iface, BSTR bstrName, *pid = DISPID_EXTERNAL_WRITESTREAM; return S_OK; } + if(!lstrcmpW(bstrName, L"setUA")) { + *pid = DISPID_EXTERNAL_SET_UA; + return S_OK; + }
ok(0, "unexpected name %s\n", wine_dbgstr_w(bstrName)); return DISP_E_UNKNOWNNAME; @@ -716,6 +721,22 @@ static HRESULT WINAPI externalDisp_InvokeEx(IDispatchEx *iface, DISPID id, LCID stream_write(V_BSTR(pdp->rgvarg+1), V_BSTR(pdp->rgvarg)); return S_OK;
+ case DISPID_EXTERNAL_SET_UA: { + char buf[128]; + DWORD size; + + ok(pdp != NULL, "pdp == NULL\n"); + ok(pdp->rgvarg != NULL, "rgvarg == NULL\n"); + ok(V_VT(pdp->rgvarg) == VT_BSTR, "V_VT(rgvarg) = %d\n", V_VT(pdp->rgvarg)); + ok(!pdp->rgdispidNamedArgs, "rgdispidNamedArgs != NULL\n"); + ok(pdp->cArgs == 1, "cArgs = %d\n", pdp->cArgs); + ok(!pdp->cNamedArgs, "cNamedArgs = %d\n", pdp->cNamedArgs); + ok(pei != NULL, "pei == NULL\n"); + + size = WideCharToMultiByte(CP_ACP, 0, V_BSTR(pdp->rgvarg), -1, buf, sizeof(buf), NULL, NULL); + return UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, buf, size, 0); + } + default: ok(0, "unexpected call\n"); return E_NOTIMPL; @@ -3624,6 +3645,17 @@ static void run_js_tests(void) run_script_as_http_with_mode("documentmode.js", "11", "edge;123");
run_script_as_http_with_mode("asyncscriptload.js", NULL, "9"); + + /* Run these last since they mess with the process-wide user agent */ + run_script_as_http_with_mode("useragent.js", "0", NULL); + run_script_as_http_with_mode("useragent.js", "5", "5"); + run_script_as_http_with_mode("useragent.js", "5", "6"); + run_script_as_http_with_mode("useragent.js", "7", "7"); + run_script_as_http_with_mode("useragent.js", "8", "8"); + run_script_as_http_with_mode("useragent.js", "9", "9"); + run_script_as_http_with_mode("useragent.js", "10", "10;abc"); + run_script_as_http_with_mode("useragent.js", "11", "11"); + run_script_as_http_with_mode("useragent.js", "11", "edge;123"); }
static BOOL init_registry(BOOL init) diff --git a/dlls/mshtml/tests/useragent.js b/dlls/mshtml/tests/useragent.js new file mode 100644 index 0000000..f36869d --- /dev/null +++ b/dlls/mshtml/tests/useragent.js @@ -0,0 +1,29 @@ +/* + * Copyright 2022 Gabriel Ivăncescu for CodeWeavers + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA + */ + +var tests = []; + +sync_test("user_agent", function() { + var ua = "1234567890xxxABC"; + + if(document.documentMode === 5) + window.external.setUA(ua); + + ok(navigator.userAgent === ua, "userAgent = " + navigator.userAgent); + ok(navigator.appVersion === "90xxxABC", "appVersion = " + navigator.appVersion); +}); diff --git a/dlls/urlmon/urlmon.spec b/dlls/urlmon/urlmon.spec index 2fda69e..8725c8c 100644 --- a/dlls/urlmon/urlmon.spec +++ b/dlls/urlmon/urlmon.spec @@ -110,6 +110,6 @@ 410 stdcall @(long long) LogSqmBits 423 stdcall @(long long long long) LogSqmUXCommandOffsetInternal 444 stdcall @(long long long) MapUriToBrowserEmulationState -445 stdcall @(ptr ptr) MapBrowserEmulationModeToUserAgent +445 stdcall -noname MapBrowserEmulationModeToUserAgent(ptr ptr) 446 stdcall @(long) CoInternetGetBrowserProfile 455 stdcall @() FlushUrlmonZonesCache
Hi,
While running your changed tests, I think I found new failures. Being a bot and all I'm not very good at pattern recognition, so I might be wrong, but could you please double-check?
Full results can be found at: https://testbot.winehq.org/JobDetails.pl?Key=112256
Your paranoid android.
=== w1064_tsign (64 bit report) ===
mshtml: htmldoc.c:2541: Test failed: unexpected call UpdateUI htmldoc.c:2853: Test failed: unexpected call Exec_UPDATECOMMANDS htmldoc.c:350: Test failed: expected Exec_SETTITLE htmldoc.c:2859: Test failed: unexpected call Exec_SETTITLE
=== debian11 (32 bit WoW report) ===
mshtml: htmldoc.c:2541: Test failed: unexpected call UpdateUI htmldoc.c:2853: Test failed: unexpected call Exec_UPDATECOMMANDS htmldoc.c:350: Test failed: expected Exec_SETTITLE htmldoc.c:2859: Test failed: unexpected call Exec_SETTITLE
On 4/8/22 16:35, Gabriel Ivăncescu wrote:
This uses the undocumented MapBrowserEmulationModeToUserAgent and only the first field of the unknown struct, but it is enough for this purpose. It is important for some apps (e.g. FFXIV Launcher) which expects it to work like this.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com
I've exhaustively tried other options (and flags) for ObtainUserAgentString, but none of them gave any special result on native. This is not fixable on urlmon side for ObtainUserAgentString API, see next patch (which would break the mshtml tests by itself, that's why it is after the fix).
dlls/mshtml/omnavigator.c | 42 +++++++++++++++------------------- dlls/mshtml/tests/dom.c | 6 +++++ dlls/mshtml/tests/rsrc.rc | 3 +++ dlls/mshtml/tests/script.c | 32 ++++++++++++++++++++++++++ dlls/mshtml/tests/useragent.js | 29 +++++++++++++++++++++++ dlls/urlmon/urlmon.spec | 2 +- 6 files changed, 89 insertions(+), 25 deletions(-) create mode 100644 dlls/mshtml/tests/useragent.js
diff --git a/dlls/mshtml/omnavigator.c b/dlls/mshtml/omnavigator.c index 0a2a6bb..844fa63 100644 --- a/dlls/mshtml/omnavigator.c +++ b/dlls/mshtml/omnavigator.c @@ -1182,57 +1182,51 @@ static unsigned int get_ua_version(OmNavigator *navigator) return 0; }
+/* undocumented, added in IE8 */ +extern HRESULT WINAPI MapBrowserEmulationModeToUserAgent(const void*,WCHAR**);
- static HRESULT WINAPI OmNavigator_get_appVersion(IOmNavigator *iface, BSTR *p) { OmNavigator *This = impl_from_IOmNavigator(iface);
- char user_agent[512];
- DWORD size;
DWORD len, version = get_ua_version(This);
WCHAR *user_agent; HRESULT hres; const unsigned skip_prefix = 8; /* strlen("Mozilla/") */
TRACE("(%p)->(%p)\n", This, p);
- size = sizeof(user_agent);
- hres = ObtainUserAgentString(get_ua_version(This), user_agent, &size);
- hres = MapBrowserEmulationModeToUserAgent(&version, &user_agent);
I think that it would be better to have details of MapBrowserEmulationModeToUserAgent in just one place. How about replacing get_ua_version() with a helper that would also do MapBrowserEmulationModeToUserAgent call?
static HRESULT WINAPI OmNavigator_javaEnabled(IOmNavigator *iface, VARIANT_BOOL *enabled) diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index b66073f..2de34b5 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -6479,6 +6479,12 @@ static void test_navigator(IHTMLDocument2 *doc) ok(!lstrcmpW(bstr, buf+8), "appVersion returned %s, expected "%s"\n", wine_dbgstr_w(bstr), wine_dbgstr_w(buf+8)); SysFreeString(bstr);
- bstr = NULL;
- hres = IOmNavigator_get_userAgent(navigator, &bstr);
- ok(hres == S_OK, "get_userAgent failed: %08lx\n", hres);
- ok(!lstrcmpW(bstr, buf), "userAgent returned %s, expected "%s"\n", wine_dbgstr_w(bstr), wine_dbgstr_w(buf));
- SysFreeString(bstr);
hres = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, buf, lstrlenW(buf), 0); ok(hres == S_OK, "UrlMkSetSessionOption failed: %08lx\n", hres);
diff --git a/dlls/mshtml/tests/rsrc.rc b/dlls/mshtml/tests/rsrc.rc index 61e6c94..2b9a9ba 100644 --- a/dlls/mshtml/tests/rsrc.rc +++ b/dlls/mshtml/tests/rsrc.rc @@ -64,6 +64,9 @@ events.js HTML "events.js" /* @makedep: documentmode.js */ documentmode.js HTML "documentmode.js"
+/* @makedep: useragent.js */ +useragent.js HTML "useragent.js"
- /* @makedep: blank.html */ blank.html HTML "blank.html"
diff --git a/dlls/mshtml/tests/script.c b/dlls/mshtml/tests/script.c index de42e89..05cdad1 100644 --- a/dlls/mshtml/tests/script.c +++ b/dlls/mshtml/tests/script.c @@ -151,6 +151,7 @@ DEFINE_EXPECT(GetTypeInfo); #define DISPID_EXTERNAL_BROKEN 0x300004 #define DISPID_EXTERNAL_WIN_SKIP 0x300005 #define DISPID_EXTERNAL_WRITESTREAM 0x300006 +#define DISPID_EXTERNAL_SET_UA 0x300007
static const GUID CLSID_TestScript = {0x178fc163,0xf585,0x4e24,{0x9c,0x13,0x4b,0xb7,0xfa,0xf8,0x07,0x46}}; @@ -589,6 +590,10 @@ static HRESULT WINAPI externalDisp_GetDispID(IDispatchEx *iface, BSTR bstrName, *pid = DISPID_EXTERNAL_WRITESTREAM; return S_OK; }
if(!lstrcmpW(bstrName, L"setUA")) {
*pid = DISPID_EXTERNAL_SET_UA;
return S_OK;
}
ok(0, "unexpected name %s\n", wine_dbgstr_w(bstrName)); return DISP_E_UNKNOWNNAME;
@@ -716,6 +721,22 @@ static HRESULT WINAPI externalDisp_InvokeEx(IDispatchEx *iface, DISPID id, LCID stream_write(V_BSTR(pdp->rgvarg+1), V_BSTR(pdp->rgvarg)); return S_OK;
- case DISPID_EXTERNAL_SET_UA: {
char buf[128];
DWORD size;
ok(pdp != NULL, "pdp == NULL\n");
ok(pdp->rgvarg != NULL, "rgvarg == NULL\n");
ok(V_VT(pdp->rgvarg) == VT_BSTR, "V_VT(rgvarg) = %d\n", V_VT(pdp->rgvarg));
ok(!pdp->rgdispidNamedArgs, "rgdispidNamedArgs != NULL\n");
ok(pdp->cArgs == 1, "cArgs = %d\n", pdp->cArgs);
ok(!pdp->cNamedArgs, "cNamedArgs = %d\n", pdp->cNamedArgs);
ok(pei != NULL, "pei == NULL\n");
size = WideCharToMultiByte(CP_ACP, 0, V_BSTR(pdp->rgvarg), -1, buf, sizeof(buf), NULL, NULL);
return UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, buf, size, 0);
- }
default: ok(0, "unexpected call\n"); return E_NOTIMPL;
@@ -3624,6 +3645,17 @@ static void run_js_tests(void) run_script_as_http_with_mode("documentmode.js", "11", "edge;123");
run_script_as_http_with_mode("asyncscriptload.js", NULL, "9");
/* Run these last since they mess with the process-wide user agent */
run_script_as_http_with_mode("useragent.js", "0", NULL);
run_script_as_http_with_mode("useragent.js", "5", "5");
run_script_as_http_with_mode("useragent.js", "5", "6");
run_script_as_http_with_mode("useragent.js", "7", "7");
run_script_as_http_with_mode("useragent.js", "8", "8");
run_script_as_http_with_mode("useragent.js", "9", "9");
run_script_as_http_with_mode("useragent.js", "10", "10;abc");
run_script_as_http_with_mode("useragent.js", "11", "11");
run_script_as_http_with_mode("useragent.js", "11", "edge;123"); }
static BOOL init_registry(BOOL init)
diff --git a/dlls/mshtml/tests/useragent.js b/dlls/mshtml/tests/useragent.js new file mode 100644 index 0000000..f36869d --- /dev/null +++ b/dlls/mshtml/tests/useragent.js @@ -0,0 +1,29 @@ +/*
- Copyright 2022 Gabriel Ivăncescu for CodeWeavers
- This library is free software; you can redistribute it and/or
- modify it under the terms of the GNU Lesser General Public
- License as published by the Free Software Foundation; either
- version 2.1 of the License, or (at your option) any later version.
- This library is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- Lesser General Public License for more details.
- You should have received a copy of the GNU Lesser General Public
- License along with this library; if not, write to the Free Software
- Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
+var tests = [];
+sync_test("user_agent", function() {
- var ua = "1234567890xxxABC";
- if(document.documentMode === 5)
window.external.setUA(ua);
- ok(navigator.userAgent === ua, "userAgent = " + navigator.userAgent);
- ok(navigator.appVersion === "90xxxABC", "appVersion = " + navigator.appVersion);
+});
I think that this is making tests much more complicated that they need to be. At very least, you could call setUA() to C, before launching useragent.js tests. Launching them in every possible compat mode is also not really interesting. I'm not sure if using JS here is worth it at all, we could just have them implemented in C in dom.c and execute them for two of three compat modes.
Thanks,
Jacek
ObtainUserAgentString returns the currently set (custom) user agent unless the version requested is a valid one (7, 8, ... 11, or 7 | UAS_EXACTLEGACY), in which case it uses that version to build the user agent.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/urlmon/session.c | 14 +++++++++----- dlls/urlmon/tests/misc.c | 20 ++++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-)
diff --git a/dlls/urlmon/session.c b/dlls/urlmon/session.c index 671d74d..1f6f857 100644 --- a/dlls/urlmon/session.c +++ b/dlls/urlmon/session.c @@ -508,9 +508,9 @@ static BOOL user_agent_set;
static size_t obtain_user_agent(unsigned int version, WCHAR *ret, size_t size) { + BOOL is_wow, quirks = FALSE, use_current = FALSE; OSVERSIONINFOW info = {sizeof(info)}; const WCHAR *os_type, *is_nt; - BOOL is_wow, quirks = FALSE; DWORD res; size_t len = 0; HKEY key; @@ -519,17 +519,18 @@ static size_t obtain_user_agent(unsigned int version, WCHAR *ret, size_t size) version &= ~UAS_EXACTLEGACY; if(version == 7) quirks = TRUE; - else + else { + use_current = TRUE; version = 7; - }else if(version < 7) { - version = 7; + } } + if(version > 11) { FIXME("Unsupported version %u\n", version); version = 11; }
- if(version < 7 || (version == 7 && !quirks)) { + if(version < 7 || use_current) { EnterCriticalSection(&session_cs); if(user_agent) { len = wcslen(user_agent) + 1; @@ -539,6 +540,9 @@ static size_t obtain_user_agent(unsigned int version, WCHAR *ret, size_t size) if(len) return len; }
+ if(version < 7) + version = 7; + swprintf(ret, size, L"Mozilla/%s (", version < 9 ? L"4.0" : L"5.0"); len = lstrlenW(ret); if(version < 11) { diff --git a/dlls/urlmon/tests/misc.c b/dlls/urlmon/tests/misc.c index 72b3442..08c249a 100644 --- a/dlls/urlmon/tests/misc.c +++ b/dlls/urlmon/tests/misc.c @@ -1635,6 +1635,26 @@ static void test_user_agent(void) ok(hres == E_OUTOFMEMORY, "UrlMkGetSessionOption failed: %08lx\n", hres); ok(size == sizeof(test_str) && !memcmp(str2, test_str, sizeof(test_str)), "wrong user agent\n");
+ for(i = 0; i < 12; i++) { + size = sizeof(ua); + hres = pObtainUserAgentString(i, ua, &size); + ok(hres == S_OK, "[%u] ObtainUserAgentString failed: %08lx\n", i, hres); + ok(size == strlen(ua) + 1, "[%u] unexpected size %lu, expected %Iu\n", i, size, strlen(ua) + 1); + if(i >= 7) + ok(strcmp(ua, test_str), "[%u] unexpected UA, did not expect "test"\n", i); + else + ok(!strcmp(ua, test_str), "[%u] unexpected UA %s, expected "test"\n", i, wine_dbgstr_a(ua)); + + size = sizeof(ua); + hres = pObtainUserAgentString(i | UAS_EXACTLEGACY, ua, &size); + ok(hres == S_OK, "[%u] ObtainUserAgentString failed: %08lx\n", i, hres); + ok(size == strlen(ua) + 1, "[%u] unexpected size %lu, expected %Iu\n", i, size, strlen(ua) + 1); + if(i == 7) + ok(strcmp(ua, test_str), "[%u] unexpected UA, did not expect "test"\n", i); + else + ok(!strcmp(ua, test_str), "[%u] unexpected UA %s, expected "test"\n", i, wine_dbgstr_a(ua)); + } + hres = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, test2_str, sizeof(test2_str), 0); ok(hres == S_OK, "UrlMkSetSessionOption failed: %08lx\n", hres);
Hi Gabriel,
On 4/8/22 16:35, Gabriel Ivăncescu wrote:
@@ -748,6 +750,44 @@ HRESULT WINAPI ObtainUserAgentString(DWORD option, char *ret, DWORD *ret_size) return hres; }
+/***********************************************************************
MapBrowserEmulationModeToUserAgent (URLMON.445)
- Undocumented, added in IE8
- */
+HRESULT WINAPI MapBrowserEmulationModeToUserAgent(const void *arg, WCHAR **ret) +{
- DWORD size, version;
- const WCHAR *ua;
- WCHAR buf[1024];
- FIXME("%p %p\n", arg, ret);
I think that TRACE() would be fine here with your implementation. If you'd like to point out that it's not complete, you may add something like "semi-stub" in the message itself.
+static void test_MapBrowserEmulationModeToUserAgent(BOOL custom_ua) +{
- /* Undocumented structure of unknown size, crashes if it's too small (with random values from stack) */
- struct
- {
DWORD version;
char unknown[252];
- } arg;
- static char test_str[] = "test";
- const char *custom_ua_msg = "";
- HRESULT hres;
- unsigned i;
- WCHAR *ua;
- if(!pMapBrowserEmulationModeToUserAgent) {
win_skip("MapBrowserEmulationModeToUserAgent not available\n");
return;
- }
- memset(&arg, 0, sizeof(arg));
- if(custom_ua) {
hres = UrlMkSetSessionOption(URLMON_OPTION_USERAGENT, test_str, sizeof(test_str), 0);
ok(hres == S_OK, "UrlMkSetSessionOption failed: %08lx\n", hres);
custom_ua_msg = " (with custom ua)";
- }
- for(i = 0; i < 12; i++) {
arg.version = i;
ua = (WCHAR*)0xdeadbeef;
hres = pMapBrowserEmulationModeToUserAgent(&arg, &ua);
ok(hres == (i == 5 || i >= 7 || custom_ua ? S_OK : E_FAIL),
"[%d] MapBrowserEmulationModeToUserAgent%s returned %08lx\n", i, custom_ua_msg, hres);
if(hres != S_OK)
ok(ua == NULL, "[%d] ua%s = %p\n", i, custom_ua_msg, ua);
else {
char buf[1024];
DWORD size = sizeof(buf);
WCHAR *ua2;
hres = pObtainUserAgentString(custom_ua ? 0 : i, buf, &size);
ok(hres == S_OK, "[%d] ObtainUserAgentString%s failed: %08lx\n", i, custom_ua_msg, hres);
ua2 = a2co(buf);
ok(!lstrcmpW(ua, ua2), "[%d] ua%s = %s, expected %s\n", i, custom_ua_msg, wine_dbgstr_w(ua), wine_dbgstr_w(ua2));
CoTaskMemFree(ua2);
CoTaskMemFree(ua);
}
In custom_ua case, you could just compare the result of MapBrowserEmulationModeToUserAgent directly to custom_ua_msg. I think it would make it easier to see what you're testing.
Thanks,
Jacek