diff --git a/dll/win32/shlwapi/shlwapi.spec b/dll/win32/shlwapi/shlwapi.spec index 1e706500db0..e8f0aedc2c0 100644 --- a/dll/win32/shlwapi/shlwapi.spec +++ b/dll/win32/shlwapi/shlwapi.spec @@ -448,10 +448,10 @@ 448 stdcall -noname FixSlashesAndColonW(wstr) 449 stub -noname NextPathA 450 stub -noname NextPathW -451 stub -noname CharUpperNoDBCSA -452 stub -noname CharUpperNoDBCSW -453 stub -noname CharLowerNoDBCSA -454 stub -noname CharLowerNoDBCSW +451 stdcall -noname CharUpperNoDBCSA(str) +452 stdcall -noname CharUpperNoDBCSW(wstr) +453 stdcall -noname CharLowerNoDBCSA(str) +454 stdcall -noname CharLowerNoDBCSW(wstr) 455 stdcall -noname PathIsValidCharA(long long) 456 stdcall -noname PathIsValidCharW(long long) 457 stdcall -noname GetLongPathNameWrapW(wstr ptr long) kernel32.GetLongPathNameW diff --git a/dll/win32/shlwapi/utils.cpp b/dll/win32/shlwapi/utils.cpp index ef692dd7916..632848376a0 100644 --- a/dll/win32/shlwapi/utils.cpp +++ b/dll/win32/shlwapi/utils.cpp @@ -41,6 +41,64 @@ GetVersionMajorMinor() return MAKEWORD(HIBYTE(version), LOBYTE(version)); } +static BOOL CharLowerNoDBCSAWorker(PSTR lpString, INT cchMax, BOOL bUppercase) +{ + CHAR szBuff[MAX_PATH]; + INT cch; + if (!lpString) + return FALSE; + cch = cchMax ? cchMax : lstrlenA(lpString); + if (FAILED(StringCchCopyA(szBuff, _countof(szBuff), lpString))) + return FALSE; + return LCMapStringA(LOCALE_SYSTEM_DEFAULT, (bUppercase ? LCMAP_UPPERCASE : LCMAP_LOWERCASE), + szBuff, cch, lpString, cch); +} + +static BOOL CharLowerNoDBCSWWorker(PWSTR lpString, INT cchMax, BOOL bUppercase) +{ + WCHAR szDest[MAX_PATH]; + INT cch; + if (!lpString) + return FALSE; + cch = cchMax ? cchMax : lstrlenW(lpString); + if (FAILED(StringCchCopyW(szDest, _countof(szDest), lpString))) + return FALSE; + return LCMapStringW(LOCALE_SYSTEM_DEFAULT, (bUppercase ? LCMAP_UPPERCASE : LCMAP_LOWERCASE), + szDest, cch, lpString, cch); +} + +/************************************************************************* + * CharLowerNoDBCSA [SHLWAPI.453] + */ +EXTERN_C PSTR WINAPI CharLowerNoDBCSA(_Inout_ PSTR lpString) +{ + return CharLowerNoDBCSAWorker(lpString, 0, FALSE) ? lpString : NULL; +} + +/************************************************************************* + * CharLowerNoDBCSW [SHLWAPI.454] + */ +EXTERN_C PWSTR WINAPI CharLowerNoDBCSW(_Inout_ PWSTR lpString) +{ + return CharLowerNoDBCSWWorker(lpString, 0, FALSE) ? lpString : NULL; +} + +/************************************************************************* + * CharUpperNoDBCSA [SHLWAPI.451] + */ +EXTERN_C PSTR WINAPI CharUpperNoDBCSA(_Inout_ PSTR lpString) +{ + return CharLowerNoDBCSAWorker(lpString, 0, TRUE) ? lpString : NULL; +} + +/************************************************************************* + * CharUpperNoDBCSW [SHLWAPI.452] + */ +EXTERN_C PWSTR WINAPI CharUpperNoDBCSW(_Inout_ PWSTR lpString) +{ + return CharLowerNoDBCSWWorker(lpString, 0, TRUE) ? lpString : NULL; +} + static HRESULT SHInvokeCommandOnContextMenuInternal( _In_opt_ HWND hWnd, diff --git a/modules/rostests/apitests/shlwapi/CMakeLists.txt b/modules/rostests/apitests/shlwapi/CMakeLists.txt index e4dbaf756dd..9de8e484547 100644 --- a/modules/rostests/apitests/shlwapi/CMakeLists.txt +++ b/modules/rostests/apitests/shlwapi/CMakeLists.txt @@ -6,6 +6,7 @@ include_directories($) list(APPEND SOURCE AssocQueryString.c + CharUpperNoDBCS.cpp IShellFolderHelpers.cpp IsQSForward.cpp IStreamPidl.cpp diff --git a/modules/rostests/apitests/shlwapi/CharUpperNoDBCS.cpp b/modules/rostests/apitests/shlwapi/CharUpperNoDBCS.cpp new file mode 100644 index 00000000000..f6d85d3b284 --- /dev/null +++ b/modules/rostests/apitests/shlwapi/CharUpperNoDBCS.cpp @@ -0,0 +1,276 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Tests for CharUpperNoDBCSA/W and CharLowerNoDBCSA/W + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#include +#include +#include +#include + +typedef PSTR (WINAPI *FN_CharLowerNoDBCSA)(PSTR); +typedef PWSTR (WINAPI *FN_CharLowerNoDBCSW)(PWSTR); +typedef PSTR (WINAPI *FN_CharUpperNoDBCSA)(PSTR); +typedef PWSTR (WINAPI *FN_CharUpperNoDBCSW)(PWSTR); + +static FN_CharLowerNoDBCSA g_fnCharLowerNoDBCSA = NULL; +static FN_CharLowerNoDBCSW g_fnCharLowerNoDBCSW = NULL; +static FN_CharUpperNoDBCSA g_fnCharUpperNoDBCSA = NULL; +static FN_CharUpperNoDBCSW g_fnCharUpperNoDBCSW = NULL; + +static void Test_CharUpperNoDBCSA(void) +{ + // Basic ASCII lowercase → uppercase + { + char buf[] = "hello world"; + PSTR ret = g_fnCharUpperNoDBCSA(buf); + ok(ret == buf, "ret was %p\n", ret); + ok_str(buf, "HELLO WORLD"); + } + + // Already uppercase → unchanged + { + char buf[] = "HELLO WORLD"; + g_fnCharUpperNoDBCSA(buf); + ok_str(buf, "HELLO WORLD"); + } + + // Mixed case + { + char buf[] = "Hello World"; + g_fnCharUpperNoDBCSA(buf); + ok_str(buf, "HELLO WORLD"); + } + + // Digits and symbols are unchanged + { + char buf[] = "abc123!@#"; + g_fnCharUpperNoDBCSA(buf); + ok_str(buf, "ABC123!@#"); + } + + // Empty string + { + char buf[] = ""; + PCSTR ret = g_fnCharUpperNoDBCSA(buf); + ok(ret == NULL, "ret was not NULL.\n"); + ok_int(buf[0], ANSI_NULL); + } + + // NULL pointer → must return NULL without crashing + { + PSTR ret = g_fnCharUpperNoDBCSA(NULL); + ok(ret == NULL, "ret was %p\n", ret); + } +} + +static void Test_CharLowerNoDBCSA(void) +{ + // Basic ASCII uppercase → lowercase + { + char buf[] = "HELLO WORLD"; + PSTR ret = g_fnCharLowerNoDBCSA(buf); + ok(ret == buf, "ret was %p\n", ret); + ok_str(buf, "hello world"); + } + + // Already lowercase → unchanged + { + char buf[] = "hello world"; + g_fnCharLowerNoDBCSA(buf); + ok_str(buf, "hello world"); + } + + // Mixed case + { + char buf[] = "Hello World"; + g_fnCharLowerNoDBCSA(buf); + ok_str(buf, "hello world"); + } + + // Digits and symbols are unchanged + { + char buf[] = "ABC123!@#"; + g_fnCharLowerNoDBCSA(buf); + ok_str(buf, "abc123!@#"); + } + + // Empty string + { + char buf[] = ""; + PCSTR ret = g_fnCharLowerNoDBCSA(buf); + ok(ret == NULL, "ret was not NULL.\n"); + ok_int(buf[0], ANSI_NULL); + } + + // NULL pointer → must return NULL without crashing + { + PSTR ret = g_fnCharLowerNoDBCSA(NULL); + ok(ret == NULL, "ret was %p\n", ret); + } +} + +static void Test_CharUpperNoDBCSW(void) +{ + // Basic ASCII lowercase → uppercase (wide) + { + wchar_t buf[] = L"hello world"; + PWSTR ret = g_fnCharUpperNoDBCSW(buf); + ok(ret == buf, "ret was %p\n", ret); + ok_wstr(buf, L"HELLO WORLD"); + } + + // Already uppercase → unchanged + { + wchar_t buf[] = L"HELLO WORLD"; + g_fnCharUpperNoDBCSW(buf); + ok_wstr(buf, L"HELLO WORLD"); + } + + // Mixed case + { + wchar_t buf[] = L"Hello World"; + g_fnCharUpperNoDBCSW(buf); + ok_wstr(buf, L"HELLO WORLD"); + } + + // Digits and symbols are unchanged + { + wchar_t buf[] = L"abc123!@#"; + g_fnCharUpperNoDBCSW(buf); + ok_wstr(buf, L"ABC123!@#"); + } + + // Empty string + { + wchar_t buf[] = L""; + PCWSTR ret = g_fnCharUpperNoDBCSW(buf); + ok(ret == NULL, "ret was not NULL.\n"); + ok_int(buf[0], UNICODE_NULL); + } + + // NULL pointer → must return NULL without crashing + { + PWSTR ret = g_fnCharUpperNoDBCSW(NULL); + ok(ret == NULL, "ret was %p\n", ret); + } +} + +static void Test_CharLowerNoDBCSW(void) +{ + // Basic ASCII uppercase → lowercase (wide) + { + wchar_t buf[] = L"HELLO WORLD"; + PWSTR ret = g_fnCharLowerNoDBCSW(buf); + ok(ret == buf, "ret was %p\n", ret); + ok_wstr(buf, L"hello world"); + } + + // Already lowercase → unchanged + { + wchar_t buf[] = L"hello world"; + g_fnCharLowerNoDBCSW(buf); + ok_wstr(buf, L"hello world"); + } + + // Mixed case + { + wchar_t buf[] = L"Hello World"; + g_fnCharLowerNoDBCSW(buf); + ok_wstr(buf, L"hello world"); + } + + // Digits and symbols are unchanged + { + wchar_t buf[] = L"ABC123!@#"; + g_fnCharLowerNoDBCSW(buf); + ok_wstr(buf, L"abc123!@#"); + } + + // Empty string + { + wchar_t buf[] = L""; + PCWSTR ret = g_fnCharLowerNoDBCSW(buf); + ok(ret == NULL, "ret was not NULL.\n"); + ok_int(buf[0], UNICODE_NULL); + } + + // NULL pointer → must return NULL without crashing + { + PWSTR ret = g_fnCharLowerNoDBCSW(NULL); + ok(ret == NULL, "ret was %p\n", ret); + } +} + +static void Test_RoundTrip(void) +{ + { + char upper[] = "Hello World 123"; + char lower[] = "Hello World 123"; + g_fnCharUpperNoDBCSA(upper); // "HELLO WORLD 123" + g_fnCharLowerNoDBCSA(lower); // "hello world 123" + + char rt1[] = "hello world 123"; + g_fnCharUpperNoDBCSA(rt1); // Lower then Upper → "HELLO WORLD 123" + ok_str(rt1, upper); + + char rt2[] = "HELLO WORLD 123"; + g_fnCharLowerNoDBCSA(rt2); // Upper then Lower → "hello world 123" + ok_str(rt2, lower); + } + + { + wchar_t upper[] = L"Hello World 123"; + wchar_t lower[] = L"Hello World 123"; + g_fnCharUpperNoDBCSW(upper); + g_fnCharLowerNoDBCSW(lower); + + wchar_t rt1[] = L"hello world 123"; + g_fnCharUpperNoDBCSW(rt1); + ok_wstr(rt1, upper); + + wchar_t rt2[] = L"HELLO WORLD 123"; + g_fnCharLowerNoDBCSW(rt2); + ok_wstr(rt2, lower); + } +} + +static void Test_NoDBCS(void) +{ + BYTE buf[] = { 0x82, 'a', ANSI_NULL }; + g_fnCharUpperNoDBCSA((PSTR)buf); + ok_int(buf[2], ANSI_NULL); +} + +START_TEST(CharUpperNoDBCS) +{ + HINSTANCE hSHLWAPI = LoadLibraryW(L"shlwapi"); + if (!hSHLWAPI) + { + skip("shlwapi not found\n"); + return; + } + + g_fnCharLowerNoDBCSA = (FN_CharLowerNoDBCSA)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(453)); + g_fnCharLowerNoDBCSW = (FN_CharLowerNoDBCSW)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(454)); + g_fnCharUpperNoDBCSA = (FN_CharUpperNoDBCSA)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(451)); + g_fnCharUpperNoDBCSW = (FN_CharUpperNoDBCSW)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(452)); + if (!g_fnCharLowerNoDBCSA || !g_fnCharLowerNoDBCSW || + !g_fnCharUpperNoDBCSA || !g_fnCharUpperNoDBCSW) + { + skip("CharUpperNoDBCSA/W or CharLowerNoDBCSA/W not found\n"); + FreeLibrary(hSHLWAPI); + return; + } + + Test_CharUpperNoDBCSA(); + Test_CharLowerNoDBCSA(); + Test_CharUpperNoDBCSW(); + Test_CharLowerNoDBCSW(); + Test_RoundTrip(); + Test_NoDBCS(); + + FreeLibrary(hSHLWAPI); +} diff --git a/modules/rostests/apitests/shlwapi/testlist.c b/modules/rostests/apitests/shlwapi/testlist.c index 196ddaea91d..aafec585617 100644 --- a/modules/rostests/apitests/shlwapi/testlist.c +++ b/modules/rostests/apitests/shlwapi/testlist.c @@ -2,6 +2,7 @@ #include extern void func_AssocQueryString(void); +extern void func_CharUpperNoDBCS(void); extern void func_PathFileExistsDefExtAndAttributesW(void); extern void func_PathFindOnPath(void); extern void func_IShellFolderHelpers(void); @@ -24,6 +25,7 @@ extern void func_StrToInt(void); const struct test winetest_testlist[] = { { "AssocQueryString", func_AssocQueryString }, + { "CharUpperNoDBCS", func_CharUpperNoDBCS }, { "PathFileExistsDefExtAndAttributesW", func_PathFileExistsDefExtAndAttributesW }, { "PathFindOnPath", func_PathFindOnPath }, { "IShellFolderHelpers", func_IShellFolderHelpers }, diff --git a/sdk/include/reactos/shlwapi_undoc.h b/sdk/include/reactos/shlwapi_undoc.h index 6a6f3012a48..15ce4726bf7 100644 --- a/sdk/include/reactos/shlwapi_undoc.h +++ b/sdk/include/reactos/shlwapi_undoc.h @@ -129,6 +129,11 @@ MayExecForward( _In_ VARIANT *pvaIn, _Inout_ VARIANT *pvaOut); +PSTR WINAPI CharLowerNoDBCSA(_Inout_ PSTR lpString); +PWSTR WINAPI CharLowerNoDBCSW(_Inout_ PWSTR lpString); +PSTR WINAPI CharUpperNoDBCSA(_Inout_ PSTR lpString); +PWSTR WINAPI CharUpperNoDBCSW(_Inout_ PWSTR lpString); + HRESULT WINAPI IsQSForward(_In_opt_ REFGUID pguidCmdGroup, _In_ ULONG cCmds, _In_ OLECMD *prgCmds); BOOL WINAPI SHIsChildOrSelf(HWND hParent, HWND hChild); HRESULT WINAPI SHForwardContextMenuMsg(IUnknown* pUnk, UINT uMsg, WPARAM wParam, LPARAM lParam, LRESULT* pResult, BOOL useIContextMenu2);