Signed-off-by: Zhiyi Zhang zzhang@codeweavers.com --- .../api-ms-win-core-path-l1-1-0.spec | 2 +- dlls/kernelbase/kernelbase.spec | 2 +- dlls/kernelbase/path.c | 110 +++++++++++++++ dlls/kernelbase/tests/path.c | 128 ++++++++++++++++++ include/pathcch.h | 1 + 5 files changed, 241 insertions(+), 2 deletions(-)
diff --git a/dlls/api-ms-win-core-path-l1-1-0/api-ms-win-core-path-l1-1-0.spec b/dlls/api-ms-win-core-path-l1-1-0/api-ms-win-core-path-l1-1-0.spec index 53f7b987ca..e07fe02a51 100644 --- a/dlls/api-ms-win-core-path-l1-1-0/api-ms-win-core-path-l1-1-0.spec +++ b/dlls/api-ms-win-core-path-l1-1-0/api-ms-win-core-path-l1-1-0.spec @@ -16,7 +16,7 @@ @ stdcall PathCchRemoveExtension(wstr long) kernelbase.PathCchRemoveExtension @ stub PathCchRemoveFileSpec @ stdcall PathCchRenameExtension(wstr long wstr) kernelbase.PathCchRenameExtension -@ stub PathCchSkipRoot +@ stdcall PathCchSkipRoot(wstr ptr) kernelbase.PathCchSkipRoot @ stdcall PathCchStripPrefix(wstr long) kernelbase.PathCchStripPrefix @ stub PathCchStripToRoot @ stdcall PathIsUNCEx(wstr ptr) kernelbase.PathIsUNCEx diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index 8f4136eb6b..eae67be147 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -1045,7 +1045,7 @@ @ stdcall PathCchRemoveExtension(wstr long) # @ stub PathCchRemoveFileSpec @ stdcall PathCchRenameExtension(wstr long wstr) -# @ stub PathCchSkipRoot +@ stdcall PathCchSkipRoot(wstr ptr) @ stdcall PathCchStripPrefix(wstr long) # @ stub PathCchStripToRoot @ stdcall PathCombineA(ptr str str) shlwapi.PathCombineA diff --git a/dlls/kernelbase/path.c b/dlls/kernelbase/path.c index 366efa41e1..168edd0610 100644 --- a/dlls/kernelbase/path.c +++ b/dlls/kernelbase/path.c @@ -50,6 +50,84 @@ static BOOL is_prefixed_disk(const WCHAR *string) return !memcmp(string, prefix, sizeof(prefix)) && isalphaW(string[4]) && string[5] == ':'; }
+static BOOL is_prefixed_volume(const WCHAR *string) +{ + static const WCHAR prefixed_volume[] = {'\', '\', '?', '\', 'V', 'o', 'l', 'u', 'm', 'e'}; + const WCHAR *guid; + INT ret; + INT i = 0; + + ret = memicmpW(string, prefixed_volume, ARRAY_SIZE(prefixed_volume)); + if (ret) return FALSE; + + guid = string + ARRAY_SIZE(prefixed_volume); + + while (i <= 37) + { + switch (i) + { + case 0: + if (guid[i] != '{') return FALSE; + break; + case 9: + case 14: + case 19: + case 24: + if (guid[i] != '-') return FALSE; + break; + case 37: + if (guid[i] != '}') return FALSE; + break; + default: + if (!isalnumW(guid[i])) return FALSE; + break; + } + i++; + } + + return TRUE; +} + +/* Get the next character beyond end of the segment. + Return TRUE if the last segment ends with a backslash */ +static BOOL get_next_segment(const WCHAR *next, const WCHAR **next_segment) +{ + while (*next && *next != '\') next++; + if (*next == '\') + { + *next_segment = next + 1; + return TRUE; + } + else + { + *next_segment = next; + return FALSE; + } +} + +/* Find the last character of the root in a path, if there is one, without any segments */ +static const WCHAR *get_root_end(const WCHAR *path) +{ + /* Find path root */ + if (is_prefixed_volume(path)) + return path[48] == '\' ? path + 48 : path + 47; + else if (is_prefixed_unc(path)) + return path + 7; + else if (is_prefixed_disk(path)) + return path[6] == '\' ? path + 6 : path + 5; + /* \ */ + else if (path[0] == '\' && path[1] == '\') + return path + 1; + /* \ */ + else if (path[0] == '\') + return path; + /* X:\ */ + else if (isalphaW(path[0]) && path[1] == ':') + return path[2] == '\' ? path + 2 : path + 1; + else + return NULL; +} + HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size) { return PathCchAddBackslashEx(path, size, NULL, NULL); @@ -196,6 +274,38 @@ HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *ext return FAILED(hr) ? hr : S_OK; }
+HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end) +{ + static const WCHAR unc_prefix[] = {'\', '\', '?'}; + + TRACE("%s %p\n", debugstr_w(path), root_end); + + if (!path || !path[0] || !root_end + || (!memicmpW(unc_prefix, path, ARRAY_SIZE(unc_prefix)) && !is_prefixed_volume(path) && !is_prefixed_unc(path) + && !is_prefixed_disk(path))) + return E_INVALIDARG; + + *root_end = get_root_end(path); + if (*root_end) + { + (*root_end)++; + if (is_prefixed_unc(path)) + { + get_next_segment(*root_end, root_end); + get_next_segment(*root_end, root_end); + } + else if (path[0] == '\' && path[1] == '\' && path[2] != '?') + { + /* Skip share server */ + get_next_segment(*root_end, root_end); + /* If mount point is empty, don't skip over mount point */ + if (**root_end != '\') get_next_segment(*root_end, root_end); + } + } + + return *root_end ? S_OK : E_INVALIDARG; +} + HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size) { TRACE("%s %lu\n", wine_dbgstr_w(path), size); diff --git a/dlls/kernelbase/tests/path.c b/dlls/kernelbase/tests/path.c index af15e83d60..f54e77be46 100644 --- a/dlls/kernelbase/tests/path.c +++ b/dlls/kernelbase/tests/path.c @@ -37,6 +37,7 @@ HRESULT (WINAPI *pPathCchCombineEx)(WCHAR *out, SIZE_T size, const WCHAR *path1, HRESULT (WINAPI *pPathCchFindExtension)(const WCHAR *path, SIZE_T size, const WCHAR **extension); HRESULT (WINAPI *pPathCchRemoveExtension)(WCHAR *path, SIZE_T size); HRESULT (WINAPI *pPathCchRenameExtension)(WCHAR *path, SIZE_T size, const WCHAR *extension); +HRESULT (WINAPI *pPathCchSkipRoot)(const WCHAR *path, const WCHAR **root_end); HRESULT (WINAPI *pPathCchStripPrefix)(WCHAR *path, SIZE_T size); BOOL (WINAPI *pPathIsUNCEx)(const WCHAR *path, const WCHAR **server);
@@ -634,6 +635,131 @@ static void test_PathCchRenameExtension(void) } }
+struct skiproot_test +{ + const char *path; + int root_offset; + HRESULT hr; +}; + +static const struct skiproot_test skiproot_tests [] = +{ + /* Basic combination */ + {"", 0, E_INVALIDARG}, + {"C:\", 3, S_OK}, + {"\", 1, S_OK}, + {"\\.\", 4, S_OK}, + {"\\?\UNC\", 8, S_OK}, + {"\\?\C:\", 7, S_OK}, + + /* Basic + \ */ + {"C:\\", 3, S_OK}, + {"\\", 2, S_OK}, + {"\\.\\", 4, S_OK}, + {"\\?\UNC\\", 9, S_OK}, + {"\\?\C:\\", 7, S_OK}, + + /* Basic + a */ + {"a", 0, E_INVALIDARG}, + {"C:\a", 3, S_OK}, + {"\a", 1, S_OK}, + {"\\.\a", 5, S_OK}, + {"\\?\UNC\a", 9, S_OK}, + + /* Basic + \a */ + {"\a", 1, S_OK}, + {"C:\\a", 3, S_OK}, + {"\\a", 3, S_OK}, + {"\\.\\a", 4, S_OK}, + {"\\?\UNC\\a", 10, S_OK}, + {"\\?\C:\\a", 7, S_OK}, + + /* Basic + a\ */ + {"a\", 0, E_INVALIDARG}, + {"C:\a\", 3, S_OK}, + {"\a\", 1, S_OK}, + {"\\.\a\", 6, S_OK}, + {"\\?\UNC\a\", 10, S_OK}, + {"\\?\C:\a\", 7, S_OK}, + + /* Network share */ + {"\\\\", 3, S_OK}, + {"\\a\", 4, S_OK}, + {"\\a\b", 5, S_OK}, + {"\\a\b\", 6, S_OK}, + {"\\a\b\\", 6, S_OK}, + {"\\a\b\\c", 6, S_OK}, + {"\\a\b\c", 6, S_OK}, + {"\\a\b\c\", 6, S_OK}, + {"\\a\b\c\d", 6, S_OK}, + {"\\a\\b\c\", 4, S_OK}, + {"\\aa\bb\cc\", 8, S_OK}, + + /* UNC */ + {"\\?\UNC\\", 9, S_OK}, + {"\\?\UNC\a\b", 11, S_OK}, + {"\\?\UNC\a\b", 11, S_OK}, + {"\\?\UNC\a\b\", 12, S_OK}, + {"\\?\UNC\a\b\c", 12, S_OK}, + {"\\?\UNC\a\b\c\", 12, S_OK}, + {"\\?\UNC\a\b\c\d", 12, S_OK}, + {"\\?\C:", 6, S_OK}, + {"\\?\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}", 48, S_OK}, + {"\\?\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\", 49, S_OK}, + {"\\?\unc\a\b", 11, S_OK}, + {"\\?\volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\", 49, S_OK}, + {"\\?\volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\a", 49, S_OK}, + + /* Malformed */ + {"C:", 2, S_OK}, + {":", 0, E_INVALIDARG}, + {":\", 0, E_INVALIDARG}, + {"C\", 0, E_INVALIDARG}, + {"\?", 1, S_OK}, + {"\?\UNC", 1, S_OK}, + {"\\?\", 0, E_INVALIDARG}, + {"\\?\UNC", 0, E_INVALIDARG}, + {"\\?\::\", 0, E_INVALIDARG}, + {"\\?\Volume", 0, E_INVALIDARG}, + {"\.", 1, S_OK}, + {"\\..", 4, S_OK}, + {"\\..a", 5, S_OK} +}; + +static void test_PathCchSkipRoot(void) +{ + WCHAR pathW[MAX_PATH]; + const WCHAR *root_end; + HRESULT hr; + INT i; + + if (!pPathCchSkipRoot) + { + win_skip("PathCchSkipRoot() is not available.\n"); + return; + } + + root_end = (const WCHAR *)0xdeadbeef; + hr = pPathCchSkipRoot(NULL, &root_end); + ok(hr == E_INVALIDARG, "Expect result %#x, got %#x\n", E_INVALIDARG, hr); + ok(root_end == (const WCHAR *)0xdeadbeef, "Expect root_end 0xdeadbeef, got %p\n", root_end); + + MultiByteToWideChar(CP_ACP, 0, "C:\", -1, pathW, ARRAY_SIZE(pathW)); + hr = pPathCchSkipRoot(pathW, NULL); + ok(hr == E_INVALIDARG, "Expect result %#x, got %#x\n", E_INVALIDARG, hr); + + for (i = 0; i < ARRAY_SIZE(skiproot_tests); i++) + { + const struct skiproot_test *t = skiproot_tests + i; + MultiByteToWideChar(CP_ACP, 0, t->path, -1, pathW, ARRAY_SIZE(pathW)); + hr = pPathCchSkipRoot(pathW, &root_end); + ok(hr == t->hr, "path %s expect result %#x, got %#x\n", t->path, t->hr, hr); + if (SUCCEEDED(hr)) + ok(root_end - pathW == t->root_offset, "path %s expect root offset %d, got %d\n", t->path, t->root_offset, + root_end - pathW); + } +} + struct stripprefix_test { const CHAR *path; @@ -789,6 +915,7 @@ START_TEST(path) pPathCchFindExtension = (void *)GetProcAddress(hmod, "PathCchFindExtension"); pPathCchRemoveExtension = (void *)GetProcAddress(hmod, "PathCchRemoveExtension"); pPathCchRenameExtension = (void *)GetProcAddress(hmod, "PathCchRenameExtension"); + pPathCchSkipRoot = (void *)GetProcAddress(hmod, "PathCchSkipRoot"); pPathCchStripPrefix = (void *)GetProcAddress(hmod, "PathCchStripPrefix"); pPathIsUNCEx = (void *)GetProcAddress(hmod, "PathIsUNCEx");
@@ -799,6 +926,7 @@ START_TEST(path) test_PathCchFindExtension(); test_PathCchRemoveExtension(); test_PathCchRenameExtension(); + test_PathCchSkipRoot(); test_PathCchStripPrefix(); test_PathIsUNCEx(); } diff --git a/include/pathcch.h b/include/pathcch.h index 1ff9e24648..42bae4edd0 100644 --- a/include/pathcch.h +++ b/include/pathcch.h @@ -32,5 +32,6 @@ HRESULT WINAPI PathCchCombineEx(WCHAR *out, SIZE_T size, const WCHAR *path1, con HRESULT WINAPI PathCchFindExtension(const WCHAR *path, SIZE_T size, const WCHAR **extension); HRESULT WINAPI PathCchRemoveExtension(WCHAR *path, SIZE_T size); HRESULT WINAPI PathCchRenameExtension(WCHAR *path, SIZE_T size, const WCHAR *extension); +HRESULT WINAPI PathCchSkipRoot(const WCHAR *path, const WCHAR **root_end); HRESULT WINAPI PathCchStripPrefix(WCHAR *path, SIZE_T size); BOOL WINAPI PathIsUNCEx(const WCHAR *path, const WCHAR **server);