diff options
author | Edward Thomson <ethomson@edwardthomson.com> | 2018-10-20 20:25:51 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-10-20 20:25:51 +0100 |
commit | 7c791f3dfcc00b892bcea9a1df63b36737d6eb1a (patch) | |
tree | 7839882f029e854f7fb9b3a25f1e35614998765d | |
parent | 6cc14ae3a76586722526eb6cca784b4386bfb2a1 (diff) | |
parent | a34f5b0db55ba2bd0080ccd8f08db157a76168a9 (diff) | |
download | libgit2-7c791f3dfcc00b892bcea9a1df63b36737d6eb1a.tar.gz |
Merge pull request #4852 from libgit2/ethomson/unc_paths
Win32 path canonicalization refactoring
-rw-r--r-- | src/win32/path_w32.c | 108 | ||||
-rw-r--r-- | src/win32/path_w32.h | 18 | ||||
-rw-r--r-- | src/win32/posix_w32.c | 6 | ||||
-rw-r--r-- | src/win32/w32_util.c | 68 | ||||
-rw-r--r-- | src/win32/w32_util.h | 18 | ||||
-rw-r--r-- | tests/path/win32.c | 51 |
6 files changed, 172 insertions, 97 deletions
diff --git a/src/win32/path_w32.c b/src/win32/path_w32.c index 5e24260f7..b955b024c 100644 --- a/src/win32/path_w32.c +++ b/src/win32/path_w32.c @@ -220,7 +220,7 @@ int git_win32_path_from_utf8(git_win32_path out, const char *src) goto on_error; } - /* Skip the drive letter specification ("C:") */ + /* Skip the drive letter specification ("C:") */ if (git__utf8_to_16(dest + 2, MAX_PATH - 2, src) < 0) goto on_error; } @@ -315,7 +315,7 @@ static bool path_is_volume(wchar_t *target, size_t target_len) } /* On success, returns the length, in characters, of the path stored in dest. -* On failure, returns a negative value. */ + * On failure, returns a negative value. */ int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) { BYTE buf[MAXIMUM_REPARSE_DATA_BUFFER_SIZE]; @@ -360,16 +360,16 @@ int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path) if (path_is_volume(target, target_len)) { /* This path is a reparse point that represents another volume mounted - * at this location, it is not a symbolic link our input was canonical. - */ + * at this location, it is not a symbolic link our input was canonical. + */ errno = EINVAL; error = -1; } else if (target_len) { - /* The path may need to have a prefix removed. */ - target_len = git_win32__canonicalize_path(target, target_len); + /* The path may need to have a namespace prefix removed. */ + target_len = git_win32_path_remove_namespace(target, target_len); /* Need one additional character in the target buffer - * for the terminating NULL. */ + * for the terminating NULL. */ if (GIT_WIN_PATH_UTF16 > target_len) { wcscpy(dest, target); error = (int)target_len; @@ -380,3 +380,97 @@ on_error: CloseHandle(handle); return error; } + +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len) +{ + while (1) { + if (!len || str[len - 1] != L'\\') + break; + + /* + * Don't trim backslashes from drive letter paths, which + * are 3 characters long and of the form C:\, D:\, etc. + */ + if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') + break; + + len--; + } + + str[len] = L'\0'; + + return len; +} + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len) +{ + static const wchar_t dosdevices_namespace[] = L"\\\?\?\\"; + static const wchar_t nt_namespace[] = L"\\\\?\\"; + static const wchar_t unc_namespace_remainder[] = L"UNC\\"; + static const wchar_t unc_prefix[] = L"\\\\"; + + const wchar_t *prefix = NULL, *remainder = NULL; + size_t prefix_len = 0, remainder_len = 0; + + /* "\??\" -- DOS Devices prefix */ + if (len >= CONST_STRLEN(dosdevices_namespace) && + !wcsncmp(str, dosdevices_namespace, CONST_STRLEN(dosdevices_namespace))) { + remainder = str + CONST_STRLEN(dosdevices_namespace); + remainder_len = len - CONST_STRLEN(dosdevices_namespace); + } + /* "\\?\" -- NT namespace prefix */ + else if (len >= CONST_STRLEN(nt_namespace) && + !wcsncmp(str, nt_namespace, CONST_STRLEN(nt_namespace))) { + remainder = str + CONST_STRLEN(nt_namespace); + remainder_len = len - CONST_STRLEN(nt_namespace); + } + + /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ + if (remainder_len >= CONST_STRLEN(unc_namespace_remainder) && + !wcsncmp(remainder, unc_namespace_remainder, CONST_STRLEN(unc_namespace_remainder))) { + + /* + * The proper Win32 path for a UNC share has "\\" at beginning of it + * and looks like "\\server\share\<folderStructure>". So remove the + * UNC namespace and add a prefix of "\\" in its place. + */ + remainder += CONST_STRLEN(unc_namespace_remainder); + remainder_len -= CONST_STRLEN(unc_namespace_remainder); + + prefix = unc_prefix; + prefix_len = CONST_STRLEN(unc_prefix); + } + + if (remainder) { + /* + * Sanity check that the new string isn't longer than the old one. + * (This could only happen due to programmer error introducing a + * prefix longer than the namespace it replaces.) + */ + assert(len >= remainder_len + prefix_len); + + if (prefix) + memmove(str, prefix, prefix_len * sizeof(wchar_t)); + + memmove(str + prefix_len, remainder, remainder_len * sizeof(wchar_t)); + + len = remainder_len + prefix_len; + str[len] = L'\0'; + } + + return git_win32_path_trim_end(str, len); +} diff --git a/src/win32/path_w32.h b/src/win32/path_w32.h index 83ffd1f6f..facbced81 100644 --- a/src/win32/path_w32.h +++ b/src/win32/path_w32.h @@ -83,4 +83,22 @@ extern char *git_win32_path_8dot3_name(const char *path); extern int git_win32_path_readlink_w(git_win32_path dest, const git_win32_path path); +/** + * Removes any trailing backslashes from a path, except in the case of a drive + * letter path (C:\, D:\, etc.). This function cannot fail. + * + * @param path The path which should be trimmed. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_trim_end(wchar_t *str, size_t len); + +/** + * Removes any of the following namespace prefixes from a path, + * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. + * + * @param path The path which should be converted. + * @return The length of the modified string (<= the input length) + */ +size_t git_win32_path_remove_namespace(wchar_t *str, size_t len); + #endif diff --git a/src/win32/posix_w32.c b/src/win32/posix_w32.c index 8617e45e9..8c321ef3c 100644 --- a/src/win32/posix_w32.c +++ b/src/win32/posix_w32.c @@ -354,7 +354,7 @@ static int do_lstat(const char *path, struct stat *buf, bool posixly_correct) if ((len = git_win32_path_from_utf8(path_w, path)) < 0) return -1; - git_win32__path_trim_end(path_w, len); + git_win32_path_trim_end(path_w, len); return lstat_w(path_w, buf, posixly_correct); } @@ -648,8 +648,8 @@ static int getfinalpath_w( if (!dwChars || dwChars >= GIT_WIN_PATH_UTF16) return -1; - /* The path may be delivered to us with a prefix; canonicalize */ - return (int)git_win32__canonicalize_path(dest, dwChars); + /* The path may be delivered to us with a namespace prefix; remove */ + return (int)git_win32_path_remove_namespace(dest, dwChars); } static int follow_and_lstat_link(git_win32_path path, struct stat* buf) diff --git a/src/win32/w32_util.c b/src/win32/w32_util.c index b7b1ffa10..5996c9fb9 100644 --- a/src/win32/w32_util.c +++ b/src/win32/w32_util.c @@ -93,71 +93,3 @@ int git_win32__hidden(bool *out, const char *path) *out = (attrs & FILE_ATTRIBUTE_HIDDEN) ? true : false; return 0; } - -/** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32__path_trim_end(wchar_t *str, size_t len) -{ - while (1) { - if (!len || str[len - 1] != L'\\') - break; - - /* Don't trim backslashes from drive letter paths, which - * are 3 characters long and of the form C:\, D:\, etc. */ - if (len == 3 && git_win32__isalpha(str[0]) && str[1] == ':') - break; - - len--; - } - - str[len] = L'\0'; - - return len; -} - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32__canonicalize_path(wchar_t *str, size_t len) -{ - static const wchar_t dosdevices_prefix[] = L"\\\?\?\\"; - static const wchar_t nt_prefix[] = L"\\\\?\\"; - static const wchar_t unc_prefix[] = L"UNC\\"; - size_t to_advance = 0; - - /* "\??\" -- DOS Devices prefix */ - if (len >= CONST_STRLEN(dosdevices_prefix) && - !wcsncmp(str, dosdevices_prefix, CONST_STRLEN(dosdevices_prefix))) { - to_advance += CONST_STRLEN(dosdevices_prefix); - len -= CONST_STRLEN(dosdevices_prefix); - } - /* "\\?\" -- NT namespace prefix */ - else if (len >= CONST_STRLEN(nt_prefix) && - !wcsncmp(str, nt_prefix, CONST_STRLEN(nt_prefix))) { - to_advance += CONST_STRLEN(nt_prefix); - len -= CONST_STRLEN(nt_prefix); - } - - /* "\??\UNC\", "\\?\UNC\" -- UNC prefix */ - if (to_advance && len >= CONST_STRLEN(unc_prefix) && - !wcsncmp(str + to_advance, unc_prefix, CONST_STRLEN(unc_prefix))) { - to_advance += CONST_STRLEN(unc_prefix); - len -= CONST_STRLEN(unc_prefix); - } - - if (to_advance) { - memmove(str, str + to_advance, len * sizeof(wchar_t)); - str[len] = L'\0'; - } - - return git_win32__path_trim_end(str, len); -} diff --git a/src/win32/w32_util.h b/src/win32/w32_util.h index 6531f47a7..5216a1396 100644 --- a/src/win32/w32_util.h +++ b/src/win32/w32_util.h @@ -60,24 +60,6 @@ extern int git_win32__set_hidden(const char *path, bool hidden); extern int git_win32__hidden(bool *hidden, const char *path); /** - * Removes any trailing backslashes from a path, except in the case of a drive - * letter path (C:\, D:\, etc.). This function cannot fail. - * - * @param path The path which should be trimmed. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32__path_trim_end(wchar_t *str, size_t len); - -/** - * Removes any of the following namespace prefixes from a path, - * if found: "\??\", "\\?\", "\\?\UNC\". This function cannot fail. - * - * @param path The path which should be converted. - * @return The length of the modified string (<= the input length) - */ -size_t git_win32__canonicalize_path(wchar_t *str, size_t len); - -/** * Converts a FILETIME structure to a struct timespec. * * @param FILETIME A pointer to a FILETIME diff --git a/tests/path/win32.c b/tests/path/win32.c index 4ff039738..a5413c781 100644 --- a/tests/path/win32.c +++ b/tests/path/win32.c @@ -129,7 +129,7 @@ void test_path_win32__absolute_from_relative(void) #endif } -void test_canonicalize(const wchar_t *in, const wchar_t *expected) +static void test_canonicalize(const wchar_t *in, const wchar_t *expected) { #ifdef GIT_WIN32 git_win32_path canonical; @@ -145,6 +145,55 @@ void test_canonicalize(const wchar_t *in, const wchar_t *expected) #endif } +static void test_remove_namespace(const wchar_t *in, const wchar_t *expected) +{ +#ifdef GIT_WIN32 + git_win32_path canonical; + + cl_assert(wcslen(in) < MAX_PATH); + wcscpy(canonical, in); + + cl_must_pass(git_win32_path_remove_namespace(canonical, wcslen(in))); + cl_assert_equal_wcs(expected, canonical); +#else + GIT_UNUSED(in); + GIT_UNUSED(expected); +#endif +} + +void test_path_win32__remove_namespace(void) +{ + test_remove_namespace(L"\\\\?\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\\\?\\C:\\", L"C:\\"); + test_remove_namespace(L"\\\\?\\", L""); + + test_remove_namespace(L"\\??\\C:\\Temp\\Foo", L"C:\\Temp\\Foo"); + test_remove_namespace(L"\\??\\C:\\", L"C:\\"); + test_remove_namespace(L"\\??\\", L""); + + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\?\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\?\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\?\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\??\\UNC\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\??\\UNC\\server\\", L"\\\\server"); + test_remove_namespace(L"\\??\\UNC\\server", L"\\\\server"); + + test_remove_namespace(L"\\\\server\\C$\\folder", L"\\\\server\\C$\\folder"); + test_remove_namespace(L"\\\\server\\C$", L"\\\\server\\C$"); + test_remove_namespace(L"\\\\server\\", L"\\\\server"); + test_remove_namespace(L"\\\\server", L"\\\\server"); + + test_remove_namespace(L"C:\\Foo\\Bar", L"C:\\Foo\\Bar"); + test_remove_namespace(L"C:\\", L"C:\\"); + test_remove_namespace(L"", L""); + +} + void test_path_win32__canonicalize(void) { #ifdef GIT_WIN32 |