From fe5b75083ecc7db6825c491bc332029302015efb Mon Sep 17 00:00:00 2001 From: Katayama Hirofumi MZ Date: Wed, 3 Jun 2026 21:38:21 +0900 Subject: [PATCH] [SHLWAPI][SHLWAPI_APITEST][SDK] Support NextPathA/W (#9039) Implementing missing features... JIRA issue: CORE-19278 - Implement NextPathA and NextPathW functions. - Modify shlwapi.spec. - Add prototypes into . - Add NextPath testcase. --- dll/win32/shlwapi/shlwapi.spec | 4 +- dll/win32/shlwapi/utils.cpp | 79 +++++++++ .../rostests/apitests/shlwapi/CMakeLists.txt | 1 + modules/rostests/apitests/shlwapi/NextPath.c | 158 ++++++++++++++++++ modules/rostests/apitests/shlwapi/testlist.c | 2 + sdk/include/reactos/shlwapi_undoc.h | 15 ++ 6 files changed, 257 insertions(+), 2 deletions(-) create mode 100644 modules/rostests/apitests/shlwapi/NextPath.c diff --git a/dll/win32/shlwapi/shlwapi.spec b/dll/win32/shlwapi/shlwapi.spec index d25033f7bf3..8df8fb606dd 100644 --- a/dll/win32/shlwapi/shlwapi.spec +++ b/dll/win32/shlwapi/shlwapi.spec @@ -446,8 +446,8 @@ 446 stdcall -noname PathFileExistsAndAttributesW(wstr ptr) 447 stdcall -noname FixSlashesAndColonA(str) 448 stdcall -noname FixSlashesAndColonW(wstr) -449 stub -noname NextPathA -450 stub -noname NextPathW +449 stdcall -noname NextPathA(str ptr long) +450 stdcall -noname NextPathW(wstr ptr long) 451 stdcall -noname CharUpperNoDBCSA(str) 452 stdcall -noname CharUpperNoDBCSW(wstr) 453 stdcall -noname CharLowerNoDBCSA(str) diff --git a/dll/win32/shlwapi/utils.cpp b/dll/win32/shlwapi/utils.cpp index 82d153a544b..6871764c154 100644 --- a/dll/win32/shlwapi/utils.cpp +++ b/dll/win32/shlwapi/utils.cpp @@ -878,3 +878,82 @@ SHDialogBox( SHDIALOG data = { fn, pThis }; return DialogBoxParamA(hInstance, lpTemplateName, hWndParent, SHDialogProc, (LPARAM)&data); } + +/************************************************************************* + * NextPathA [SHLWAPI.449] + * + * See NextPathW. + */ +EXTERN_C PSTR WINAPI +NextPathA( + _In_ PCSTR pszStart, + _Out_writes_(cchDest) PSTR pszDest, + _In_ UINT cchDest) +{ + if (!pszStart) + return NULL; + + PCSTR pchStart = pszStart; + while (*pchStart == ';') + ++pchStart; + + if (!*pchStart) + return NULL; + + PSTR pchEnd = StrChrA(pchStart, ';'); + if (!pchEnd) + pchEnd = (PSTR)(pchStart + lstrlenA(pchStart)); + + const UINT cchSegment = (UINT)(pchEnd - pchStart); + const UINT cchToCopy = min(cchSegment + 1, cchDest); + lstrcpynA(pszDest, pchStart, cchToCopy); + pszDest[cchSegment] = ANSI_NULL; + + PathRemoveBlanksA(pszDest); + if (!*pszDest) + return NULL; + + return (*pchEnd == ';') ? (pchEnd + 1) : pchEnd; +} + +/************************************************************************* + * NextPathW [SHLWAPI.450] + * + * Extracts the next path from a semicolon-separated path string (Unicode version) + * + * @param pszStart Parsing start position (semicolon-separated path string) + * @param pszDest Buffer to store the extracted path + * @param cchDest Buffer size (number of characters) + * @return Pointer to the beginning of the next path. NULL if there are no more paths. + */ +EXTERN_C PWSTR WINAPI +NextPathW( + _In_ PCWSTR pszStart, + _Out_writes_(cchDest) PWSTR pszDest, + _In_ UINT cchDest) +{ + if (!pszStart) + return NULL; + + PCWSTR pchStart = pszStart; + while (*pchStart == L';') + ++pchStart; + + if (!*pchStart) + return NULL; + + PWSTR pchEnd = StrChrW(pchStart, L';'); + if (!pchEnd) + pchEnd = (PWSTR)(pchStart + lstrlenW(pchStart)); + + const UINT cchSegment = (UINT)(pchEnd - pchStart); + const UINT cchToCopy = min(cchSegment + 1, cchDest); + lstrcpynW(pszDest, pchStart, cchToCopy); + pszDest[cchSegment] = UNICODE_NULL; + + PathRemoveBlanksW(pszDest); + if (!*pszDest) + return NULL; + + return (*pchEnd == L';') ? (pchEnd + 1) : pchEnd; +} diff --git a/modules/rostests/apitests/shlwapi/CMakeLists.txt b/modules/rostests/apitests/shlwapi/CMakeLists.txt index f258f1a8a0f..d9344893a45 100644 --- a/modules/rostests/apitests/shlwapi/CMakeLists.txt +++ b/modules/rostests/apitests/shlwapi/CMakeLists.txt @@ -10,6 +10,7 @@ list(APPEND SOURCE IShellFolderHelpers.cpp IsQSForward.cpp IStreamPidl.cpp + NextPath.c PathFileExistsDefExtAndAttributesW.c PathFindOnPath.c PathIsUNC.c diff --git a/modules/rostests/apitests/shlwapi/NextPath.c b/modules/rostests/apitests/shlwapi/NextPath.c new file mode 100644 index 00000000000..edf4855a9e6 --- /dev/null +++ b/modules/rostests/apitests/shlwapi/NextPath.c @@ -0,0 +1,158 @@ +/* + * PROJECT: ReactOS api tests + * LICENSE: GPL-2.0-or-later (https://spdx.org/licenses/GPL-2.0-or-later) + * PURPOSE: Tests for NextPathA/W + * COPYRIGHT: Copyright 2026 Katayama Hirofumi MZ + */ + +#include +#include + +typedef PSTR (WINAPI *FN_NextPathA)(PCSTR, PSTR, UINT); +typedef PWSTR (WINAPI *FN_NextPathW)(PCWSTR, PWSTR, UINT); + +static FN_NextPathA s_pNextPathA = NULL; +static FN_NextPathW s_pNextPathW = NULL; + +static void TEST_NextPathA(void) +{ + PSTR pch; + CHAR sz[MAX_PATH]; + + /* NULL pszStart returns NULL */ + pch = s_pNextPathA(NULL, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Basic semicolon-separated paths */ + pch = s_pNextPathA("C:\\TEST1;C:\\TEST2;C:\\TEST3", sz, _countof(sz)); + ok_str(sz, "C:\\TEST1"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok_str(sz, "C:\\TEST2"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok_str(sz, "C:\\TEST3"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Whitespace-only segment */ + pch = s_pNextPathA("C:\\TEST1; ;C:\\TEST3", sz, _countof(sz)); + ok_str(sz, "C:\\TEST1"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Empty string: no paths at all */ + pch = s_pNextPathA("", sz, _countof(sz)); + ok(pch == NULL, "empty string: pch was %p\n", pch); + + /* Leading semicolons are skipped */ + pch = s_pNextPathA(";;;C:\\TEST1", sz, _countof(sz)); + ok_str(sz, "C:\\TEST1"); + ok(pch != NULL, "leading semicolons: pch should not be NULL\n"); + + /* Trailing semicolon */ + pch = s_pNextPathA("C:\\TEST1;", sz, _countof(sz)); + ok_str(sz, "C:\\TEST1"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok(pch == NULL, "trailing semicolon: pch was %p\n", pch); + + /* Only semicolons */ + pch = s_pNextPathA(";;;", sz, _countof(sz)); + ok(pch == NULL, "only semicolons: pch was %p\n", pch); + + /* Path with surrounding spaces */ + pch = s_pNextPathA(" C:\\TEST1 ;C:\\TEST2", sz, _countof(sz)); + ok_str(sz, "C:\\TEST1"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok_str(sz, "C:\\TEST2"); + + /* Single path, no semicolon */ + pch = s_pNextPathA("C:\\SINGLE", sz, _countof(sz)); + ok_str(sz, "C:\\SINGLE"); + pch = s_pNextPathA(pch, sz, _countof(sz)); + ok(pch == NULL, "single path: pch was %p\n", pch); + + /* cchDest = 0 */ + sz[0] = '*'; + sz[1] = ANSI_NULL; + pch = s_pNextPathA("C:\\TEST1;C:\\TEST2;C:\\TEST3", sz, 0); + ok_str(pch, "C:\\TEST2;C:\\TEST3"); + ok_str(sz, "*"); +} + +static void TEST_NextPathW(void) +{ + PWSTR pch; + WCHAR sz[MAX_PATH]; + + /* NULL pszStart returns NULL */ + pch = s_pNextPathW(NULL, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Basic semicolon-separated paths */ + pch = s_pNextPathW(L"C:\\TEST1;C:\\TEST2;C:\\TEST3", sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST1"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST2"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST3"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Whitespace-only segment */ + pch = s_pNextPathW(L"C:\\TEST1; ;C:\\TEST3", sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST1"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok(pch == NULL, "pch was %p\n", pch); + + /* Empty string */ + pch = s_pNextPathW(L"", sz, _countof(sz)); + ok(pch == NULL, "empty string: pch was %p\n", pch); + + /* Leading semicolons are skipped */ + pch = s_pNextPathW(L";;;C:\\TEST1", sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST1"); + ok(pch != NULL, "leading semicolons: pch should not be NULL\n"); + + /* Trailing semicolon */ + pch = s_pNextPathW(L"C:\\TEST1;", sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST1"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok(pch == NULL, "trailing semicolon: pch was %p\n", pch); + + /* Only semicolons */ + pch = s_pNextPathW(L";;;", sz, _countof(sz)); + ok(pch == NULL, "only semicolons: pch was %p\n", pch); + + /* Path with surrounding spaces */ + pch = s_pNextPathW(L" C:\\TEST1 ;C:\\TEST2", sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST1"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok_wstr(sz, L"C:\\TEST2"); + + /* Single path, no semicolon */ + pch = s_pNextPathW(L"C:\\SINGLE", sz, _countof(sz)); + ok_wstr(sz, L"C:\\SINGLE"); + pch = s_pNextPathW(pch, sz, _countof(sz)); + ok(pch == NULL, "single path: pch was %p\n", pch); + + /* cchDest = 0 */ + sz[0] = L'*'; + sz[1] = UNICODE_NULL; + pch = s_pNextPathW(L"C:\\TEST1;C:\\TEST2;C:\\TEST3", sz, 0); + ok_wstr(pch, L"C:\\TEST2;C:\\TEST3"); + ok_wstr(sz, L"*"); +} + +START_TEST(NextPath) +{ + HINSTANCE hSHLWAPI = GetModuleHandleA("shlwapi"); + s_pNextPathA = (FN_NextPathA)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(449)); + s_pNextPathW = (FN_NextPathW)GetProcAddress(hSHLWAPI, MAKEINTRESOURCEA(450)); + if (!s_pNextPathA || !s_pNextPathW) + { + skip("NextPath not found\n"); + return; + } + + TEST_NextPathA(); + TEST_NextPathW(); +} diff --git a/modules/rostests/apitests/shlwapi/testlist.c b/modules/rostests/apitests/shlwapi/testlist.c index 33f6089ac4f..55e45414642 100644 --- a/modules/rostests/apitests/shlwapi/testlist.c +++ b/modules/rostests/apitests/shlwapi/testlist.c @@ -8,6 +8,7 @@ extern void func_PathFindOnPath(void); extern void func_IShellFolderHelpers(void); extern void func_IsQSForward(void); extern void func_IStreamPidl(void); +extern void func_NextPath(void); extern void func_PathIsUNC(void); extern void func_PathIsUNCServer(void); extern void func_PathIsUNCServerShare(void); @@ -33,6 +34,7 @@ const struct test winetest_testlist[] = { "IShellFolderHelpers", func_IShellFolderHelpers }, { "IsQSForward", func_IsQSForward }, { "IStreamPidl", func_IStreamPidl }, + { "NextPath", func_NextPath }, { "PathIsUNC", func_PathIsUNC }, { "PathIsUNCServer", func_PathIsUNCServer }, { "PathIsUNCServerShare", func_PathIsUNCServerShare }, diff --git a/sdk/include/reactos/shlwapi_undoc.h b/sdk/include/reactos/shlwapi_undoc.h index d760fa08973..37f55f3f0ea 100644 --- a/sdk/include/reactos/shlwapi_undoc.h +++ b/sdk/include/reactos/shlwapi_undoc.h @@ -60,10 +60,25 @@ SHRestrictionLookup( BOOL WINAPI SHAboutInfoA(LPSTR lpszDest, DWORD dwDestLen); BOOL WINAPI SHAboutInfoW(LPWSTR lpszDest, DWORD dwDestLen); + +PSTR WINAPI +NextPathA( + _In_ PCSTR pszStart, + _Out_writes_(cchDest) PSTR pszDest, + _In_ UINT cchDest); + +PWSTR WINAPI +NextPathW( + _In_ PCWSTR pszStart, + _Out_writes_(cchDest) PWSTR pszDest, + _In_ UINT cchDest); + #ifdef UNICODE #define SHAboutInfo SHAboutInfoW +#define NextPath NextPathW #else #define SHAboutInfo SHAboutInfoA +#define NextPath NextPathA #endif HRESULT WINAPI CLSIDFromStringWrap(_In_ LPCWSTR idstr, _Out_ CLSID *id);