[PATCH 4/6] kernelbase: Implement PathAllocCanonicalize.

Zhiyi Zhang zzhang at codeweavers.com
Thu Nov 22 21:18:05 CST 2018


Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 .../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 725f16448f..9bce0ace95 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 e7bb62d75c..8273d5f1b1 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 c3fca8b9cf..659a17bd09 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 8981b3a08a..0a55369d99 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 12a6fc5511..b1d6be249d 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);
-- 
2.19.1





More information about the wine-devel mailing list