From: Zhiyi Zhang Subject: [PATCH] kernelbase: Fix PathAllocCanonicalize handling segments that contain dots. Message-Id: Date: Wed, 18 Sep 2019 17:40:52 +0800 Wine-Bug: https://bugs.winehq.org/show_bug.cgi?id=47766 Signed-off-by: Zhiyi Zhang --- 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