Unverified Commit 7c791f3d by Edward Thomson Committed by GitHub

Merge pull request #4852 from libgit2/ethomson/unc_paths

Win32 path canonicalization refactoring
parents 6cc14ae3 a34f5b0d
......@@ -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);
}
......@@ -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
......@@ -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)
......
......@@ -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);
}
......@@ -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
......
......@@ -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
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment