Signed-off-by: Zhiyi Zhang <zzhang(a)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);
--
2.19.1