From 165f777c3819515f25bc55dac0df5ac2809fa585 Mon Sep 17 00:00:00 2001 From: Zhiyi Zhang Date: Fri, 23 Nov 2018 11:18:05 +0800 Subject: [PATCH] kernelbase: Implement PathAllocCanonicalize. Signed-off-by: Zhiyi Zhang Signed-off-by: Alexandre Julliard --- .../api-ms-win-core-path-l1-1-0.spec | 2 +- dlls/kernelbase/kernelbase.spec | 2 +- dlls/kernelbase/path.c | 182 +++++++++++ dlls/kernelbase/tests/path.c | 306 ++++++++++++++++++ include/pathcch.h | 2 + 5 files changed, 492 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 725f16448fd..9bce0ace95e 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 @@ -1,4 +1,4 @@ -@ stub PathAllocCanonicalize +@ stdcall PathAllocCanonicalize(wstr long ptr) kernelbase.PathAllocCanonicalize @ stub PathAllocCombine @ stdcall PathCchAddBackslash(wstr long) kernelbase.PathCchAddBackslash @ stdcall PathCchAddBackslashEx(wstr long ptr ptr) kernelbase.PathCchAddBackslashEx diff --git a/dlls/kernelbase/kernelbase.spec b/dlls/kernelbase/kernelbase.spec index e7bb62d75c1..8273d5f1b1a 100644 --- a/dlls/kernelbase/kernelbase.spec +++ b/dlls/kernelbase/kernelbase.spec @@ -1023,7 +1023,7 @@ @ stdcall PathAddBackslashW(wstr) shlwapi.PathAddBackslashW @ stdcall PathAddExtensionA(str str) shlwapi.PathAddExtensionA @ stdcall PathAddExtensionW(wstr wstr) shlwapi.PathAddExtensionW -# @ stub PathAllocCanonicalize +@ stdcall PathAllocCanonicalize(wstr long ptr) # @ stub PathAllocCombine @ stdcall PathAppendA(str str) shlwapi.PathAppendA @ stdcall PathAppendW(wstr wstr) shlwapi.PathAppendW diff --git a/dlls/kernelbase/path.c b/dlls/kernelbase/path.c index c3fca8b9cfa..659a17bd096 100644 --- a/dlls/kernelbase/path.c +++ b/dlls/kernelbase/path.c @@ -126,6 +126,188 @@ static const WCHAR *get_root_end(const WCHAR *path) return NULL; } +HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out) +{ + WCHAR *buffer, *dst; + const WCHAR *src; + const WCHAR *root_end; + SIZE_T buffer_size, length; + + TRACE("%s %#x %p\n", debugstr_w(path_in), flags, path_out); + + if (!path_in || !path_out + || ((flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS) && (flags & PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS)) + || (flags & (PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS) + && !(flags & PATHCCH_ALLOW_LONG_PATHS)) + || ((flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) && (flags & PATHCCH_ALLOW_LONG_PATHS))) + { + if (path_out) *path_out = NULL; + return E_INVALIDARG; + } + + length = strlenW(path_in); + if ((length + 1 > MAX_PATH && !(flags & (PATHCCH_ALLOW_LONG_PATHS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH))) + || (length + 1 > PATHCCH_MAX_CCH)) + { + *path_out = NULL; + return HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE); + } + + /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH implies PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */ + if (flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH) flags |= PATHCCH_DO_NOT_NORMALIZE_SEGMENTS; + + /* path length + possible \\?\ addition + possible \ addition + NUL */ + buffer_size = (length + 6) * sizeof(WCHAR); + buffer = LocalAlloc(LMEM_ZEROINIT, buffer_size); + if (!buffer) + { + *path_out = NULL; + return E_OUTOFMEMORY; + } + + src = path_in; + dst = buffer; + + root_end = get_root_end(path_in); + if (root_end) root_end = buffer + (root_end - path_in); + + /* Copy path root */ + if (root_end) + { + memcpy(dst, src, (root_end - buffer + 1) * sizeof(WCHAR)); + src += root_end - buffer + 1; + if(PathCchStripPrefix(dst, length + 6) == S_OK) + { + /* Fill in \ in X:\ if the \ is missing */ + if(isalphaW(dst[0]) && dst[1] == ':' && dst[2]!= '\\') + { + dst[2] = '\\'; + dst[3] = 0; + } + dst = buffer + strlenW(buffer); + root_end = dst; + } + else + dst += root_end - buffer + 1; + } + + while (*src) + { + if (src[0] == '.') + { + if (src[1] == '.') + { + /* Keep one . after * */ + if (dst > buffer && dst[-1] == '*') + { + *dst++ = *src++; + continue; + } + + /* Keep the . if one of the following is true: + * 1. PATHCCH_DO_NOT_NORMALIZE_SEGMENTS + * 2. in form of a..b + */ + if (dst > buffer + && (((flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS) && dst[-1] != '\\') + || (dst[-1] != '\\' && src[2] != '\\' && src[2]))) + { + *dst++ = *src++; + *dst++ = *src++; + continue; + } + + /* Remove the \ before .. if the \ is not part of root */ + if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) + { + *--dst = '\0'; + /* Remove characters until a \ is encountered */ + while (dst > buffer) + { + if (dst[-1] == '\\') + { + *--dst = 0; + break; + } + else + *--dst = 0; + } + } + /* Remove the extra \ after .. if the \ before .. wasn't deleted */ + else if (src[2] == '\\') + src++; + + src += 2; + } + else + { + /* Keep the . if one of the following is true: + * 1. PATHCCH_DO_NOT_NORMALIZE_SEGMENTS + * 2. in form of a.b, which is used in domain names + * 3. *. + */ + if (dst > buffer + && ((flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS && dst[-1] != '\\') + || (dst[-1] != '\\' && src[1] != '\\' && src[1]) || (dst[-1] == '*'))) + { + *dst++ = *src++; + continue; + } + + /* Remove the \ before . if the \ is not part of root */ + if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end)) dst--; + /* Remove the extra \ after . if the \ before . wasn't deleted */ + else if (src[1] == '\\') + src++; + + src++; + } + + /* If X:\ is not complete, then complete it */ + if (isalphaW(buffer[0]) && buffer[1] == ':' && buffer[2] != '\\') + { + root_end = buffer + 2; + dst = buffer + 3; + buffer[2] = '\\'; + /* If next character is \, use the \ to fill in */ + if (src[0] == '\\') src++; + } + } + /* Copy over */ + else + *dst++ = *src++; + } + /* End the path */ + *dst = 0; + + /* If result path is empty, fill in \ */ + if (!*buffer) + { + buffer[0] = '\\'; + buffer[1] = 0; + } + + /* Extend the path if needed */ + length = strlenW(buffer); + if (((length + 1 > MAX_PATH && isalphaW(buffer[0]) && buffer[1] == ':') + || (isalphaW(buffer[0]) && buffer[1] == ':' && flags & PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH)) + && !(flags & PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS)) + { + memmove(buffer + 4, buffer, (length + 1) * sizeof(WCHAR)); + buffer[0] = '\\'; + buffer[1] = '\\'; + buffer[2] = '?'; + buffer[3] = '\\'; + } + + /* Add a trailing backslash to the path if needed */ + if (flags & PATHCCH_ENSURE_TRAILING_SLASH) + PathCchAddBackslash(buffer, buffer_size); + + *path_out = buffer; + return S_OK; +} + HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size) { return PathCchAddBackslashEx(path, size, NULL, NULL); diff --git a/dlls/kernelbase/tests/path.c b/dlls/kernelbase/tests/path.c index 2faf9e9ae1e..2e496f72a82 100644 --- a/dlls/kernelbase/tests/path.c +++ b/dlls/kernelbase/tests/path.c @@ -30,6 +30,7 @@ #include "wine/test.h" +HRESULT (WINAPI *pPathAllocCanonicalize)(const WCHAR *path_in, DWORD flags, WCHAR **path_out); HRESULT (WINAPI *pPathCchAddBackslash)(WCHAR *out, SIZE_T size); HRESULT (WINAPI *pPathCchAddBackslashEx)(WCHAR *out, SIZE_T size, WCHAR **endptr, SIZE_T *remaining); HRESULT (WINAPI *pPathCchAddExtension)(WCHAR *path, SIZE_T size, const WCHAR *extension); @@ -46,6 +47,309 @@ HRESULT (WINAPI *pPathCchStripPrefix)(WCHAR *path, SIZE_T size); HRESULT (WINAPI *pPathCchStripToRoot)(WCHAR *path, SIZE_T size); BOOL (WINAPI *pPathIsUNCEx)(const WCHAR *path, const WCHAR **server); +struct alloccanonicalize_test +{ + const CHAR *path_in; + const CHAR *path_out; + DWORD flags; + HRESULT hr; +}; + +static const struct alloccanonicalize_test alloccanonicalize_tests[] = +{ + /* Malformed path */ + {"C:a", "C:a", 0, S_OK}, + {"\\\\?\\C:", "C:\\", 0, S_OK}, + {"\\\\?C:\\a", "\\\\?C:\\a", 0, S_OK}, + {"\\\\?UNC\\a", "\\\\?UNC\\a", 0, S_OK}, + {"\\\\?\\UNCa", "\\\\?\\UNCa", 0, S_OK}, + {"\\\\?C:a", "\\\\?C:a", 0, S_OK}, + + /* No . */ + {"", "\\", 0, S_OK}, + {"C:", "C:", 0, S_OK}, + {"C:\\", "C:\\", 0, S_OK}, + {"\\\\?\\C:\\a", "C:\\a", 0, S_OK}, + {"\\\\?\\UNC\\a", "\\\\a", 0, S_OK}, + + /* . */ + {".", "\\", 0, S_OK}, + {"..", "\\", 0, S_OK}, + {"...", "\\", 0, S_OK}, + {"*.", "*.", 0, S_OK}, + {"*..", "*.", 0, S_OK}, + {"*...", "*.", 0, S_OK}, + {"a.", "a", 0, S_OK}, + {"a.b", "a.b", 0, S_OK}, + {"a\\.", "a", 0, S_OK}, + {"a\\.\\b", "a\\b", 0, S_OK}, + {"C:.", "C:\\", 0, S_OK}, + {"C:\\.", "C:\\", 0, S_OK}, + {"C:\\.\\", "C:\\", 0, S_OK}, + {"C:\\a.", "C:\\a", 0, S_OK}, + {"C:\\a\\.", "C:\\a", 0, S_OK}, + {"C:\\a\\\\.", "C:\\a\\", 0, S_OK}, + {"C:\\a\\\\\\.", "C:\\a\\\\", 0, S_OK}, + {"\\.", "\\", 0, S_OK}, + {"\\\\.", "\\\\", 0, S_OK}, + {"\\\\.\\", "\\\\", 0, S_OK}, + {"\\\\\\.", "\\\\", 0, S_OK}, + {"\\\\.\\\\", "\\\\\\", 0, S_OK}, + {"\\\\\\\\.", "\\\\\\", 0, S_OK}, + {"\\?\\.", "\\?", 0, S_OK}, + {"\\\\?\\.", "\\\\?", 0, S_OK}, + {"\\192.168.1.1\\a", "\\192.168.1.1\\a", 0, S_OK}, + {"\\a.168.1.1\\a", "\\a.168.1.1\\a", 0, S_OK}, + {"\\\\192.168.1.1\\a", "\\\\192.168.1.1\\a", 0, S_OK}, + {"\\\\a.168.1.1\\b", "\\\\a.168.1.1\\b", 0, S_OK}, + {"\\\\?\\C:.", "C:\\", 0, S_OK}, + {"\\\\?\\C:\\.", "C:\\", 0, S_OK}, + {"\\\\?\\UNC\\.", "\\\\", 0, S_OK}, + {"\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\.", + "\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\", 0, S_OK}, + + /* .. */ + {"a..", "a", 0, S_OK}, + {"a..b", "a..b", 0, S_OK}, + {"a\\..", "\\", 0, S_OK}, + {"a\\..\\", "\\", 0, S_OK}, + {"a\\..\\b", "\\b", 0, S_OK}, + {"C:..", "C:\\", 0, S_OK}, + {"C:\\..", "C:\\", 0, S_OK}, + {"C:\\\\..", "C:\\", 0, S_OK}, + {"C:\\..\\", "C:\\", 0, S_OK}, + {"C:\\a\\..", "C:\\", 0, S_OK}, + {"C:\\a\\\\..", "C:\\a", 0, S_OK}, + {"C:\\a\\\\\\..", "C:\\a\\", 0, S_OK}, + {"C:\\a\\..\\b", "C:\\b", 0, S_OK}, + {"C:\\a\\..\\\\b", "C:\\\\b", 0, S_OK}, + {"\\..", "\\", 0, S_OK}, + {"\\\\..", "\\\\", 0, S_OK}, + {"\\\\\\..", "\\", 0, S_OK}, + {"\\\\..\\", "\\\\", 0, S_OK}, + {"\\\\\\..", "\\", 0, S_OK}, + {"\\\\..\\\\", "\\\\\\", 0, S_OK}, + {"\\\\\\\\..", "\\\\", 0, S_OK}, + {"\\?\\..", "\\", 0, S_OK}, + {"\\a\\..", "\\", 0, S_OK}, + {"\\\\?\\..", "\\", 0, S_OK}, + {"\\\\a\\..", "\\", 0, S_OK}, + {"\\a\\..\\b", "\\b", 0, S_OK}, + {"\\a\\b\\..", "\\a", 0, S_OK}, + {"\\?\\UNC\\..", "\\?", 0, S_OK}, + {"\\?\\C:\\..", "\\?", 0, S_OK}, + {"\\\\?\\C:..", "C:\\", 0, S_OK}, + {"\\\\?\\C:\\..", "C:\\", 0, S_OK}, + {"\\\\?\\UNC\\..", "\\\\", 0, S_OK}, + {"\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}..", + "\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}", 0, S_OK}, + {"\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\..", + "\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\", 0, S_OK}, + {"\\\\?\\UNC\\a\\b\\..", "\\\\a", 0, S_OK}, + {"\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\a\\b\\..", + "\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\a", 0, S_OK}, + + /* . and .. */ + {"C:\\a\\.\\b\\..\\", "C:\\a\\", 0, S_OK}, + {"\\a\\.\\b\\..\\", "\\a\\", 0, S_OK}, + {"\\?\\a\\.\\b\\..\\", "\\?\\a\\", 0, S_OK}, + {"\\\\.\\a\\.\\b\\..\\", "\\\\a\\", 0, S_OK}, + {"\\\\?\\a\\.\\b\\..\\", "\\\\?\\a\\", 0, S_OK}, + {"\\\\.\\..", "\\\\", 0, S_OK}, + + /* PATHCCH_ALLOW_LONG_PATHS */ + /* Input path with prefix \\?\ and length of MAXPATH + 1, HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE) = 0x800700ce */ + {"\\\\?\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", NULL, 0, 0x800700ce}, + /* Input path with prefix C:\ and length of MAXPATH + 1 */ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", NULL, 0, 0x800700ce}, + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "\\\\?\\C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + /* Input path with prefix C: and length of MAXPATH + 1 */ + {"C:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "\\\\?\\C:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + /* Input path with prefix C:\ and length of MAXPATH + 1 and with .. */ + {"C:\\..\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + /* Input path with prefix \\?\ and length of MAXPATH + 1 */ + {"\\\\?\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "\\\\?\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + /* Input path with prefix \ and length of MAXPATH + 1 */ + {"\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + /* Input path with length of MAXPATH with PATHCCH_ALLOW_LONG_PATHS disabled*/ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 0, S_OK}, + /* Input path with length of MAXPATH */ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", PATHCCH_ALLOW_LONG_PATHS, S_OK}, + + /* Flags added after Windows 10 1709 */ + /* PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS */ + /* PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS without PATHCCH_ALLOW_LONG_PATHS */ + {"", NULL, PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, E_INVALIDARG}, + /* Input path with prefix C:\ and length of MAXPATH + 1 and PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS */ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, S_OK}, + + /* PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS */ + /* PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS without PATHCCH_ALLOW_LONG_PATHS */ + {"", NULL, PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS, E_INVALIDARG}, + /* Both PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS and PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS */ + {"", "\\", PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS, + E_INVALIDARG}, + /* Input path with prefix C:\ and length of MAXPATH + 1 and PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS */ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "\\\\?\\C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS, S_OK}, + /* Input path with prefix C:\ and length of MAXPATH and PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS */ + {"C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "C:\\aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" + "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + PATHCCH_ALLOW_LONG_PATHS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS, S_OK}, + + /* PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */ + /* No effect for spaces */ + {"C:\\a \\", "C:\\a \\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a\\ ", "C:\\a\\ ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a ", "C:\\a ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a ", "C:\\a ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a. ", "C:\\a. ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"\\a \\", "\\a \\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"\\a\\ ", "\\a\\ ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"\\\\a \\", "\\\\a \\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"\\\\a\\ ", "\\\\a\\ ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"\\\\?\\ ", "\\\\?\\ ", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + /* Keep trailing dot */ + {"*..", "*..", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {".", "\\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"..", "\\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:.", "C:.", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:..", "C:..", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a\\.", "C:\\a", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a\\..", "C:\\", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a.", "C:\\a.", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + {"C:\\a..", "C:\\a..", PATHCCH_DO_NOT_NORMALIZE_SEGMENTS, S_OK}, + + /* PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH */ + {"C:\\a\\", "\\\\?\\C:\\a\\", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, S_OK}, + {"", NULL, PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH | PATHCCH_ALLOW_LONG_PATHS, E_INVALIDARG}, + {"\\a\\", "\\a\\", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, S_OK}, + {"\\\\?\\C:\\a\\", "\\\\?\\C:\\a\\", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, S_OK}, + /* Implication of PATHCCH_DO_NOT_NORMALIZE_SEGMENTS by PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH */ + {"\\a.", "\\a.", PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH, S_OK}, + + /* PATHCCH_ENSURE_TRAILING_SLASH */ + {"\\", "\\", PATHCCH_ENSURE_TRAILING_SLASH, S_OK}, + {"C:\\", "C:\\", PATHCCH_ENSURE_TRAILING_SLASH, S_OK}, + {"C:\\a\\.", "C:\\a\\", PATHCCH_ENSURE_TRAILING_SLASH, S_OK}, + {"C:\\a", "C:\\a\\", PATHCCH_ENSURE_TRAILING_SLASH, S_OK} +}; + +static void test_PathAllocCanonicalize(void) +{ + WCHAR path_inW[1024], path_maxW[PATHCCH_MAX_CCH + 1]; + WCHAR *path_outW; + CHAR path_outA[1024]; + BOOL skip_new_flags = TRUE; + HRESULT hr; + INT i; + + if (!pPathAllocCanonicalize) + { + win_skip("PathAllocCanonicalize() is not available.\n"); + return; + } + + /* No NULL check for path on Windows */ + if (0) + { + hr = pPathAllocCanonicalize(NULL, 0, &path_outW); + ok(hr == E_INVALIDARG, "expect hr %#x, got %#x\n", E_INVALIDARG, hr); + } + + MultiByteToWideChar(CP_ACP, 0, "C:\\", -1, path_inW, ARRAY_SIZE(path_inW)); + hr = pPathAllocCanonicalize(path_inW, 0, NULL); + ok(hr == E_INVALIDARG, "expect hr %#x, got %#x\n", E_INVALIDARG, hr); + + /* Test longest path */ + for (i = 0; i < ARRAY_SIZE(path_maxW) - 1; i++) path_maxW[i] = 'a'; + path_maxW[PATHCCH_MAX_CCH] = '\0'; + path_outW = (WCHAR *)0xdeadbeef; + hr = pPathAllocCanonicalize(path_maxW, PATHCCH_ALLOW_LONG_PATHS, &path_outW); + ok(hr == HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), "expect hr %#x, got %#x\n", + HRESULT_FROM_WIN32(ERROR_FILENAME_EXCED_RANGE), hr); + ok(path_outW == NULL, "expect path_outW null, got %p\n", path_outW); + + path_maxW[PATHCCH_MAX_CCH - 1] = '\0'; + hr = pPathAllocCanonicalize(path_maxW, PATHCCH_ALLOW_LONG_PATHS, &path_outW); + ok(hr == S_OK, "expect hr %#x, got %#x\n", S_OK, hr); + + /* Check if flags added after Windows 10 1709 are supported */ + MultiByteToWideChar(CP_ACP, 0, "C:\\", -1, path_inW, ARRAY_SIZE(path_inW)); + hr = pPathAllocCanonicalize(path_inW, PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS, &path_outW); + if (hr == E_INVALIDARG) skip_new_flags = FALSE; + + for (i = 0; i < ARRAY_SIZE(alloccanonicalize_tests); i++) + { + const struct alloccanonicalize_test *t = alloccanonicalize_tests + i; + + if (((PATHCCH_FORCE_ENABLE_LONG_NAME_PROCESS | PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS + | PATHCCH_DO_NOT_NORMALIZE_SEGMENTS | PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH + | PATHCCH_ENSURE_TRAILING_SLASH) + & t->flags) + && skip_new_flags) + { + win_skip("Skip testing new flags added after Windows 10 1709\n"); + return; + } + + MultiByteToWideChar(CP_ACP, 0, t->path_in, -1, path_inW, ARRAY_SIZE(path_inW)); + hr = pPathAllocCanonicalize(path_inW, t->flags, &path_outW); + ok(hr == t->hr, "path %s expect result %#x, got %#x\n", t->path_in, t->hr, hr); + if (SUCCEEDED(hr)) + { + WideCharToMultiByte(CP_ACP, 0, path_outW, -1, path_outA, ARRAY_SIZE(path_outA), NULL, NULL); + ok(!lstrcmpA(path_outA, t->path_out), "path \"%s\" expect output path \"%s\", got \"%s\"\n", t->path_in, + t->path_out, path_outA); + LocalFree(path_outW); + } + } +} + static const struct { const char *path1; @@ -1386,6 +1690,7 @@ START_TEST(path) { HMODULE hmod = LoadLibraryA("kernelbase.dll"); + pPathAllocCanonicalize = (void *)GetProcAddress(hmod, "PathAllocCanonicalize"); pPathCchCombineEx = (void *)GetProcAddress(hmod, "PathCchCombineEx"); pPathCchAddBackslash = (void *)GetProcAddress(hmod, "PathCchAddBackslash"); pPathCchAddBackslashEx = (void *)GetProcAddress(hmod, "PathCchAddBackslashEx"); @@ -1402,6 +1707,7 @@ START_TEST(path) pPathCchStripToRoot = (void *)GetProcAddress(hmod, "PathCchStripToRoot"); pPathIsUNCEx = (void *)GetProcAddress(hmod, "PathIsUNCEx"); + test_PathAllocCanonicalize(); test_PathCchCombineEx(); test_PathCchAddBackslash(); test_PathCchAddBackslashEx(); diff --git a/include/pathcch.h b/include/pathcch.h index 12a6fc5511d..b1d6be249d3 100644 --- a/include/pathcch.h +++ b/include/pathcch.h @@ -22,9 +22,11 @@ #define PATHCCH_FORCE_DISABLE_LONG_NAME_PROCESS 0x04 #define PATHCCH_DO_NOT_NORMALIZE_SEGMENTS 0x08 #define PATHCCH_ENSURE_IS_EXTENDED_LENGTH_PATH 0x10 +#define PATHCCH_ENSURE_TRAILING_SLASH 0x20 #define PATHCCH_MAX_CCH 0x8000 +HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out); HRESULT WINAPI PathCchAddBackslash(WCHAR *path, SIZE_T size); HRESULT WINAPI PathCchAddBackslashEx(WCHAR *path, SIZE_T size, WCHAR **end, SIZE_T *remaining); HRESULT WINAPI PathCchAddExtension(WCHAR *path, SIZE_T size, const WCHAR *extension);