[PATCH] kernelbase: Fix PathAllocCanonicalize handling segments that contain dots.

Zhiyi Zhang zzhang at codeweavers.com
Wed Sep 18 04:40:52 CDT 2019


Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47766
Signed-off-by: Zhiyi Zhang <zzhang at codeweavers.com>
---
 dlls/kernelbase/path.c       | 168 ++++++++++++++++++++++-------------
 dlls/kernelbase/tests/path.c |  34 +++++++
 2 files changed, 138 insertions(+), 64 deletions(-)

diff --git a/dlls/kernelbase/path.c b/dlls/kernelbase/path.c
index 82708be544..e2cff64f5a 100644
--- a/dlls/kernelbase/path.c
+++ b/dlls/kernelbase/path.c
@@ -200,8 +200,9 @@ static const WCHAR *get_root_end(const WCHAR *path)
 
 HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **path_out)
 {
-    WCHAR *buffer, *dst;
-    const WCHAR *src;
+    WCHAR *buffer, *dst, *old_dst;
+    const WCHAR *src, *old_src;
+    const WCHAR *segment_start;
     const WCHAR *root_end;
     SIZE_T buffer_size, length;
 
@@ -263,78 +264,89 @@ HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **
             dst += root_end - buffer + 1;
     }
 
-    while (*src)
+    /* If segment normalization is on, remove trailing dots in the root segment */
+    if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
     {
-        if (src[0] == '.')
+        old_src = src;
+        while (*src && *src == '.')
+            ++src;
+
+        /* Not trailing dots, don't remove */
+        if (*src)
+        {
+            src = old_src;
+        }
+        /* At least have one dot */
+        else if (src > old_src)
         {
-            if (src[1] == '.')
+            /* If X:\ is incomplete, complete it */
+            if (iswalpha(buffer[0]) && buffer[1] == ':' && buffer[2] != '\\')
             {
-                /* Keep one . after * */
-                if (dst > buffer && dst[-1] == '*')
-                {
-                    *dst++ = *src++;
-                    continue;
-                }
+                root_end = buffer + 2;
+                dst = buffer + 3;
+                buffer[2] = '\\';
+                /* If the next character is \, skip it to avoid duplicated \ */
+                if (src[0] == '\\')
+                    ++src;
+            }
+        }
+    }
 
-                /* 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;
-                }
+    /* Dots after X: are part of the root segment */
+    if (iswalpha(buffer[0]) && buffer[1] == ':' && !buffer[2])
+    {
+        while (*src && *src == '.')
+            *dst++ = *src++;
+    }
 
-                /* Remove the \ before .. if the \ is not part of root */
-                if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
+    while (*src)
+    {
+        /* .. up one level */
+        if (src[0] == '.' && src[1] == '.' && (src[2] == '\\' || !src[2]))
+        {
+            /* 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)
                 {
-                    *--dst = '\0';
-                    /* Remove characters until a \ is encountered */
-                    while (dst > buffer)
+                    if (dst[-1] == '\\')
                     {
-                        if (dst[-1] == '\\')
-                        {
-                            *--dst = 0;
-                            break;
-                        }
-                        else
-                            *--dst = 0;
+                        *--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++;
+            /* Remove the extra \ after .. if the \ before .. wasn't deleted */
+            else if (src[2] == '\\')
+                ++src;
 
-                src++;
+            src += 2;
+            /* If X:\ is not complete, then complete it */
+            if (iswalpha(buffer[0]) && buffer[1] == ':' && buffer[2] != '\\')
+            {
+                root_end = buffer + 2;
+                dst = buffer + 3;
+                buffer[2] = '\\';
+                /* If the next character is \, skip it to avoid duplicated \ */
+                if (src[0] == '\\')
+                    ++src;
             }
-
+        }
+        /* . current level */
+        else if (src[0] == '.' && (src[1] == '\\' || !src[1]))
+        {
+            /* Remove the \ before . if the \ is not part of root */
+            if (dst > buffer && dst[-1] == '\\' && (!root_end || dst - 1 > root_end))
+                *--dst = 0;
+            /* 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 (iswalpha(buffer[0]) && buffer[1] == ':' && buffer[2] != '\\')
             {
@@ -345,9 +357,37 @@ HRESULT WINAPI PathAllocCanonicalize(const WCHAR *path_in, DWORD flags, WCHAR **
                 if (src[0] == '\\') src++;
             }
         }
-        /* Copy over */
-        else
+        /* *\.+ */
+        else if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS) && src[0] == '*' && src[1] == '.')
+        {
+            /* Keep only one . after * */
             *dst++ = *src++;
+            *dst++ = *src++;
+            while (*src == '.')
+                ++src;
+        }
+        /* Normal segments */
+        else
+        {
+            segment_start = dst;
+            /* Copy the whole segment */
+            while (*src && *src != '\\')
+                *dst++ = *src++;
+
+            /* Remove trailing dots, aka segment normalization */
+            if (!(flags & PATHCCH_DO_NOT_NORMALIZE_SEGMENTS))
+            {
+                old_dst = dst;
+                while (dst > segment_start && (dst[-1] == '.'))
+                    --dst;
+                /* Not trailing dots, don't remove */
+                if (dst == segment_start)
+                    dst = old_dst;
+            }
+
+            if (*src == '\\')
+                *dst++ = *src++;
+        }
     }
     /* End the path */
     *dst = 0;
diff --git a/dlls/kernelbase/tests/path.c b/dlls/kernelbase/tests/path.c
index b5da303629..d7492a1e9f 100644
--- a/dlls/kernelbase/tests/path.c
+++ b/dlls/kernelbase/tests/path.c
@@ -72,6 +72,7 @@ static const struct alloccanonicalize_test alloccanonicalize_tests[] =
     {"\\\\?C:a", "\\\\?C:a", 0, S_OK},
 
     /* No . */
+    {"*", "*", 0, S_OK},
     {"", "\\", 0, S_OK},
     {"C:", "C:", 0, S_OK},
     {"C:\\", "C:\\", 0, S_OK},
@@ -85,17 +86,26 @@ static const struct alloccanonicalize_test alloccanonicalize_tests[] =
     {"*.", "*.", 0, S_OK},
     {"*..", "*.", 0, S_OK},
     {"*...", "*.", 0, S_OK},
+    {"*....", "*.", 0, S_OK},
+    {".a", ".a", 0, S_OK},
     {"a.", "a", 0, S_OK},
+    {".a.", ".a", 0, S_OK},
     {"a.b", "a.b", 0, S_OK},
+    {".a.b.", ".a.b", 0, S_OK},
     {"a\\.", "a", 0, S_OK},
     {"a\\.\\b", "a\\b", 0, S_OK},
+    {":.", ":", 0, S_OK},
     {"C:.", "C:\\", 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:\\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},
     {"C:\\a\\\\\\.", "C:\\a\\\\", 0, S_OK},
+    {".\\", "\\", 0, S_OK},
     {"\\.", "\\", 0, S_OK},
     {"\\\\.", "\\\\", 0, S_OK},
     {"\\\\.\\", "\\\\", 0, S_OK},
@@ -115,21 +125,42 @@ static const struct alloccanonicalize_test alloccanonicalize_tests[] =
       "\\\\?\\Volume{e51a1864-6f2d-4019-b73d-f4e60e600c26}\\", 0, S_OK},
 
     /* .. */
+    {"..a", "..a", 0, S_OK},
+    {"...a", "...a", 0, S_OK},
+    {"....a", "....a", 0, S_OK},
     {"a..", "a", 0, S_OK},
+    {"a...", "a", 0, S_OK},
+    {"a....", "a", 0, S_OK},
+    {"..a..", "..a", 0, S_OK},
     {"a..b", "a..b", 0, S_OK},
+    {"..a..b..", "..a..b", 0, S_OK},
     {"a\\..", "\\", 0, S_OK},
     {"a\\..\\", "\\", 0, S_OK},
     {"a\\..\\b", "\\b", 0, S_OK},
+    {":..", ":", 0, S_OK},
     {"C:..", "C:\\", 0, S_OK},
+    {"C:...", "C:\\", 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:\\..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},
     {"C:\\\\..", "C:\\", 0, S_OK},
     {"C:\\..\\", "C:\\", 0, S_OK},
+    {"C:\\...\\", "C:\\...\\", 0, S_OK},
     {"C:\\a\\..", "C:\\", 0, S_OK},
+    {"C:\\a\\b..", "C:\\a\\b", 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},
@@ -249,6 +280,8 @@ static const struct alloccanonicalize_test alloccanonicalize_tests[] =
 
     /* PATHCCH_DO_NOT_NORMALIZE_SEGMENTS */
     /* No effect for spaces */
+    {"a ", "a ", 0, S_OK},
+    {"C:\\a ", "C:\\a ", 0, 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},
@@ -265,6 +298,7 @@ static const struct alloccanonicalize_test alloccanonicalize_tests[] =
     {"..", "\\", 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:...", "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},
-- 
2.20.1




More information about the wine-devel mailing list